@rcrsr/rill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +69 -0
- package/dist/cli.js.map +1 -0
- package/dist/demo.d.ts +6 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +121 -0
- package/dist/demo.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/errors.d.ts +9 -0
- package/dist/lexer/errors.d.ts.map +1 -0
- package/dist/lexer/errors.js +12 -0
- package/dist/lexer/errors.js.map +1 -0
- package/dist/lexer/helpers.d.ts +14 -0
- package/dist/lexer/helpers.d.ts.map +1 -0
- package/dist/lexer/helpers.js +30 -0
- package/dist/lexer/helpers.js.map +1 -0
- package/dist/lexer/index.d.ts +8 -0
- package/dist/lexer/index.d.ts.map +1 -0
- package/dist/lexer/index.js +8 -0
- package/dist/lexer/index.js.map +1 -0
- package/dist/lexer/operators.d.ts +11 -0
- package/dist/lexer/operators.d.ts.map +1 -0
- package/dist/lexer/operators.js +58 -0
- package/dist/lexer/operators.js.map +1 -0
- package/dist/lexer/readers.d.ts +12 -0
- package/dist/lexer/readers.d.ts.map +1 -0
- package/dist/lexer/readers.js +144 -0
- package/dist/lexer/readers.js.map +1 -0
- package/dist/lexer/state.d.ts +18 -0
- package/dist/lexer/state.d.ts.map +1 -0
- package/dist/lexer/state.js +37 -0
- package/dist/lexer/state.js.map +1 -0
- package/dist/lexer/tokenizer.d.ts +9 -0
- package/dist/lexer/tokenizer.d.ts.map +1 -0
- package/dist/lexer/tokenizer.js +100 -0
- package/dist/lexer/tokenizer.js.map +1 -0
- package/dist/lexer.d.ts +19 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +344 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser/arithmetic.d.ts +16 -0
- package/dist/parser/arithmetic.d.ts.map +1 -0
- package/dist/parser/arithmetic.js +128 -0
- package/dist/parser/arithmetic.js.map +1 -0
- package/dist/parser/boolean.d.ts +15 -0
- package/dist/parser/boolean.d.ts.map +1 -0
- package/dist/parser/boolean.js +20 -0
- package/dist/parser/boolean.js.map +1 -0
- package/dist/parser/control-flow.d.ts +56 -0
- package/dist/parser/control-flow.d.ts.map +1 -0
- package/dist/parser/control-flow.js +167 -0
- package/dist/parser/control-flow.js.map +1 -0
- package/dist/parser/expressions.d.ts +23 -0
- package/dist/parser/expressions.d.ts.map +1 -0
- package/dist/parser/expressions.js +950 -0
- package/dist/parser/expressions.js.map +1 -0
- package/dist/parser/extraction.d.ts +48 -0
- package/dist/parser/extraction.d.ts.map +1 -0
- package/dist/parser/extraction.js +279 -0
- package/dist/parser/extraction.js.map +1 -0
- package/dist/parser/functions.d.ts +20 -0
- package/dist/parser/functions.d.ts.map +1 -0
- package/dist/parser/functions.js +96 -0
- package/dist/parser/functions.js.map +1 -0
- package/dist/parser/helpers.d.ts +94 -0
- package/dist/parser/helpers.d.ts.map +1 -0
- package/dist/parser/helpers.js +225 -0
- package/dist/parser/helpers.js.map +1 -0
- package/dist/parser/index.d.ts +49 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +73 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/literals.d.ts +37 -0
- package/dist/parser/literals.d.ts.map +1 -0
- package/dist/parser/literals.js +373 -0
- package/dist/parser/literals.js.map +1 -0
- package/dist/parser/parser-collect.d.ts +16 -0
- package/dist/parser/parser-collect.d.ts.map +1 -0
- package/dist/parser/parser-collect.js +125 -0
- package/dist/parser/parser-collect.js.map +1 -0
- package/dist/parser/parser-control.d.ts +20 -0
- package/dist/parser/parser-control.d.ts.map +1 -0
- package/dist/parser/parser-control.js +120 -0
- package/dist/parser/parser-control.js.map +1 -0
- package/dist/parser/parser-expr.d.ts +37 -0
- package/dist/parser/parser-expr.d.ts.map +1 -0
- package/dist/parser/parser-expr.js +639 -0
- package/dist/parser/parser-expr.js.map +1 -0
- package/dist/parser/parser-extract.d.ts +17 -0
- package/dist/parser/parser-extract.d.ts.map +1 -0
- package/dist/parser/parser-extract.js +222 -0
- package/dist/parser/parser-extract.js.map +1 -0
- package/dist/parser/parser-functions.d.ts +21 -0
- package/dist/parser/parser-functions.d.ts.map +1 -0
- package/dist/parser/parser-functions.js +155 -0
- package/dist/parser/parser-functions.js.map +1 -0
- package/dist/parser/parser-literals.d.ts +22 -0
- package/dist/parser/parser-literals.d.ts.map +1 -0
- package/dist/parser/parser-literals.js +288 -0
- package/dist/parser/parser-literals.js.map +1 -0
- package/dist/parser/parser-script.d.ts +21 -0
- package/dist/parser/parser-script.d.ts.map +1 -0
- package/dist/parser/parser-script.js +174 -0
- package/dist/parser/parser-script.js.map +1 -0
- package/dist/parser/parser-variables.d.ts +20 -0
- package/dist/parser/parser-variables.d.ts.map +1 -0
- package/dist/parser/parser-variables.js +146 -0
- package/dist/parser/parser-variables.js.map +1 -0
- package/dist/parser/parser.d.ts +49 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +54 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/script.d.ts +14 -0
- package/dist/parser/script.d.ts.map +1 -0
- package/dist/parser/script.js +196 -0
- package/dist/parser/script.js.map +1 -0
- package/dist/parser/state.d.ts +40 -0
- package/dist/parser/state.d.ts.map +1 -0
- package/dist/parser/state.js +129 -0
- package/dist/parser/state.js.map +1 -0
- package/dist/parser/variables.d.ts +10 -0
- package/dist/parser/variables.d.ts.map +1 -0
- package/dist/parser/variables.js +215 -0
- package/dist/parser/variables.js.map +1 -0
- package/dist/runtime/ast-equals.d.ts +13 -0
- package/dist/runtime/ast-equals.d.ts.map +1 -0
- package/dist/runtime/ast-equals.js +447 -0
- package/dist/runtime/ast-equals.js.map +1 -0
- package/dist/runtime/builtins.d.ts +13 -0
- package/dist/runtime/builtins.d.ts.map +1 -0
- package/dist/runtime/builtins.js +180 -0
- package/dist/runtime/builtins.js.map +1 -0
- package/dist/runtime/callable.d.ts +88 -0
- package/dist/runtime/callable.d.ts.map +1 -0
- package/dist/runtime/callable.js +98 -0
- package/dist/runtime/callable.js.map +1 -0
- package/dist/runtime/context.d.ts +13 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +73 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/core/callable.d.ts +171 -0
- package/dist/runtime/core/callable.d.ts.map +1 -0
- package/dist/runtime/core/callable.js +246 -0
- package/dist/runtime/core/callable.js.map +1 -0
- package/dist/runtime/core/context.d.ts +29 -0
- package/dist/runtime/core/context.d.ts.map +1 -0
- package/dist/runtime/core/context.js +154 -0
- package/dist/runtime/core/context.js.map +1 -0
- package/dist/runtime/core/equals.d.ts +9 -0
- package/dist/runtime/core/equals.d.ts.map +1 -0
- package/dist/runtime/core/equals.js +381 -0
- package/dist/runtime/core/equals.js.map +1 -0
- package/dist/runtime/core/eval/base.d.ts +65 -0
- package/dist/runtime/core/eval/base.d.ts.map +1 -0
- package/dist/runtime/core/eval/base.js +112 -0
- package/dist/runtime/core/eval/base.js.map +1 -0
- package/dist/runtime/core/eval/evaluator.d.ts +47 -0
- package/dist/runtime/core/eval/evaluator.d.ts.map +1 -0
- package/dist/runtime/core/eval/evaluator.js +73 -0
- package/dist/runtime/core/eval/evaluator.js.map +1 -0
- package/dist/runtime/core/eval/index.d.ts +57 -0
- package/dist/runtime/core/eval/index.d.ts.map +1 -0
- package/dist/runtime/core/eval/index.js +95 -0
- package/dist/runtime/core/eval/index.js.map +1 -0
- package/dist/runtime/core/eval/mixins/annotations.d.ts +19 -0
- package/dist/runtime/core/eval/mixins/annotations.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/annotations.js +146 -0
- package/dist/runtime/core/eval/mixins/annotations.js.map +1 -0
- package/dist/runtime/core/eval/mixins/closures.d.ts +49 -0
- package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/closures.js +479 -0
- package/dist/runtime/core/eval/mixins/closures.js.map +1 -0
- package/dist/runtime/core/eval/mixins/collections.d.ts +24 -0
- package/dist/runtime/core/eval/mixins/collections.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/collections.js +466 -0
- package/dist/runtime/core/eval/mixins/collections.js.map +1 -0
- package/dist/runtime/core/eval/mixins/control-flow.d.ts +27 -0
- package/dist/runtime/core/eval/mixins/control-flow.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/control-flow.js +369 -0
- package/dist/runtime/core/eval/mixins/control-flow.js.map +1 -0
- package/dist/runtime/core/eval/mixins/core.d.ts +24 -0
- package/dist/runtime/core/eval/mixins/core.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/core.js +335 -0
- package/dist/runtime/core/eval/mixins/core.js.map +1 -0
- package/dist/runtime/core/eval/mixins/expressions.d.ts +19 -0
- package/dist/runtime/core/eval/mixins/expressions.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/expressions.js +202 -0
- package/dist/runtime/core/eval/mixins/expressions.js.map +1 -0
- package/dist/runtime/core/eval/mixins/extraction.d.ts +10 -0
- package/dist/runtime/core/eval/mixins/extraction.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/extraction.js +250 -0
- package/dist/runtime/core/eval/mixins/extraction.js.map +1 -0
- package/dist/runtime/core/eval/mixins/literals.d.ts +23 -0
- package/dist/runtime/core/eval/mixins/literals.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/literals.js +180 -0
- package/dist/runtime/core/eval/mixins/literals.js.map +1 -0
- package/dist/runtime/core/eval/mixins/types.d.ts +20 -0
- package/dist/runtime/core/eval/mixins/types.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/types.js +109 -0
- package/dist/runtime/core/eval/mixins/types.js.map +1 -0
- package/dist/runtime/core/eval/mixins/variables.d.ts +34 -0
- package/dist/runtime/core/eval/mixins/variables.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/variables.js +247 -0
- package/dist/runtime/core/eval/mixins/variables.js.map +1 -0
- package/dist/runtime/core/eval/types.d.ts +41 -0
- package/dist/runtime/core/eval/types.d.ts.map +1 -0
- package/dist/runtime/core/eval/types.js +10 -0
- package/dist/runtime/core/eval/types.js.map +1 -0
- package/dist/runtime/core/evaluate.d.ts +42 -0
- package/dist/runtime/core/evaluate.d.ts.map +1 -0
- package/dist/runtime/core/evaluate.debug.js +1251 -0
- package/dist/runtime/core/evaluate.js +1913 -0
- package/dist/runtime/core/evaluate.js.map +1 -0
- package/dist/runtime/core/execute.d.ts +26 -0
- package/dist/runtime/core/execute.d.ts.map +1 -0
- package/dist/runtime/core/execute.js +177 -0
- package/dist/runtime/core/execute.js.map +1 -0
- package/dist/runtime/core/signals.d.ts +19 -0
- package/dist/runtime/core/signals.d.ts.map +1 -0
- package/dist/runtime/core/signals.js +26 -0
- package/dist/runtime/core/signals.js.map +1 -0
- package/dist/runtime/core/types.d.ts +177 -0
- package/dist/runtime/core/types.d.ts.map +1 -0
- package/dist/runtime/core/types.js +50 -0
- package/dist/runtime/core/types.js.map +1 -0
- package/dist/runtime/core/values.d.ts +66 -0
- package/dist/runtime/core/values.d.ts.map +1 -0
- package/dist/runtime/core/values.js +240 -0
- package/dist/runtime/core/values.js.map +1 -0
- package/dist/runtime/evaluate.d.ts +32 -0
- package/dist/runtime/evaluate.d.ts.map +1 -0
- package/dist/runtime/evaluate.js +1111 -0
- package/dist/runtime/evaluate.js.map +1 -0
- package/dist/runtime/execute.d.ts +26 -0
- package/dist/runtime/execute.d.ts.map +1 -0
- package/dist/runtime/execute.js +121 -0
- package/dist/runtime/execute.js.map +1 -0
- package/dist/runtime/ext/builtins.d.ts +16 -0
- package/dist/runtime/ext/builtins.d.ts.map +1 -0
- package/dist/runtime/ext/builtins.js +528 -0
- package/dist/runtime/ext/builtins.js.map +1 -0
- package/dist/runtime/ext/content-parser.d.ts +83 -0
- package/dist/runtime/ext/content-parser.d.ts.map +1 -0
- package/dist/runtime/ext/content-parser.js +536 -0
- package/dist/runtime/ext/content-parser.js.map +1 -0
- package/dist/runtime/index.d.ts +28 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +34 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/signals.d.ts +19 -0
- package/dist/runtime/signals.d.ts.map +1 -0
- package/dist/runtime/signals.js +26 -0
- package/dist/runtime/signals.js.map +1 -0
- package/dist/runtime/types.d.ts +169 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +50 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/values.d.ts +50 -0
- package/dist/runtime/values.d.ts.map +1 -0
- package/dist/runtime/values.js +209 -0
- package/dist/runtime/values.js.map +1 -0
- package/dist/runtime.d.ts +254 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +2014 -0
- package/dist/runtime.js.map +1 -0
- package/dist/types.d.ts +752 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +189 -0
- package/dist/types.js.map +1 -0
- package/docs/00_INDEX.md +65 -0
- package/docs/01_guide.md +390 -0
- package/docs/02_types.md +399 -0
- package/docs/03_variables.md +314 -0
- package/docs/04_operators.md +551 -0
- package/docs/05_control-flow.md +350 -0
- package/docs/06_closures.md +353 -0
- package/docs/07_collections.md +686 -0
- package/docs/08_iterators.md +330 -0
- package/docs/09_strings.md +205 -0
- package/docs/10_parsing.md +366 -0
- package/docs/11_reference.md +350 -0
- package/docs/12_examples.md +771 -0
- package/docs/13_modules.md +519 -0
- package/docs/14_host-integration.md +826 -0
- package/docs/15_grammar.ebnf +693 -0
- package/docs/16_conventions.md +696 -0
- package/docs/99_llm-reference.txt +300 -0
- package/docs/assets/logo.png +0 -0
- package/package.json +70 -0
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression Parsing
|
|
3
|
+
* Primary expressions, postfix expressions, pipe chains, and pipe targets
|
|
4
|
+
*/
|
|
5
|
+
import { ParseError, TOKEN_TYPES } from '../types.js';
|
|
6
|
+
import { check, advance, expect, current, makeSpan, peek, } from './state.js';
|
|
7
|
+
import { isHostCall, isClosureCall, canStartPipeInvoke, isMethodCall, isTypedCaptureWithArrow, isInlineCaptureWithArrow, isClosureChainTarget, isNegativeNumber, isLiteralStart, isClosureStart, makeBoolLiteralBlock, VALID_TYPE_NAMES, parseTypeName, } from './helpers.js';
|
|
8
|
+
import { parseVariable, setParseBlock as setVariablesParseBlock, setParsePipeChain as setVariablesParsePipeChain, } from './variables.js';
|
|
9
|
+
import { parseLiteral, parseString, parseClosure, setParseExpression as setLiteralsParseExpression, setParseBlock as setLiteralsParseBlock, setParseGrouped as setLiteralsParseGrouped, setParsePostfixExpr as setLiteralsParsePostfixExpr, setLiteralsParsePipeChain, } from './literals.js';
|
|
10
|
+
import { parseHostCall, parseClosureCall, parsePipeInvoke, parseMethodCall, setParseExpression as setFunctionsParseExpression, } from './functions.js';
|
|
11
|
+
import { parsePipedConditional, parseConditionalWithCondition, parseConditionalRest, parseLoop, parseLoopWithInput, parseBlock, } from './control-flow.js';
|
|
12
|
+
import { parseClosureChain, parseDestructure, parseSlice, parseSpread, parseSpreadTarget, setParsePostfixExpr as setExtractionParsePostfixExpr, setParseGrouped as setExtractionParseGrouped, } from './extraction.js';
|
|
13
|
+
/**
|
|
14
|
+
* Parse constructs common to both primary expressions and pipe targets.
|
|
15
|
+
* Returns null if no common construct matches.
|
|
16
|
+
*/
|
|
17
|
+
function parseCommonConstruct(state) {
|
|
18
|
+
// Boolean negation: !expr (for filter predicates like !.empty in pipes)
|
|
19
|
+
// Can be: !expr ? then ! else OR standalone !expr (returns true/false)
|
|
20
|
+
if (check(state, TOKEN_TYPES.BANG)) {
|
|
21
|
+
const start = current(state).span.start;
|
|
22
|
+
advance(state); // consume !
|
|
23
|
+
// Use parsePostfixExprBase to avoid consuming `?` - we handle it ourselves
|
|
24
|
+
const operand = parsePostfixExprBase(state);
|
|
25
|
+
const span = makeSpan(start, operand.span.end);
|
|
26
|
+
// Build the negation condition as unified expression
|
|
27
|
+
const unaryExpr = {
|
|
28
|
+
type: 'UnaryExpr',
|
|
29
|
+
op: '!',
|
|
30
|
+
operand,
|
|
31
|
+
span,
|
|
32
|
+
};
|
|
33
|
+
const negationCondition = {
|
|
34
|
+
type: 'GroupedExpr',
|
|
35
|
+
expression: {
|
|
36
|
+
type: 'PipeChain',
|
|
37
|
+
head: unaryExpr,
|
|
38
|
+
pipes: [],
|
|
39
|
+
terminator: null,
|
|
40
|
+
span,
|
|
41
|
+
},
|
|
42
|
+
span,
|
|
43
|
+
};
|
|
44
|
+
// Check for conditional: !expr ? then ! else
|
|
45
|
+
if (check(state, TOKEN_TYPES.QUESTION)) {
|
|
46
|
+
advance(state); // consume ?
|
|
47
|
+
return parseConditionalRest(state, negationCondition, start);
|
|
48
|
+
}
|
|
49
|
+
// Standalone negation: evaluates to true/false
|
|
50
|
+
return {
|
|
51
|
+
type: 'Conditional',
|
|
52
|
+
input: null,
|
|
53
|
+
condition: negationCondition,
|
|
54
|
+
thenBranch: makeBoolLiteralBlock(true, operand.span),
|
|
55
|
+
elseBranch: makeBoolLiteralBlock(false, operand.span),
|
|
56
|
+
span,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Piped conditional: bare `?` uses $ as condition
|
|
60
|
+
if (check(state, TOKEN_TYPES.QUESTION)) {
|
|
61
|
+
return parsePipedConditional(state);
|
|
62
|
+
}
|
|
63
|
+
// Loop: @ body [? cond]
|
|
64
|
+
if (check(state, TOKEN_TYPES.AT)) {
|
|
65
|
+
return parseLoop(state, null);
|
|
66
|
+
}
|
|
67
|
+
// Block (may be followed by @ for loop with input, or ? for conditional)
|
|
68
|
+
if (check(state, TOKEN_TYPES.LBRACE)) {
|
|
69
|
+
const block = parseBlock(state);
|
|
70
|
+
// Check for loop: { input } @ body
|
|
71
|
+
if (check(state, TOKEN_TYPES.AT)) {
|
|
72
|
+
return parseLoopWithInput(state, block);
|
|
73
|
+
}
|
|
74
|
+
// Check for conditional: { expr } ? then ! else
|
|
75
|
+
if (check(state, TOKEN_TYPES.QUESTION)) {
|
|
76
|
+
return parseConditionalWithCondition(state, block);
|
|
77
|
+
}
|
|
78
|
+
return block;
|
|
79
|
+
}
|
|
80
|
+
// Grouped expression: ( inner-expr )
|
|
81
|
+
// Allows arithmetic, pipes, and compound expressions
|
|
82
|
+
// May be followed by: @ for loop, ? for conditional
|
|
83
|
+
if (check(state, TOKEN_TYPES.LPAREN)) {
|
|
84
|
+
const grouped = parseGrouped(state);
|
|
85
|
+
// Check for loop: (expr) @ body
|
|
86
|
+
if (check(state, TOKEN_TYPES.AT)) {
|
|
87
|
+
return parseLoopWithInput(state, grouped);
|
|
88
|
+
}
|
|
89
|
+
// Check for conditional: (expr) ? then ! else
|
|
90
|
+
if (check(state, TOKEN_TYPES.QUESTION)) {
|
|
91
|
+
return parseConditionalWithCondition(state, grouped);
|
|
92
|
+
}
|
|
93
|
+
return grouped;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
// ============================================================
|
|
98
|
+
// EXPRESSION PARSING
|
|
99
|
+
// ============================================================
|
|
100
|
+
export function parseExpression(state) {
|
|
101
|
+
return parsePipeChain(state);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Helper to create implicit pipe variable ($) for bare break/return
|
|
105
|
+
*/
|
|
106
|
+
function implicitPipeVar(span) {
|
|
107
|
+
const varNode = {
|
|
108
|
+
type: 'Variable',
|
|
109
|
+
name: null,
|
|
110
|
+
isPipeVar: true,
|
|
111
|
+
accessChain: [],
|
|
112
|
+
fieldAccess: [],
|
|
113
|
+
bracketAccess: [],
|
|
114
|
+
defaultValue: null,
|
|
115
|
+
existenceCheck: null,
|
|
116
|
+
span,
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
type: 'PostfixExpr',
|
|
120
|
+
primary: varNode,
|
|
121
|
+
methods: [],
|
|
122
|
+
span,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function parsePipeChain(state) {
|
|
126
|
+
const start = current(state).span.start;
|
|
127
|
+
// Handle bare break: "break" ≡ "$ -> break"
|
|
128
|
+
if (check(state, TOKEN_TYPES.BREAK)) {
|
|
129
|
+
const token = advance(state);
|
|
130
|
+
return {
|
|
131
|
+
type: 'PipeChain',
|
|
132
|
+
head: implicitPipeVar(token.span),
|
|
133
|
+
pipes: [],
|
|
134
|
+
terminator: { type: 'Break', span: token.span },
|
|
135
|
+
span: token.span,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Handle bare return: "return" ≡ "$ -> return"
|
|
139
|
+
if (check(state, TOKEN_TYPES.RETURN)) {
|
|
140
|
+
const token = advance(state);
|
|
141
|
+
return {
|
|
142
|
+
type: 'PipeChain',
|
|
143
|
+
head: implicitPipeVar(token.span),
|
|
144
|
+
pipes: [],
|
|
145
|
+
terminator: { type: 'Return', span: token.span },
|
|
146
|
+
span: token.span,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Parse expression head with full precedence chain:
|
|
150
|
+
// logical-or -> logical-and -> comparison -> additive -> multiplicative -> unary -> postfix
|
|
151
|
+
let head = parseLogicalOr(state);
|
|
152
|
+
// Check for loop: expr @ body
|
|
153
|
+
// This allows: $status.pending @ { ... }, ($x < 10) @ { ... }
|
|
154
|
+
if (check(state, TOKEN_TYPES.AT)) {
|
|
155
|
+
const headAsPipeChain = {
|
|
156
|
+
type: 'PipeChain',
|
|
157
|
+
head,
|
|
158
|
+
pipes: [],
|
|
159
|
+
terminator: null,
|
|
160
|
+
span: head.span,
|
|
161
|
+
};
|
|
162
|
+
const loop = parseLoopWithInput(state, headAsPipeChain);
|
|
163
|
+
const span = makeSpan(head.span.start, current(state).span.end);
|
|
164
|
+
head = wrapLoopInPostfixExpr(loop, span);
|
|
165
|
+
}
|
|
166
|
+
// Check for conditional: expr ? then ! else
|
|
167
|
+
// This allows: 5 + 3 ? "big" ! "small", $ready ? "go" ! "wait"
|
|
168
|
+
if (check(state, TOKEN_TYPES.QUESTION)) {
|
|
169
|
+
const headAsPipeChain = {
|
|
170
|
+
type: 'PipeChain',
|
|
171
|
+
head,
|
|
172
|
+
pipes: [],
|
|
173
|
+
terminator: null,
|
|
174
|
+
span: head.span,
|
|
175
|
+
};
|
|
176
|
+
const conditional = parseConditionalWithCondition(state, headAsPipeChain);
|
|
177
|
+
const span = makeSpan(head.span.start, current(state).span.end);
|
|
178
|
+
head = wrapConditionalInPostfixExpr(conditional, span);
|
|
179
|
+
}
|
|
180
|
+
const pipes = [];
|
|
181
|
+
let terminator = null;
|
|
182
|
+
while (check(state, TOKEN_TYPES.ARROW)) {
|
|
183
|
+
advance(state);
|
|
184
|
+
// Check for break terminator: -> break
|
|
185
|
+
if (check(state, TOKEN_TYPES.BREAK)) {
|
|
186
|
+
const token = advance(state);
|
|
187
|
+
terminator = { type: 'Break', span: token.span };
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
// Check for return terminator: -> return
|
|
191
|
+
if (check(state, TOKEN_TYPES.RETURN)) {
|
|
192
|
+
const token = advance(state);
|
|
193
|
+
terminator = { type: 'Return', span: token.span };
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
// Check for capture vs ClosureCall: $identifier
|
|
197
|
+
if (check(state, TOKEN_TYPES.DOLLAR)) {
|
|
198
|
+
// ClosureCall: $name( - pass to parsePipeTarget
|
|
199
|
+
if (isClosureCall(state)) {
|
|
200
|
+
pipes.push(parsePipeTarget(state));
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
// Inline capture: $name -> (followed by arrow)
|
|
204
|
+
if (isInlineCaptureWithArrow(state)) {
|
|
205
|
+
pipes.push(parseCapture(state));
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
// Inline capture with type: $name:type -> (followed by arrow)
|
|
209
|
+
if (isTypedCaptureWithArrow(state)) {
|
|
210
|
+
pipes.push(parseCapture(state));
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// Terminal capture: $name or $name:type (end of chain)
|
|
214
|
+
terminator = parseCapture(state);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
pipes.push(parsePipeTarget(state));
|
|
218
|
+
}
|
|
219
|
+
// Check for conditional after pipe chain: $val -> :?string ? then ! else
|
|
220
|
+
if (check(state, TOKEN_TYPES.QUESTION) && pipes.length > 0) {
|
|
221
|
+
const span = makeSpan(start, current(state).span.end);
|
|
222
|
+
const chainAsCondition = {
|
|
223
|
+
type: 'PipeChain',
|
|
224
|
+
head,
|
|
225
|
+
pipes,
|
|
226
|
+
terminator: null,
|
|
227
|
+
span,
|
|
228
|
+
};
|
|
229
|
+
const conditional = parseConditionalWithCondition(state, chainAsCondition);
|
|
230
|
+
const resultSpan = makeSpan(start, current(state).span.end);
|
|
231
|
+
return {
|
|
232
|
+
type: 'PipeChain',
|
|
233
|
+
head: wrapConditionalInPostfixExpr(conditional, resultSpan),
|
|
234
|
+
pipes: [],
|
|
235
|
+
terminator: null,
|
|
236
|
+
span: resultSpan,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
type: 'PipeChain',
|
|
241
|
+
head,
|
|
242
|
+
pipes,
|
|
243
|
+
terminator,
|
|
244
|
+
span: makeSpan(start, current(state).span.end),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
export function parsePostfixExpr(state) {
|
|
248
|
+
const postfixExpr = parsePostfixExprBase(state);
|
|
249
|
+
// Check if this postfix-expr is a condition for a conditional: expr ? then ! else
|
|
250
|
+
// This allows: $ready ? "go" ! "wait", $data.valid ? process() ! skip()
|
|
251
|
+
if (check(state, TOKEN_TYPES.QUESTION)) {
|
|
252
|
+
const conditional = parseConditionalWithCondition(state, postfixExpr);
|
|
253
|
+
const span = makeSpan(postfixExpr.span.start, current(state).span.end);
|
|
254
|
+
return wrapConditionalInPostfixExpr(conditional, span);
|
|
255
|
+
}
|
|
256
|
+
return postfixExpr;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Parse postfix expression without checking for trailing `?` conditional.
|
|
260
|
+
* Used when the caller needs to handle the `?` themselves (e.g., for negation).
|
|
261
|
+
*/
|
|
262
|
+
function parsePostfixExprBase(state) {
|
|
263
|
+
const start = current(state).span.start;
|
|
264
|
+
let primary = parsePrimary(state);
|
|
265
|
+
// Check for postfix type assertion: expr:type or expr:?type
|
|
266
|
+
// This binds tighter than method calls: 42:number.str means (42:number).str
|
|
267
|
+
if (check(state, TOKEN_TYPES.COLON)) {
|
|
268
|
+
primary = parsePostfixTypeOperation(state, primary, start);
|
|
269
|
+
}
|
|
270
|
+
const methods = [];
|
|
271
|
+
// Parse method calls and invocations
|
|
272
|
+
// Method call: .name(args) or .name
|
|
273
|
+
// Invocation: (args) - calls the result as a closure
|
|
274
|
+
while (isMethodCall(state) || check(state, TOKEN_TYPES.LPAREN)) {
|
|
275
|
+
if (isMethodCall(state)) {
|
|
276
|
+
methods.push(parseMethodCall(state));
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// Postfix invocation: expr(args)
|
|
280
|
+
methods.push(parseInvoke(state));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
type: 'PostfixExpr',
|
|
285
|
+
primary,
|
|
286
|
+
methods,
|
|
287
|
+
span: makeSpan(start, current(state).span.end),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Parse postfix invocation: (args)
|
|
292
|
+
* This allows calling the result of any expression as a closure.
|
|
293
|
+
* Examples: $handlers[0](), $dict.method()(), ($closure)()
|
|
294
|
+
*/
|
|
295
|
+
function parseInvoke(state) {
|
|
296
|
+
const start = current(state).span.start;
|
|
297
|
+
expect(state, TOKEN_TYPES.LPAREN, 'Expected (');
|
|
298
|
+
const args = [];
|
|
299
|
+
if (!check(state, TOKEN_TYPES.RPAREN)) {
|
|
300
|
+
args.push(parsePipeChain(state));
|
|
301
|
+
while (check(state, TOKEN_TYPES.COMMA)) {
|
|
302
|
+
advance(state); // consume ,
|
|
303
|
+
args.push(parsePipeChain(state));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
expect(state, TOKEN_TYPES.RPAREN, 'Expected )');
|
|
307
|
+
return {
|
|
308
|
+
type: 'Invoke',
|
|
309
|
+
args,
|
|
310
|
+
span: makeSpan(start, current(state).span.end),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Parse postfix type operation: primary:type or primary:?type
|
|
315
|
+
* Creates TypeAssertion or TypeCheck node with the primary as operand.
|
|
316
|
+
*/
|
|
317
|
+
function parsePostfixTypeOperation(state, primary, start) {
|
|
318
|
+
expect(state, TOKEN_TYPES.COLON, 'Expected :');
|
|
319
|
+
// Check for type check (question mark)
|
|
320
|
+
const isCheck = check(state, TOKEN_TYPES.QUESTION);
|
|
321
|
+
if (isCheck) {
|
|
322
|
+
advance(state); // consume ?
|
|
323
|
+
}
|
|
324
|
+
// Parse type name
|
|
325
|
+
const typeName = parseTypeName(state, VALID_TYPE_NAMES);
|
|
326
|
+
// Wrap primary in PostfixExprNode for the operand
|
|
327
|
+
const operand = {
|
|
328
|
+
type: 'PostfixExpr',
|
|
329
|
+
primary,
|
|
330
|
+
methods: [],
|
|
331
|
+
span: makeSpan(start, current(state).span.end),
|
|
332
|
+
};
|
|
333
|
+
const span = makeSpan(start, current(state).span.end);
|
|
334
|
+
if (isCheck) {
|
|
335
|
+
return {
|
|
336
|
+
type: 'TypeCheck',
|
|
337
|
+
operand,
|
|
338
|
+
typeName,
|
|
339
|
+
span,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
type: 'TypeAssertion',
|
|
344
|
+
operand,
|
|
345
|
+
typeName,
|
|
346
|
+
span,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// ============================================================
|
|
350
|
+
// PRIMARY PARSING
|
|
351
|
+
// ============================================================
|
|
352
|
+
export function parsePrimary(state) {
|
|
353
|
+
// Spread operator: *expr - convert tuple/dict to args
|
|
354
|
+
if (check(state, TOKEN_TYPES.STAR)) {
|
|
355
|
+
return parseSpread(state);
|
|
356
|
+
}
|
|
357
|
+
// Unary minus for negative numbers: -42
|
|
358
|
+
if (isNegativeNumber(state)) {
|
|
359
|
+
const start = current(state).span.start;
|
|
360
|
+
advance(state); // consume -
|
|
361
|
+
const numToken = advance(state); // consume number
|
|
362
|
+
return {
|
|
363
|
+
type: 'NumberLiteral',
|
|
364
|
+
value: -parseFloat(numToken.value),
|
|
365
|
+
span: makeSpan(start, numToken.span.end),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
// Closure: |params| body or || body
|
|
369
|
+
if (isClosureStart(state)) {
|
|
370
|
+
return parseClosure(state);
|
|
371
|
+
}
|
|
372
|
+
// Literal (strings, numbers, booleans, tuples/dicts)
|
|
373
|
+
if (isLiteralStart(state)) {
|
|
374
|
+
return parseLiteral(state);
|
|
375
|
+
}
|
|
376
|
+
// Closure call: $fn(args) - closure invocation
|
|
377
|
+
if (isClosureCall(state)) {
|
|
378
|
+
return parseClosureCall(state);
|
|
379
|
+
}
|
|
380
|
+
// Variable
|
|
381
|
+
if (check(state, TOKEN_TYPES.DOLLAR, TOKEN_TYPES.PIPE_VAR)) {
|
|
382
|
+
return parseVariable(state);
|
|
383
|
+
}
|
|
384
|
+
// Bare method call: .method ≡ $ -> .method (implicit pipe var receiver)
|
|
385
|
+
if (isMethodCall(state)) {
|
|
386
|
+
return parseMethodCall(state);
|
|
387
|
+
}
|
|
388
|
+
// Function call (identifier followed by paren)
|
|
389
|
+
if (isHostCall(state)) {
|
|
390
|
+
return parseHostCall(state);
|
|
391
|
+
}
|
|
392
|
+
// Common constructs: conditionals, loops, blocks, grouped expressions
|
|
393
|
+
const common = parseCommonConstruct(state);
|
|
394
|
+
if (common)
|
|
395
|
+
return common;
|
|
396
|
+
throw new ParseError(`Unexpected token: ${current(state).value}`, current(state).span.start);
|
|
397
|
+
}
|
|
398
|
+
// ============================================================
|
|
399
|
+
// PIPE TARGET PARSING
|
|
400
|
+
// ============================================================
|
|
401
|
+
export function parsePipeTarget(state) {
|
|
402
|
+
// Type operations: -> :type or -> :?type
|
|
403
|
+
if (check(state, TOKEN_TYPES.COLON)) {
|
|
404
|
+
return parseTypeOperation(state);
|
|
405
|
+
}
|
|
406
|
+
// Spread as pipe target: -> * (convert pipe value to args)
|
|
407
|
+
if (check(state, TOKEN_TYPES.STAR)) {
|
|
408
|
+
return parseSpreadTarget(state);
|
|
409
|
+
}
|
|
410
|
+
// Extraction operators
|
|
411
|
+
if (check(state, TOKEN_TYPES.STAR_LT)) {
|
|
412
|
+
return parseDestructure(state);
|
|
413
|
+
}
|
|
414
|
+
if (check(state, TOKEN_TYPES.SLASH_LT)) {
|
|
415
|
+
return parseSlice(state);
|
|
416
|
+
}
|
|
417
|
+
// Collection operators: -> each, -> map, -> fold, -> filter
|
|
418
|
+
if (check(state, TOKEN_TYPES.EACH)) {
|
|
419
|
+
return parseEachExpr(state);
|
|
420
|
+
}
|
|
421
|
+
if (check(state, TOKEN_TYPES.MAP)) {
|
|
422
|
+
return parseMapExpr(state);
|
|
423
|
+
}
|
|
424
|
+
if (check(state, TOKEN_TYPES.FOLD)) {
|
|
425
|
+
return parseFoldExpr(state);
|
|
426
|
+
}
|
|
427
|
+
if (check(state, TOKEN_TYPES.FILTER)) {
|
|
428
|
+
return parseFilterExpr(state);
|
|
429
|
+
}
|
|
430
|
+
// Method call (starts with .) - may be condition for conditional
|
|
431
|
+
if (check(state, TOKEN_TYPES.DOT)) {
|
|
432
|
+
const methodCall = parseMethodCall(state);
|
|
433
|
+
// Check if this is a condition for a conditional: .valid ? then ! else
|
|
434
|
+
if (check(state, TOKEN_TYPES.QUESTION)) {
|
|
435
|
+
// Wrap in PostfixExpr with implicit $ receiver for the condition
|
|
436
|
+
const postfixExpr = {
|
|
437
|
+
type: 'PostfixExpr',
|
|
438
|
+
primary: {
|
|
439
|
+
type: 'Variable',
|
|
440
|
+
name: null,
|
|
441
|
+
isPipeVar: true,
|
|
442
|
+
accessChain: [],
|
|
443
|
+
fieldAccess: [],
|
|
444
|
+
bracketAccess: [],
|
|
445
|
+
defaultValue: null,
|
|
446
|
+
existenceCheck: null,
|
|
447
|
+
span: methodCall.span,
|
|
448
|
+
},
|
|
449
|
+
methods: [methodCall],
|
|
450
|
+
span: methodCall.span,
|
|
451
|
+
};
|
|
452
|
+
return parseConditionalWithCondition(state, postfixExpr);
|
|
453
|
+
}
|
|
454
|
+
return methodCall;
|
|
455
|
+
}
|
|
456
|
+
// Closure call: $fn(args) - closure invocation as pipe target
|
|
457
|
+
if (isClosureCall(state)) {
|
|
458
|
+
return parseClosureCall(state);
|
|
459
|
+
}
|
|
460
|
+
// Sequential spread: -> @$var or -> @[closures] (not @{ } which is for-loop, not @( which is while)
|
|
461
|
+
if (isClosureChainTarget(state)) {
|
|
462
|
+
return parseClosureChain(state);
|
|
463
|
+
}
|
|
464
|
+
// Pipe invoke: -> $() or -> $(args) - invoke pipe value as closure
|
|
465
|
+
// The $ prefix distinguishes from grouped expressions: -> (expr)
|
|
466
|
+
if (canStartPipeInvoke(state)) {
|
|
467
|
+
return parsePipeInvoke(state);
|
|
468
|
+
}
|
|
469
|
+
// String literal (template with {$} interpolation)
|
|
470
|
+
if (check(state, TOKEN_TYPES.STRING)) {
|
|
471
|
+
return parseString(state);
|
|
472
|
+
}
|
|
473
|
+
// Function call with parens
|
|
474
|
+
if (isHostCall(state)) {
|
|
475
|
+
return parseHostCall(state);
|
|
476
|
+
}
|
|
477
|
+
// Bare function name: "-> greet" ≡ "-> greet()" with $ as implicit arg
|
|
478
|
+
if (check(state, TOKEN_TYPES.IDENTIFIER)) {
|
|
479
|
+
const start = current(state).span.start;
|
|
480
|
+
const nameToken = advance(state);
|
|
481
|
+
return {
|
|
482
|
+
type: 'HostCall',
|
|
483
|
+
name: nameToken.value,
|
|
484
|
+
args: [],
|
|
485
|
+
span: makeSpan(start, current(state).span.end),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// Common constructs: conditionals, loops, blocks, arithmetic
|
|
489
|
+
const common = parseCommonConstruct(state);
|
|
490
|
+
if (common) {
|
|
491
|
+
// Check for postfix type assertion on the common construct: (expr):type
|
|
492
|
+
if (check(state, TOKEN_TYPES.COLON)) {
|
|
493
|
+
return parsePostfixTypeOperation(state, common, common.span.start);
|
|
494
|
+
}
|
|
495
|
+
return common;
|
|
496
|
+
}
|
|
497
|
+
throw new ParseError(`Expected pipe target, got: ${current(state).value}`, current(state).span.start);
|
|
498
|
+
}
|
|
499
|
+
// ============================================================
|
|
500
|
+
// CAPTURE PARSING
|
|
501
|
+
// ============================================================
|
|
502
|
+
export function parseCapture(state) {
|
|
503
|
+
const start = current(state).span.start;
|
|
504
|
+
expect(state, TOKEN_TYPES.DOLLAR, 'Expected $');
|
|
505
|
+
const nameToken = expect(state, TOKEN_TYPES.IDENTIFIER, 'Expected variable name');
|
|
506
|
+
// Optional type annotation: $name:type
|
|
507
|
+
let typeName = null;
|
|
508
|
+
if (check(state, TOKEN_TYPES.COLON)) {
|
|
509
|
+
advance(state);
|
|
510
|
+
typeName = parseTypeName(state, VALID_TYPE_NAMES);
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
type: 'Capture',
|
|
514
|
+
name: nameToken.value,
|
|
515
|
+
typeName,
|
|
516
|
+
span: makeSpan(start, current(state).span.end),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
// ============================================================
|
|
520
|
+
// HELPER: Create pipe chain from single primary
|
|
521
|
+
// ============================================================
|
|
522
|
+
export function makePipeChain(primary, start) {
|
|
523
|
+
return {
|
|
524
|
+
type: 'PipeChain',
|
|
525
|
+
head: {
|
|
526
|
+
type: 'PostfixExpr',
|
|
527
|
+
primary,
|
|
528
|
+
methods: [],
|
|
529
|
+
span: makeSpan(start, start),
|
|
530
|
+
},
|
|
531
|
+
pipes: [],
|
|
532
|
+
terminator: null,
|
|
533
|
+
span: makeSpan(start, start),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
// ============================================================
|
|
537
|
+
// GROUPED EXPRESSION & ARITHMETIC PARSING
|
|
538
|
+
// ============================================================
|
|
539
|
+
/**
|
|
540
|
+
* Grouped expression: ( expression )
|
|
541
|
+
* Single-expression block with () delimiters.
|
|
542
|
+
* Provides scoping — captures inside are local.
|
|
543
|
+
*
|
|
544
|
+
* Note: Boolean operators (&&, ||, !) are only supported in while loop
|
|
545
|
+
* conditions @(condition), not in general grouped expressions.
|
|
546
|
+
*/
|
|
547
|
+
export function parseGrouped(state) {
|
|
548
|
+
const start = current(state).span.start;
|
|
549
|
+
expect(state, TOKEN_TYPES.LPAREN, 'Expected (');
|
|
550
|
+
const expression = parsePipeChain(state);
|
|
551
|
+
expect(state, TOKEN_TYPES.RPAREN, 'Expected )');
|
|
552
|
+
return {
|
|
553
|
+
type: 'GroupedExpr',
|
|
554
|
+
expression,
|
|
555
|
+
span: makeSpan(start, current(state).span.end),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Check if current token is a comparison operator.
|
|
560
|
+
*/
|
|
561
|
+
function isComparisonOp(state) {
|
|
562
|
+
return check(state, TOKEN_TYPES.EQ, TOKEN_TYPES.NE, TOKEN_TYPES.LT, TOKEN_TYPES.GT, TOKEN_TYPES.LE, TOKEN_TYPES.GE);
|
|
563
|
+
}
|
|
564
|
+
/** Map token type to comparison operator string */
|
|
565
|
+
function tokenToComparisonOp(tokenType) {
|
|
566
|
+
switch (tokenType) {
|
|
567
|
+
case TOKEN_TYPES.EQ:
|
|
568
|
+
return '==';
|
|
569
|
+
case TOKEN_TYPES.NE:
|
|
570
|
+
return '!=';
|
|
571
|
+
case TOKEN_TYPES.LT:
|
|
572
|
+
return '<';
|
|
573
|
+
case TOKEN_TYPES.GT:
|
|
574
|
+
return '>';
|
|
575
|
+
case TOKEN_TYPES.LE:
|
|
576
|
+
return '<=';
|
|
577
|
+
default:
|
|
578
|
+
return '>=';
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/** Wrap a conditional node in a PostfixExpr */
|
|
582
|
+
function wrapConditionalInPostfixExpr(conditional, span) {
|
|
583
|
+
return {
|
|
584
|
+
type: 'PostfixExpr',
|
|
585
|
+
primary: conditional,
|
|
586
|
+
methods: [],
|
|
587
|
+
span,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/** Wrap a loop node in a PostfixExpr */
|
|
591
|
+
function wrapLoopInPostfixExpr(loop, span) {
|
|
592
|
+
return {
|
|
593
|
+
type: 'PostfixExpr',
|
|
594
|
+
primary: loop,
|
|
595
|
+
methods: [],
|
|
596
|
+
span,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
// ============================================================
|
|
600
|
+
// EXPRESSION PRECEDENCE CHAIN
|
|
601
|
+
// ============================================================
|
|
602
|
+
// Precedence (lowest to highest):
|
|
603
|
+
// logical-or (||) -> logical-and (&&) -> comparison -> additive -> multiplicative -> unary -> postfix
|
|
604
|
+
/**
|
|
605
|
+
* Parse logical OR expression: logical-and ('||' logical-and)*
|
|
606
|
+
*/
|
|
607
|
+
function parseLogicalOr(state) {
|
|
608
|
+
const start = current(state).span.start;
|
|
609
|
+
let left = parseLogicalAnd(state);
|
|
610
|
+
while (check(state, TOKEN_TYPES.OR)) {
|
|
611
|
+
advance(state);
|
|
612
|
+
const right = parseLogicalAnd(state);
|
|
613
|
+
left = {
|
|
614
|
+
type: 'BinaryExpr',
|
|
615
|
+
op: '||',
|
|
616
|
+
left,
|
|
617
|
+
right,
|
|
618
|
+
span: makeSpan(start, current(state).span.end),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
return left;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Parse logical AND expression: comparison ('&&' comparison)*
|
|
625
|
+
*/
|
|
626
|
+
function parseLogicalAnd(state) {
|
|
627
|
+
const start = current(state).span.start;
|
|
628
|
+
let left = parseComparison(state);
|
|
629
|
+
while (check(state, TOKEN_TYPES.AND)) {
|
|
630
|
+
advance(state);
|
|
631
|
+
const right = parseComparison(state);
|
|
632
|
+
left = {
|
|
633
|
+
type: 'BinaryExpr',
|
|
634
|
+
op: '&&',
|
|
635
|
+
left,
|
|
636
|
+
right,
|
|
637
|
+
span: makeSpan(start, current(state).span.end),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
return left;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Parse comparison expression: additive (comp-op additive)?
|
|
644
|
+
*/
|
|
645
|
+
function parseComparison(state) {
|
|
646
|
+
const start = current(state).span.start;
|
|
647
|
+
let left = parseAdditive(state);
|
|
648
|
+
if (isComparisonOp(state)) {
|
|
649
|
+
const opToken = advance(state);
|
|
650
|
+
const op = tokenToComparisonOp(opToken.type);
|
|
651
|
+
const right = parseAdditive(state);
|
|
652
|
+
left = {
|
|
653
|
+
type: 'BinaryExpr',
|
|
654
|
+
op,
|
|
655
|
+
left,
|
|
656
|
+
right,
|
|
657
|
+
span: makeSpan(start, current(state).span.end),
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
return left;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Parse additive expression: multiplicative (('+' | '-') multiplicative)*
|
|
664
|
+
*/
|
|
665
|
+
function parseAdditive(state) {
|
|
666
|
+
const start = current(state).span.start;
|
|
667
|
+
let left = parseMultiplicative(state);
|
|
668
|
+
while (check(state, TOKEN_TYPES.PLUS, TOKEN_TYPES.MINUS)) {
|
|
669
|
+
const opToken = advance(state);
|
|
670
|
+
const op = opToken.type === TOKEN_TYPES.PLUS ? '+' : '-';
|
|
671
|
+
const right = parseMultiplicative(state);
|
|
672
|
+
left = {
|
|
673
|
+
type: 'BinaryExpr',
|
|
674
|
+
op,
|
|
675
|
+
left,
|
|
676
|
+
right,
|
|
677
|
+
span: makeSpan(start, current(state).span.end),
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return left;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Parse multiplicative expression: unary (('*' | '/' | '%') unary)*
|
|
684
|
+
*/
|
|
685
|
+
function parseMultiplicative(state) {
|
|
686
|
+
const start = current(state).span.start;
|
|
687
|
+
let left = parseUnary(state);
|
|
688
|
+
while (check(state, TOKEN_TYPES.STAR, TOKEN_TYPES.SLASH, TOKEN_TYPES.PERCENT)) {
|
|
689
|
+
const opToken = advance(state);
|
|
690
|
+
const op = opToken.type === TOKEN_TYPES.STAR
|
|
691
|
+
? '*'
|
|
692
|
+
: opToken.type === TOKEN_TYPES.SLASH
|
|
693
|
+
? '/'
|
|
694
|
+
: '%';
|
|
695
|
+
const right = parseUnary(state);
|
|
696
|
+
left = {
|
|
697
|
+
type: 'BinaryExpr',
|
|
698
|
+
op,
|
|
699
|
+
left,
|
|
700
|
+
right,
|
|
701
|
+
span: makeSpan(start, current(state).span.end),
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return left;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Parse unary expression: ('-' | '!') unary | postfix-expr
|
|
708
|
+
*/
|
|
709
|
+
function parseUnary(state) {
|
|
710
|
+
if (check(state, TOKEN_TYPES.MINUS)) {
|
|
711
|
+
const start = current(state).span.start;
|
|
712
|
+
advance(state);
|
|
713
|
+
const operand = parseUnary(state);
|
|
714
|
+
return {
|
|
715
|
+
type: 'UnaryExpr',
|
|
716
|
+
op: '-',
|
|
717
|
+
operand,
|
|
718
|
+
span: makeSpan(start, operand.span.end),
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
if (check(state, TOKEN_TYPES.BANG)) {
|
|
722
|
+
const start = current(state).span.start;
|
|
723
|
+
advance(state);
|
|
724
|
+
const operand = parseUnary(state);
|
|
725
|
+
return {
|
|
726
|
+
type: 'UnaryExpr',
|
|
727
|
+
op: '!',
|
|
728
|
+
operand,
|
|
729
|
+
span: makeSpan(start, operand.span.end),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
return parsePostfixExpr(state);
|
|
733
|
+
}
|
|
734
|
+
// ============================================================
|
|
735
|
+
// TYPE OPERATIONS
|
|
736
|
+
// ============================================================
|
|
737
|
+
/**
|
|
738
|
+
* Parse type operation as pipe target: :type or :?type
|
|
739
|
+
* These are shorthand for $:type and $:?type (type assertion/check on pipe value).
|
|
740
|
+
*/
|
|
741
|
+
function parseTypeOperation(state) {
|
|
742
|
+
const start = current(state).span.start;
|
|
743
|
+
expect(state, TOKEN_TYPES.COLON, 'Expected :');
|
|
744
|
+
// Check for type check (question mark)
|
|
745
|
+
const isCheck = check(state, TOKEN_TYPES.QUESTION);
|
|
746
|
+
if (isCheck) {
|
|
747
|
+
advance(state); // consume ?
|
|
748
|
+
}
|
|
749
|
+
// Parse type name
|
|
750
|
+
const typeName = parseTypeName(state, VALID_TYPE_NAMES);
|
|
751
|
+
const span = makeSpan(start, current(state).span.end);
|
|
752
|
+
if (isCheck) {
|
|
753
|
+
return {
|
|
754
|
+
type: 'TypeCheck',
|
|
755
|
+
operand: null, // null means use pipe value ($)
|
|
756
|
+
typeName,
|
|
757
|
+
span,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
type: 'TypeAssertion',
|
|
762
|
+
operand: null, // null means use pipe value ($)
|
|
763
|
+
typeName,
|
|
764
|
+
span,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
// ============================================================
|
|
768
|
+
// COLLECTION OPERATORS (each, map, fold)
|
|
769
|
+
// ============================================================
|
|
770
|
+
/**
|
|
771
|
+
* Parse collection body: the body for each/map/fold operators.
|
|
772
|
+
* Valid forms:
|
|
773
|
+
* - |x| body -- inline closure
|
|
774
|
+
* - { body } -- block expression
|
|
775
|
+
* - (expr) -- grouped expression
|
|
776
|
+
* - $fn -- variable closure
|
|
777
|
+
* - $ -- identity (returns element)
|
|
778
|
+
* - * -- spread (converts element to tuple)
|
|
779
|
+
*/
|
|
780
|
+
function parseIteratorBody(state) {
|
|
781
|
+
// Inline closure: |x| body or |x, acc = init| body
|
|
782
|
+
if (isClosureStart(state)) {
|
|
783
|
+
return parseClosure(state);
|
|
784
|
+
}
|
|
785
|
+
// Block: { body }
|
|
786
|
+
if (check(state, TOKEN_TYPES.LBRACE)) {
|
|
787
|
+
return parseBlock(state);
|
|
788
|
+
}
|
|
789
|
+
// Grouped: (expr)
|
|
790
|
+
if (check(state, TOKEN_TYPES.LPAREN)) {
|
|
791
|
+
return parseGrouped(state);
|
|
792
|
+
}
|
|
793
|
+
// Variable closure: $fn or identity $
|
|
794
|
+
if (check(state, TOKEN_TYPES.DOLLAR) || check(state, TOKEN_TYPES.PIPE_VAR)) {
|
|
795
|
+
return parseVariable(state);
|
|
796
|
+
}
|
|
797
|
+
// Spread: * (converts element to tuple)
|
|
798
|
+
if (check(state, TOKEN_TYPES.STAR)) {
|
|
799
|
+
return parseSpread(state);
|
|
800
|
+
}
|
|
801
|
+
throw new ParseError(`Expected collection body (closure, block, grouped, variable, or spread), got: ${current(state).value}`, current(state).span.start);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Check if the next token sequence indicates an accumulator followed by a body.
|
|
805
|
+
* Disambiguation rule from spec:
|
|
806
|
+
* - (expr) at end of statement or before -> → grouped expression (body)
|
|
807
|
+
* - (expr) { block } → accumulator, block body
|
|
808
|
+
* - (expr) |x| body → accumulator, closure body
|
|
809
|
+
* - (expr1) (expr2) → accumulator, grouped body
|
|
810
|
+
*/
|
|
811
|
+
function hasAccumulatorPrefix(state) {
|
|
812
|
+
if (!check(state, TOKEN_TYPES.LPAREN)) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
// Find matching close paren
|
|
816
|
+
let depth = 1;
|
|
817
|
+
let i = 1;
|
|
818
|
+
while (depth > 0) {
|
|
819
|
+
const token = peek(state, i);
|
|
820
|
+
if (!token)
|
|
821
|
+
return false;
|
|
822
|
+
if (token.type === TOKEN_TYPES.LPAREN)
|
|
823
|
+
depth++;
|
|
824
|
+
else if (token.type === TOKEN_TYPES.RPAREN)
|
|
825
|
+
depth--;
|
|
826
|
+
i++;
|
|
827
|
+
}
|
|
828
|
+
// Look at what follows the closing paren
|
|
829
|
+
const afterParen = peek(state, i);
|
|
830
|
+
if (!afterParen)
|
|
831
|
+
return false;
|
|
832
|
+
// If followed by body starters, this paren is accumulator
|
|
833
|
+
return (afterParen.type === TOKEN_TYPES.LBRACE || // (init) { body }
|
|
834
|
+
afterParen.type === TOKEN_TYPES.PIPE_BAR || // (init) |x| body
|
|
835
|
+
afterParen.type === TOKEN_TYPES.OR || // (init) || body
|
|
836
|
+
afterParen.type === TOKEN_TYPES.LPAREN // (init) (expr)
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Parse each expression: -> each [accumulator] body
|
|
841
|
+
*
|
|
842
|
+
* Syntax:
|
|
843
|
+
* -> each |x| body
|
|
844
|
+
* -> each { body }
|
|
845
|
+
* -> each (expr)
|
|
846
|
+
* -> each $fn
|
|
847
|
+
* -> each $
|
|
848
|
+
* -> each(init) { body } -- with accumulator ($@ in body)
|
|
849
|
+
* -> each |x, acc = init| body -- with accumulator (closure param)
|
|
850
|
+
*/
|
|
851
|
+
function parseEachExpr(state) {
|
|
852
|
+
const start = current(state).span.start;
|
|
853
|
+
expect(state, TOKEN_TYPES.EACH, 'Expected each');
|
|
854
|
+
let accumulator = null;
|
|
855
|
+
// Check for accumulator prefix: (init) followed by body
|
|
856
|
+
if (hasAccumulatorPrefix(state)) {
|
|
857
|
+
accumulator = parseGrouped(state).expression;
|
|
858
|
+
}
|
|
859
|
+
const body = parseIteratorBody(state);
|
|
860
|
+
return {
|
|
861
|
+
type: 'EachExpr',
|
|
862
|
+
body,
|
|
863
|
+
accumulator,
|
|
864
|
+
span: makeSpan(start, current(state).span.end),
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Parse map expression: -> map body
|
|
869
|
+
*
|
|
870
|
+
* Syntax:
|
|
871
|
+
* -> map |x| body
|
|
872
|
+
* -> map { body }
|
|
873
|
+
* -> map (expr)
|
|
874
|
+
* -> map $fn
|
|
875
|
+
* -> map $
|
|
876
|
+
*
|
|
877
|
+
* No accumulator (parallel execution has no "previous").
|
|
878
|
+
*/
|
|
879
|
+
function parseMapExpr(state) {
|
|
880
|
+
const start = current(state).span.start;
|
|
881
|
+
expect(state, TOKEN_TYPES.MAP, 'Expected map');
|
|
882
|
+
const body = parseIteratorBody(state);
|
|
883
|
+
return {
|
|
884
|
+
type: 'MapExpr',
|
|
885
|
+
body,
|
|
886
|
+
span: makeSpan(start, current(state).span.end),
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Parse fold expression: -> fold body
|
|
891
|
+
*
|
|
892
|
+
* Syntax:
|
|
893
|
+
* -> fold |x, acc = init| body -- accumulator in closure params
|
|
894
|
+
* -> fold(init) { body } -- accumulator via $@
|
|
895
|
+
* -> fold $fn -- fn must have accumulator param
|
|
896
|
+
*
|
|
897
|
+
* Accumulator is required.
|
|
898
|
+
*/
|
|
899
|
+
function parseFoldExpr(state) {
|
|
900
|
+
const start = current(state).span.start;
|
|
901
|
+
expect(state, TOKEN_TYPES.FOLD, 'Expected fold');
|
|
902
|
+
let accumulator = null;
|
|
903
|
+
// Check for accumulator prefix: (init) followed by body
|
|
904
|
+
if (hasAccumulatorPrefix(state)) {
|
|
905
|
+
accumulator = parseGrouped(state).expression;
|
|
906
|
+
}
|
|
907
|
+
const body = parseIteratorBody(state);
|
|
908
|
+
return {
|
|
909
|
+
type: 'FoldExpr',
|
|
910
|
+
body,
|
|
911
|
+
accumulator,
|
|
912
|
+
span: makeSpan(start, current(state).span.end),
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Parse filter expression: -> filter body
|
|
917
|
+
*
|
|
918
|
+
* Syntax:
|
|
919
|
+
* -> filter |x| body
|
|
920
|
+
* -> filter { body }
|
|
921
|
+
* -> filter (expr)
|
|
922
|
+
* -> filter $fn
|
|
923
|
+
*
|
|
924
|
+
* Predicate returns truthy/falsy. Elements where predicate is truthy are kept.
|
|
925
|
+
*/
|
|
926
|
+
function parseFilterExpr(state) {
|
|
927
|
+
const start = current(state).span.start;
|
|
928
|
+
expect(state, TOKEN_TYPES.FILTER, 'Expected filter');
|
|
929
|
+
const body = parseIteratorBody(state);
|
|
930
|
+
return {
|
|
931
|
+
type: 'FilterExpr',
|
|
932
|
+
body,
|
|
933
|
+
span: makeSpan(start, current(state).span.end),
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
// ============================================================
|
|
937
|
+
// WIRE UP CIRCULAR DEPENDENCIES
|
|
938
|
+
// ============================================================
|
|
939
|
+
// Set up all circular dependency injections
|
|
940
|
+
setLiteralsParseExpression(parseExpression);
|
|
941
|
+
setLiteralsParseBlock(parseBlock);
|
|
942
|
+
setLiteralsParseGrouped(parseGrouped);
|
|
943
|
+
setLiteralsParsePostfixExpr(parsePostfixExpr);
|
|
944
|
+
setLiteralsParsePipeChain(parsePipeChain);
|
|
945
|
+
setFunctionsParseExpression(parseExpression);
|
|
946
|
+
setExtractionParsePostfixExpr(parsePostfixExpr);
|
|
947
|
+
setExtractionParseGrouped(parseGrouped);
|
|
948
|
+
setVariablesParseBlock(parseBlock);
|
|
949
|
+
setVariablesParsePipeChain(parsePipeChain);
|
|
950
|
+
//# sourceMappingURL=expressions.js.map
|