@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,693 @@
|
|
|
1
|
+
(* rill v0.1.0 Grammar - EBNF Notation *)
|
|
2
|
+
(* Pipe-based scripting language for prompt-drive workflows *)
|
|
3
|
+
|
|
4
|
+
(* ============================================================ *)
|
|
5
|
+
(* DESIGN PRINCIPLES *)
|
|
6
|
+
(* ============================================================ *)
|
|
7
|
+
|
|
8
|
+
(* No Null:rill has no null or nil type. Operations that might produce
|
|
9
|
+
"nothing" in other languages instead stop execution with an error.
|
|
10
|
+
This eliminates null-checking boilerplate and makes failures explicit
|
|
11
|
+
at the point of origin rather than propagating silently. *)
|
|
12
|
+
|
|
13
|
+
(* Content Equality: All comparisons (==, !=, .eq, .ne) use structural
|
|
14
|
+
equality, not reference identity. Two values are equal if they have
|
|
15
|
+
the same content:
|
|
16
|
+
- Primitives: value equality
|
|
17
|
+
- Lists: same length + element-wise equality
|
|
18
|
+
- Dicts: same keys + value-wise equality (order-independent)
|
|
19
|
+
- Closures: same params + body AST structure + captured values
|
|
20
|
+
(Source location does not affect equality - two identical closures
|
|
21
|
+
defined in different places are equal if their structure matches) *)
|
|
22
|
+
|
|
23
|
+
(* ============================================================ *)
|
|
24
|
+
(* SCRIPT STRUCTURE *)
|
|
25
|
+
(* ============================================================ *)
|
|
26
|
+
|
|
27
|
+
script = [ frontmatter ] , { statement } ;
|
|
28
|
+
(* Semantic: Returns the value of the last statement.
|
|
29
|
+
"5" evaluates to 5, equivalent to "5 -> return".
|
|
30
|
+
Empty script returns null at the API boundary. *)
|
|
31
|
+
|
|
32
|
+
(* Frontmatter is syntactically recognized but semantically opaque torill.
|
|
33
|
+
The content between --- markers is passed through to the runtime/caller
|
|
34
|
+
for application-specific interpretation.rill does not parse or validate
|
|
35
|
+
frontmatter fields — that responsibility belongs to the embedding application. *)
|
|
36
|
+
frontmatter = "---" , newline , { yaml-line } , "---" , newline ;
|
|
37
|
+
yaml-line = { yaml-char } , newline ;
|
|
38
|
+
yaml-char = letter | digit | ":" | " " | "-" | "_" | "." | "[" | "]" | "," | "'" | '"' ;
|
|
39
|
+
|
|
40
|
+
(* ============================================================ *)
|
|
41
|
+
(* STATEMENTS *)
|
|
42
|
+
(* ============================================================ *)
|
|
43
|
+
|
|
44
|
+
(* Statement boundaries:rill requires no semicolons or explicit terminators.
|
|
45
|
+
The grammar is unambiguous because continuation and start tokens are disjoint.
|
|
46
|
+
|
|
47
|
+
Continuation tokens (extend current statement):
|
|
48
|
+
"->" pipe operator (to pipe-target or capture)
|
|
49
|
+
":>" capture operator (captures value and continues chain)
|
|
50
|
+
"." method chain
|
|
51
|
+
"?" conditional operator (expr ? then ! else)
|
|
52
|
+
"!" else clause (after conditional then-branch)
|
|
53
|
+
|
|
54
|
+
Statement-start tokens (begin new statement):
|
|
55
|
+
"$" variable or closure-call (if followed by "name(")
|
|
56
|
+
identifier host call (followed by "(")
|
|
57
|
+
"@" for-loop or while-loop
|
|
58
|
+
"{" block
|
|
59
|
+
"|" closure (|params| body)
|
|
60
|
+
literals strings, numbers, bools, tuples, dicts
|
|
61
|
+
|
|
62
|
+
After any complete expression, if the next token is not a continuation,
|
|
63
|
+
it must begin a new statement. No lookahead beyond one token is needed.
|
|
64
|
+
|
|
65
|
+
Line continuation: -> or :> at the beginning of a line continues the
|
|
66
|
+
previous line's pipe chain. This allows readable multi-line chains:
|
|
67
|
+
"hello"
|
|
68
|
+
:> $greeting
|
|
69
|
+
-> .upper *)
|
|
70
|
+
|
|
71
|
+
(* Parsing note: "->" capture vs "->" pipe-target is unambiguous because
|
|
72
|
+
capture ("$" identifier) and pipe-target are disjoint sets. Parsers
|
|
73
|
+
should greedily consume pipe-chain, then check for trailing "-> $id". *)
|
|
74
|
+
|
|
75
|
+
(* Capture operators:
|
|
76
|
+
- ":>" (capture-arrow) captures value and continues chain
|
|
77
|
+
- "->" to "$var" (terminal) captures value and ends chain
|
|
78
|
+
|
|
79
|
+
":>" is preferred for most captures because it's clearer:
|
|
80
|
+
"hello" :> $x -> .upper -- capture "hello", chain continues, result is "HELLO"
|
|
81
|
+
"hello" :> $x -- capture and emit (result is "hello")
|
|
82
|
+
|
|
83
|
+
Chain terminators: break and return terminate pipe chains.
|
|
84
|
+
|
|
85
|
+
"hello" -> $x -- terminal capture (ends chain)
|
|
86
|
+
"hello" -> $x -> .len -- inline capture (-> $x ->, value flows through)
|
|
87
|
+
"hello" :> $x -> .len -- capture arrow (clearer, same effect)
|
|
88
|
+
[1,2,3] -> each { break } -- break terminates (exits loop)
|
|
89
|
+
"result" -> return -- return terminates (exits function) *)
|
|
90
|
+
|
|
91
|
+
statement = [ annotation ] , pipe-chain ;
|
|
92
|
+
(* pipe-chain includes optional terminator: capture | "break" | "return" *)
|
|
93
|
+
|
|
94
|
+
(* Annotations: ^(key: value, ...) prefix modifying statement behavior.
|
|
95
|
+
Common use: loop limits via ^(limit: N).
|
|
96
|
+
annotation = "^" , "(" , [ annotation-arg , { "," , annotation-arg } ] , ")" ;
|
|
97
|
+
annotation-arg = identifier , ":" , literal ;
|
|
98
|
+
*)
|
|
99
|
+
annotation = "^" , "(" , [ annotation-arg , { "," , annotation-arg } ] , ")" ;
|
|
100
|
+
annotation-arg = identifier , ":" , literal ;
|
|
101
|
+
|
|
102
|
+
(* ============================================================ *)
|
|
103
|
+
(* EXPRESSIONS *)
|
|
104
|
+
(* ============================================================ *)
|
|
105
|
+
|
|
106
|
+
expression = pipe-chain ;
|
|
107
|
+
|
|
108
|
+
pipe-chain = value-expr , [ "?" , body , [ "!" , body ] ]
|
|
109
|
+
, { ( "->" , ( pipe-target | capture ) ) | ( ":>" , capture ) }
|
|
110
|
+
, [ "->" , chain-terminator ] ;
|
|
111
|
+
(* Expression head can include arithmetic, comparisons, and logical ops.
|
|
112
|
+
5 + 3 -- standalone arithmetic
|
|
113
|
+
5 > 3 -- comparison (returns bool)
|
|
114
|
+
$a && $b -- logical and
|
|
115
|
+
$x > 0 && $x < 10 -- comparison with logical ops
|
|
116
|
+
!$ready || $timeout -- logical or with negation
|
|
117
|
+
5 > 3 ? "yes" ! "no" -- expression as conditional
|
|
118
|
+
When the head depends on $, "$ ->" is implied:
|
|
119
|
+
.len ≡ $ -> .len, ? "yes" ! "no" ≡ $ -> ? "yes" ! "no" *)
|
|
120
|
+
|
|
121
|
+
(* Expression precedence (lowest to highest):
|
|
122
|
+
|| -> && -> comparison -> + - -> * / % -> unary (- !) -> postfix *)
|
|
123
|
+
(* value-expr is the expression before any pipes or conditionals *)
|
|
124
|
+
value-expr = logical-or ;
|
|
125
|
+
logical-or = logical-and , { "||" , logical-and } ;
|
|
126
|
+
logical-and = comparison , { "&&" , comparison } ;
|
|
127
|
+
comparison = additive , [ comparison-op , additive ] ;
|
|
128
|
+
additive = multiplicative , { ( "+" | "-" ) , multiplicative } ;
|
|
129
|
+
multiplicative = unary , { ( "*" | "/" | "%" ) , unary } ;
|
|
130
|
+
unary = [ "-" | "!" ] , postfix-expr ;
|
|
131
|
+
|
|
132
|
+
comparison-op = "==" | "!=" | "<" | ">" | "<=" | ">=" ;
|
|
133
|
+
|
|
134
|
+
chain-terminator = "break" | "return" ;
|
|
135
|
+
(* Note: capture can appear mid-chain or at end; handled in pipe-chain *)
|
|
136
|
+
|
|
137
|
+
postfix-expr = primary , { "." , method-name , [ "(" , [ arguments ] , ")" ] } ;
|
|
138
|
+
|
|
139
|
+
primary = literal
|
|
140
|
+
| variable
|
|
141
|
+
| closure-call
|
|
142
|
+
| host-call
|
|
143
|
+
| conditional
|
|
144
|
+
| loop
|
|
145
|
+
| block
|
|
146
|
+
| grouped-expr
|
|
147
|
+
| spread ; (* *expr - convert list/dict to tuple *)
|
|
148
|
+
|
|
149
|
+
(* implicit-primary: used in contexts where implicit $ is available (body, pipe targets).
|
|
150
|
+
Includes method-call because .method() expands to $ -> .method().
|
|
151
|
+
primary excludes method-call since statements cannot start with a bare method call. *)
|
|
152
|
+
implicit-primary = literal | variable | closure-call | host-call | method-call | block | grouped-expr ;
|
|
153
|
+
|
|
154
|
+
(* ============================================================ *)
|
|
155
|
+
(* LITERALS *)
|
|
156
|
+
(* ============================================================ *)
|
|
157
|
+
|
|
158
|
+
literal = string | number | bool | list | dict | closure ;
|
|
159
|
+
|
|
160
|
+
(* Closures are first-class values with optional typed parameters.
|
|
161
|
+
The body can be a block, grouped expression, or postfix expression.
|
|
162
|
+
|
|
163
|
+
|| { "hello" } -> $greet -- no params, block body
|
|
164
|
+
|x| { $x -> .len } -> $strlen -- untyped param
|
|
165
|
+
|x: string| { $x -> .len } -> $strlen -- typed param
|
|
166
|
+
|x: string = "default"| { $x } -> $fn -- typed with default
|
|
167
|
+
|x = "default"| { $x } -> $fn -- default with inferred type
|
|
168
|
+
|a: number, b: number = 0| { } -> $add -- mixed params
|
|
169
|
+
|x| ($x + 1) -> $inc -- grouped expression body
|
|
170
|
+
|x| $x -> $identity -- postfix expression body
|
|
171
|
+
|
|
172
|
+
Scope rules (same as blocks):
|
|
173
|
+
- Captures outer scope variables (read-only)
|
|
174
|
+
- Local variables (declared inside) are mutable
|
|
175
|
+
- Cannot modify outer scope variables
|
|
176
|
+
|
|
177
|
+
Invocation:
|
|
178
|
+
- $fn() -- call with no args
|
|
179
|
+
- $fn("arg") -- call with args
|
|
180
|
+
- $fn -> $() -- pipe-style invoke (equivalent)
|
|
181
|
+
- "value" -> $fn() -- pipe value as first arg
|
|
182
|
+
- *[1, 2] -> $fn() -- tuple unpacking at invocation *)
|
|
183
|
+
|
|
184
|
+
closure-param = identifier , [ ":" , param-type ] , [ "=" , literal ]
|
|
185
|
+
| identifier , "=" , literal ; (* type inferred from default *)
|
|
186
|
+
param-type = "string" | "number" | "bool" ; (* primitives only for closure params *)
|
|
187
|
+
body = block | grouped-expr | postfix-expr ;
|
|
188
|
+
closure = "|" , [ closure-param , { "," , closure-param } ] , "|" , body ;
|
|
189
|
+
|
|
190
|
+
string = '"' , { char | escape | brace-escape | interpolation } , '"'
|
|
191
|
+
| "<<" , delimiter , newline , heredoc-body , delimiter ;
|
|
192
|
+
|
|
193
|
+
char = letter | digit | " " | "!" | "#" | "$" | "%" | "&" | "'" | "(" | ")"
|
|
194
|
+
| "*" | "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | "=" | ">"
|
|
195
|
+
| "?" | "@" | "[" | "]" | "^" | "_" | "`" | "|" | "~" | newline ;
|
|
196
|
+
(* excludes " \ { } which have special meaning in strings *)
|
|
197
|
+
escape = "\\" , ( "n" | "r" | "t" | "\\" | '"' ) ;
|
|
198
|
+
brace-escape = "{{" | "}}" ; (* {{ -> literal {, }} -> literal } *)
|
|
199
|
+
delimiter = identifier ;
|
|
200
|
+
heredoc-body = { heredoc-line } ; (* heredocs also support interpolation *)
|
|
201
|
+
heredoc-line = { heredoc-char | interpolation | brace-escape } , newline ;
|
|
202
|
+
heredoc-char = char | '"' | "\\" ; (* excludes { } which have special meaning *)
|
|
203
|
+
|
|
204
|
+
interpolation = "{" , expression , "}" ;
|
|
205
|
+
|
|
206
|
+
number = [ "-" ] , digit , { digit } , [ "." , digit , { digit } ] ;
|
|
207
|
+
bool = "true" | "false" ;
|
|
208
|
+
|
|
209
|
+
list = "[" , "]"
|
|
210
|
+
| "[" , expression , { "," , expression } , "]" ;
|
|
211
|
+
|
|
212
|
+
dict = "[" , ":" , "]"
|
|
213
|
+
| "[" , dict-entry , { "," , dict-entry } , "]" ;
|
|
214
|
+
dict-entry = identifier , ":" , expression ; (* intentional: keys are static identifiers *)
|
|
215
|
+
(* Semantic: keys "keys", "values", "entries" are reserved (cannot be used as dict keys).
|
|
216
|
+
Semantic: closure values become "dict closures" where $ is late-bound
|
|
217
|
+
to the containing dict at invocation time (like "this" in other languages). *)
|
|
218
|
+
|
|
219
|
+
(* ============================================================ *)
|
|
220
|
+
(* VARIABLES *)
|
|
221
|
+
(* ============================================================ *)
|
|
222
|
+
|
|
223
|
+
variable = "$" , identifier , { property-access } , [ default-value ] , [ existence-check ]
|
|
224
|
+
| "$" , { property-access } , [ default-value ] , [ existence-check ] ;
|
|
225
|
+
(* $ARGS and $ENV are outer-scope variables provided by the runtime,
|
|
226
|
+
not grammar-level constructs. Same scoping rules apply. *)
|
|
227
|
+
|
|
228
|
+
(* Closure calls invoke closures stored in variables:
|
|
229
|
+
$strlen("hello") -- call closure with string arg
|
|
230
|
+
$add(1, 2) -- call closure with multiple args
|
|
231
|
+
$transform($data) -- call closure with variable arg
|
|
232
|
+
In string interpolation: "Length: {$strlen("word")}" *)
|
|
233
|
+
closure-call = "$" , identifier , "(" , [ arguments ] , ")" ;
|
|
234
|
+
|
|
235
|
+
(* Property access chain supports both dot and bracket access in any order:
|
|
236
|
+
$data.user.name -- literal fields
|
|
237
|
+
$data.items[0] -- bracket index
|
|
238
|
+
$data.items[-1] -- negative index (from end)
|
|
239
|
+
$data.user.$field -- variable as key
|
|
240
|
+
$data.items.($i + 1) -- computed expression
|
|
241
|
+
$data.{get_key()} -- block returning key
|
|
242
|
+
$data.user.(nick || name) -- alternatives (try left-to-right)
|
|
243
|
+
*)
|
|
244
|
+
property-access = field-access | bracket-access ;
|
|
245
|
+
|
|
246
|
+
field-access = "." , ( identifier
|
|
247
|
+
| "$" , identifier (* variable as key *)
|
|
248
|
+
| "(" , pipe-chain , ")" (* computed expression *)
|
|
249
|
+
| "{" , block , "}" (* block returning key *)
|
|
250
|
+
| "(" , identifier , { "||" , identifier } , ")" ) ; (* alternatives *)
|
|
251
|
+
|
|
252
|
+
bracket-access = "[" , pipe-chain , "]" ; (* index access, supports negative *)
|
|
253
|
+
|
|
254
|
+
(* Default value if property is missing: $data.name ?? "Anonymous" *)
|
|
255
|
+
default-value = "??" , body ;
|
|
256
|
+
|
|
257
|
+
(* Existence check: $data.user.?email returns bool
|
|
258
|
+
Type narrowing uses & (not :) because the expression returns bool.
|
|
259
|
+
Using :type would suggest type assertion on a non-bool value.
|
|
260
|
+
$data.?field&string means "does field exist and is it a string?" -> bool *)
|
|
261
|
+
existence-check = ".?" , ( identifier | "$" , identifier ) , [ "&" , type-name ] ;
|
|
262
|
+
|
|
263
|
+
digits = digit , { digit } ;
|
|
264
|
+
|
|
265
|
+
(* Capture with optional type annotation.
|
|
266
|
+
Variables are type-locked after first assignment.
|
|
267
|
+
|
|
268
|
+
Capture arrow (:>) captures value into variable:
|
|
269
|
+
"hello" :> $name -- capture, chain continues with "hello"
|
|
270
|
+
"hello" :> $name -> .upper -- capture, then transform (result: "HELLO")
|
|
271
|
+
"hello" :> $name:string -- explicit type (validated on assignment)
|
|
272
|
+
|
|
273
|
+
Pipe arrow (-> $var) invokes closure stored in $var:
|
|
274
|
+
"hello" -> $fn -- invoke $fn("hello") if $fn is closure
|
|
275
|
+
"hello" -> $fn -> .len -- invoke, then continue chain
|
|
276
|
+
|
|
277
|
+
Type locking:
|
|
278
|
+
"hello" :> $name -- $name is now type-locked to string
|
|
279
|
+
5 :> $name -- ERROR: $name was previously string *)
|
|
280
|
+
capture = "$" , identifier , [ ":" , capture-type ] ;
|
|
281
|
+
capture-type = "string" | "number" | "bool" | "closure" | "list" | "dict" | "tuple" ;
|
|
282
|
+
|
|
283
|
+
(* ============================================================ *)
|
|
284
|
+
(* HOST FUNCTIONS *)
|
|
285
|
+
(* ============================================================ *)
|
|
286
|
+
|
|
287
|
+
host-call = host-name , "(" , [ arguments ] , ")" ;
|
|
288
|
+
|
|
289
|
+
host-name = identifier , { "::" , identifier } ;
|
|
290
|
+
(* Namespaced functions use :: separator: math::add, io::file::read
|
|
291
|
+
rill is a vanilla language. The host provides all domain functions.
|
|
292
|
+
|
|
293
|
+
Core global functions (built-in):
|
|
294
|
+
- type($value) -- returns type name: "string", "number", "bool", "closure", "list", "dict", "tuple"
|
|
295
|
+
- log($value) -- logs value using .str, returns value unchanged (passthrough)
|
|
296
|
+
- json($value) -- converts any value to JSON string
|
|
297
|
+
- parse_json($text) -- parses JSON string to value (with repair)
|
|
298
|
+
- identity($value) -- returns value unchanged
|
|
299
|
+
- range(start, end, step?) -- iterator from start (inclusive) to end (exclusive)
|
|
300
|
+
- repeat(value, count) -- iterator repeating value count times
|
|
301
|
+
|
|
302
|
+
Content parsing functions (built-in):
|
|
303
|
+
- parse_auto($text) -- auto-detect and parse structured content
|
|
304
|
+
- parse_xml($text, tag?) -- extract content from XML tags
|
|
305
|
+
- parse_fence($text, lang?) -- extract fenced code block content
|
|
306
|
+
- parse_fences($text) -- extract all fenced code blocks
|
|
307
|
+
- parse_frontmatter($text) -- parse YAML frontmatter and body
|
|
308
|
+
- parse_checklist($text) -- parse markdown checklist items
|
|
309
|
+
|
|
310
|
+
All other functions are registered by the host via RuntimeContext.functions. *)
|
|
311
|
+
|
|
312
|
+
arguments = expression , { "," , expression } ;
|
|
313
|
+
|
|
314
|
+
(* ============================================================ *)
|
|
315
|
+
(* METHODS *)
|
|
316
|
+
(* ============================================================ *)
|
|
317
|
+
|
|
318
|
+
(* Methods: ".method" or ".method()" with implied $ receiver.
|
|
319
|
+
0-arg methods may omit parens: .empty ≡ .empty() ≡ $.empty()
|
|
320
|
+
In pipe: "x -> .str" passes x as receiver to str method.
|
|
321
|
+
In conditional: ".empty ? then ! else" tests $.empty for truthiness.
|
|
322
|
+
Pipe semantics: "$ -> fn" ≡ "fn($)" — piped value becomes first arg.
|
|
323
|
+
Global functions (type, log, json) are not methods — use as: "x -> log" *)
|
|
324
|
+
|
|
325
|
+
method-call = "." , method-name , [ "(" , [ arguments ] , ")" ] ;
|
|
326
|
+
|
|
327
|
+
method-name = "contains" | "match" | "is_match" (* pattern: 1 arg *)
|
|
328
|
+
| "starts_with" | "ends_with" (* prefix/suffix: 1 arg *)
|
|
329
|
+
| "lower" | "upper" (* case: 0 args *)
|
|
330
|
+
| "replace" | "replace_all" (* regex replace: 2 args *)
|
|
331
|
+
| "index_of" (* position: 1 arg *)
|
|
332
|
+
| "repeat" (* repeat: 1 arg *)
|
|
333
|
+
| "pad_start" | "pad_end" (* padding: 1-2 args *)
|
|
334
|
+
| "empty" | "lines" (* utility: 0 args *)
|
|
335
|
+
| "eq" | "ne" | "lt" | "gt" | "le" | "ge" (* comparison: 1 arg *)
|
|
336
|
+
| "str" | "num" | "len" | "trim" (* conversion: 0 args *)
|
|
337
|
+
| "head" | "tail" (* element access: 0 args *)
|
|
338
|
+
| "first" (* iterator: 0 args, returns iterator *)
|
|
339
|
+
| "at" (* element access: 1 arg *)
|
|
340
|
+
| "split" | "join" (* string ops: 0-1 args *)
|
|
341
|
+
| "keys" | "values" | "entries" ; (* dict: 0 args *)
|
|
342
|
+
(* Note: .str is built-in on ALL types including tuple.
|
|
343
|
+
Use global log() and json() functions instead of methods. *)
|
|
344
|
+
|
|
345
|
+
(* Arity constraints (semantic check, not grammar-enforced):
|
|
346
|
+
The grammar allows [ arguments ] for flexibility, but these methods
|
|
347
|
+
have fixed arity that parsers/interpreters must validate:
|
|
348
|
+
0 args: empty, lines, str, num, len, trim, head, tail, first, keys, values, entries, lower, upper
|
|
349
|
+
0-1 args: split, join (separator defaults to newline/comma)
|
|
350
|
+
1 arg: contains, match, is_match, starts_with, ends_with, index_of, repeat, eq, ne, lt, gt, le, ge, at
|
|
351
|
+
1-2 args: pad_start, pad_end (fill defaults to space)
|
|
352
|
+
2 args: replace, replace_all *)
|
|
353
|
+
|
|
354
|
+
(* ============================================================ *)
|
|
355
|
+
(* PIPES *)
|
|
356
|
+
(* ============================================================ *)
|
|
357
|
+
|
|
358
|
+
(* pipe-chain is defined in EXPRESSIONS section *)
|
|
359
|
+
|
|
360
|
+
(* Capture: stores value in a variable and terminates the pipe chain.
|
|
361
|
+
The stored value also becomes the expression result.
|
|
362
|
+
|
|
363
|
+
Example: fetch("url") -> $result
|
|
364
|
+
The value is stored in $result and returned.
|
|
365
|
+
|
|
366
|
+
Note: Unlike earlier versions, capture ALWAYS terminates the chain.
|
|
367
|
+
Use explicit chaining for multi-step operations:
|
|
368
|
+
fetch("url") -> $result
|
|
369
|
+
$result -> log -> ?(.contains("x")) { } *)
|
|
370
|
+
|
|
371
|
+
(* Pipe targets: bare host names allowed without parens.
|
|
372
|
+
"$ -> greet" ≡ "greet($)" — piped value becomes implicit first arg. *)
|
|
373
|
+
|
|
374
|
+
pipe-target = host-call (* $ -> greet("world") — call with explicit args *)
|
|
375
|
+
| closure-call (* $ -> $fn("arg") — invoke closure with args *)
|
|
376
|
+
| pipe-invoke (* $ -> $() — invoke $ as closure, $ -> $(args) with args *)
|
|
377
|
+
| variable (* $ -> $fn — invoke closure stored in $fn with $ as arg *)
|
|
378
|
+
| host-name (* $ -> greet — bare name, $ becomes implicit first arg *)
|
|
379
|
+
| method-call (* $ -> .len — method on $, $ -> .at(0) with args *)
|
|
380
|
+
| string (* $ -> "hello {$}" — template with interpolation *)
|
|
381
|
+
| conditional (* $ -> ? "yes" ! "no" — conditional on $ *)
|
|
382
|
+
| loop (* $ -> @(cond) { body } — while loop *)
|
|
383
|
+
| each-expr (* $ -> each { body } — sequential iteration *)
|
|
384
|
+
| map-expr (* $ -> map { body } — parallel iteration *)
|
|
385
|
+
| fold-expr (* $ -> fold(init) { body } — reduction *)
|
|
386
|
+
| filter-expr (* $ -> filter { cond } — parallel filtering *)
|
|
387
|
+
| block (* $ -> { .len } — block with implicit $ head *)
|
|
388
|
+
| grouped-expr (* $ -> ($ + 1) — arithmetic/compound expression *)
|
|
389
|
+
| closure-chain (* $ -> @[$f, $g] — chain: $g($f($)) *)
|
|
390
|
+
| destructure (* $ -> *<$a, $b> — extract elements into vars *)
|
|
391
|
+
| slice (* $ -> /<0:3> — extract portion of $ *)
|
|
392
|
+
| type-assertion (* $ -> :string — assert $ is string type *)
|
|
393
|
+
| type-check (* $ -> :?string — check if $ is string type *)
|
|
394
|
+
| "*" ; (* $ -> * — convert $ to tuple for unpacking *)
|
|
395
|
+
|
|
396
|
+
(* Closure chain (@): compose closures left-to-right.
|
|
397
|
+
- Chain: x -> @[$f,$g,$h] -- $h($g($f(x)))
|
|
398
|
+
The target must be a closure or list of closures. *)
|
|
399
|
+
|
|
400
|
+
closure-chain = "@" , ( variable | list ) ; (* @$var or @[closures] *)
|
|
401
|
+
|
|
402
|
+
(* ============================================================ *)
|
|
403
|
+
(* EXTRACTION OPERATORS *)
|
|
404
|
+
(* ============================================================ *)
|
|
405
|
+
|
|
406
|
+
(* Extraction operators transform collections by extracting elements.
|
|
407
|
+
They return the extracted values while optionally binding to variables.
|
|
408
|
+
|
|
409
|
+
Destructure: extract elements into variables
|
|
410
|
+
- List: [1, 2, 3] -> *<$a, $b, $c>
|
|
411
|
+
- Dict: [name: "x"] -> *<name: $n>
|
|
412
|
+
- Skip: [1, 2, 3] -> *<$a, _, $c>
|
|
413
|
+
- Nested: [[1, 2], 3] -> *<*<$a, $b>, $c>
|
|
414
|
+
|
|
415
|
+
Slice: extract portions using Python-style start:stop:step
|
|
416
|
+
- [1, 2, 3, 4, 5] -> /<1:4> -> [2, 3, 4]
|
|
417
|
+
- [1, 2, 3, 4, 5] -> /<::-1> -> [5, 4, 3, 2, 1]
|
|
418
|
+
- "hello" -> /<1:4> -> "ell"
|
|
419
|
+
|
|
420
|
+
Enumerate: use global enumerate() function instead
|
|
421
|
+
- enumerate([10, 20]) -> [[index: 0, value: 10], [index: 1, value: 20]]
|
|
422
|
+
- enumerate([a: 1]) -> [[index: 0, key: "a", value: 1]] *)
|
|
423
|
+
|
|
424
|
+
destructure = "*<" , [ destruct-pattern , { "," , destruct-pattern } ] , ">" ;
|
|
425
|
+
destruct-pattern = "$" , identifier , [ ":" , capture-type ] (* variable *)
|
|
426
|
+
| identifier , ":" , "$" , identifier , [ ":" , capture-type ] (* key: $var *)
|
|
427
|
+
| "_" (* skip placeholder *)
|
|
428
|
+
| destructure ; (* nested pattern *)
|
|
429
|
+
|
|
430
|
+
slice = "/<" , [ slice-bound ] , ":" , [ slice-bound ] , [ ":" , [ slice-bound ] ] , ">" ;
|
|
431
|
+
slice-bound = number | variable | grouped-expr ;
|
|
432
|
+
|
|
433
|
+
(* Spread: convert list/dict to tuple for unpacking at closure invocation.
|
|
434
|
+
The tuple type exists solely to signal "unpack these values as separate arguments."
|
|
435
|
+
|
|
436
|
+
Syntax:
|
|
437
|
+
*[1, 2, 3] -- positional tuple from list literal
|
|
438
|
+
*[x: 1, y: 2] -- named tuple from dict literal
|
|
439
|
+
*$myList -- positional tuple from list variable
|
|
440
|
+
*$myDict -- named tuple from dict variable
|
|
441
|
+
[1, 2, 3] -> * -- convert pipe value to tuple
|
|
442
|
+
|
|
443
|
+
Key-based matching at invocation:
|
|
444
|
+
- Numeric keys (from list): match parameters by position
|
|
445
|
+
- String keys (from dict): match parameters by name
|
|
446
|
+
|
|
447
|
+
Example:
|
|
448
|
+
|a, b| { [$b, $a] } -> $flip
|
|
449
|
+
*[1, 2] -> $flip() -- $a=1, $b=2, returns [2, 1]
|
|
450
|
+
*[a: 1, b: 2] -> $flip() -- named tuple, same result
|
|
451
|
+
|
|
452
|
+
Strict validation: missing or extra arguments error at invocation.
|
|
453
|
+
Parameter defaults provide explicit opt-in leniency. *)
|
|
454
|
+
|
|
455
|
+
spread = "*" , [ postfix-expr ] ;
|
|
456
|
+
(* Bare * uses implicit $: each { * } ≡ each { $ -> * } *)
|
|
457
|
+
|
|
458
|
+
(* Pipe-invoke: call the pipe value ($) as a closure
|
|
459
|
+
||{ "hello" } -> $() -- invoke with no args
|
|
460
|
+
|x|$x -> $("world") -- invoke with args
|
|
461
|
+
$closure -> $() -- invoke variable that holds a closure *)
|
|
462
|
+
pipe-invoke = "$" , "(" , [ arguments ] , ")" ;
|
|
463
|
+
|
|
464
|
+
(* Type assertion: assert value is a specific type, error if mismatch.
|
|
465
|
+
Returns value unchanged on success. Can be used as:
|
|
466
|
+
- Postfix: 42:number, (expr):type - binds tighter than method calls
|
|
467
|
+
- Pipe target: -> :type - asserts on pipe value ($)
|
|
468
|
+
|
|
469
|
+
Examples:
|
|
470
|
+
42:number -- postfix assertion
|
|
471
|
+
(1 + 2):number -- postfix on grouped expr
|
|
472
|
+
(($ < 5) @ ($ + 1)):number -- postfix on loop result
|
|
473
|
+
"hello" -> :string -- pipe target assertion
|
|
474
|
+
$val -> :dict -> .keys -- assert then continue chain *)
|
|
475
|
+
type-assertion = ":" , type-name ;
|
|
476
|
+
|
|
477
|
+
(* Type check: check if value is a specific type, returns bool.
|
|
478
|
+
Can be used as postfix (expr:?type) or pipe target (-> :?type).
|
|
479
|
+
|
|
480
|
+
Examples:
|
|
481
|
+
42:?number -- true (postfix)
|
|
482
|
+
"hello":?number -- false (postfix)
|
|
483
|
+
$val -> :?list ? process() -- conditional on type *)
|
|
484
|
+
type-check = ":" , "?" , type-name ;
|
|
485
|
+
|
|
486
|
+
type-name = "string" | "number" | "bool" | "closure" | "list" | "dict" | "tuple" ;
|
|
487
|
+
|
|
488
|
+
(* ============================================================ *)
|
|
489
|
+
(* CONTROL FLOW *)
|
|
490
|
+
(* ============================================================ *)
|
|
491
|
+
|
|
492
|
+
(* Conditional syntax: condition ? then-branch ! else-branch
|
|
493
|
+
The condition MUST evaluate to a boolean value (true or false).
|
|
494
|
+
Non-boolean values will cause a runtime error.
|
|
495
|
+
|
|
496
|
+
Examples:
|
|
497
|
+
$ready ? "go" ! "wait" -- variable must be boolean
|
|
498
|
+
.valid ? process() ! reject() -- method must return boolean
|
|
499
|
+
($x > 10) ? "big" ! "small" -- comparison returns boolean
|
|
500
|
+
true ? "yes" ! "no" -- literal boolean
|
|
501
|
+
|
|
502
|
+
Piped form (? alone uses $ as condition):
|
|
503
|
+
true -> ? "yes" ! "no" -- pipe value must be boolean
|
|
504
|
+
($x > 5) -> ? "big" ! "small" -- expression evaluates to boolean
|
|
505
|
+
|
|
506
|
+
Else-if chaining:
|
|
507
|
+
.eq("A") ? 1 ! .eq("B") ? 2 ! 3 -- chained conditions *)
|
|
508
|
+
|
|
509
|
+
conditional = body , "?" , body , [ "!" , else-clause ]
|
|
510
|
+
| "?" , body , [ "!" , else-clause ] ; (* piped form: condition is $ *)
|
|
511
|
+
else-clause = conditional | body ;
|
|
512
|
+
|
|
513
|
+
(* Loop syntax: condition @ body [? condition]
|
|
514
|
+
The @ operator is for while loops only. For iteration, use collection operators.
|
|
515
|
+
Both while and do-while conditions MUST evaluate to boolean (true or false).
|
|
516
|
+
Non-boolean values will cause a runtime error.
|
|
517
|
+
|
|
518
|
+
- bool input: while loop (re-evaluates condition each iteration)
|
|
519
|
+
- trailing ? condition: do-while (body first, then check)
|
|
520
|
+
|
|
521
|
+
For-each iteration uses collection operators (each, map, filter, fold).
|
|
522
|
+
See docs/collections.md for collection operators.
|
|
523
|
+
|
|
524
|
+
Examples:
|
|
525
|
+
($x < 10) @ { ($x + 1) :> $x } -- while loop (condition is bool)
|
|
526
|
+
@ { $ } ? ($x < 10) -- do-while (body, then check)
|
|
527
|
+
[1, 2, 3] -> each { $ * 2 } -- for-each (use each operator)
|
|
528
|
+
"abc" -> each { $ } -- iterate over string chars
|
|
529
|
+
[a: 1, b: 2] -> each { $.key } -- iterate over dict entries
|
|
530
|
+
$items -> each { process($) } -- iterate over variable *)
|
|
531
|
+
|
|
532
|
+
loop = [ body ] , "@" , body , [ "?" , body ] ;
|
|
533
|
+
(* First body: input (condition for while, iterable for for-each)
|
|
534
|
+
Second body: loop body
|
|
535
|
+
Third body (after ?): do-while condition *)
|
|
536
|
+
|
|
537
|
+
(* ============================================================ *)
|
|
538
|
+
(* COLLECTION OPERATORS *)
|
|
539
|
+
(* ============================================================ *)
|
|
540
|
+
|
|
541
|
+
(* Collection operators for iteration and reduction.
|
|
542
|
+
See docs/collections.md for complete documentation.
|
|
543
|
+
|
|
544
|
+
Operator comparison:
|
|
545
|
+
- each: Sequential iteration, returns list of all results
|
|
546
|
+
- map: Parallel iteration (Promise.all), returns list of all results
|
|
547
|
+
- filter: Parallel filtering, returns elements where predicate is truthy
|
|
548
|
+
- fold: Sequential reduction, returns final result only
|
|
549
|
+
|
|
550
|
+
Body forms (all operators):
|
|
551
|
+
- block: { body } -- $ is current element
|
|
552
|
+
- grouped: ( expr ) -- $ is current element
|
|
553
|
+
- closure: |x| body -- named parameter
|
|
554
|
+
- variable: $fn -- pre-defined closure
|
|
555
|
+
- identity: $ -- return elements unchanged
|
|
556
|
+
|
|
557
|
+
Accumulator (each and fold only):
|
|
558
|
+
- Block form: (init) { body } -- $@ is accumulator, $ is element
|
|
559
|
+
- Closure: |x, acc = init| -- named accumulator parameter
|
|
560
|
+
|
|
561
|
+
Dict iteration binds $ to { key, value } objects.
|
|
562
|
+
|
|
563
|
+
Examples:
|
|
564
|
+
[1, 2, 3] -> each { $ * 2 } -- [2, 4, 6]
|
|
565
|
+
[1, 2, 3] -> map ($ + 10) -- [11, 12, 13]
|
|
566
|
+
[1, 2, 3, 4, 5] -> filter { $ > 2 } -- [3, 4, 5]
|
|
567
|
+
[1, 2, 3] -> fold(0) { $@ + $ } -- 6
|
|
568
|
+
[1, 2, 3] -> each(0) { $@ + $ } -- [1, 3, 6] (running sum)
|
|
569
|
+
[1, 2, 3] -> each |x, s = 0| ($s + $x) -- [1, 3, 6]
|
|
570
|
+
*)
|
|
571
|
+
|
|
572
|
+
iterator-body = closure | block | grouped-expr | postfix-expr | host-name ;
|
|
573
|
+
(* Valid body forms for each, map, fold, filter operators.
|
|
574
|
+
closure: |x| body or |x, acc = init| body
|
|
575
|
+
block: { body } where $ is current element
|
|
576
|
+
grouped-expr: ( expr ) where $ is current element
|
|
577
|
+
postfix-expr: includes variables ($fn), bare $ for identity, and .method shorthand
|
|
578
|
+
host-name: bare function name (func or ns::func), element passed as first arg
|
|
579
|
+
Note: variable not listed separately since postfix-expr -> primary -> variable
|
|
580
|
+
.method shorthand applies method to each element: ["a","b"] -> map .upper *)
|
|
581
|
+
|
|
582
|
+
each-expr = "each" , [ "(" , expression , ")" ] , iterator-body ;
|
|
583
|
+
(* Sequential iteration returning list of all body results.
|
|
584
|
+
Optional (expression) provides initial accumulator value.
|
|
585
|
+
With accumulator: $@ is accumulator, $ is current element.
|
|
586
|
+
Supports break for early termination.
|
|
587
|
+
[1, 2, 3] -> each { $ * 2 } -- [2, 4, 6]
|
|
588
|
+
[1, 2, 3] -> each(0) { $@ + $ } -- [1, 3, 6] *)
|
|
589
|
+
|
|
590
|
+
map-expr = "map" , iterator-body ;
|
|
591
|
+
(* Parallel iteration returning list of all body results.
|
|
592
|
+
Executes all iterations concurrently via Promise.all.
|
|
593
|
+
Order is preserved despite parallel execution.
|
|
594
|
+
No accumulator (parallel has no "previous" value).
|
|
595
|
+
No break support.
|
|
596
|
+
[1, 2, 3] -> map { $ * 2 } -- [2, 4, 6]
|
|
597
|
+
$urls -> map |url| fetch($url) -- concurrent fetch *)
|
|
598
|
+
|
|
599
|
+
fold-expr = "fold" , ( "(" , expression , ")" , iterator-body | closure | variable ) ;
|
|
600
|
+
(* Sequential reduction returning final accumulated value.
|
|
601
|
+
Requires accumulator (reduction semantics need initial value).
|
|
602
|
+
Block form: (init) { body } where $@ is accumulator.
|
|
603
|
+
Closure form: |x, acc = init| body.
|
|
604
|
+
Variable form: $fn where closure defines accumulator.
|
|
605
|
+
No break support.
|
|
606
|
+
[1, 2, 3] -> fold(0) { $@ + $ } -- 6
|
|
607
|
+
[1, 2, 3] -> fold |x, s = 0| ($s + $x) -- 6 *)
|
|
608
|
+
|
|
609
|
+
filter-expr = "filter" , iterator-body ;
|
|
610
|
+
(* Parallel filtering returning elements where predicate is true.
|
|
611
|
+
Predicate MUST return boolean (true or false).
|
|
612
|
+
Non-boolean values will cause a runtime error.
|
|
613
|
+
Executes all predicates concurrently via Promise.all.
|
|
614
|
+
Order is preserved despite parallel execution.
|
|
615
|
+
No accumulator (each element evaluated independently).
|
|
616
|
+
[1, 2, 3, 4, 5] -> filter { $ > 2 } -- [3, 4, 5]
|
|
617
|
+
$users -> filter |u| ($u.active) -- active users (must be bool) *)
|
|
618
|
+
|
|
619
|
+
block = "{" , statement , { statement } , "}" ;
|
|
620
|
+
(* Empty blocks not allowed. Blocks return their last expression's value,
|
|
621
|
+
enabling use in boolean contexts and as pipe sources. *)
|
|
622
|
+
|
|
623
|
+
(* ============================================================ *)
|
|
624
|
+
(* NOTE: BOOLEAN/LOGICAL OPERATORS *)
|
|
625
|
+
(* ============================================================ *)
|
|
626
|
+
|
|
627
|
+
(* Logical operators (&&, ||, !) are integrated into the main expression
|
|
628
|
+
grammar (see value-expr above). They work everywhere expressions
|
|
629
|
+
work — no special-casing needed.
|
|
630
|
+
|
|
631
|
+
Precedence is standard: || < && < comparison < arithmetic < unary
|
|
632
|
+
|
|
633
|
+
Examples:
|
|
634
|
+
$a && $b -- logical and
|
|
635
|
+
$x > 0 || $x < -10 -- comparison with logical or
|
|
636
|
+
!$ready && $timeout -- negation with logical and
|
|
637
|
+
($a || $b) && $c -- grouped for precedence
|
|
638
|
+
$valid && $ready ? "go" ! "wait" -- used as conditional *)
|
|
639
|
+
|
|
640
|
+
(* ============================================================ *)
|
|
641
|
+
(* ARITHMETIC EXPRESSIONS *)
|
|
642
|
+
(* ============================================================ *)
|
|
643
|
+
|
|
644
|
+
(* Arithmetic uses parenthesis grouping: ( expr )
|
|
645
|
+
Operators follow standard precedence: * / % before + -
|
|
646
|
+
|
|
647
|
+
(5 + 3) -> 8
|
|
648
|
+
(2 + 3 * 4) -> 14
|
|
649
|
+
((2 + 3) * 4) -> 20
|
|
650
|
+
10 -> ($ + 5) -> 15 ($ is pipe value)
|
|
651
|
+
3 -> $x ($ * 2) -> 6 (variables in expressions)
|
|
652
|
+
(10 / 0) -> ERROR (division by zero)
|
|
653
|
+
|
|
654
|
+
Type constraint: all operands must be numbers.
|
|
655
|
+
"5" -> ($ + 1) -> ERROR (string is not a number)
|
|
656
|
+
|
|
657
|
+
Grouped expressions can appear:
|
|
658
|
+
- As standalone expressions: (5 + 3)
|
|
659
|
+
- As pipe targets: 5 -> ($ * 2)
|
|
660
|
+
- Nested: ((1 + 2) * 3)
|
|
661
|
+
- As closure bodies: |x| ($x + 1)
|
|
662
|
+
|
|
663
|
+
Note: Arithmetic works everywhere now, not just in grouped expressions.
|
|
664
|
+
5 + 3, { 5 + 3 }, and (5 + 3) all work. Grouped expressions provide
|
|
665
|
+
explicit scoping for captures. *)
|
|
666
|
+
|
|
667
|
+
grouped-expr = "(" , pipe-chain , ")" ;
|
|
668
|
+
(* Single-expression block with () delimiters.
|
|
669
|
+
Uses the same parsing as any expression. *)
|
|
670
|
+
|
|
671
|
+
(* ============================================================ *)
|
|
672
|
+
(* LEXICAL *)
|
|
673
|
+
(* ============================================================ *)
|
|
674
|
+
|
|
675
|
+
identifier = ( letter | "_" ) , { letter | digit | "_" } ;
|
|
676
|
+
(* Style: snake_case is idiomatic for identifiers.
|
|
677
|
+
Functions: parse_json, replace_all, pad_start
|
|
678
|
+
Variables: $user_name, $total_count, $api_response
|
|
679
|
+
Methods: .starts_with, .ends_with, .index_of *)
|
|
680
|
+
|
|
681
|
+
letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
|
|
682
|
+
| "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
|
|
683
|
+
| "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
|
|
684
|
+
| "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" ;
|
|
685
|
+
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
|
|
686
|
+
newline = "\n" | "\r\n" ;
|
|
687
|
+
|
|
688
|
+
(* ============================================================ *)
|
|
689
|
+
(* WHITESPACE & COMMENTS *)
|
|
690
|
+
(* ============================================================ *)
|
|
691
|
+
|
|
692
|
+
(* Whitespace (spaces, tabs, newlines) between tokens is insignificant.
|
|
693
|
+
Comments start with # and extend to end of line. *)
|