/* 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. replace const with _const. settable new G22Utils g22utils; L functionContainers; new LinkedHashSet knownVars; BuildingScript currentReturnableScript; int parenLevels; // object can be a class srecord MethodOnObject(O object, S method) {} class BuildingScript { bool returnable; new Script script; new L steps; Map functionDefs = new Map; *(bool *returnable) {} 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(+script, +returnable); } } Script parse(S text) { setText(text); ret (Script) parseScript(true); } // if returnable is true, it's always a Script Evaluable parseScript(bool returnable) { BuildingScript script = new(returnable); var lastReturnableScript = currentReturnableScript; if (returnable) currentReturnableScript = script; try { parseScript_2(script); var builtScript = script!; currentReturnableScript = lastReturnableScript; ret builtScript; } catch e { print("Parsed so far:\n" + script); throw rethrowAndAppendToMessage(e, squareBracketed(str(lineAndColumn()))); } } void parseScript_2(BuildingScript script) { while (mainLoop()) { if (is(";")) continue with next(); if (is("}")) break; S t = token(); print("First token of command: " + t); if (is("def")) continue with parseFunctionDefinition(); if (is("while")) continue with script.add(parseWhileLoop()); if (is("for")) continue with script.add(parseForEach()); if (is("if")) continue with script.add(parseIfStatement()); // return is just ignored for now if (is("return")) { consume(); var expr = parseExpr(); continue with script.add(new Toolbox.ReturnFromScript(currentReturnableScript.script, expr)); } print("next tokens: " + quote(token(1)) + " " + quote(token(2))); if (isIdentifier(t) && eq(token(1), "<") && eq(token(2), "-")) { print("Found assignment"); next(3); knownVars.add(t); script.add(new Toolbox.Assignment(t, parseExpr())); } else script.add(parseExpr()); } } 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 // int or double literal if (is("-") && empty(nextSpace()) && startsWithDigit(token(1)) || startsWithDigit(t)) { t = consumeMultiTokenLiteral(); ret isInteger(t) ? const(parseInt(t)) : const(parseDouble(t)); } if (isQuoted(t)) { consume(); ret const(unquote(t)); } if (isIdentifier(t)) { consume(); print("Consumed identifier " + t + ", next token: " + token() + ", inner: " + inner); ret parseExprStartingWithIdentifier(t, inner); } // nested expression if (eq(t, "(")) { parenLevels++; consume(); print("Consumed opening parens (level " + parenLevels + ")"); var e = parseExpr(); print("Consuming closing parens for expr: " + e + " (closing paren level " + parenLevels + ")"); consume(")"); parenLevels--; ret inner ? e : parseCall(e); } fail("Identifier, literal or opening parentheses expected (got: " + quote(t)); } // 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()); fail("Class not found: " + className); } if (knownVars.contains(t)) { var e = new Toolbox.GetVar(t); print("Found var acccess: " + e + ", " + (!inner ? "Checking for call" : "Returning expression")); ret inner ? e : parseCall(e); } if (!inner) { var fdef = currentReturnableScript.functionDefs.get(t); if (fdef != null) ret new Toolbox.CallFunction(fdef, parseArguments()); } O o = findExternalObject(t); if (o == null) fail("Unknown object: " + t); else if (inner) ret const(o); else if (o cast Class) { /*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(o, parseArguments()); } else*/ if (isIdentifier()) { S name = tpp(); // look for method first if (hasMethodNamed(o, name)) ret new Toolbox.CallMethod(const(o), name, parseArguments()); // look for field second var field = getField(o, name); if (field != null) { assertCmdEnd(); 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)); } L parseArguments() { //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 parenLevels == 0 && atEndOrLineBreak() || is(";") || is("}") || is(")"); } void assertCmdEnd() { if (!atCmdEnd()) fail("Expected end of command"); } 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); null; } selfType allowTheWorld() { ret allowTheWorld(mc()); } selfType allowTheWorld(O... functionContainers) { this.functionContainers = asList(functionContainers); globalClassNames_cache = null; // recalculate this; } void printFunctionDefs(Script script) { print(values(script.functionDefs)); } void parseFunctionDefinition() { consume("def"); S functionName = assertIdentifier(tpp()); new LS args; while (isIdentifier()) args.add(tpp()); temp tempAddAll(knownVars, args); var functionBody = parseCurlyBlock(true); currentReturnableScript.functionDefs.put(functionName, new Toolbox.FunctionDef(functionName, args, functionBody)); } Evaluable parseCurlyBlock(bool returnable) { //print(+knownVars); consume("{"); var script = parseScript(returnable); consume("}"); ret script; } Evaluable parseWhileLoop() { consume("while"); var condition = parseExpr(); var body = parseCurlyBlock(false); ret new Toolbox.While(condition, body); } Evaluable parseForEach() { consume("for"); S var = assertIdentifier(tpp()); print("for var", var); consume("in"); var collection = parseExpr(); print(+collection); temp tempAdd(knownVars, var); var body = parseCurlyBlock(false); ret new Toolbox.ForEach(collection, var, body); } Evaluable parseIfStatement() { consume("if"); var condition = parseExpr(); var body = parseCurlyBlock(false); ret new Toolbox.IfThen(condition, body); } // declare an external variable void addVar(S var) { knownVars.add(var); } // short name to full name 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 (!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) + "$"); } }