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