Package loop

Source Code of loop.Executable

package loop;

import loop.ast.ClassDecl;
import loop.ast.Node;
import loop.ast.script.FunctionDecl;
import loop.ast.script.RequireDecl;
import loop.ast.script.Unit;
import loop.lisp.SexprParser;
import loop.runtime.Scope;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Reprents an executable loop program (or script). An executable is produced
* from loop source code by pushing it through the following phases that represent
* "compilation".
*
* The output of each phase is used as input to the next phase.
*
* <ol>
*   <li>Tokenizer (Tokenizing) - converts the text of the program to well-understood tokens (sometimes called lexing)</li>
*   <li>Tokenizer (Normalizing) - inserts additional tokens as appropriate to convert context-sensitive
*      grammatical constructs to relatively regular constructs</li>
*   <li>Parser - processes the stream of tokens to create productions in the form of an AST</li>
*   <li>Reducer - strips the AST of redundant or crufty nodes to make a compact AST</li>
*   <li>Verifier - Analyzes the compact AST for scope, symbol and import errors and reports them</li>
*   <li>AsmCodeEmitter - Translates the compact AST into JVM bytecode (loadable Classes)</li>
*   <li>LoopClassLoader - Loads the raw bytecode into a special classloader during execution</li>
* </ol>
*
* @author dhanji@gmail.com (Dhanji R. Prasanna)
*/
public class Executable {
  private static final Pattern INDENT_REGEX = Pattern.compile("^(\\s+)");
  private static final int MAX_BACKTRACK_LINES = 5;

  private volatile String source;      // Raw source code, discarded after compile.
  private final List<String> lines;    // Loop source code lines (for error tracing).

  private Scope scope;

  private List<AnnotatedError> staticErrors;
  private Class<?> compiled;
  private boolean runMain;
  private final String file;
  private final boolean isLisp;

  public Executable(Reader source) {
    this(source, null, false);
  }

  public Executable(Reader source, String file) {
    this(source, file, false);
  }

