/* e.g. overlay <- ScreenOverlay bounds <- rightScreenBounds overlay bounds bounds overlay show Can separate commands with ";" also. For how to define functions in a script see #1033988. Note: LeftArrowScriptAutoCompleter uses a lot of this class's internals */ sclass GazelleV_LeftArrowScriptParser > SimpleLeftToRightParser { replace Toolbox with GazelleV_LeftArrowScript. delegate Script to Toolbox. delegate Evaluable to Toolbox. delegate FunctionDef to Toolbox. replace const with _const. settable new G22Utils g22utils; new L functionContainers; new LinkedHashMap knownVars; BuildingScript currentReturnableScript; BuildingScript currentLoop; bool inParens; int idCounter; // record which vars are known at current parse location event knownVarsSnapshot(Map knownVars); replace print with if (GazelleV_LeftArrowScriptParser.this.scaffoldingEnabled()) print. replace printVars with if (GazelleV_LeftArrowScriptParser.this.scaffoldingEnabled()) printVars. // object can be a class srecord MethodOnObject(O object, S method) {} srecord EvaluableWrapper(Evaluable expr) {} class BuildingScript { int id = ++idCounter; bool returnable, isLoopBody; BuildingScript returnableParent, loopParent; new Script script; new L steps; Map functionDefs = new Map; *(bool *returnable) {} *(bool *returnable, bool *isLoopBody) {} void add(Evaluable step) { if (step != null) steps.add(step); } Evaluable get() { // if the last command is a return from THIS script, // convert into a simple expression var lastStep = last(steps); if (lastStep cast Toolbox.ReturnFromScript) if (lastStep.script == script) replaceLast(steps, lastStep.value); // if the script is only step, is not returnable // and defines no functions, replace it with its only step if (!returnable && l(steps) == 1 && empty(functionDefs)) ret first(steps); // make and return actual script script.functionDefs = functionDefs; script.steps = toTypedArray(Evaluable.class, steps); ret script; } S toStringLong() { ret pnlToLines(steps); } toString { ret formatRecordVars BuildingScript(+id, +returnable , +returnableParent, +script); } } // end of BuildingScript Script parse(S text) { setText(text); ret parse(); } Script parse() { ret (Script) parseScript(true); } // if returnable is true, it's always a Script Evaluable parseScript(bool returnable, bool isLoopBody default false) { BuildingScript script = new(returnable, isLoopBody); script.returnableParent = currentReturnableScript; script.loopParent = currentLoop; if (returnable) currentReturnableScript = script; if (isLoopBody) currentLoop = script; ret parseScript(script); } Evaluable parseScript(BuildingScript script) { try { parseScript_2(script); var builtScript = script!; currentReturnableScript = script.returnableParent; currentLoop = script.loopParent; ret builtScript; } catch e { print("Parsed so far:\n" + script); throw rethrowAndAppendToMessage(e, squareBracketed(str(lineAndColumn()))); } } void parseScript_2(BuildingScript script) { new AssureAdvance assure; while (assure!) { // for auto-completer knownVarsSnapshot(knownVars); if (is(";")) continue with next(); if (isOneOf("}", ")")) break; if (is("def")) continue with parseFunctionDefinition(); if (is("param")) { consume(); S var = assertIdentifier(tpp()); knownVars.put(var, new LASValueDescriptor); continue; } if (isOneOf("return", "ret")) { consume(); Evaluable expr; if (atCmdEnd()) expr = const(null); else expr = parseAssignmentOrExpr(); continue with script.add(new Toolbox.ReturnFromScript(currentReturnableScript.script, expr)); } if (is("continue")) { consume(); assertCmdEnd(); if (currentLoop == null) fail("continue outside of loop"); continue with script.add(new Toolbox.Continue(currentLoop.script)); } if (is("temp")) { consume(); Evaluable tempExpr = parseExpr(); print(+tempExpr); Evaluable body = parseScript(false); print(+body); continue with script.add(new Toolbox.TempBlock(tempExpr, body)); } script.add(parseAssignmentOrExpr()); } knownVarsSnapshot(knownVars); } Evaluable parseAssignmentOrExpr() { try object parseAssignmentOpt(); ret parseExpr(); } Toolbox.Assignment parseAssignmentOpt() { S t = token(); if (isIdentifier(t) && eq(token(1), "<") && eq(token(2), "-")) { print("Found assignment"); next(3); Evaluable rhs = parseExpr(); knownVars.put(t, new LASValueDescriptor); ret new Toolbox.Assignment(t, rhs); } null; } Evaluable parseOptionalInnerExpression() { printVars parseOptionalInnerExpression(+token()); if (atCmdEnd() || is("{")) null; ret parseInnerExpr(); } Evaluable const(O o) { ret new Toolbox.Const(o); } Evaluable parseInnerExpr() { ret parseExpr(true); } Evaluable parseExpr(bool inner default false) { if (atEnd()) null; S t = token(); printVars parseExpr(token := t); if (is(";")) null; // empty command if (is("{")) ret parseCurlyBlock(false); // int or double literal if (is("-") && empty(nextSpace()) && startsWithDigit(token(1)) || startsWithDigit(t)) { t = consumeMultiTokenLiteral(); if (swic(t, "0x")) ret const(parseHexInt(dropFirst(t, 2))); if (swic(t, "0b")) ret const(intFromBinary(dropFirst(t, 2))); if (isInteger(t)) ret const(parseInt(t)); if (endsWith(t, "f")) ret const(parseFloat(t)); ret const(parseDouble(t)); } if (isQuoted(t)) { consume(); ret const(unquote(t)); } if (isIdentifier(t)) { if (is("while")) ret parseWhileLoop(); if (is("for")) ret parseForEach(); if (is("if")) ret parseIfStatement(); if (is("repeat")) ret parseRepeatStatement(); if (is("outer")) { consume(); var a = parseAssignmentOpt(); assertNotNull("Assignment expected", a); ret new Toolbox.AssignmentToOuterVar(a.var, a.expression); } consume(); print("Consumed identifier " + t + ", next token: " + token() + ", inner: " + inner); ret parseExprStartingWithIdentifier(t, inner); } // nested expression if (eq(t, "(")) { bool inParensOld = inParens; inParens = true; consume(); //print("Consumed opening parens (level " + parenLevels + ")"); var e = parseExpr(); //print("Consuming closing parens for expr: " + e + " (closing paren level " + parenLevels + ")"); consume(")"); inParens = inParensOld; ret inner ? e : parseCall(e); } if (isOneOf("&", "|") && empty(nextSpace()) && is(1, token())) { ret parseBinaryOperator(); } fail("Identifier, literal, operator or opening parentheses expected (got: " + quote(t)); } Evaluable parseBinaryOperator() { bool and = is("&"); next(2); Evaluable a = parseInnerExpr(); Evaluable b = parseInnerExpr(); ret and ? new Toolbox.BoolAnd(a, b) : new Toolbox.BoolOr(a, b); } // t is last consumed token (the identifier the expression starts with) Evaluable parseExprStartingWithIdentifier(S t, bool inner) { if (eq(t, "true")) ret const(true); if (eq(t, "false")) ret const(false); if (eq(t, "null")) ret const(null); if (eq(t, "new")) { S className = assertIdentifier(tpp()); O o = findExternalObject(className); if (o cast Class) ret new Toolbox.NewObject(o, parseArguments()); throw new ClassNotFound(className); } var type = knownVars.get(t); if (type != null) { var e = new Toolbox.GetVar(t).returnType(type); print("Found var acccess: " + e + ", " + (!inner ? "Checking for call" : "Returning expression")); ret inner ? e : parseCall(e); } if (!inner) { var fdef = lookupFunction(t); if (fdef != null) ret new Toolbox.CallFunction(fdef, parseArguments()); } if (eq(t, "_context")) ret new Toolbox.GetVarContext; O o = findExternalObject(t); if (o == null) throw new UnknownObject(t); else if (o cast EvaluableWrapper) { ret inner ? o.expr : parseCall(o.expr); } else if (inner) ret const(o); else if (o cast Class) { // expression starts with a class Class c = o; // new without new if (is("(")) ret new Toolbox.NewObject(c, parseArguments()); /*if (atCmdEnd()) ret new Toolbox.NewObject(o);*/ /* old object creation syntax (e.g. Pair new a b) if (is("new")) { next(); ret new Toolbox.NewObject(c, parseArguments()); } else*/ // check if it's a lambda definition first (with ->) try object parseLambdaOpt(c); if (isIdentifier()) { S name = tpp(); // We have now parsed a class (c) plus an as yet unknown identifier (name) right next to it // look for a static method call if (hasMethodNamed(c, name)) ret new Toolbox.CallMethod(const(c), name, parseArguments()); // if the class is an interface, it's a lambda method reference or a lambda if (isInterface(c)) ret parseLambdaMethodRef(c, name); // look for field second var field = getField(o, name); if (field != null) { assertCmdEnd(); if (!isStaticField(field)) fail(field + " is not a static field"); ret new Toolbox.GetStaticField(field); } fail(name + " not found in " + o + " (looked for method or field)"); } else fail("Method name expected: " + token()); } else if (o cast MethodOnObject) { if (inner) fail("Can't call methods in arguments"); ret new Toolbox.CallMethod(const(o.object), o.method, parseArguments()); } else ret parseCall(const(o)); } // parse lambda, e.g. IF1 a -> plus a a // returns null if it's not a lambda Evaluable parseLambdaOpt(Class c) { int nArgs = 0; while (isIdentifier(token(nArgs))) nArgs++; if (!(is(nArgs, "-") && is(nArgs+1, ">"))) null; // not a lambda // read parameter names and add to known variables S[] argNames = consumeArray(nArgs); temp tempAddKnownVars(argNames); skip(2); // skip the arrow // parse lambda body as returnable script // TODO: avoid script overhead if there are no return calls inside Evaluable body = parseScript(true); // return lambda ret new Toolbox.LambdaDef(c, argNames, body); } // method ref, e.g. "IF1 l" meaning "l1 l" // also, constructor ref, e.g. "IF1 Pair" meaning (x -> new Pair(x)) // // c must be a single method interface Evaluable parseLambdaMethodRef(Class c, S name) { var fdef = lookupFunction(name); if (fdef != null) { Evaluable[] curriedArguments = parseArguments(); ret new Toolbox.CurriedScriptFunctionLambda(c, fdef, curriedArguments); } O function = findExternalObject(name); if (function == null) throw new UnknownObject(name); if (function cast MethodOnObject) { O target = function.object; S targetMethod = function.method; Evaluable[] curriedArguments = parseArguments(); ret new Toolbox.CurriedMethodLambda(c, target, targetMethod, curriedArguments); } else if (function cast Class) { Class c2 = function; // No currying here yet assertCmdEnd(); var ctors = constructorsWithNumberOfArguments(c2, 1); if (empty(ctors)) fail("No single argument constructor found in " + c2); ret new Toolbox.CurriedConstructorLambda(c, toArray Constructor(ctors), null); } else fail(function + " is not an instantiable class or callable method"); } FunctionDef lookupFunction(S name) { var script = currentReturnableScript; while (script != null) { var f = script.functionDefs.get(name); printVars("lookupFunction", +script, +name, +f); if (f != null) ret f; script = script.returnableParent; } null; } Evaluable[] parseArguments() { ret toArrayOrNull(Evaluable.class, parseArgumentsAsList()); } L parseArgumentsAsList() { //ret collectWhileNotNull(-> parseOptionalInnerExpression()); new L l; try { while (true) { Evaluable a = parseOptionalInnerExpression(); if (a == null) break; l.add(a); } ret l; } on fail { print("Arguments parsed so far: " + l); } } S consumeMultiTokenLiteral() { ret consumeUntilSpaceOr(-> atCmdEnd()); } bool atCmdEnd() { ret !inParens && atEndOrLineBreak() || is(";") || is("}") || is(")"); } void assertCmdEnd() { if (!atCmdEnd()) fail("Expected end of command, token is: " + quote(token())); } Evaluable parseCall(Evaluable target) { if (atCmdEnd() || !isIdentifier()) ret target; var start = ptr(); S methodName = tpp(); var args = parseArguments(); if (nempty(args)) ret new Toolbox.CallMethod(target, methodName, args); else ret src(start, new Toolbox.CallMethodOrGetField(target, methodName)); } A src(TokPtr start, A a) { if (a cast IHasTokenRangeWithSrc) a.setTokenRangeWithSrc(TokenRangeWithSrc(start, ptr())); ret a; } // can return MethodOnObject swappable O findExternalObject(S name) ctex { //try object findClassThroughDefaultClassFinder(name); //try object findClassInStandardImports(name); S fullName = globalClassNames().get(name); if (fullName != null) ret Class.forName(fullName); fOr (container : functionContainers) { if (hasMethodNamed(container, name)) ret new MethodOnObject(container, name); var field = getField(container, name); if (field != null && isStaticField(field)) ret new EvaluableWrapper(new Toolbox.GetStaticField(field)); } null; } selfType allowTheWorld() { ret allowTheWorld(mc()); } // first containers within argument list have priority // last calls to allowTheWorld have priority selfType allowTheWorld(O... functionContainers) { fOr (O o : reversed(functionContainers)) if (!contains(this.functionContainers, o)) { this.functionContainers.add(0, o); globalClassNames_cache = null; // recalculate } this; } void printFunctionDefs(Script script) { print(values(script.functionDefs)); } AutoCloseable tempAddKnownVars(S... vars) { ret tempAddKnownVars(asList(vars)); } AutoCloseable tempAddKnownVars(Iterable vars) { ret tempMapPutAll(knownVars, mapWithSingleValue(vars, new LASValueDescriptor)); } void parseFunctionDefinition() { consume("def"); S functionName = assertIdentifier(tpp()); new LS args; while (isIdentifier()) args.add(tpp()); temp tempAddKnownVars(args); var functionBody = parseCurlyBlock(true); print("Defined function " + functionName + " in " + currentReturnableScript); currentReturnableScript.functionDefs.put(functionName, new Toolbox.FunctionDef(functionName, args, functionBody)); } Evaluable parseCurlyBlock(bool returnable, bool isLoopBody default false) { //print(+knownVars); consume("{"); bool inParensOld = inParens; inParens = false; var script = parseScript(returnable, isLoopBody); consume("}"); inParens = inParensOld; ret script; } Evaluable parseWhileLoop() { consume("while"); var condition = parseExpr(); var body = parseCurlyBlock(false, true); ret new Toolbox.While(condition, body); } Evaluable parseForEach() { ret new ParseForEach()!; } class ParseForEach { Evaluable collection, body; IF0 finish; IF0 enterBody; Evaluable get() { consume("for"); int iIn = relativeIndexOf("in"); if (iIn < 0) fail("for without in"); print(+iIn); if (iIn == 1) { // just a variable S var = consumeIdentifier(); enterBody = -> tempAddKnownVars(var); finish = -> new Toolbox.ForEach(collection, var, body); } else if (iIn == 3) { if (isOneOf("pair", "Pair")) { // "pair a b" or "Pair a b" consume(); S varA = consumeIdentifier(); S varB = consumeIdentifier(); printVars(+varA, +varB); enterBody = -> tempAddKnownVars(varA, varB); finish = -> new Toolbox.ForPairs(collection, body, varA, varB); } else { // "a, b" S varA = consumeIdentifier(); consume(","); S varB = consumeIdentifier(); enterBody = -> tempAddKnownVars(varA, varB); finish = -> new Toolbox.ForKeyValue(collection, body, varA, varB); } } else if (iIn == 4) { consume("index"); S varIndex = consumeIdentifier(); consume(","); S varElement = consumeIdentifier(); enterBody = -> tempAddKnownVars(varIndex, varElement); finish = -> new Toolbox.ForIndex(collection, body, varIndex, varElement); } else fail("Unknown pattern for 'for' loop"); consume("in"); collection = parseExpr(); print(+collection); temp enterBody!; body = parseCurlyBlock(false, true); ret finish!; } } Evaluable parseIfStatement() { consume("if"); var condition = parseExpr(); var body = parseCurlyBlock(false); Evaluable elseBranch = null; if (is("else")) { consume(); if (is("if")) elseBranch = parseIfStatement(); else elseBranch = parseCurlyBlock(false); } ret new Toolbox.IfThen(condition, body, elseBranch); } Evaluable parseRepeatStatement() { consume("repeat"); var n = parseExpr(); var body = parseCurlyBlock(false); ret new Toolbox.RepeatN(n, body); } // declare an external variable with optional type info void addVar(S var, LASValueDescriptor type default new) { knownVars.put(var, type); } void addVar(S var, Class type, bool canBeNull) { addVar(var, new LASValueDescriptor.NonExact(type, canBeNull); } // short name to full name // TODO: sort by priority if classes appear twice simplyCached SS globalClassNames() { var packages = mapToTreeSet(importedPackages(), pkg -> pkg + "."); // add inner classes of function containers var classContainers = classContainerPrefixes(); new SS out; for (className : g22utils.classNameResolver().allFullyQualifiedClassNames()) { if (isAnonymousClassName(className)) continue; if (!contains(className, '$')) { S pkg = longestPrefixInTreeSet(className, packages); if (pkg != null) { S shortName = dropPrefix(pkg, className); if (!shortName.contains(".")) out.put(shortName, className); } } S container = longestPrefixInTreeSet(className, classContainers); if (container != null) out.put(dropPrefix(container, className), className); } ret out; } swappable Cl importedPackages() { ret itemPlus("java.lang", standardImports_fullyImportedPackages()); } TreeSet classContainerPrefixes() { ret mapToTreeSet(functionContainers, fc -> className(fc) + "$"); } // typed exceptions (unusual, I know!) srecord noeq UnknownObject(S name) > RuntimeException { public S getMessage() { ret "Unknown object: " + name; } } sclass ClassNotFound > UnknownObject { *(S className) { super(className); } public S getMessage() { ret "Class not found: " + name; } } } // end of GazelleV_LeftArrowScriptParser