Package loop

Source Code of loop.LoopShell$MetaCommandCompleter

package loop;

import jline.console.ConsoleReader;
import jline.console.completer.Completer;
import jline.console.completer.FileNameCompleter;
import loop.ast.Assignment;
import loop.ast.Node;
import loop.ast.Variable;
import loop.ast.script.FunctionDecl;
import loop.ast.script.ModuleDecl;
import loop.ast.script.ModuleLoader;
import loop.ast.script.RequireDecl;
import loop.ast.script.Unit;
import loop.lang.LoopObject;
import loop.runtime.Closure;

import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* @author dhanji@gmail.com (Dhanji R. Prasanna)
*/
public class LoopShell {
  private static Map<String, Object> shellContext;

  public static Object shellObtain(String var) {
    return shellContext.get(var);
  }

  public static void shell() throws Exception {
    System.out.println("loOp (http://looplang.org)");
    System.out.println("     by Dhanji R. Prasanna\n");

    ConsoleReader reader = new ConsoleReader();
    try {
      reader.setExpandEvents(false);
      reader.addCompleter(new MetaCommandCompleter());

      Unit shellScope = new Unit(null, ModuleDecl.SHELL);
      FunctionDecl main = new FunctionDecl("main", null);
      shellScope.declare(main);
      shellContext = new HashMap<String, Object>();

      boolean inFunction = false;

      // Used to build up multiline statement blocks (like functions)
      StringBuilder block = null;
      //noinspection InfiniteLoopStatement
      do {
        String prompt = (block != null) ? "|    " : ">> ";
        String rawLine = reader.readLine(prompt);

        if (inFunction) {
          if (rawLine == null || rawLine.trim().isEmpty()) {
            inFunction = false;

            // Eval the function to verify it.
            printResult(Loop.evalClassOrFunction(block.toString(), shellScope));
            block = null;
            continue;
          }

          block.append(rawLine).append('\n');
          continue;
        }

        if (rawLine == null) {
          quit(reader);
        }

        //noinspection ConstantConditions
        String line = rawLine.trim();
        if (line.isEmpty())
          continue;

        // Add a require import.
        if (line.startsWith("require ")) {
          shellScope.declare(new LexprParser(new Tokenizer(line + '\n').tokenize()).require());
          shellScope.loadDeps("<shell>");
          continue;
        }

        if (line.startsWith(":q") || line.startsWith(":quit")) {
          quit(reader);
        }

        if (line.startsWith(":h") || line.startsWith(":help")) {
          printHelp();
        }

        if (line.startsWith(":run")) {
          String[] split = line.split("[ ]+", 2);
          if (split.length < 2 || !split[1].endsWith(".loop"))
            System.out.println("You must specify a .loop file to run.");
          Loop.run(split[1]);
          continue;
        }

        if (line.startsWith(":r") || line.startsWith(":reset")) {
          System.out.println("Context reset.");
          shellScope = new Unit(null, ModuleDecl.SHELL);
          main = new FunctionDecl("main", null);
          shellScope.declare(main);
          shellContext = new HashMap<String, Object>();
          continue;
        }
        if (line.startsWith(":i") || line.startsWith(":imports")) {
          for (RequireDecl requireDecl : shellScope.imports()) {
            System.out.println(requireDecl.toSymbol());
          }
          System.out.println();
          continue;
        }
        if (line.startsWith(":f") || line.startsWith(":functions")) {
          for (FunctionDecl functionDecl : shellScope.functions()) {
            StringBuilder args = new StringBuilder();
            List<Node> children = functionDecl.arguments().children();
            for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
              Node arg = children.get(i);
              args.append(arg.toSymbol());

              if (i < childrenSize - 1)
                args.append(", ");
            }

            System.out.println(functionDecl.name()
                + ": (" + args.toString() + ")"
                + (functionDecl.patternMatching ? " #pattern-matching" : "")
            );
          }
          System.out.println();
          continue;
        }
        if (line.startsWith(":t") || line.startsWith(":type")) {
          String[] split = line.split("[ ]+", 2);
          if (split.length <= 1) {
            System.out.println("Give me an expression to determine the type for.\n");
            continue;
          }

          Object result = evalInFunction(split[1], main, shellScope, false);
          printTypeOf(result);
          continue;
        }

        if (line.startsWith(":javatype")) {
          String[] split = line.split("[ ]+", 2);
          if (split.length <= 1) {
            System.out.println("Give me an expression to determine the type for.\n");
            continue;
          }

          Object result = evalInFunction(split[1], main, shellScope, false);
          if (result instanceof LoopError)
            System.out.println(result.toString());
          else
            System.out.println(result == null ? "null" : result.getClass().getName());
          continue;
        }

        // Function definitions can be multiline.
        if (line.endsWith("->") || line.endsWith("=>")) {
          inFunction = true;
          block = new StringBuilder(line).append('\n');
          continue;
        } else if (isDangling(line)) {
          if (block == null)
            block = new StringBuilder();

          block.append(line).append('\n');
          continue;
        }

        if (block != null) {
          rawLine = block.append(line).toString();
          block = null;
        }

        // First determine what kind of expression this is.
        main.children().clear();

        // OK execute expression.
        try {
          printResult(evalInFunction(rawLine, main, shellScope, true));
        } catch (ClassCastException e) {
          StackTraceSanitizer.cleanForShell(e);
          System.out.println("#error: " + e.getMessage());
          System.out.println();
        } catch (RuntimeException e) {
          StackTraceSanitizer.cleanForShell(e);
          e.printStackTrace();
          System.out.println();
        }

      } while (true);
    } catch (IOException e) {
      System.err.println("Something went wrong =(");
      reader.getTerminal().reset();
      System.exit(1);
    }
  }

  private static void printHelp() {
    System.out.println("loOp Shell v1.0");
    System.out.println("  :run <file.loop>  - executes the specified loop file");
    System.out.println("  :reset            - discards current shell context (variables, funcs, etc.)");
    System.out.println("  :imports          - lists all currently imported loop modules and Java types");
    System.out.println("  :functions        - lists all currently defined functions by signature");
    System.out.println("  :type <expr>      - prints the type of the given expression");
    System.out.println("  :javatype <expr>  - prints the underlying java type (for examining loop internals)");
    System.out.println("  :quit (or Ctrl-D) - exits the loop shell");
    System.out.println("  :help             - prints this help card");
    System.out.println();
    System.out.println("  Hint :h is short for :help, etc.");
  }

  private static void printTypeOf(Object result) {
    if (result instanceof LoopError)
      System.out.println(result.toString());
    else if (result instanceof LoopObject)
      System.out.println(((LoopObject)result).getType());
    else if (result instanceof Closure)
      System.out.println("#function: " + ((Closure)result).name);
    else
      System.out.println(result == null ? "Nothing" : "#java: " + result.getClass().getName());
  }

  private static Object evalInFunction(String rawLine,
                                       FunctionDecl func,
                                       Unit shellScope,
                                       boolean addToWhereBlock) {
    rawLine = rawLine.trim() + '\n';
    Executable executable = new Executable(new StringReader(rawLine));
    Node parsedLine;
    try {
      Parser parser = new LexprParser(new Tokenizer(rawLine).tokenize(), shellScope);
      parsedLine = parser.line();
      if (parsedLine == null || !parser.getErrors().isEmpty()) {
        executable.printErrors(parser.getErrors());
        return "";
      }

      // If this is an assignment, just check the rhs portion of it.
      // This is a bit hacky but prevents verification from balking about new
      // vars declared in the lhs.
      if (parsedLine instanceof Assignment) {
        new Reducer(parsedLine).reduce();
        Assignment assignment = (Assignment) parsedLine;

        // Strip the lhs of the assignment if this is a simple variable setter
        // as that will happen after the fact in a where-block.
        // However we still do have Assignment nodes that assign "in-place", i.e.
        // mutate the state of existing variables (example: a.b = c), and these need
        // to continue untouched.
        if (assignment.lhs() instanceof Variable)
          func.children().add(assignment.rhs());
        else
          func.children().add(parsedLine);
      } else
        func.children().add(parsedLine);

      // Compress nodes and eliminate redundancies.
      new Reducer(func).reduce();

      shellScope.loadDeps("<shell>");
      executable.runMain(true);
      executable.compileExpression(shellScope);

      if (executable.hasErrors()) {
        executable.printStaticErrorsIfNecessary();

        return "";
      }
    } catch (Exception e) {
      e.printStackTrace();
      return new LoopError("malformed expression " + rawLine);
    }

    try {
      Object result = Loop.safeEval(executable, null);

      if (addToWhereBlock && parsedLine instanceof Assignment) {
        Assignment assignment = (Assignment) parsedLine;

        boolean shouldReplace = false, shouldAddToWhere = true;
        if (assignment.lhs() instanceof Variable) {
          String name = ((Variable) assignment.lhs()).name;
          shellContext.put(name, result);

          // Look up the value of the RHS of the variable from the shell context,
          // if this is the second reference to the same variable.
          assignment.setRhs(new LexprParser(new Tokenizer(
              "`loop.LoopShell`.shellObtain('" + name + "')").tokenize()).parse());
          shouldReplace = true;
        } else
          shouldAddToWhere = false;   // Do not add state-mutating assignments to where block.

        // If this assignment is already present in the current scope, we should replace it.
        if (shouldReplace)
          for (Iterator<Node> iterator = func.whereBlock().iterator(); iterator.hasNext(); ) {
            Node node = iterator.next();
            if (node instanceof Assignment && ((Assignment) node).lhs() instanceof Variable) {
              iterator.remove();
            }
          }

        if (shouldAddToWhere)
          func.declareLocally(parsedLine);
      }

      return result;
    } finally {
      ModuleLoader.reset();     // Cleans up the loaded classes.
    }
  }

  private static void printResult(Object result) {
    if (result instanceof Closure) {
      Closure fun = (Closure) result;
      System.out.println("#function: " + fun.name);
    } else if (result instanceof Set) {
      String r = result.toString();
      System.out.println('{' + r.substring(1, r.length() - 1) + '}');
    }
    else
      System.out.println(result == null ? "#nothing" : result);
  }

  private static boolean isLoadCommand(String line) {
    return line.startsWith(":run");
  }

  private static void quit(ConsoleReader reader) throws Exception {
    reader.getTerminal().restore();

    System.out.println("Bye.");
    System.exit(0);
  }

  // For tracking multiline expressions.
  private static int braces = 0, brackets = 0, parens = 0;

  private static boolean isDangling(String line) {
    for (Token token : new Tokenizer(line).tokenize()) {
      switch (token.kind) {
        case LBRACE:
          braces++;
          break;
        case LBRACKET:
          brackets++;
          break;
        case LPAREN:
          parens++;
          break;
        case RBRACE:
          braces--;
          break;
        case RBRACKET:
          brackets--;
          break;
        case RPAREN:
          parens--;
          break;
      }
    }

    return braces > 0 || brackets > 0 || parens > 0;
  }

  private static class MetaCommandCompleter implements Completer {
    private final List<String> commands = Arrays.asList(
        ":help",
        ":run",
        ":quit",
        ":reset",
        ":type",
        ":imports",
        ":javatype",
        ":functions"
    );

    private final FileNameCompleter fileNameCompleter = new FileNameCompleter();

    @Override public int complete(String buffer, int cursor, List<CharSequence> candidates) {
      if (buffer == null) {
        buffer = "";
      } else
        buffer = buffer.trim();

      // See if we should chain to the filename completer first.
      if (isLoadCommand(buffer)) {
        String[] split = buffer.split("[ ]+");

        // Always complete the first argument.
        if (split.length > 1)
          return fileNameCompleter.complete(split[split.length - 1], cursor, candidates);
      }

      for (String command : commands) {
        if (command.startsWith(buffer)) {
          candidates.add(command.substring(buffer.length()) + ' ');
        }
      }

      return cursor;
    }
  }
}
TOP

Related Classes of loop.LoopShell$MetaCommandCompleter

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.