  public Executable(Reader source, String file, boolean isLisp) {
    this.file = file;
    this.isLisp = isLisp;

    List<String> lines = new ArrayList<String>();
    StringBuilder builder;
    try {
      BufferedReader br = new BufferedReader(source);

      builder = new StringBuilder();
      while (br.ready()) {
        String line = br.readLine();
        if (line == null)
          break;

        builder.append(line);
        builder.append('\n');

        lines.add(line);
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    this.source = builder.toString();
    this.lines = lines;
  }

  private Unit parse(String input) {
    Parser parser = isLisp
        ? new SexprParser(new Tokenizer(input).tokenize())
        : new LexprParser(new Tokenizer(input).tokenize());
    Unit unit = null;
    try {
      unit = parser.script(file);
      unit.reduceAll();

      this.scope = unit;
    } catch (RuntimeException e) {
      // Ignored.
      System.out.println("Parse errors exist.");
      if (!(e instanceof LoopCompileException))
        e.printStackTrace();
    }

    if (!parser.getErrors().isEmpty())
      this.staticErrors = parser.getErrors();

    return unit;
  }

  public boolean verify(Unit unit) {
    return null == (this.staticErrors = new Verifier(unit).verify());
  }

  public String printStaticErrorsIfNecessary() {
    if (staticErrors != null)
      return printErrors(getStaticErrors());

    return "";
  }

  public void printErrorsTo(PrintStream out, List<AnnotatedError> errors) {
    for (int i = 0, errorsSize = errors.size(); i < errorsSize; i++) {
      AnnotatedError error = errors.get(i);
      out.println((i + 1) + ") " + error.getMessage());
      out.println();

      // Unwrap to previous line if column is 0, or line is empty.
      int errorLineNumber = error.line(), column = error.column();
      if (errorLineNumber >= lines.size())
        errorLineNumber = lines.size() - 1;

      if (error.column() == 0 || lines.get(errorLineNumber).trim().isEmpty()) {
        errorLineNumber = Math.max(0, errorLineNumber - 1);
        column = lines.get(errorLineNumber).length();
      }

      String thisLine = lines.get(errorLineNumber);

      // Detect the nearest indent-drop above the error, but only if there is an indent.
      int indent, startLine = errorLineNumber;

      Matcher matcher = INDENT_REGEX.matcher(thisLine);
      if (matcher.find()) {
        indent = matcher.group(1).length();

        // Find an indent-drop before this line.
        ListIterator<String> lineIterator = lines.listIterator(errorLineNumber);
        int backtrackCount = 0;
        while (backtrackCount <= MAX_BACKTRACK_LINES && lineIterator.hasPrevious()) {
          String previous = lineIterator.previous();
          Matcher previousMatcher = INDENT_REGEX.matcher(previous);
          if (!previousMatcher.find() || previousMatcher.group(1).length() < indent) {
            startLine = Math.max(0, errorLineNumber - backtrackCount - 1);
            break;
          }

          backtrackCount++;
        }

        // If we weren't able to find an indent drop within MAX_BACKTRACK_LINES,
        // just show MAX_BACKTRACK_LINES of context.
        if (startLine == errorLineNumber) {
          startLine -= backtrackCount;
        }

      } // otherwise this is an unindented line anyway.

      // Print from startLine to error line.
      for (int lineNumber = startLine; lineNumber <= errorLineNumber; lineNumber++) {
        String line = lines.get(lineNumber);
        int lineNumberLabel = lineNumber + 1;

        // if there is a change in line number label width, we need to change the indent
        // so that everything lines up properly.
        int leader = 2;
        if (Math.floor(Math.log10(lineNumberLabel)) > Math.floor(Math.log10(lineNumber)))
          leader--;

        out.println(whitespace(leader) + lineNumberLabel + ":  " + line);
      }

      // Caret line (^)
      int spaces = column + Integer.toString(errorLineNumber).length() + 1;
      out.println("  " + whitespace(spaces) + "^\n");
    }
  }

  public String printErrors(List<AnnotatedError> errors) {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    printErrorsTo(new PrintStream(buffer), errors);
    String errorText = buffer.toString();

    System.out.print(errorText);
    return errorText;
  }

  public String file() {
    return file;
  }

  public boolean runMain() {
    return runMain;
  }

  public boolean hasErrors() {
    return staticErrors != null;
  }

  private void requireJavaImports(Set<RequireDecl> imports) {
    for (RequireDecl requireDecl : imports) {
      if (requireDecl.javaLiteral != null)
        try {
          Class.forName(requireDecl.javaLiteral);
        } catch (ClassNotFoundException e) {
          if (staticErrors == null)
            staticErrors = new ArrayList<AnnotatedError>();

          staticErrors.add(new StaticError("Unable to find Java type for import: "
              + requireDecl.javaLiteral, requireDecl.sourceLine, requireDecl.sourceColumn));
        }
    }
  }

  public Object main(String[] commandLine) {
    FunctionDecl main = scope.resolveFunction("main", false);
    if (main != null) {
      int args = main.arguments().children().size();
      if(commandLine == null)
        commandLine = new String[] {};
      try {
        if (args == 0)
          return compiled.getDeclaredMethod("main").invoke(null);
        else
          return compiled.getDeclaredMethod("main", Object.class).invoke(null, Arrays.asList(commandLine));
      } catch (NoSuchMethodException e) {
        System.out.println("Incorrect main method declaration in: " + file);
      } catch (InvocationTargetException e) {
        // Unwrap Java stack trace using our special wrapper exception.
        Throwable cause = e.getCause();
        StackTraceSanitizer.clean(cause);

        if (cause instanceof VerifyError)
          throw (Error) cause;

        // Rethrow cleaned up exception.
        if (cause instanceof RuntimeException)
          throw (RuntimeException) cause;

        throw new RuntimeException(cause);
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
      }
    } else {
      // Attempt to force class initialization.
      try {
        Class.forName(compiled.getName(), true, LoopClassLoader.CLASS_LOADER);
      } catch (ClassNotFoundException e) {
        throw new Error("Not supposed to happen. A previously loaded class disappeared.", e);
      }
    }
    return null;
  }

  public void compile() {
    Unit unit = parse(source);
    if (hasErrors())
      return;

    // Recursively loads and compiles all dependency modules.
    List<AnnotatedError> depErrors = unit.loadDeps(file);
    if (depErrors != null) {
      this.staticErrors = depErrors;
      return;
    }

    // Run the verifier just before we emit code.
    if (!verify(unit))
      return;

    AsmCodeEmitter codeEmitter = new AsmCodeEmitter(unit);
    this.scope = unit;
    this.compiled = codeEmitter.write(unit);

    requireJavaImports(unit.imports());

    this.source = null;
  }

  public void compileExpression(Unit scope) {
    this.scope = scope;

    if (!verify(scope))
      return;

    AsmCodeEmitter codeEmitter = new AsmCodeEmitter(scope);
    this.compiled = codeEmitter.write(scope);
    this.source = null;

    requireJavaImports(scope.requires());
  }

  public void compileClassOrFunction(Unit scope) {
    this.scope = scope;
    List<Token> tokens = new Tokenizer(source).tokenize();
    Parser parser = isLisp
        ? new SexprParser(tokens)
        : new LexprParser(tokens);
    FunctionDecl functionDecl = parser.functionDecl();
    ClassDecl classDecl = null;
    Node node;
    if (null == functionDecl) {
      classDecl = parser.classDecl();
      node = classDecl;
    } else
      node = functionDecl;

    if (hasErrors())
      return;

    if (node == null) {
      this.staticErrors = Arrays.<AnnotatedError>asList(
          new StaticError("malformed function definition",
          tokens.get(tokens.size() - 1)));
      return;
    }

    new Reducer(node).reduce();

    if (!verify(scope))
      return;

    // We don't need to actually compile this code, yet.

    this.source = null;
    requireJavaImports(scope.requires());

    if (functionDecl != null)
      scope.declare(functionDecl);
    else
      scope.declare(classDecl);
  }

  private static String whitespace(int amount) {
    StringBuilder builder = new StringBuilder(amount);
    for (int i = 0; i < amount; i++) {
      builder.append(' ');
    }
    return builder.toString();
  }

  public Class<?> getCompiled() {
    return compiled;
  }

  public void runMain(boolean runMain) {
    if (runMain)
      this.runMain = runMain;
  }

  @SuppressWarnings("unchecked")
  public List<AnnotatedError> getStaticErrors() {
    return (List) staticErrors;
  }

  public Scope getScope() {
    return scope;
  }
}
TOP

Related Classes of loop.Executable

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.