Package com.stuffwithstuff.magpie.interpreter

Source Code of com.stuffwithstuff.magpie.interpreter.ExprEvaluator

package com.stuffwithstuff.magpie.interpreter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.stuffwithstuff.magpie.ast.*;
import com.stuffwithstuff.magpie.ast.pattern.MatchCase;
import com.stuffwithstuff.magpie.ast.pattern.Pattern;
import com.stuffwithstuff.magpie.intrinsic.IntrinsicLoader;
import com.stuffwithstuff.magpie.util.Pair;

/**
* Implements the visitor pattern on AST nodes, in order to evaluate
* expressions. This is the heart of the interpreter and is where Magpie code is
* actually executed.
*/
public class ExprEvaluator implements ExprVisitor<Obj, Scope> {
  public ExprEvaluator(Context context) {
    mContext = context;
  }

  /**
   * Evaluates the given expression in the given context.
   * @param   expr     The expression to evaluate.
   * @param   context  The context in which to evaluate the expression.
   * @return           The result of evaluating the expression.
   */
  public Obj evaluate(Expr expr, Scope scope) {
    if (expr == null) return null;
    return expr.accept(this, scope);
  }

  @Override
  public Obj visit(ArrayExpr expr, Scope scope) {
    // Evaluate the elements.
    List<Obj> elements = new ArrayList<Obj>();
    for (Expr element : expr.getElements()) {
      elements.add(evaluate(element, scope));
    }

    return mContext.toArray(elements);
  }

  @Override
  public Obj visit(AssignExpr expr, Scope scope) {
    Obj value = evaluate(expr.getValue(), scope);

    // Try to assign to a local variable.
    if (scope.assign(expr.getName(), value)) return value;
   
    // TODO(bob): Detect this statically.
    throw mContext.error("NoVariableError",
        "Could not find a variable named \"" + expr.getName() + "\".");
  }

  @Override
  public Obj visit(BoolExpr expr, Scope scope) {
    return mContext.toObj(expr.getValue());
  }

  @Override
  public Obj visit(BreakExpr expr, Scope scope) {
    // Outside of a loop, "break" does nothing.
    if (mLoopDepth > 0) {
      throw new BreakException();
    }
    return mContext.nothing();
  }
 
  @Override
  public Obj visit(CallExpr expr, Scope scope) {
    Multimethod multimethod = scope.lookUpMultimethod(expr.getName());
    if (multimethod == null) {
      throw mContext.error(Name.NO_METHOD_ERROR,
          "Could not find a method named \"" + expr.getName() + "\". (" +
          expr.getPosition() + ")");
    }

    Obj arg = evaluate(expr.getArg(), scope);
    return multimethod.invoke(expr.getName(), mContext, arg);
  }
 
  @Override
  public Obj visit(ClassExpr expr, Scope scope) {
    // Look up the parents.
    List<ClassObj> parents = new ArrayList<ClassObj>();
    for (String parentName : expr.getParents()) {
      parents.add(scope.lookUp(parentName).asClass());
    }
   
    ClassObj classObj = mContext.getInterpreter().createClass(expr.getName(),
        parents, expr.getFields(), scope, expr.getDoc());
   
    scope.define(false, expr.getName(), classObj);

    return classObj;
  }

  @Override
  public Obj visit(FnExpr expr, Scope scope) {
    return mContext.toFunction(expr, scope);
  }

  @Override
  public Obj visit(ImportExpr expr, Scope scope) {
    // TODO(bob): Eventually the schemes should be host-provided plug-ins.
    if (expr.getScheme() == null) {
      Module module = mContext.getInterpreter().importModule(expr.getModule());

      // Map names to declarations.
      Map<String, ImportDeclaration> declarations =
          new HashMap<String, ImportDeclaration>();
      for (ImportDeclaration declaration : expr.getDeclarations()) {
        declarations.put(declaration.getName(), declaration);
      }
     
      Set<String> importedNames;
     
      if (expr.isOnly()) {
        importedNames = declarations.keySet();
      } else {
        importedNames = module.getExportedNames();
      }

      // Import the names.
      for (String name : importedNames) {
        ImportDeclaration declaration = declarations.get(name);
       
        String rename = name;
       
        // Rename it, if given one.
        if ((declaration != null) && (declaration.getRename() != null)) {
          rename = declaration.getRename();
        }

        // Apply the prefix, if given.
        if (expr.getPrefix() != null) {
          rename = expr.getPrefix() + "." + rename;
        }

        boolean export = false;
        if (declaration != null) {
          export = declaration.isExported();
        }
       
        scope.importName(name, rename, module, export);
      }
    } else if (expr.getScheme().equals("classfile")) {
      if (!IntrinsicLoader.loadClass(expr.getModule(), scope)) {
        // TODO(bob): Throw better error.
        throw mContext.error("Error", "Could not load classfile \"" +
            expr.getModule() + "\".");
      }
    }
   
    return mContext.nothing();
  }

  @Override
  public Obj visit(IntExpr expr, Scope scope) {
    return mContext.toObj(expr.getValue());
  }
 
  @Override
  public Obj visit(LoopExpr expr, Scope scope) {
    try {
      mLoopDepth++;

      // Loop forever. A "break" expression will throw a BreakException to
      // escape this loop.
      while (true) {
        // Evaluate the body in its own scope.
        evaluate(expr.getBody(), scope.push());
      }
    } catch (BreakException ex) {
      // Nothing to do.
    } finally {
      mLoopDepth--;
    }

    // TODO(bob): It would be cool if loops could have "else" clauses and then
    // reliably return a value.
    return mContext.nothing();
  }

