Uses 679K of libraries. Click here for Pure Java version (35531L/177K).
1 | /* e.g. |
2 | |
3 | overlay <- ScreenOverlay |
4 | bounds <- rightScreenBounds |
5 | overlay bounds bounds |
6 | overlay show |
7 | |
8 | Can separate commands with ";" also. |
9 | For how to define functions in a script see #1033988. |
10 | |
11 | Note: LeftArrowScriptAutoCompleter uses a lot of this class's internals |
12 | |
13 | */ |
14 | |
15 | // TODO: "then" is used twice which is probably not good |
16 | // (for if-then and for chaining calls) |
17 | |
18 | sclass GazelleV_LeftArrowScriptParser > SimpleLeftToRightParser { |
19 | // most important "experimental" setting (will set this to true) |
20 | settable bool magicSwitch = true; |
21 | |
22 | // New object scopes (class members without "this") |
23 | // - MAY potentially break old scripts (although probably not) |
24 | settable bool objectScopesEnabled = true; |
25 | |
26 | replace Toolbox with GazelleV_LeftArrowScript. |
27 | delegate Script, Evaluable, FunctionDef, FixedVarBase, |
28 | LambdaMethodOnArgument, CallOnTarget to Toolbox. |
29 | replace const with _const. |
30 | |
31 | ClassNameResolver classNameResolver; |
32 | new L functionContainers; |
33 | |
34 | settable ILASClassLoader lasClassLoader; |
35 | settable S classDefPrefix; |
36 | |
37 | // will be noted in source references |
38 | settable O sourceInfo; |
39 | |
40 | settable bool optimize = true; |
41 | settable bool useFixedVarContexts = false; // doesn't completely work yet |
42 | |
43 | Scope scope; |
44 | new LinkedHashMap<S, LASValueDescriptor> knownVars; |
45 | |
46 | //new L<FixedVarBase> varAccessesToFix; |
47 | |
48 | Set<S> closerTokens = litset(";", "}", ")"); |
49 | BuildingScript currentReturnableScript; |
50 | BuildingScript currentLoop; |
51 | bool inParens; |
52 | int idCounter; |
53 | |
54 | new Map<S, LASClassDef> classDefs; |
55 | |
56 | gettable Map<S, FunctionDef> functionDefs = new Map; |
57 | |
58 | Set<S> flags = ciSet(); |
59 | |
60 | settable bool internStringLiterals = true; |
61 | |
62 | // events for auto-completer |
63 | // 1. record which vars are known at current parse location |
64 | event knownVarsSnapshot(Map<S, LASValueDescriptor> knownVars); |
65 | |
66 | // 2. indicates we just parsed an expression of type type |
67 | // (useful for listing methods+fields for auto-completion) |
68 | event typeHook(LASValueDescriptor type); |
69 | |
70 | replace print with if (GazelleV_LeftArrowScriptParser.this.scaffoldingEnabled()) print. |
71 | replace printVars with if (GazelleV_LeftArrowScriptParser.this.scaffoldingEnabled()) printVars. |
72 | |
73 | srecord EvaluableWrapper(Evaluable expr) {} |
74 | |
75 | class BuildingScript { |
76 | int id = ++idCounter; |
77 | settable bool returnable; |
78 | settable bool isLoopBody; |
79 | BuildingScript returnableParent, loopParent; |
80 | //settable LASScope scope; |
81 | |
82 | new Script script; |
83 | new L<Evaluable> steps; |
84 | Map<S, FunctionDef> functionDefs = new Map; |
85 | |
86 | *(bool *returnable) { this(); } |
87 | *(bool *returnable, bool *isLoopBody) { this(); } |
88 | *() { /*scope = currentScope();*/ } |
89 | |
90 | void add(Evaluable step) { if (step != null) steps.add(step); } |
91 | |
92 | Evaluable get() { |
93 | //script.scope = scope; |
94 | |
95 | // if the last command is a return from THIS script, |
96 | // convert into a simple expression |
97 | |
98 | var lastStep = last(steps); |
99 | if (lastStep cast Toolbox.ReturnFromScript) |
100 | if (lastStep.script == script) |
101 | replaceLast(steps, lastStep.value); |
102 | |
103 | // if the script is only step, is not returnable |
104 | // and defines no functions, replace it with its only step |
105 | |
106 | if (!returnable && l(steps) == 1 && empty(functionDefs)) |
107 | ret first(steps); |
108 | |
109 | // make and return actual script |
110 | |
111 | if (nempty(functionDefs)) script.functionDefs = functionDefs; |
112 | script.steps = toTypedArray(Evaluable.class, steps); |
113 | ret script; |
114 | } |
115 | |
116 | S toStringLong() { ret pnlToLines(steps); } |
117 | toString { |
118 | ret formatRecordVars BuildingScript(+id, +returnable , +returnableParent, +script); |
119 | } |
120 | } // end of BuildingScript |
121 | |
122 | // methods begin here |
123 | |
124 | { |
125 | tokenize = text -> { |
126 | LS tok = tokenize_base(text); |
127 | tok = preprocess(tok); |
128 | print("=== PREPROCESSED\n" + indentx(join(tok)) + "\n==="); |
129 | ret tok; |
130 | }; |
131 | } |
132 | |
133 | LS preprocess(LS tok) { |
134 | tok = tokenIndexedList3(tok); |
135 | tok_ifdef(tok, l1 getFlag); |
136 | tok_pcall_script(tok); |
137 | tok_script_settable(tok); |
138 | jreplace(tok, "LS", "L<S>"); |
139 | jreplace(tok, ", +<id>", ", $3 $3"); |
140 | ret cloneList(tok); |
141 | } |
142 | |
143 | Script parse(S text) { |
144 | setText(text); |
145 | init(); |
146 | Script script = parse(); |
147 | if (!atEnd()) |
148 | fail("End of script expected"); |
149 | ret script; |
150 | } |
151 | |
152 | Script parse() { |
153 | Script script = parseReturnableScript(); |
154 | /*for (varAccess : varAccessesToFix) |
155 | varAccess.resolve();*/ |
156 | if (optimize) script = script.optimizeScript(); |
157 | ret script; |
158 | } |
159 | |
160 | Script parseReturnableScript() { |
161 | ret (Script) parseScript(new BuildingScript().returnable(true)); |
162 | } |
163 | |
164 | // if returnable is true, it's always a Script |
165 | Evaluable parseScript(BuildingScript script) { |
166 | ret linkToSrc(-> { |
167 | script.returnableParent = currentReturnableScript; |
168 | script.loopParent = currentLoop; |
169 | if (script.returnable) |
170 | currentReturnableScript = script; |
171 | if (script.isLoopBody) |
172 | currentLoop = script; |
173 | ret parseBuildingScript(script); |
174 | }); |
175 | } |
176 | |
177 | Evaluable parseBuildingScript(BuildingScript script) { |
178 | try { |
179 | parseScript_2(script); |
180 | var builtScript = script!; |
181 | currentReturnableScript = script.returnableParent; |
182 | currentLoop = script.loopParent; |
183 | ret builtScript; |
184 | } catch e { |
185 | print("Parsed so far:\n" + script); |
186 | |
187 | // TODO: could use an exception class [like ScriptError] |
188 | throw rethrowAndAppendToMessage(e, |
189 | squareBracketed(spaceCombine(sourceInfo, lineAndColumn(-1))); |
190 | } |
191 | } |
192 | |
193 | void parseScript_2(BuildingScript script) { |
194 | temp tempRestoreMap(knownVars); |
195 | |
196 | new AssureAdvance assure; |
197 | while (assure!) { |
198 | // for auto-completer |
199 | knownVarsSnapshot(knownVars); |
200 | |
201 | print("parseScript_2: Next token is " + quote(token())); |
202 | |
203 | if (is(";")) continue with next(); |
204 | if (isOneOf("}", ")")) break; |
205 | |
206 | Evaluable instruction = linkToSrc(-> parseInstruction(script)); |
207 | if (instruction != null) |
208 | script.add(instruction); |
209 | } |
210 | |
211 | print("parseScript_2 done"); |
212 | |
213 | knownVarsSnapshot(knownVars); |
214 | } |
215 | |
216 | Evaluable parseParamDecl(BuildingScript script) { |
217 | S var = assertIdentifier(tpp()); |
218 | new LASValueDescriptor valueDescriptor; |
219 | if (is(":")) { |
220 | var type = parseColonType(); |
221 | valueDescriptor = LASValueDescriptor.nonExactCanBeNull(type); |
222 | } |
223 | knownVars.put(var, valueDescriptor); |
224 | script.script.params = putOrCreateLinkedHashMap(script.script.params, var, valueDescriptor); |
225 | null; |
226 | } |
227 | |
228 | Evaluable parseInstruction(BuildingScript script) { |
229 | /*if (is("var") && isIdentifier(token(1))) { |
230 | consume(); |
231 | ret parseAssignment(); |
232 | }TODO*/ |
233 | |
234 | // Script parameter (old) |
235 | if (consumeOpt("param")) |
236 | ret parseParamDecl(script); |
237 | |
238 | if (is("throw")) { |
239 | consume(); |
240 | ret new Toolbox.Throw(parseExpr()); |
241 | } |
242 | |
243 | /* Hmm. Too hard. Need to use jreplace. |
244 | if (is("pcall")) { |
245 | consume(); |
246 | Evaluable body = parseCurlyBlock(); |
247 | |
248 | S var = "_pcallError"; |
249 | temp tempAddKnownVars(var); |
250 | Evaluable catchBlock = parseCurlyBlock("{ pcallFail " + var + " }); |
251 | ret new Toolbox.TryCatch(body, var, catchBlock); |
252 | }*/ |
253 | |
254 | if (isOneOf("return", "ret")) { |
255 | consume(); |
256 | Evaluable expr; |
257 | if (atCmdEnd()) |
258 | expr = const(null); |
259 | else |
260 | expr = parseAssignmentOrExpr(); |
261 | ret new Toolbox.ReturnFromScript(currentReturnableScript.script, expr); |
262 | } |
263 | |
264 | if (consumeOpt("continue")) { |
265 | assertCmdEnd(); |
266 | if (currentLoop == null) |
267 | fail("continue outside of loop"); |
268 | ret new Toolbox.Continue(currentLoop.script); |
269 | } |
270 | |
271 | /* TODO |
272 | if (consumeOpt("break")) { |
273 | assertCmdEnd(); |
274 | if (currentLoop == null) |
275 | fail("break outside of loop"); |
276 | ret new Toolbox.Break(currentLoop.script); |
277 | }*/ |
278 | |
279 | if (is("temp")) { |
280 | consume(); |
281 | Evaluable tempExpr = parseAssignmentOrExpr(); |
282 | print(+tempExpr); |
283 | Evaluable body = parseScript(new BuildingScript); |
284 | print(+body); |
285 | ret new Toolbox.TempBlock(tempExpr, body); |
286 | } |
287 | |
288 | if (is("will") && is(1, "return")) { |
289 | consume(2); |
290 | Evaluable exp = parseAssignmentOrExpr(); |
291 | Evaluable body = parseScript(new BuildingScript); |
292 | ret new Toolbox.WillReturn(exp, body); |
293 | } |
294 | |
295 | if (is("var") && isIdentifier(token(1))) { |
296 | consume(); |
297 | S varName = consumeIdentifier(); |
298 | var type = parseColonTypeOpt(); |
299 | |
300 | if (consumeLeftArrowOpt()) { |
301 | print("Found assignment"); |
302 | Evaluable rhs = parseExpr(); |
303 | assertNotNull("Expression expected", rhs); |
304 | |
305 | knownVars.put(varName, type == null |
306 | ? or(rhs.returnType(), new LASValueDescriptor) |
307 | : LASValueDescriptor.nonExactCanBeNull(type)); |
308 | ret new Toolbox.VarDeclaration(varName, typeToClass(type), rhs); |
309 | } |
310 | } |
311 | |
312 | ret parseAssignmentOrExpr(); |
313 | } |
314 | |
315 | Evaluable parseAssignmentOrExpr() { |
316 | try object parseAssignmentOpt(); |
317 | ret parseExpr(); |
318 | } |
319 | |
320 | // Parse assignment starting with a simple identifier (e.g. "i <- 5") |
321 | Evaluable parseAssignmentOpt() { |
322 | S t = token(); |
323 | if (isIdentifier(t)) { |
324 | Type type = null; |
325 | var ptr = ptr(); // save parsing position |
326 | |
327 | // optional type |
328 | if (is(1, ":")) { |
329 | next(); |
330 | type = parseColonType(); |
331 | unconsume(); |
332 | } |
333 | |
334 | if (is(1, "<") && is(2, "-")) { |
335 | print("Found assignment"); |
336 | next(3); |
337 | Evaluable rhs = parseExpr(); |
338 | assertNotNull("Expression expected", rhs); |
339 | |
340 | bool newVar = !knownVars.containsKey(t); |
341 | printVars(+newVar, +t, +knownVars); |
342 | |
343 | if (newVar && scope != null) |
344 | try object scope.completeAssignmentOpt(t, rhs); |
345 | |
346 | knownVars.put(t, type == null |
347 | ? or(rhs.returnType(), new LASValueDescriptor) |
348 | : LASValueDescriptor.nonExactCanBeNull(type)); |
349 | ret newVar |
350 | ? new Toolbox.VarDeclaration(t, null, rhs) |
351 | : new Toolbox.Assignment(t, rhs); |
352 | } |
353 | |
354 | // revert parser, it was just a typed expression |
355 | // (TODO: this technically violates the O(n) parsing principle) |
356 | ptr(ptr); |
357 | } |
358 | null; |
359 | } |
360 | |
361 | Evaluable parseOptionalInnerExpression() { |
362 | printVars parseOptionalInnerExpression(+token()); |
363 | if (atCmdEnd() || isOneOf("{", ",", "then")) null; |
364 | ret parseInnerExpr(); |
365 | } |
366 | |
367 | Evaluable const(O o) { ret new Toolbox.Const(o); } |
368 | |
369 | Evaluable parseInnerExpr() { ret parseExpr(true); } |
370 | |
371 | scaffolded Evaluable parseExpr(bool inner default false) { |
372 | Evaluable e = linkToSrc(-> inner ? parseExpr_impl(true) : parseFullExpr()); |
373 | print("parseExpr done:\n" + Toolbox.indentedScriptStruct(e)); |
374 | ret e; |
375 | } |
376 | |
377 | Evaluable parseFullExpr() { |
378 | ret linkToSrc(-> parseExprPlusOptionalComma()); |
379 | //ret parseExprPlusOptionalCommaOrThen(); |
380 | } |
381 | |
382 | /*Evaluable parseExprPlusOptionalCommaOrThen() { |
383 | Evaluable expr = parseExpr_impl(false); |
384 | |
385 | //printVars("parseExprPlusOptionalCommaOrThen", t := t()); |
386 | while true { |
387 | if (is("then")) |
388 | expr = parseThenCall(expr); |
389 | else if (consumeOpt(",")) |
390 | expr = parseCall_noCmdEndCheck(expr); |
391 | else |
392 | break; |
393 | //printVars("parseExprPlusOptionalCommaOrThen", t2 := t()); |
394 | } |
395 | ret expr; |
396 | }*/ |
397 | |
398 | // TODO: How to store the object between the two calls? |
399 | // We need to put it in the VarContext somehow. |
400 | // Or allow passing in a target to CallOnTarget |
401 | CallOnTarget parseThenCall(CallOnTarget expr) { |
402 | consume("then"); |
403 | CallOnTarget secondCall = cast parseCall(null); |
404 | ret new Toolbox.Then(expr, secondCall); |
405 | } |
406 | |
407 | Evaluable parseExprPlusOptionalComma() { |
408 | Evaluable expr = parseExpr_impl(false); |
409 | |
410 | //printVars("parseExprPlusOptionalComma", t := t()); |
411 | while (consumeOpt(",")) { |
412 | expr = parseCall_noCmdEndCheck(expr); |
413 | //printVars("parseExprPlusOptionalComma", t2 := t()); |
414 | } |
415 | ret expr; |
416 | } |
417 | |
418 | Evaluable parseExpr_impl(bool inner) { |
419 | if (atEnd()) null; |
420 | |
421 | S t = token(); |
422 | printVars parseExpr(token := t); |
423 | |
424 | if (is(";")) null; // empty command |
425 | |
426 | if (consumeOpt("try")) { |
427 | Evaluable body = parseCurlyBlock(); |
428 | |
429 | while (consumeOpt("catch")) { |
430 | S var = consumeIdentifierOpt(); |
431 | temp tempAddKnownVars(var); |
432 | Evaluable catchBlock = parseCurlyBlock(); |
433 | body = new Toolbox.TryCatch(body, var, catchBlock); |
434 | } |
435 | |
436 | if (consumeOpt("finally")) { |
437 | Evaluable finallyBlock = parseCurlyBlock(); |
438 | ret new Toolbox.TryFinally(body, finallyBlock); |
439 | } |
440 | |
441 | ret body; |
442 | } |
443 | |
444 | if (is("def")) { |
445 | var fd = parseFunctionDefinition(currentReturnableScript.functionDefs); |
446 | ret const(fd); |
447 | } |
448 | |
449 | if (is("{")) |
450 | ret parseCurlyBlock(); |
451 | |
452 | // int or double literal |
453 | if (is("-") && empty(nextSpace()) && startsWithDigit(token(1)) |
454 | || startsWithDigit(t)) { |
455 | var e = parseNumberLiteral(); |
456 | ret parseCall(inner, e); |
457 | } |
458 | |
459 | if (isQuoted(t)) { |
460 | var e = parseStringLiteral(); |
461 | ret parseCall(inner, e); |
462 | } |
463 | |
464 | if (startsWith(t, '\'')) { |
465 | consume(); |
466 | var e = const(first(unquote(t))); |
467 | ret parseCall(inner, e); |
468 | } |
469 | |
470 | if (isIdentifier(t)) { |
471 | if (consumeOpt("synchronized")) { |
472 | var target = parseExpr(); |
473 | var body = parseCurlyBlock(); |
474 | ret new Toolbox.Synchronized(target, body); |
475 | } |
476 | |
477 | if (is("while")) |
478 | ret parseWhileLoop(); |
479 | |
480 | if (is("do")) |
481 | ret parseDoWhileLoop(); |
482 | |
483 | if (is("for")) |
484 | ret parseForEach(); |
485 | |
486 | if (is("if")) |
487 | ret parseIfStatement(); |
488 | |
489 | if (is("repeat")) |
490 | ret parseRepeatStatement(); |
491 | |
492 | if (is("outer")) { |
493 | consume(); |
494 | var a = parseAssignmentOpt(); |
495 | if (!a instanceof Toolbox.Assignment) |
496 | fail("Assignment expected"); |
497 | cast a to Toolbox.Assignment; |
498 | ret new Toolbox.AssignmentToOuterVar(a.var, a.expression); |
499 | } |
500 | |
501 | consume(); |
502 | print("Consumed identifier " + t + ", next token: " + token() + ", inner: " + inner); |
503 | var e = parseExprStartingWithIdentifier(t, inner); |
504 | ret inner ? parseCall(inner, e) : e; |
505 | } |
506 | |
507 | // nested expression |
508 | |
509 | if (eq(t, "(")) { |
510 | consume(); |
511 | //print("Consumed opening parens (level " + parenLevels + ")"); |
512 | Evaluable e = parseExpr_inParens(); |
513 | //print("Consuming closing parens for expr: " + e + " (closing paren level " + parenLevels + ")"); |
514 | consume(")"); |
515 | ret parseCall(inner, e); |
516 | } |
517 | |
518 | if (isOneOf("&", "|") && empty(nextSpace()) && is(1, token())) { |
519 | ret parseBinaryOperator(); |
520 | } |
521 | |
522 | fail("Identifier, literal, operator or opening parentheses expected (got: " + quote(t)); |
523 | } |
524 | |
525 | Evaluable parseExpr_inParens() { |
526 | bool inParensOld = inParens; |
527 | inParens = true; |
528 | try { |
529 | ret parseExpr(); |
530 | } finally { |
531 | inParens = inParensOld; |
532 | } |
533 | } |
534 | |
535 | Evaluable parseNumberLiteral() { |
536 | S t = consumeMultiTokenLiteral(); |
537 | |
538 | if (swic(t, "0x")) ret const(parseHexInt(dropFirst(t, 2))); |
539 | if (swic(t, "0b")) ret const(intFromBinary(dropFirst(t, 2))); |
540 | |
541 | if (isInteger(t)) ret const(parseIntOrLong(t)); |
542 | if (ewic(t, "f")) ret const(parseFloat(t)); |
543 | |
544 | if (endsWith(t, "L")) ret const(parseLong(t)); |
545 | |
546 | ret const(parseDouble(t)); |
547 | } |
548 | |
549 | Evaluable parseBinaryOperator() { |
550 | bool and = is("&"); |
551 | next(2); |
552 | Evaluable a = parseInnerExpr(); |
553 | Evaluable b = parseInnerExpr(); |
554 | ret and |
555 | ? new Toolbox.BoolAnd(a, b) |
556 | : new Toolbox.BoolOr(a, b); |
557 | } |
558 | |
559 | bool qualifiedNameContinues() { |
560 | ret empty(prevSpace()) && eq(token(), ".") && empty(nextSpace()) |
561 | && isIdentifier(token(1)); |
562 | } |
563 | |
564 | // assumes we have consumed the "new" |
565 | Evaluable parseNewExpr() { |
566 | S className = assertIdentifier(tpp()); |
567 | |
568 | parseTypeArguments(null); |
569 | |
570 | // script-defined class? |
571 | |
572 | LASClassDef cd = classDefs.get(className); |
573 | if (cd != null) |
574 | ret new Toolbox.NewObject_LASClass(cd.resolvable(lasClassLoader)); |
575 | |
576 | // class in variable? |
577 | |
578 | var type = knownVars.get(className); |
579 | if (type != null) |
580 | ret new Toolbox.NewObject_UnknownClass(new Toolbox.GetVar(className), parseArguments()); |
581 | |
582 | // external class |
583 | |
584 | O o = findExternalObject(className); |
585 | if (o cast Class) { |
586 | Class c = o; |
587 | |
588 | if (c == L.class) c = ArrayList.class; |
589 | else if (c == Map.class) c = HashMap.class; |
590 | else if (c == Set.class) c = HashSet.class; |
591 | |
592 | ret new Toolbox.NewObject(c, parseArguments()); |
593 | } |
594 | |
595 | throw new ClassNotFound(className); |
596 | } |
597 | |
598 | // t is last consumed token (the identifier the expression starts with) |
599 | // TODO: do parseCall in only one place! |
600 | Evaluable parseExprStartingWithIdentifier(S t, bool inner) { |
601 | if (eq(t, "true")) ret const(true); |
602 | if (eq(t, "false")) ret const(false); |
603 | if (eq(t, "null")) ret const(null); |
604 | |
605 | if (eq(t, "new")) |
606 | ret parseNewExpr(); |
607 | |
608 | if (eq(t, "class")) { |
609 | unconsume(); |
610 | ret new Toolbox.ClassDef(parseClassDef().resolvable(lasClassLoader)); |
611 | } |
612 | |
613 | if (eq(t, "list") && is("{")) { |
614 | Script block = parseReturnableCurlyBlock(); |
615 | Evaluable e = new Toolbox.ListFromScript(block); |
616 | ret parseCall(inner, e); |
617 | } |
618 | |
619 | // Search in locally known variables |
620 | |
621 | var type = knownVars.get(t); |
622 | if (type != null) { |
623 | var e = new Toolbox.GetVar(t).returnType(type); |
624 | print("Found var acccess: " + e + ", " + (!inner ? "Checking for call" : "Returning expression")); |
625 | ret parseCall(inner, e); |
626 | } |
627 | |
628 | // Search in scope |
629 | if (scope != null) { |
630 | try object scope.parseExprStartingWithIdentifierOpt(t, inner); |
631 | } |
632 | |
633 | // script-defined class? |
634 | |
635 | LASClassDef cd = classDefs.get(t); |
636 | if (cd != null) |
637 | ret new Toolbox.ClassDef(cd.resolvable(lasClassLoader)); |
638 | |
639 | if (!inner) { |
640 | var fdef = lookupFunction(t); |
641 | if (fdef != null) { |
642 | if (is("~")) |
643 | ret parseTildeCall(new Toolbox.CallFunction(fdef, new Evaluable[0])); |
644 | |
645 | ret new Toolbox.CallFunction(fdef, parseArguments()); |
646 | } |
647 | } |
648 | |
649 | if (eq(t, "_context")) |
650 | ret new Toolbox.GetVarContext; |
651 | |
652 | O o = findExternalObject(t); |
653 | |
654 | var start = ptr().minus(2); |
655 | |
656 | if (o == null) { |
657 | //unconsume(); |
658 | throw new UnknownObject(t); |
659 | } else if (o cast EvaluableWrapper) { |
660 | ret parseCall(inner, o.expr); |
661 | } else if (inner) |
662 | ret linkToSrc(start, const(o)); |
663 | else if (o cast Class) { |
664 | ret parseExprStartingWithClass(start, o); |
665 | } else if (o cast MethodOnObject) { |
666 | // it's a function from a functionContainer |
667 | |
668 | if (inner) fail("Can't call methods in arguments"); // Does this ever happen?! |
669 | |
670 | ret new Toolbox.CallMethod(const(o.object), o.method, parseArguments()); |
671 | } else |
672 | ret parseCall(const(o)); |
673 | } |
674 | |
675 | Evaluable parseExprStartingWithClass(TokPtr start, Class c) { |
676 | printVars("parseExprStartingWithClass", +c); |
677 | |
678 | if (atCmdEnd()) |
679 | ret linkToSrc(start, const(c)); |
680 | |
681 | // new without new |
682 | if (is("(")) |
683 | ret new Toolbox.NewObject(c, parseArguments()); |
684 | |
685 | /* old object creation syntax (e.g. Pair new a b) |
686 | if (is("new")) { |
687 | next(); |
688 | ret new Toolbox.NewObject(c, parseArguments()); |
689 | } else*/ |
690 | |
691 | // check if it's a lambda definition first (with ->) |
692 | try object parseLambdaOpt(c); |
693 | |
694 | if (isIdentifier()) { |
695 | S name = tpp(); |
696 | // We have now parsed a class (c) plus an as yet unknown identifier (name) right next to it |
697 | |
698 | // look for a static method call |
699 | if (hasStaticMethodNamed(c, name)) |
700 | ret new Toolbox.CallMethod(const(c), name, parseArguments()); |
701 | |
702 | // if the class is an interface, it's a lambda method reference or a lambda |
703 | if (isInterface(c)) |
704 | ret parseLambdaMethodRef(c, name); |
705 | |
706 | // look for field second |
707 | |
708 | var field = getField(c, name); |
709 | if (field == null) |
710 | field = findFieldInInterfaces(c, name); |
711 | |
712 | if (field != null) { |
713 | if (!isStaticField(field)) |
714 | fail(field + " is not a static field"); |
715 | |
716 | // check for field assignment |
717 | if (consumeLeftArrowOpt()) { |
718 | Evaluable rhs = parseExpr(); |
719 | ret new Toolbox.SetStaticField(field, rhs); |
720 | } |
721 | |
722 | assertCmdEnd(); |
723 | |
724 | ret new Toolbox.GetStaticField(field); |
725 | } |
726 | |
727 | fail(name + " not found in " + c + " (looked for method or field)"); |
728 | } else |
729 | fail("Method name expected: " + token()); |
730 | } |
731 | |
732 | // parse lambda, e.g. IF1 a -> plus a a |
733 | // returns null if it's not a lambda |
734 | // assumes that c was just parsed |
735 | Evaluable parseLambdaOpt(Class c) { |
736 | // must be an interface |
737 | if (!isInterface(c)) null; |
738 | |
739 | // speculatively parse optional type args first |
740 | var ptr = ptr(); |
741 | var parameterized = parseTypeArgumentsOpt(c); |
742 | |
743 | // e.g. IF0 { 5 } |
744 | if (is("{")) |
745 | ret finishLambdaDef(c, null); |
746 | |
747 | // e.g. IF1 _ size |
748 | if (consumeOpt("_")) { |
749 | S methodName = consumeIdentifier(); |
750 | ret new LambdaMethodOnArgument(c, methodName, parseArguments()); |
751 | } |
752 | |
753 | new LinkedHashMap<S, LASValueDescriptor> args; |
754 | |
755 | while (isIdentifier()) { |
756 | S name = consumeIdentifier(); |
757 | args.put(name, typeToValueDescriptor(parseColonTypeOpt())); |
758 | } |
759 | |
760 | // Check for lambda arrow |
761 | if (!(is("-") && is(1, ">"))) { |
762 | ptr(ptr); // revert pointer |
763 | null; // not a lambda |
764 | } |
765 | |
766 | skip(2); // skip the arrow |
767 | ret finishLambdaDef(c, args); |
768 | } |
769 | |
770 | Evaluable finishLambdaDef(Class c, LinkedHashMap<S, LASValueDescriptor> args) { |
771 | temp tempAddKnownVars(args); |
772 | knownVarsSnapshot(knownVars); |
773 | |
774 | Evaluable body; |
775 | |
776 | // if curly brackets: parse lambda body as returnable script |
777 | // TODO: avoid script overhead if there are no return calls inside |
778 | if (is("{")) |
779 | body = parseReturnableCurlyBlock(); |
780 | else |
781 | body = parseExpr(); |
782 | |
783 | // return lambda |
784 | var lambda = new Toolbox.LambdaDef(c, toStringArray(keys(args)), body); |
785 | print("finishLambdaDef done:\n" + Toolbox.indentedScriptStruct(lambda)); |
786 | ret lambda; |
787 | } |
788 | |
789 | // method ref, e.g. "IF1 l" meaning "l1 l" |
790 | // also, constructor ref, e.g. "IF1 Pair" meaning (x -> new Pair(x)) |
791 | // |
792 | // c must be a single method interface |
793 | Evaluable parseLambdaMethodRef(Class c, S name) { |
794 | var fdef = lookupFunction(name); |
795 | if (fdef != null) { |
796 | Evaluable[] curriedArguments = parseArguments(); |
797 | ret new Toolbox.CurriedScriptFunctionLambda(c, fdef, curriedArguments); |
798 | } |
799 | |
800 | O function = findExternalObject(name); |
801 | |
802 | if (function == null) |
803 | throw new UnknownObject(name); |
804 | |
805 | if (function cast MethodOnObject) { |
806 | // it's a function from a functionContainer |
807 | |
808 | O target = function.object; |
809 | S targetMethod = function.method; |
810 | |
811 | Evaluable[] curriedArguments = parseArguments(); |
812 | ret new Toolbox.CurriedMethodLambda(c, target, targetMethod, curriedArguments); |
813 | } else if (function cast Class) { |
814 | Class c2 = function; |
815 | |
816 | // No currying here yet |
817 | assertCmdEnd(); |
818 | |
819 | var ctors = constructorsWithNumberOfArguments(c2, 1); |
820 | if (empty(ctors)) |
821 | fail("No single argument constructor found in " + c2); |
822 | |
823 | ret new Toolbox.CurriedConstructorLambda(c, toArray Constructor(ctors), null); |
824 | } else |
825 | fail(function + " is not an instantiable class or callable method"); |
826 | |
827 | } |
828 | |
829 | FunctionDef lookupFunction(S name) { |
830 | var script = currentReturnableScript; |
831 | while (script != null) { |
832 | var f = script.functionDefs.get(name); |
833 | printVars("lookupFunction", +script, +name, +f); |
834 | if (f != null) ret f; |
835 | script = script.returnableParent; |
836 | } |
837 | |
838 | // Try parser-wide function defs |
839 | ret functionDefs.get(name); |
840 | } |
841 | |
842 | Evaluable[] parseArguments() { |
843 | new L<Evaluable> l; |
844 | try { |
845 | while (true) { |
846 | print("parseArgumentsAsList: token is " + quote(t())); |
847 | |
848 | // +id |
849 | if (is("+") && empty(nextSpace()) && isIdentifier(token(1))) { |
850 | consume(); |
851 | l.add(const(token())); |
852 | |
853 | // just leave identifier unconsumed |
854 | continue; |
855 | } |
856 | |
857 | if (consumeOpt("<")) { |
858 | Evaluable a = parseFullExpr(); |
859 | print("parseArgumentsAsList: token after expr is " + quote(t())); |
860 | if (a == null) fail("expression expected"); |
861 | l.add(a); |
862 | break; |
863 | } |
864 | |
865 | Evaluable a = parseOptionalInnerExpression(); |
866 | if (a == null) break; |
867 | l.add(a); |
868 | } |
869 | |
870 | ret toArrayOrNull(Evaluable.class, l); |
871 | } on fail { |
872 | print("Arguments parsed so far: " + l); |
873 | } |
874 | } |
875 | |
876 | S consumeMultiTokenLiteral() { |
877 | ret consumeUntilSpaceOr(-> atCmdEnd() || is(",")); |
878 | } |
879 | |
880 | bool atCmdEnd() { |
881 | ret |
882 | !inParens && atEndOrLineBreak() |
883 | || closerTokens.contains(token()); |
884 | } |
885 | |
886 | void assertCmdEnd() { |
887 | if (!atCmdEnd()) |
888 | fail("Expected end of command, token is: " + quote(token())); |
889 | } |
890 | |
891 | // After we just parsed an expression, see if there is something |
892 | // to the right of it. |
893 | // Currently that can be |
894 | // -a method call |
895 | // -a field access |
896 | // -a field assignment (e.g.: myPair a <- 1) |
897 | Evaluable parseCall(bool inner default false, Evaluable target) { |
898 | returnTypeHook(target); |
899 | |
900 | // normally, do a check for end of line |
901 | if (atCmdEnd()) ret target; |
902 | |
903 | if (inner) { |
904 | try object parseTildeCall(target); |
905 | ret target; |
906 | } |
907 | |
908 | ret parseCall_noCmdEndCheck(target); |
909 | } |
910 | |
911 | // For auto-completer |
912 | void returnTypeHook(Evaluable e) { |
913 | if (e != null && e.returnType() != null) |
914 | typeHook(e.returnType()); |
915 | } |
916 | |
917 | Evaluable parseCall_noCmdEndCheck(Evaluable target) { |
918 | var start = ptr(); |
919 | |
920 | returnTypeHook(target); |
921 | |
922 | // We usually expect an identifier |
923 | |
924 | if (isIdentifier()) { |
925 | S name = tpp(); // the identifier |
926 | |
927 | // check for field assignment |
928 | if (consumeLeftArrowOpt()) { |
929 | Evaluable rhs = parseExpr(); |
930 | ret new Toolbox.SetField(target, name, rhs); |
931 | } |
932 | |
933 | bool allowNullReference = consumeOpt("?"); |
934 | |
935 | var args = parseArguments(); |
936 | var call = finalizeCall(start, target, name, args); |
937 | set(call, +allowNullReference); |
938 | ret call; |
939 | } |
940 | |
941 | // ~ also does stuff |
942 | // (map ~a == map getOpt "a") |
943 | // (list ~0 == list listGet 0) |
944 | |
945 | try object parseTildeCall(target); |
946 | |
947 | // special syntax to get a field, bypassing a method of the same name |
948 | if (is("!") && is(1, "_getField_")) { |
949 | next(2); |
950 | S field = consumeIdentifier(); |
951 | ret new Toolbox.GetField(target, field); |
952 | } |
953 | |
954 | // no call found |
955 | ret target; |
956 | } |
957 | |
958 | bool consumeLeftArrowOpt() { |
959 | if (is("<") && eq(token(1), "-")) |
960 | ret true with next(2); |
961 | false; |
962 | } |
963 | |
964 | Evaluable parseTildeCall(Evaluable target) { |
965 | var start = ptr(); |
966 | |
967 | if (consumeOpt("~")) { |
968 | if (isIdentifier()) { |
969 | S key = consumeIdentifier(); |
970 | please include function getOpt. |
971 | CallOnTarget call = finalizeCall1(start, target, "getOpt", const(key)); |
972 | call.allowNullReference(true); |
973 | ret parseCall(finalizeCall2(call)); |
974 | } else { |
975 | int idx = consumeInteger(); |
976 | please include function listGet. |
977 | ret parseCall(finalizeCall(start, target, "listGet", const(idx))); |
978 | } |
979 | } |
980 | |
981 | null; |
982 | } |
983 | |
984 | Evaluable finalizeCall(TokPtr start, Evaluable target, S name, Evaluable... args) { |
985 | ret finalizeCall2(finalizeCall1(start, target, name, args)); |
986 | } |
987 | |
988 | Evaluable finalizeCall2(CallOnTarget call) { |
989 | while (is("then")) |
990 | call = parseThenCall(call); |
991 | ret call; |
992 | } |
993 | |
994 | CallOnTarget finalizeCall1(TokPtr start, Evaluable target, S name, Evaluable... args) { |
995 | // Is the method name also the name of a global function? |
996 | if (magicSwitch) { |
997 | O ext = findExternalObject(name); |
998 | if (ext cast MethodOnObject) { |
999 | if (nempty(args)) |
1000 | ret new Toolbox.CallMethodOrGlobalFunction(target, name, ext, args); |
1001 | else |
1002 | ret new Toolbox.CallMethodOrGetFieldOrGlobalFunction(target, name, ext); |
1003 | } |
1004 | } |
1005 | |
1006 | if (nempty(args)) |
1007 | ret new Toolbox.CallMethod(target, name, args); |
1008 | else |
1009 | ret linkToSrc(start, new Toolbox.CallMethodOrGetField(target, name)); |
1010 | } // end of parseCall_noCmdEndCheck |
1011 | |
1012 | // add link to source code to parsed object if it supports it |
1013 | // and doesn't have a source code link already |
1014 | <A> A linkToSrc(TokPtr start, A a) { |
1015 | if (a cast IHasTokenRangeWithSrc) |
1016 | if (a.tokenRangeWithSrc() == null) |
1017 | a.setTokenRangeWithSrc(TokenRangeWithSrc(start, ptr().plus(-1)).sourceInfo(sourceInfo())); |
1018 | ret a; |
1019 | } |
1020 | |
1021 | // lambda version of linkToSrc |
1022 | <A> A linkToSrc(IF0<A> a) { |
1023 | var start = ptr(); |
1024 | ret linkToSrc(start, a!); |
1025 | } |
1026 | |
1027 | // can return MethodOnObject |
1028 | // Note: Assumes we just parsed the name |
1029 | // TODO: rename this part of the method |
1030 | swappable O findExternalObject(S name) { |
1031 | //try object findClassThroughDefaultClassFinder(name); |
1032 | //try object findClassInStandardImports(name); |
1033 | |
1034 | if (eq(name, "void")) ret void.class; |
1035 | try object parsePrimitiveType(name); |
1036 | |
1037 | if (qualifiedNameContinues()) { |
1038 | int idx = idx()-2; |
1039 | do |
1040 | next(2); |
1041 | while (qualifiedNameContinues()); |
1042 | |
1043 | S fqn = joinSubList(tok, idx, idx()-1); |
1044 | ret classForName(fqn); |
1045 | } |
1046 | |
1047 | ret findExternalObject2(name); |
1048 | } |
1049 | |
1050 | swappable O findExternalObject2(S name) { |
1051 | S fullName = globalClassNames().get(name); |
1052 | if (fullName != null) |
1053 | ret classForName(fullName); |
1054 | |
1055 | try object classForNameOpt_noCache(name); |
1056 | |
1057 | fOr (container : functionContainers) { |
1058 | if (hasMethodNamed(container, name) |
1059 | && !isBlockedFunctionContainerMethod(container, name)) { |
1060 | var moo = new MethodOnObject(container, name); |
1061 | ifdef debugMethodFinding _print(+moo); endifdef |
1062 | ret moo; |
1063 | } |
1064 | |
1065 | var field = getField(container, name); |
1066 | if (field != null && isStaticField(field)) |
1067 | ret new EvaluableWrapper(new Toolbox.GetStaticField(field)); |
1068 | } |
1069 | |
1070 | null; |
1071 | } |
1072 | |
1073 | swappable bool isBlockedFunctionContainerMethod(O container, S name) { |
1074 | false; |
1075 | } |
1076 | |
1077 | selfType allowTheWorld() { ret allowTheWorld(mc()); } |
1078 | |
1079 | // first containers within argument list have priority |
1080 | // last calls to allowTheWorld/first arguments passed have priority |
1081 | selfType allowTheWorld(O... functionContainers) { |
1082 | fOr (O o : reversed(functionContainers)) |
1083 | if (!contains(this.functionContainers, o)) { |
1084 | this.functionContainers.add(0, o); |
1085 | globalClassNames_cache = null; // recalculate |
1086 | } |
1087 | this; |
1088 | } |
1089 | |
1090 | void printFunctionDefs(Script script) { |
1091 | print(values(script.functionDefs)); |
1092 | } |
1093 | |
1094 | AutoCloseable tempAddKnownVars(S... vars) { |
1095 | ret tempAddKnownVars(nonNulls(vars)); |
1096 | } |
1097 | |
1098 | AutoCloseable tempAddKnownTypedVars(Iterable<TypedVar> vars) { |
1099 | ret tempAddKnownVars(mapToMap(vars, v -> pair(v.name, v.type))); |
1100 | } |
1101 | |
1102 | AutoCloseable tempAddKnownVars(Iterable<S> vars) { |
1103 | var newVars = mapWithSingleValue(vars, new LASValueDescriptor); |
1104 | ret tempAddKnownVars(newVars); |
1105 | } |
1106 | |
1107 | AutoCloseable tempAddKnownVar(S var, LASValueDescriptor type) { |
1108 | ret tempAddKnownVars(litmap(var, type)); |
1109 | } |
1110 | |
1111 | AutoCloseable tempAddKnownVars(Map<S, LASValueDescriptor> newVars) { |
1112 | /*if (scope != null) |
1113 | for (name, type : newVars) |
1114 | scope.addDeclaredVar(name, type);*/ |
1115 | ret tempMapPutAll(knownVars, newVars); |
1116 | } |
1117 | |
1118 | S parseFunctionDefArg(Map<S, LASValueDescriptor> argsOut) { |
1119 | if (consumeOpt("(")) { |
1120 | S result = parseFunctionDefArg(argsOut); |
1121 | consume(")"); |
1122 | ret result; |
1123 | } |
1124 | |
1125 | if (isIdentifier()) { |
1126 | S arg = tpp(); |
1127 | var type = typeToValueDescriptor(parseColonTypeOpt()); |
1128 | argsOut.put(arg, type); |
1129 | ret arg; |
1130 | } |
1131 | |
1132 | null; |
1133 | } |
1134 | |
1135 | |
1136 | Toolbox.FunctionDef parseFunctionDefinition(Map<S, FunctionDef> functionDefsToAddTo) { |
1137 | var start = ptr(); |
1138 | consume("def"); |
1139 | bool synthetic = consumeOpt("!"); |
1140 | if (synthetic) consume("synthetic"); |
1141 | S functionName = assertIdentifier(tpp()); |
1142 | print("parseFunctionDefinition " + functionName); |
1143 | ret parseFunctionDefinition_step2(functionDefsToAddTo, start, functionName) |
1144 | .synthetic(synthetic); |
1145 | } |
1146 | |
1147 | Toolbox.FunctionDef parseFunctionDefinition_step2(Map<S, FunctionDef> functionDefsToAddTo, TokPtr start, S functionName) { |
1148 | // argument names and types |
1149 | new LinkedHashMap<S, LASValueDescriptor> args; |
1150 | |
1151 | while (parseFunctionDefArg(args) != null) {} |
1152 | |
1153 | var returnType = parseColonTypeOpt(); |
1154 | |
1155 | var fd = new Toolbox.FunctionDef(functionName, keysList(args), null); // no body yet |
1156 | fd.argTypes(valuesArray(LASValueDescriptor, args)); |
1157 | functionDefsToAddTo?.put(functionName, fd); |
1158 | |
1159 | //var scope = newScope(); |
1160 | //scope.useFixedVars(useFixedVarContexts); |
1161 | //temp tempScope(scope); |
1162 | temp tempAddKnownVars(args); |
1163 | print("Parsing function body"); |
1164 | fd.body = parseReturnableCurlyBlock(); |
1165 | |
1166 | print("Defined function " + functionName + ", added to " + functionDefsToAddTo); |
1167 | |
1168 | //fd.scope(scope); |
1169 | if (returnType != null) fd.returnType(returnType); |
1170 | ret linkToSrc(start, fd); |
1171 | } |
1172 | |
1173 | /*LASScope newScope() { |
1174 | ret new LASScope(scope); |
1175 | }*/ |
1176 | |
1177 | Scope currentScope() { ret scope; } |
1178 | |
1179 | // enter the scope temporarily |
1180 | AutoCloseable tempScope(Scope scope) { |
1181 | var oldScope = currentScope(); |
1182 | this.scope = scope; |
1183 | ret -> { this.scope = oldScope; }; |
1184 | } |
1185 | |
1186 | Script parseReturnableCurlyBlock() { |
1187 | ret (Script) parseCurlyBlock(new BuildingScript().returnable(true)); |
1188 | } |
1189 | |
1190 | Evaluable parseCurlyBlock(BuildingScript script default new) { |
1191 | //print(+knownVars); |
1192 | consume("{"); |
1193 | bool inParensOld = inParens; |
1194 | inParens = false; |
1195 | var body = parseScript(script); |
1196 | consume("}"); |
1197 | inParens = inParensOld; |
1198 | ret body; |
1199 | } |
1200 | |
1201 | Evaluable parseWhileLoop() { |
1202 | consume("while"); |
1203 | var condition = parseExpr(); |
1204 | var body = parseCurlyBlock(new BuildingScript().isLoopBody(true)); |
1205 | ret new Toolbox.While(condition, body); |
1206 | } |
1207 | |
1208 | Evaluable parseDoWhileLoop() { |
1209 | consume("do"); |
1210 | var body = parseCurlyBlock(new BuildingScript().isLoopBody(true)); |
1211 | consume("while"); |
1212 | var condition = parseExpr(); |
1213 | ret new Toolbox.DoWhile(condition, body); |
1214 | } |
1215 | |
1216 | Evaluable parseForEach() { |
1217 | ret new ParseForEach()!; |
1218 | } |
1219 | |
1220 | class ParseForEach { |
1221 | Evaluable collection, body; |
1222 | IF0<Evaluable> finish; |
1223 | new L<TypedVar> vars; |
1224 | |
1225 | TypedVar addVar(TypedVar var) { ret addAndReturn(vars, var); } |
1226 | TypedVar consumeVar() { |
1227 | S name = consumeIdentifier(); |
1228 | var type = parseColonTypeOpt(); |
1229 | ret addVar(new TypedVar(name, typeToValueDescriptor(type))); |
1230 | } |
1231 | |
1232 | void parseBody { |
1233 | temp tempAddKnownTypedVars(vars); |
1234 | body = parseCurlyBlock(new BuildingScript().isLoopBody(true)); |
1235 | } |
1236 | |
1237 | Evaluable get() { |
1238 | consume("for"); |
1239 | |
1240 | // for i to n { ... } |
1241 | if (is(1, "to")) { |
1242 | TypedVar var = consumeVar(); |
1243 | consume("to"); |
1244 | Evaluable endValue = parseExpr(); |
1245 | parseBody(); |
1246 | ret new Toolbox.ForIntTo(endValue, var.name, body); |
1247 | } |
1248 | |
1249 | int iIn = relativeIndexOf("in"); |
1250 | if (iIn < 0) fail("for without in"); |
1251 | print(+iIn); |
1252 | |
1253 | if (iIn == 1 || is(1, ":")) { // just a variable (with optional type) |
1254 | TypedVar var = consumeVar(); |
1255 | finish = -> new Toolbox.ForEach(collection, var.name, body).varType(var.type); |
1256 | } else if (iIn == 2) { // for iterator (mapI) |
1257 | if (consumeOpt("iterator")) { |
1258 | TypedVar var = consumeVar(); |
1259 | finish = -> new Toolbox.ForIterator(collection, var.name, body); |
1260 | } else if (consumeOpt("nested")) { |
1261 | TypedVar var = consumeVar(); |
1262 | finish = -> new Toolbox.ForNested(collection, var.name, body); |
1263 | } else |
1264 | fail("Unknown pattern for 'for' loop"); |
1265 | } else if (iIn == 3) { |
1266 | if (isOneOf("pair", "Pair")) { |
1267 | // "pair a b" or "Pair a b" |
1268 | consume(); |
1269 | TypedVar varA = consumeVar(); |
1270 | TypedVar varB = consumeVar(); |
1271 | printVars(+varA, +varB); |
1272 | |
1273 | finish = -> new Toolbox.ForPairs(collection, body, varA.name, varB.name); |
1274 | } else { |
1275 | // "a, b" |
1276 | TypedVar varA = consumeVar(); |
1277 | consume(","); |
1278 | TypedVar varB = consumeVar(); |
1279 | |
1280 | finish = -> new Toolbox.ForKeyValue(collection, body, varA.name, varB.name); |
1281 | } |
1282 | } else if (iIn == 4) { |
1283 | consume("index"); |
1284 | S varIndex = consumeVar().name; |
1285 | consume(","); |
1286 | S varElement = consumeVar().name; |
1287 | |
1288 | finish = -> new Toolbox.ForIndex(collection, body, varIndex, varElement); |
1289 | } else |
1290 | fail("Unknown pattern for 'for' loop"); |
1291 | |
1292 | consume("in"); |
1293 | collection = parseExpr_inParens(); |
1294 | print(+collection); |
1295 | parseBody(); |
1296 | ret finish!; |
1297 | } |
1298 | } |
1299 | |
1300 | Evaluable parseIfStatement() { |
1301 | consume("if"); |
1302 | Evaluable condition, body, elseBranch = null; |
1303 | { |
1304 | temp tempAdd(closerTokens, "then"); |
1305 | condition = parseExpr(); |
1306 | } |
1307 | |
1308 | if (consumeOpt("then")) { |
1309 | temp tempAdd(closerTokens, "else"); |
1310 | body = parseExpr(); |
1311 | if (consumeOpt("else")) |
1312 | elseBranch = parseExpr(); |
1313 | } else { |
1314 | body = parseCurlyBlock(); |
1315 | if (consumeOpt("else")) { |
1316 | if (is("if")) |
1317 | elseBranch = parseIfStatement(); |
1318 | else |
1319 | elseBranch = parseCurlyBlock(); |
1320 | } |
1321 | } |
1322 | |
1323 | ret new Toolbox.IfThen(condition, body, elseBranch); |
1324 | } |
1325 | |
1326 | Evaluable parseRepeatStatement() { |
1327 | consume("repeat"); |
1328 | var n = parseExpr(); |
1329 | var body = parseCurlyBlock(); |
1330 | ret new Toolbox.RepeatN(n, body); |
1331 | } |
1332 | |
1333 | // declare an external variable with optional type info |
1334 | void addVar(S var, LASValueDescriptor type default new) { knownVars.put(var, type); } |
1335 | void addVar(S var, Class type, bool canBeNull) { |
1336 | addVar(var, typeToValueDescriptor(type, canBeNull)); |
1337 | } |
1338 | |
1339 | LASValueDescriptor typeToValueDescriptor(Type type, bool canBeNull default true) { |
1340 | ret type == null ? new LASValueDescriptor |
1341 | : new LASValueDescriptor.NonExact(typeToClass(type), canBeNull); |
1342 | } |
1343 | |
1344 | // short name to full name |
1345 | // TODO: sort by priority if classes appear twice |
1346 | simplyCached SS globalClassNames() { |
1347 | var packages = mapToTreeSet(importedPackages(), pkg -> pkg + "."); |
1348 | |
1349 | new SS out; |
1350 | |
1351 | fOr (name : importedClasses()) |
1352 | out.put(shortenClassName(name), name); |
1353 | |
1354 | // add function containers themselves (if they're classes) |
1355 | for (fc : functionContainers) |
1356 | if (fc cast Class) { |
1357 | if (isAnonymousClass(fc)) continue; |
1358 | out.put(shortClassName(fc), className(fc)); |
1359 | } |
1360 | |
1361 | // add inner classes of function containers |
1362 | var classContainers = classContainerPrefixes(); |
1363 | TreeSet<S> classContainerSet = asTreeSet(classContainers); |
1364 | |
1365 | for (className : classNameResolver().allFullyQualifiedClassNames()) { |
1366 | if (isAnonymousClassName(className)) continue; |
1367 | if (!contains(className, '$')) { |
1368 | S pkg = longestPrefixInTreeSet(className, packages); |
1369 | if (pkg != null) { |
1370 | S shortName = dropPrefix(pkg, className); |
1371 | if (!shortName.contains(".") && !out.containsKey(shortName)) |
1372 | out.put(shortName, className); |
1373 | } |
1374 | } |
1375 | |
1376 | S container = longestPrefixInTreeSet(className, classContainerSet); |
1377 | if (container != null) { |
1378 | S shortName = dropPrefix(container, className); |
1379 | S existing = out.get(shortName); |
1380 | if (existing != null) { |
1381 | int priority = indexOf(classContainers, container); |
1382 | S oldContainer = longestPrefixInTreeSet(existing, classContainerSet); |
1383 | //int oldPriority = indexOf(classContainers, oldContainer); |
1384 | int oldPriority = smartIndexOf(classContainers, oldContainer); |
1385 | printVars(+className, +shortName, +container, +priority, +existing, +oldPriority); |
1386 | if (priority > oldPriority) |
1387 | continue; |
1388 | } |
1389 | out.put(shortName, className); |
1390 | } |
1391 | } |
1392 | |
1393 | fOr (key, val : classShortcuts()) { |
1394 | S fullName = out.get(val); |
1395 | if (fullName != null) |
1396 | out.put(key, fullName); |
1397 | } |
1398 | |
1399 | ret out; |
1400 | } |
1401 | |
1402 | swappable SS classShortcuts() { |
1403 | ret javaxClassShortcuts(); |
1404 | } |
1405 | |
1406 | swappable Cl<S> importedPackages() { |
1407 | ret itemPlus("java.lang", standardImports_fullyImportedPackages()); |
1408 | } |
1409 | |
1410 | swappable Cl<S> importedClasses() { |
1411 | ret standardImports_singleClasses(); |
1412 | } |
1413 | |
1414 | void addClassAlias(S alias, S longName) { |
1415 | S fullName = globalClassNames().get(longName); |
1416 | if (fullName != null) |
1417 | globalClassNames().put(alias, fullName); |
1418 | } |
1419 | |
1420 | LS classContainerPrefixes() { |
1421 | ret map(functionContainers, fc -> className(fc) + "$"); |
1422 | } |
1423 | |
1424 | // typed exceptions (unusual, I know!) |
1425 | |
1426 | srecord noeq UnknownObject(S name) > RuntimeException { |
1427 | public S getMessage() { ret "Unknown object: " + name; } |
1428 | } |
1429 | |
1430 | sclass ClassNotFound > UnknownObject { |
1431 | *(S className) { super(className); } |
1432 | |
1433 | public S getMessage() { ret "Class not found: " + name; } |
1434 | } |
1435 | |
1436 | asclass Scope { |
1437 | // Parse expression due to special rules in this scope |
1438 | // (return null if no parse) |
1439 | Evaluable parseExprStartingWithIdentifierOpt(S t, bool inner) { null; } |
1440 | |
1441 | Evaluable completeAssignmentOpt(S lhs, Evaluable rhs) { null; } |
1442 | } |
1443 | |
1444 | record ClassDefScope(LASClassDef classDef) extends Scope { |
1445 | Evaluable getThis() { ret new Toolbox.GetVar("this"); } |
1446 | |
1447 | Evaluable parseExprStartingWithIdentifierOpt(S t, bool inner) { |
1448 | printVars("ClassDefScope.parseExprStartingWithIdentifierOpt", +t); |
1449 | |
1450 | ret linkToSrc(-> { |
1451 | var field = classDef.fieldsByName.get(t); |
1452 | if (field != null) { |
1453 | print("Found field " + field); |
1454 | var e = inner |
1455 | ? new Toolbox.GetField(getThis(), field.name) |
1456 | : new Toolbox.CallMethodOrGetField(getThis(), field.name); |
1457 | ret parseCall(inner, e); |
1458 | } |
1459 | |
1460 | Class superClass = typeToClass(classDef.superClass); |
1461 | var field2 = findFieldOfClass(superClass, t); |
1462 | printVars(+field2, +superClass, +t); |
1463 | if (field2 != null) { |
1464 | print("Found field " + field2); |
1465 | var e = inner |
1466 | ? new Toolbox.GetField(getThis(), field2.getName()) |
1467 | : new Toolbox.CallMethodOrGetField(getThis(), field2.getName()); |
1468 | ret parseCall(inner, e); |
1469 | } |
1470 | |
1471 | if (!inner |
1472 | && (classDef.methodsByName.containsKey(t) |
1473 | || classHasMethodNamed(typeToClass(classDef.superClass), t))) { |
1474 | ret new Toolbox.CallMethod(getThis(), t, parseArguments()); |
1475 | } |
1476 | |
1477 | null; |
1478 | }); |
1479 | } |
1480 | |
1481 | Evaluable completeAssignmentOpt(S lhs, Evaluable rhs) { var field = classDef.fieldsByName.get(lhs); |
1482 | if (field != null) |
1483 | ret new Toolbox.SetField(getThis(), lhs, rhs); |
1484 | |
1485 | Class superClass = typeToClass(classDef.superClass); |
1486 | var field2 = findFieldOfClass(superClass, lhs); |
1487 | if (field2 != null) |
1488 | ret new Toolbox.SetField(getThis(), lhs, rhs); |
1489 | |
1490 | null; |
1491 | } |
1492 | } |
1493 | |
1494 | LASClassDef parseClassDef() { |
1495 | ret linkToSrc(-> { |
1496 | consume("class"); |
1497 | new LASClassDef classDef; |
1498 | classDef.verbose(scaffoldingEnabled()); |
1499 | if (nempty(classDefPrefix)) |
1500 | classDef.classDefPrefix(classDefPrefix); |
1501 | S name = consumeIdentifier(); |
1502 | classDef.userGivenName(name); |
1503 | classDefs.put(name, classDef); |
1504 | temp tempMapPut(classDefs, "selfType", classDef); |
1505 | |
1506 | if (consumeOpt("extends")) { |
1507 | classDef.superClass(parseType()); |
1508 | } |
1509 | |
1510 | if (consumeOpt("is")) |
1511 | classDef.interfaces.add(parseType()); |
1512 | |
1513 | consume("{"); |
1514 | |
1515 | // parse class body |
1516 | |
1517 | temp tempAddKnownVar("this", new LASValueDescriptor.NonExactType(classDef.resolvable(lasClassLoader), false)); |
1518 | temp !objectScopesEnabled ? null : tempScope(new ClassDefScope(classDef)); |
1519 | |
1520 | while (!is("}")) { |
1521 | if (is(";")) continue with next(); |
1522 | |
1523 | // parse method declaration |
1524 | |
1525 | if (is("def")) { |
1526 | Toolbox.FunctionDef fd = parseFunctionDefinition(null); |
1527 | printVars(knownVarsAfterFunctionDef := knownVars); |
1528 | classDef.addMethod(fd); |
1529 | continue; |
1530 | } |
1531 | |
1532 | // parse constructor declaration |
1533 | |
1534 | var start = ptr(); |
1535 | if (consumeOpt("ctor")) { |
1536 | var fd = parseFunctionDefinition_step2(null, start, "<init>"); |
1537 | fd.returnType(void.class); |
1538 | classDef.addMethod(fd); |
1539 | continue; |
1540 | } |
1541 | |
1542 | // parse initializer block |
1543 | |
1544 | if (consumeOpt("initializer")) { |
1545 | classDef.initializers.add(parseCurlyBlock()); |
1546 | continue; |
1547 | } |
1548 | |
1549 | // parse field declaration |
1550 | |
1551 | new LASClassDef.FieldDef fd; |
1552 | while (isOneOf("transient", "static")) { fd.addModifier(tpp()); } |
1553 | |
1554 | fd.name(consumeIdentifier()); |
1555 | var type = parseColonType(); |
1556 | fd.type(type); |
1557 | |
1558 | // parse field initializer |
1559 | if (consumeLeftArrowOpt()) |
1560 | fd.initializer(parseExpr()); |
1561 | |
1562 | classDef.addField(fd); |
1563 | } |
1564 | |
1565 | consume("}"); |
1566 | ret classDef; |
1567 | }); |
1568 | } |
1569 | |
1570 | Type parseColonTypeOpt() { |
1571 | if (is(":")) |
1572 | ret parseColonType(); |
1573 | null; |
1574 | } |
1575 | |
1576 | Type parseColonType() { |
1577 | consume(":"); |
1578 | ret parseType(); |
1579 | } |
1580 | |
1581 | Type findType(S typeName) { |
1582 | // script-defined class? |
1583 | LASClassDef cd = classDefs.get(typeName); |
1584 | if (cd != null) |
1585 | ret cd.resolvable(lasClassLoader); |
1586 | |
1587 | O o = findExternalObject(typeName); |
1588 | if (o cast Class) |
1589 | ret o; |
1590 | |
1591 | throw new ClassNotFound(typeName); |
1592 | } |
1593 | |
1594 | Type parseType() { |
1595 | S typeName = consumeIdentifier(); |
1596 | Type type = findType(typeName); |
1597 | ret parseArrayType(parseTypeArguments(type)); |
1598 | } |
1599 | |
1600 | Type parseArrayType(Type type) { |
1601 | while (is("[") && is(1, "]")) { |
1602 | consume(2); |
1603 | type = new GenericArrayTypeImpl(type); |
1604 | } |
1605 | ret type; |
1606 | } |
1607 | |
1608 | Type parseTypeArguments aka parseTypeArgumentsOpt(Type type) { |
1609 | // type arguments |
1610 | if (is("<") && empty(nextSpace()) && isIdentifier(token(1))) { |
1611 | next(); |
1612 | new L<Type> args; |
1613 | while true { |
1614 | args.add(parseType()); |
1615 | if (is(">")) break; |
1616 | consume(","); |
1617 | } |
1618 | consume(">"); |
1619 | |
1620 | if (type != null) |
1621 | type = new ParameterizedTypeImpl(null, type, toTypedArray(Type, args)); |
1622 | } |
1623 | ret type; |
1624 | } |
1625 | |
1626 | swappable Class classForName(S name) ctex { |
1627 | ret Class.forName(name); |
1628 | } |
1629 | |
1630 | // also copies the globalClassNames_cache which otherwise takes |
1631 | // a few ms to calculate |
1632 | void copyFunctionContainersFrom(GazelleV_LeftArrowScriptParser parser) { |
1633 | functionContainers = cloneList(parser.functionContainers); |
1634 | globalClassNames_cache = parser.globalClassNames(); |
1635 | isBlockedFunctionContainerMethod = parser.isBlockedFunctionContainerMethod; |
1636 | } |
1637 | |
1638 | ClassNameResolver classNameResolver() { |
1639 | if (classNameResolver == null) |
1640 | classNameResolver = new ClassNameResolver().byteCodePath(assertNotNull(getBytecodePathForClass(this))).init(); |
1641 | ret classNameResolver; |
1642 | } |
1643 | |
1644 | selfType classNameResolver(ClassNameResolver classNameResolver) { |
1645 | this.classNameResolver = classNameResolver; |
1646 | this; |
1647 | } |
1648 | |
1649 | Evaluable parseStringLiteral() { |
1650 | S s = unquote_relaxedMLS(consume()); |
1651 | if (internStringLiterals) |
1652 | s = intern(s); |
1653 | ret const(s); |
1654 | } |
1655 | |
1656 | void addFunctionDefs(Map<S, FunctionDef> map) { |
1657 | putAll(functionDefs, map); |
1658 | } |
1659 | |
1660 | selfType setFlag(S flag) { |
1661 | flags.add(flag); |
1662 | this; |
1663 | } |
1664 | |
1665 | bool getFlag(S flag) { |
1666 | ret flags.contains(flag); |
1667 | } |
1668 | |
1669 | // forget normally imported class names (e.g. for testing) |
1670 | void noImports() { |
1671 | importedPackages = -> null; |
1672 | importedClasses = -> null; |
1673 | classShortcuts = -> null; |
1674 | } |
1675 | |
1676 | srecord TypedVar(S name, LASValueDescriptor type) {} |
1677 | |
1678 | // parser initializer |
1679 | { |
1680 | internIdentifiers(true); |
1681 | } |
1682 | |
1683 | } // end of GazelleV_LeftArrowScriptParser |
download show line numbers debug dex old transpilations
Travelled to 5 computer(s): bhatertpkbcr, ekrmjmnbrukm, mowyntqkapby, mqqgnosmbjvj, wnsclhtenguj
No comments. add comment
Snippet ID: | #1033976 |
Snippet name: | GazelleV_LeftArrowScriptParser |
Eternal ID of this version: | #1033976/616 |
Text MD5: | e15b68d593c753a1d925e014d1606673 |
Transpilation MD5: | 46dec9939519f43cf30baed6ed0712d4 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2024-07-14 10:56:39 |
Source code size: | 50105 bytes / 1683 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 1474 / 5303 |
Version history: | 615 change(s) |
Referenced in: | [show references] |