All Downloads are FREE. Search and download functionalities are using the official Maven repository.

water.rapids.Exec Maven / Gradle / Ivy

package water.rapids;


import water.Iced;
import water.Key;
import water.MRTask;

import java.util.HashSet;

/**
 * Exec is an interpreter of abstract syntax trees.
 *
 * Trees have a Lisp-like structure with the following "reserved" special characters:
 *
 *     '('   signals the parser to parse a function name, the next token is an identifier or a (single char) flag
 *     '#'   signals the parser to parse a double: attached_token
 *     '"'   signals the parser to parse a String (double quote): attached_token
 *     "'"   signals the parser to parse a String (single quote): attached_token
 *     '%'   signals a variable lookup: attached_token
 *     '!'   signals a variable set: attached_token
 *     '['   signals a column slice by index - R handles all named to int conversions (as well as 1-based to 0-based)
 *     'def' signals the parser to a parse a function: (def name args body).
 *     '='   signals the parser to assign the RHS to the LHS.
 *     'g'   signals >
 *     'G'   signals >=
 *     'l'   signals <
 *     'L'   signals <=
 *     'n'   signals ==
 *     'N'   signals !=
 *     'not'   signals negation (!)
 *     '{'   signals the parser to begin parsing a ';'-separated array of flagged inputs (#, %, ", ') (ASTSeries is the resulting AST)
 *
 * In the above, attached_token signals that the special char has extra chars that must be parsed separately. These are
 * variable names (in the case of % and !), doubles (in the case of #), or Strings (in the case of ' and ").
 *
 * Everything else is a function call (prefix/infix/func) and has a leading char of '('.
 */
public class Exec extends Iced {

  //parser
  final byte[] _ast;
  final String _str;
  int _x;

  //global env
  final Env _env;

  public Exec(String ast, Env env) {
    _str = ast;
    _ast = ast == null ? null : ast.getBytes();
    _env = env;
  }

  public static Env exec( String str ) throws IllegalArgumentException {
    cluster_init();
    // Preload the global environment from existing Frames
    HashSet locked = new HashSet<>();
    Env env = Env.make(locked);

    try {
      Exec ex = new Exec(str, env);

      // Parse
      AST ast = ex.parse();
      if (!ex.allDone()) throwErr("Note that only a single statement can be processed at a time. Junk at the end of the statement: ",ex);

      // Execute
      env = ast.treeWalk(env);

      // Write back to DKV (if needed) and return
      env.postWrite();

    } catch( RuntimeException t ) {
      env.remove_and_unlock();
      throw t;
    }
    return env;
  }

  public static void new_func(final String str) throws IllegalArgumentException {
    cluster_init();

    new MRTask() {
      @Override public void setupLocal() {
        HashSet locked = new HashSet<>();
        Env env = Env.make(locked);
        Exec ex = new Exec(str, env);
        ex.parse_fun();
      }
    }.doAllNodes();
  }

  protected AST parse() {
    skipWS();
    // Parse a token --> look for a function or a special char.
    if (!hasNext()) throw new IllegalASTException("End of input unexpected. Badly formed AST.");
    String tok = parseID();
    if (!hasNext()) throw new IllegalASTException("End of input unexpected. Badly formed AST.");
    //lookup of the token
    AST ast = lookup(tok);
    return ast.parse_impl(this);
  }

  protected void parse_fun() {
    // parse a token -> should be "def"
    String tok = parseID();
    if (!tok.equals("def")) throw new IllegalArgumentException("Expected function definition but got "+tok);
    ASTFuncDef ast = new ASTFuncDef();
    ast.parse_func(this);
  }

  private AST lookup(String tok) {
    AST sym = ASTOp.SYMBOLS.get(tok);
    if (sym != null) return sym.make();
    sym = ASTOp.UDF_OPS.get(tok);
    if (sym != null) return sym.make();
    throw new IllegalArgumentException("*Unimplemented* failed lookup on token: `"+tok+"`. Contact [email protected] for more information.");
  }

  String parseID() {
    StringBuilder sb = new StringBuilder();
    if (peek() == '(') { // eat the '(' and any ws.
      _x++; skipWS();
      if( peek() == ')' ) { sb.append((char)_ast[_x++]); return sb.toString(); } // handles the case where we have a lisp-like null: ()
      return parseID();  // peel out the ID
    }
    if ( isSpecial(peek())) { return sb.append((char)_ast[_x++]).toString(); } // if attached_token, then use parse_impl
    while( _x < _ast.length && _ast[_x] != ' ' && _ast[_x] != ')' && _ast[_x] != ';' && _ast[_x]!= '\'' && _ast[_x]!='\"' ) {  // while not WS...
      sb.append((char)_ast[_x++]);
    }
    skipWS();
    return sb.toString();
  }

  String parseString(char eq) {
    StringBuilder sb = new StringBuilder();
    while(_ast[_x] != eq) {
      sb.append((char)_ast[_x++]);
    }
    _x++;
    return sb.toString();
  }

  boolean hasNext() { return _x < _ast.length; }
  boolean allDone() {
    skipWS();
    if( _x >= _ast.length ) return true;
    while( isEnd() && _x < _ast.length ) {
      _x++; skipWS();
    }
    return _x >= _ast.length;
  }
  double nextDbl() {
    AST a = parse();
    if( a instanceof ASTNum) return ((ASTNum)a)._d;
    else throw new IllegalArgumentException("Expected to parse a number, but got " + a.getClass());
  }

  String nextStr() {
    AST a = parse();
    // did it get caught by the horrible hack to auto-lookup strings?
    if( a instanceof ASTFrame ) return ((ASTFrame)a)._key;
    else if( a instanceof ASTString ) return ((ASTString)a)._s;
    else throw new IllegalArgumentException("Expected to parse a String, but got " + a.getClass());
  }

  Exec xpeek(char c) {
    assert _ast[_x] == c : "Expected '"+c+"'. Got: '"+(char)_ast[_x]+"'. unparsed: "+ unparsed() + " ; _x = "+_x;
    _x++; return this;
  }

  char ppeek() { return (char)_ast[_x-1];}  // past peek
  char peek() { return (char)_ast[_x]; }    // ppek ahead
  char peekPlus() { skipWS(); return (char)_ast[_x++]; } // peek and move ahead
  boolean isEnd() { return _x >= _ast.length || (char) _ast[_x] == ')'; } // out of chars OR end of AST (signaled by ')' )
  void eatEnd() {
    skipWS();
    if( !isEnd() ) throwErr("No end to eat!",this);
      _x++;
    skipWS();
  }
  Exec skipWS() {
    while (true) {
      if (_x >= _ast.length) break;
      if (peek() == ' ' || peek() == ';') {
        _x++;
        continue;
      }
      break;
    }
    return this;
  }

  boolean isSpecial(char c) { return c == '\"' || c == '\'' || c == '#' || c == '!' || c == '%' || c =='{'; }
  boolean isQuoted(char c) { return c == '\"' || c == '\''; }
  char getQuote() { return (char)_ast[_x++]; }
  String unparsed() { return new String(_ast,_x,_ast.length-_x); }

  static AST throwErr( String msg, Exec E) {
    int idx = E._ast.length-1;
    int lo = E._x, hi=idx;

    String str = E._str;
    if( idx < lo ) { lo = idx; hi=lo; }
    String s = msg+ '\n'+str+'\n';
    int i;
    for( i=0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy