package kawa.lang;
import gnu.mapping.*;
import gnu.expr.*;
import gnu.lists.*;
import java.io.*;

/**
 * Implement autoloading of Syntax (including macros).
 * A named class is loaded, and apply requests are forwarded to it.
 * @author	Per Bothner
 */

public class AutoloadSyntax extends Syntax implements Externalizable
{
  /** The name of the class that defines the macro/builtin.
   * It must be the name of a class in the CLASSPATH (for example:
   * "kawa.standard.list").  Either the class must extend Syntax
   * (and have a default constructor), or the class must be a ModuleMody,
   * (whose apply0 () is expected to define the Syntax in the
   * global environment. */
  String className;

  Environment env;

  /** The loaded syntax, or null if it has not yet been loaded. */
  Syntax loaded;

  public AutoloadSyntax ()
  {
  }

  public AutoloadSyntax (String name, String className)
  {
    super(name);
    this.className = className;
  }

  public AutoloadSyntax (String name, String className, Environment env)
  {
    super(name);
    this.className = className;
    this.env = env;
  }

  public void print(java.io.PrintWriter ps)
  {
    ps.print ("#<syntax ");
    if (getName() != null)
      {
	ps.print(getName());
	ps.print(' ');
      }
    if (loaded != null)
      ps.print ("autoloaded>");
    else
      {
	ps.print ("autoload ");
	ps.print (className);
	ps.print (">");
      }
  }

  private void throw_error (String prefix)
  {
    throw new GenericError (prefix + className
				+ " while autoloading "
				+ (getName() == null ? "" : getName().toString()));
  }

  /** Load the class named in className. */
  void load ()
  {
    Environment env = this.env != null ? this.env : Environment.getCurrent();
    String name = this.getName();
    try
      {
	Object value = Class.forName (className).newInstance ();
	if (value instanceof ModuleBody)
	  {
	    gnu.kawa.reflect.ClassMemberConstraint.defineAll(value, env);
	    ((ModuleBody) value).run();
	    try
	      {
		value = env.getFunction(name);
	      }
	    catch (Exception ex)
	      {
		value = null;
	      }
	    if (value == null || value == this
		|| !(value instanceof Syntax))
	      throw_error("syntax not found in ");
	    loaded = (Syntax) value;
	  }
	else if (value instanceof Syntax)
          loaded = (Syntax) value;
	else
	  throw_error ("failed to autoload valid syntax object ");
      }
    catch (ClassNotFoundException ex)
      {	throw_error ("failed to find class "); }
    catch (InstantiationException ex)
      { throw_error ("failed to instantiate class "); }
    catch (IllegalAccessException ex)
      { throw_error ("illegal access in class "); }
    catch (UnboundSymbol e)
      { throw_error ("missing symbol '" + e.getMessage () + "' "); }
    catch (WrongArguments ex)
      { throw_error ("type error"); }
  }

  public boolean scanForDefinitions (Pair st, java.util.Vector forms,
                                     ScopeExp defs, Translator tr)
  {
    if (loaded == null)
      {
	try
	  {
	    load ();
	  }
	catch (RuntimeException e)
	  {
	    tr.syntaxError (e.getMessage ());
            return false;
	  }
      }
    return loaded.scanForDefinitions(st, forms, defs, tr);
  }

  public Expression rewriteForm (Pair form, Translator tr)
  {
    if (loaded == null)
      {
	try
	  {
	    load ();
	  }
	catch (GenericError e)
	  {
	    return tr.syntaxError (e.getMessage ());
	  }
	catch (WrongType e)
	  {
	    return tr.syntaxError (e.getMessage ());
	  }
      }
    return loaded.rewriteForm(form, tr);
  }

  public void writeExternal(ObjectOutput out) throws IOException
  {
    out.writeObject(getName());
    out.writeObject(className);
  }

  public void readExternal(ObjectInput in)
    throws IOException, ClassNotFoundException
  {
    setName((String) in.readObject());
    className = (String) in.readObject();
  }
}