  @Override
  public Obj visit(MatchExpr expr, Scope scope) {
    // Push a new context so that a variable declared in the value expression
    // itself disappears after the match, i.e.:
    // match var i = 123
    // ...
    // end
    // i should be gone here
    scope = scope.push();
   
    Obj value = evaluate(expr.getValue(), scope);
   
    // Try each pattern until we get a match.
    Obj result = evaluateCases(value, expr.getCases(), scope);
    if (result != null) return result;
   
    // If we got here, no patterns matched.
    throw mContext.error(Name.NO_MATCH_ERROR, "Could not find a match for \"" +
        mContext.getInterpreter().evaluateToString(value) + "\" (" +
        expr.getPosition() + ").");
  }

  @Override
  public Obj visit(MethodExpr expr, Scope scope) {
    if (expr.getBody() != null) {
      Function method = new Function(
          Expr.fn(expr.getPosition(), expr.getDoc(),
              expr.getPattern(), expr.getBody()),
          scope);
     
      scope.define(expr.getName(), method);
    } else {
      // Defining the multimethod here but not adding any methods.
      scope.defineMultimethod(expr.getName(), expr.getDoc());
    }
   
    return mContext.nothing();
  }

  @Override
  public Obj visit(NameExpr expr, Scope scope) {
    Obj variable = scope.lookUp(expr.getName());
    if (variable != null) return variable;
   
    // TODO(bob): Detect this statically.
    throw mContext.error(Name.NO_VARIABLE_ERROR,
        "Could not find a variable named \"" + expr.getName() + "\" (" +
        expr.getPosition() + ").");
  }

  @Override
  public Obj visit(NothingExpr expr, Scope scope) {
    return mContext.nothing();
  }

  @Override
  public Obj visit(QuoteExpr expr, Scope scope) {
    return JavaToMagpie.convertAndUnquote(mContext, expr.getBody(), scope);
  }

  @Override
  public Obj visit(RecordExpr expr, Scope scope) {
   
    // TODO(bob): Hack, keep track of order keys appear for better pretty-
    // printing.
    List<String> keys = new ArrayList<String>();
   
    // Evaluate the fields.
    Map<String, Obj> fields = new HashMap<String, Obj>();
    for (Pair<String, Expr> entry : expr.getFields()) {
      keys.add(entry.getKey());
      Obj value = evaluate(entry.getValue(), scope);
      fields.put(entry.getKey(), value);
    }

    return mContext.toObj(keys, fields);
  }

  @Override
  public Obj visit(ReturnExpr expr, Scope scope) {
    Obj value = evaluate(expr.getValue(), scope);
    throw new ReturnException(value);
  }

  @Override
  public Obj visit(ScopeExpr expr, Scope scope) {
    try {
      scope = scope.push();
      return evaluate(expr.getBody(), scope);
    } catch (ErrorException err) {
      // See if we can catch it here.
      Obj result = this.evaluateCases(err.getError(), expr.getCatches(), scope);
      if (result != null) return result;

      // Not caught here, so just keep unwinding.
      throw err;
    }
  }

  @Override
  public Obj visit(SequenceExpr expr, Scope scope) {
    // Evaluate all of the expressions and return the last.
    Obj result = null;
    for (Expr thisExpr : expr.getExpressions()) {
      result = evaluate(thisExpr, scope);
    }

    return result;
  }

  @Override
  public Obj visit(StringExpr expr, Scope scope) {
    return mContext.toObj(expr.getValue());
  }

  @Override
  public Obj visit(ThrowExpr expr, Scope scope) {
    Obj value = evaluate(expr.getValue(), scope);
    throw new ErrorException(value);
  }

  @Override
  public Obj visit(UnquoteExpr expr, Scope scope) {
    throw new UnsupportedOperationException(
        "An unquoted expression cannot be directly evaluated.");
  }

  @Override
  public Obj visit(VarExpr expr, Scope scope) {
    Obj value = evaluate(expr.getValue(), scope);

    if (!PatternTester.test(mContext, expr.getPattern(), value, scope)) {
      mContext.error(Name.NO_MATCH_ERROR, "The variable pattern \"" +
          expr.getPattern() + "\" does not match the initialized value \"" +
          mContext.getInterpreter().evaluateToString(value) + "\" (" +
          expr.getPosition() + ").");
    }
   
    PatternBinder.bind(mContext, expr.isMutable(), expr.getPattern(), value,
        scope);
    return value;
  }

  private Obj evaluateCases(Obj value, List<MatchCase> cases, Scope scope) {
    if (cases == null) return null;
   
    for (MatchCase matchCase : cases) {
      Pattern pattern = matchCase.getPattern();
      if (PatternTester.test(mContext, pattern, value, scope)) {
        // Matched. Bind variables and evaluate the body.
        scope = scope.push();
        PatternBinder.bind(mContext, false, pattern, value, scope);
        return evaluate(matchCase.getBody(), scope);
      }
    }
   
    return null;
  }

  private final Context mContext;
  private int mLoopDepth = 0;
}
TOP

Related Classes of com.stuffwithstuff.magpie.interpreter.ExprEvaluator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.