@rcrsr/rill 0.5.0 → 0.6.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.
Files changed (240) hide show
  1. package/dist/generated/introspection-data.d.ts +1 -1
  2. package/dist/generated/introspection-data.d.ts.map +1 -1
  3. package/dist/generated/introspection-data.js +107 -186
  4. package/dist/generated/introspection-data.js.map +1 -1
  5. package/dist/generated/version-data.d.ts +1 -1
  6. package/dist/generated/version-data.js +3 -3
  7. package/dist/generated/version-data.js.map +1 -1
  8. package/dist/highlight-map.d.ts +4 -0
  9. package/dist/highlight-map.d.ts.map +1 -0
  10. package/dist/highlight-map.js +71 -0
  11. package/dist/highlight-map.js.map +1 -0
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +5 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/lexer/errors.d.ts.map +1 -1
  17. package/dist/lexer/errors.js +9 -3
  18. package/dist/lexer/errors.js.map +1 -1
  19. package/dist/lexer/operators.js +1 -1
  20. package/dist/lexer/tokenizer.d.ts.map +1 -1
  21. package/dist/lexer/tokenizer.js +0 -14
  22. package/dist/lexer/tokenizer.js.map +1 -1
  23. package/dist/parser/helpers.d.ts +8 -0
  24. package/dist/parser/helpers.d.ts.map +1 -1
  25. package/dist/parser/helpers.js +4 -4
  26. package/dist/parser/helpers.js.map +1 -1
  27. package/dist/parser/index.d.ts.map +1 -1
  28. package/dist/parser/index.js +1 -1
  29. package/dist/parser/index.js.map +1 -1
  30. package/dist/parser/parser-collect.js +1 -1
  31. package/dist/parser/parser-collect.js.map +1 -1
  32. package/dist/parser/parser-control.js +4 -4
  33. package/dist/parser/parser-control.js.map +1 -1
  34. package/dist/parser/parser-expr.js +32 -10
  35. package/dist/parser/parser-expr.js.map +1 -1
  36. package/dist/parser/parser-extract.js +7 -3
  37. package/dist/parser/parser-extract.js.map +1 -1
  38. package/dist/parser/parser-functions.d.ts.map +1 -1
  39. package/dist/parser/parser-functions.js +7 -18
  40. package/dist/parser/parser-functions.js.map +1 -1
  41. package/dist/parser/parser-literals.js +15 -15
  42. package/dist/parser/parser-literals.js.map +1 -1
  43. package/dist/parser/parser-script.js +3 -3
  44. package/dist/parser/parser-script.js.map +1 -1
  45. package/dist/parser/parser-variables.js +4 -4
  46. package/dist/parser/parser-variables.js.map +1 -1
  47. package/dist/parser/state.d.ts +1 -1
  48. package/dist/parser/state.d.ts.map +1 -1
  49. package/dist/parser/state.js +2 -2
  50. package/dist/parser/state.js.map +1 -1
  51. package/dist/runtime/core/callable.d.ts +20 -0
  52. package/dist/runtime/core/callable.d.ts.map +1 -1
  53. package/dist/runtime/core/callable.js +30 -7
  54. package/dist/runtime/core/callable.js.map +1 -1
  55. package/dist/runtime/core/context.d.ts +21 -0
  56. package/dist/runtime/core/context.d.ts.map +1 -1
  57. package/dist/runtime/core/context.js +75 -4
  58. package/dist/runtime/core/context.js.map +1 -1
  59. package/dist/runtime/core/eval/base.d.ts.map +1 -1
  60. package/dist/runtime/core/eval/base.js +3 -3
  61. package/dist/runtime/core/eval/base.js.map +1 -1
  62. package/dist/runtime/core/eval/index.d.ts.map +1 -1
  63. package/dist/runtime/core/eval/index.js +2 -0
  64. package/dist/runtime/core/eval/index.js.map +1 -1
  65. package/dist/runtime/core/eval/mixins/annotations.js +3 -3
  66. package/dist/runtime/core/eval/mixins/annotations.js.map +1 -1
  67. package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -1
  68. package/dist/runtime/core/eval/mixins/closures.js +69 -40
  69. package/dist/runtime/core/eval/mixins/closures.js.map +1 -1
  70. package/dist/runtime/core/eval/mixins/collections.js +15 -15
  71. package/dist/runtime/core/eval/mixins/collections.js.map +1 -1
  72. package/dist/runtime/core/eval/mixins/control-flow.d.ts.map +1 -1
  73. package/dist/runtime/core/eval/mixins/control-flow.js +12 -12
  74. package/dist/runtime/core/eval/mixins/control-flow.js.map +1 -1
  75. package/dist/runtime/core/eval/mixins/core.d.ts.map +1 -1
  76. package/dist/runtime/core/eval/mixins/core.js +12 -13
  77. package/dist/runtime/core/eval/mixins/core.js.map +1 -1
  78. package/dist/runtime/core/eval/mixins/expressions.js +9 -9
  79. package/dist/runtime/core/eval/mixins/expressions.js.map +1 -1
  80. package/dist/runtime/core/eval/mixins/extraction.d.ts.map +1 -1
  81. package/dist/runtime/core/eval/mixins/extraction.js +15 -15
  82. package/dist/runtime/core/eval/mixins/extraction.js.map +1 -1
  83. package/dist/runtime/core/eval/mixins/literals.js +22 -22
  84. package/dist/runtime/core/eval/mixins/literals.js.map +1 -1
  85. package/dist/runtime/core/eval/mixins/types.js +4 -4
  86. package/dist/runtime/core/eval/mixins/types.js.map +1 -1
  87. package/dist/runtime/core/eval/mixins/variables.js +34 -34
  88. package/dist/runtime/core/eval/mixins/variables.js.map +1 -1
  89. package/dist/runtime/core/execute.js +3 -3
  90. package/dist/runtime/core/execute.js.map +1 -1
  91. package/dist/runtime/core/introspection.d.ts +30 -1
  92. package/dist/runtime/core/introspection.d.ts.map +1 -1
  93. package/dist/runtime/core/introspection.js +47 -1
  94. package/dist/runtime/core/introspection.js.map +1 -1
  95. package/dist/runtime/core/types.d.ts +11 -0
  96. package/dist/runtime/core/types.d.ts.map +1 -1
  97. package/dist/runtime/core/types.js.map +1 -1
  98. package/dist/runtime/ext/builtins.js +22 -22
  99. package/dist/runtime/ext/builtins.js.map +1 -1
  100. package/dist/runtime/ext/extensions.d.ts +1 -1
  101. package/dist/runtime/ext/extensions.d.ts.map +1 -1
  102. package/dist/runtime/ext/extensions.js +4 -5
  103. package/dist/runtime/ext/extensions.js.map +1 -1
  104. package/dist/runtime/index.d.ts +6 -4
  105. package/dist/runtime/index.d.ts.map +1 -1
  106. package/dist/runtime/index.js +3 -2
  107. package/dist/runtime/index.js.map +1 -1
  108. package/dist/types.d.ts +36 -37
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/types.js +440 -120
  111. package/dist/types.js.map +1 -1
  112. package/package.json +7 -66
  113. package/README.md +0 -223
  114. package/dist/check/config.d.ts +0 -20
  115. package/dist/check/config.d.ts.map +0 -1
  116. package/dist/check/config.js +0 -151
  117. package/dist/check/config.js.map +0 -1
  118. package/dist/check/fixer.d.ts +0 -39
  119. package/dist/check/fixer.d.ts.map +0 -1
  120. package/dist/check/fixer.js +0 -119
  121. package/dist/check/fixer.js.map +0 -1
  122. package/dist/check/index.d.ts +0 -10
  123. package/dist/check/index.d.ts.map +0 -1
  124. package/dist/check/index.js +0 -21
  125. package/dist/check/index.js.map +0 -1
  126. package/dist/check/rules/anti-patterns.d.ts +0 -65
  127. package/dist/check/rules/anti-patterns.d.ts.map +0 -1
  128. package/dist/check/rules/anti-patterns.js +0 -481
  129. package/dist/check/rules/anti-patterns.js.map +0 -1
  130. package/dist/check/rules/closures.d.ts +0 -66
  131. package/dist/check/rules/closures.d.ts.map +0 -1
  132. package/dist/check/rules/closures.js +0 -370
  133. package/dist/check/rules/closures.js.map +0 -1
  134. package/dist/check/rules/collections.d.ts +0 -90
  135. package/dist/check/rules/collections.d.ts.map +0 -1
  136. package/dist/check/rules/collections.js +0 -373
  137. package/dist/check/rules/collections.js.map +0 -1
  138. package/dist/check/rules/conditionals.d.ts +0 -41
  139. package/dist/check/rules/conditionals.d.ts.map +0 -1
  140. package/dist/check/rules/conditionals.js +0 -134
  141. package/dist/check/rules/conditionals.js.map +0 -1
  142. package/dist/check/rules/flow.d.ts +0 -46
  143. package/dist/check/rules/flow.d.ts.map +0 -1
  144. package/dist/check/rules/flow.js +0 -206
  145. package/dist/check/rules/flow.js.map +0 -1
  146. package/dist/check/rules/formatting.d.ts +0 -133
  147. package/dist/check/rules/formatting.d.ts.map +0 -1
  148. package/dist/check/rules/formatting.js +0 -648
  149. package/dist/check/rules/formatting.js.map +0 -1
  150. package/dist/check/rules/helpers.d.ts +0 -26
  151. package/dist/check/rules/helpers.d.ts.map +0 -1
  152. package/dist/check/rules/helpers.js +0 -66
  153. package/dist/check/rules/helpers.js.map +0 -1
  154. package/dist/check/rules/index.d.ts +0 -21
  155. package/dist/check/rules/index.d.ts.map +0 -1
  156. package/dist/check/rules/index.js +0 -78
  157. package/dist/check/rules/index.js.map +0 -1
  158. package/dist/check/rules/loops.d.ts +0 -77
  159. package/dist/check/rules/loops.d.ts.map +0 -1
  160. package/dist/check/rules/loops.js +0 -310
  161. package/dist/check/rules/loops.js.map +0 -1
  162. package/dist/check/rules/naming.d.ts +0 -21
  163. package/dist/check/rules/naming.d.ts.map +0 -1
  164. package/dist/check/rules/naming.js +0 -174
  165. package/dist/check/rules/naming.js.map +0 -1
  166. package/dist/check/rules/strings.d.ts +0 -28
  167. package/dist/check/rules/strings.d.ts.map +0 -1
  168. package/dist/check/rules/strings.js +0 -79
  169. package/dist/check/rules/strings.js.map +0 -1
  170. package/dist/check/rules/types.d.ts +0 -41
  171. package/dist/check/rules/types.d.ts.map +0 -1
  172. package/dist/check/rules/types.js +0 -167
  173. package/dist/check/rules/types.js.map +0 -1
  174. package/dist/check/types.d.ts +0 -112
  175. package/dist/check/types.d.ts.map +0 -1
  176. package/dist/check/types.js +0 -6
  177. package/dist/check/types.js.map +0 -1
  178. package/dist/check/validator.d.ts +0 -18
  179. package/dist/check/validator.d.ts.map +0 -1
  180. package/dist/check/validator.js +0 -110
  181. package/dist/check/validator.js.map +0 -1
  182. package/dist/check/visitor.d.ts +0 -33
  183. package/dist/check/visitor.d.ts.map +0 -1
  184. package/dist/check/visitor.js +0 -259
  185. package/dist/check/visitor.js.map +0 -1
  186. package/dist/cli-check.d.ts +0 -43
  187. package/dist/cli-check.d.ts.map +0 -1
  188. package/dist/cli-check.js +0 -368
  189. package/dist/cli-check.js.map +0 -1
  190. package/dist/cli-eval.d.ts +0 -15
  191. package/dist/cli-eval.d.ts.map +0 -1
  192. package/dist/cli-eval.js +0 -116
  193. package/dist/cli-eval.js.map +0 -1
  194. package/dist/cli-exec.d.ts +0 -49
  195. package/dist/cli-exec.d.ts.map +0 -1
  196. package/dist/cli-exec.js +0 -183
  197. package/dist/cli-exec.js.map +0 -1
  198. package/dist/cli-module-loader.d.ts +0 -19
  199. package/dist/cli-module-loader.d.ts.map +0 -1
  200. package/dist/cli-module-loader.js +0 -83
  201. package/dist/cli-module-loader.js.map +0 -1
  202. package/dist/cli-shared.d.ts +0 -44
  203. package/dist/cli-shared.d.ts.map +0 -1
  204. package/dist/cli-shared.js +0 -108
  205. package/dist/cli-shared.js.map +0 -1
  206. package/dist/cli.d.ts +0 -13
  207. package/dist/cli.d.ts.map +0 -1
  208. package/dist/cli.js +0 -62
  209. package/dist/cli.js.map +0 -1
  210. package/dist/runtime/core/introspection-data.d.ts +0 -2
  211. package/dist/runtime/core/introspection-data.d.ts.map +0 -1
  212. package/dist/runtime/core/introspection-data.js +0 -618
  213. package/dist/runtime/core/introspection-data.js.map +0 -1
  214. package/dist/runtime/core/version-data.d.ts +0 -18
  215. package/dist/runtime/core/version-data.d.ts.map +0 -1
  216. package/dist/runtime/core/version-data.js +0 -16
  217. package/dist/runtime/core/version-data.js.map +0 -1
  218. package/docs/00_INDEX.md +0 -67
  219. package/docs/01_guide.md +0 -390
  220. package/docs/02_types.md +0 -504
  221. package/docs/03_variables.md +0 -324
  222. package/docs/04_operators.md +0 -629
  223. package/docs/05_control-flow.md +0 -692
  224. package/docs/06_closures.md +0 -787
  225. package/docs/07_collections.md +0 -688
  226. package/docs/08_iterators.md +0 -330
  227. package/docs/09_strings.md +0 -205
  228. package/docs/10_parsing.md +0 -366
  229. package/docs/11_reference.md +0 -600
  230. package/docs/12_examples.md +0 -748
  231. package/docs/13_modules.md +0 -519
  232. package/docs/14_host-integration.md +0 -985
  233. package/docs/15_grammar.ebnf +0 -773
  234. package/docs/16_conventions.md +0 -695
  235. package/docs/17_cli-tools.md +0 -184
  236. package/docs/18_design-principles.md +0 -247
  237. package/docs/19_cookbook.md +0 -628
  238. package/docs/88_errors.md +0 -902
  239. package/docs/99_llm-reference.txt +0 -614
  240. package/docs/assets/logo.png +0 -0
@@ -1,773 +0,0 @@
1
- (* rill v0.4.5 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 | assert-statement | error-statement ) ;
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
- (* Semantic: "!" requires boolean operand. No truthiness coercion.
132
- !true -> false, !false -> true
133
- !"string" -> RUNTIME_TYPE_ERROR (Negation requires boolean, got string)
134
- !0 -> RUNTIME_TYPE_ERROR (Negation requires boolean, got number) *)
135
-
136
- comparison-op = "==" | "!=" | "<" | ">" | "<=" | ">=" ;
137
-
138
- chain-terminator = "break" | "return" ;
139
- (* Note: capture can appear mid-chain or at end; handled in pipe-chain *)
140
-
141
- assert-statement = "assert" , expression , [ string ] ;
142
- (* Assert statement: validates boolean expression, halts on false.
143
- Optional string provides custom error message.
144
- Constraint: expression must evaluate to boolean, else RUNTIME_TYPE_ERROR. *)
145
-
146
- error-statement = "error" , string ;
147
-
148
- postfix-expr = primary , { "." , method-name , [ "(" , [ arguments ] , ")" ] } ;
149
-
150
- primary = literal
151
- | variable
152
- | closure-call
153
- | host-call
154
- | conditional
155
- | loop
156
- | block
157
- | grouped-expr
158
- | spread (* *expr - convert list/dict to tuple *)
159
- | pass ;
160
-
161
- (* Pass: returns current pipe value ($) unchanged.
162
- Used for explicit identity pass-through in conditionals and dict values.
163
-
164
- Examples:
165
- cond ? pass ! "fallback" -- preserve $ when condition true
166
- cond ? "value" ! pass -- preserve $ when condition false
167
- [key: pass] -- include $ as dict value
168
- -> { pass } -- block returns $
169
-
170
- Semantic: pass requires pipe context. Using pass without $ bound
171
- throws RUNTIME_UNDEFINED_VARIABLE: Variable '$' not defined *)
172
- pass = "pass" ;
173
-
174
- (* implicit-primary: used in contexts where implicit $ is available (body, pipe targets).
175
- Includes method-call because .method() expands to $ -> .method().
176
- primary excludes method-call since statements cannot start with a bare method call. *)
177
- implicit-primary = literal | variable | closure-call | host-call | method-call | block | grouped-expr ;
178
-
179
- (* ============================================================ *)
180
- (* LITERALS *)
181
- (* ============================================================ *)
182
-
183
- literal = string | number | bool | list | dict | closure ;
184
-
185
- (* Closures are first-class values with optional typed parameters.
186
- The body can be a block, grouped expression, or postfix expression.
187
-
188
- || { "hello" } -> $greet -- no params, block body
189
- |x| { $x -> .len } -> $strlen -- untyped param
190
- |x: string| { $x -> .len } -> $strlen -- typed param
191
- |x: string = "default"| { $x } -> $fn -- typed with default
192
- |x = "default"| { $x } -> $fn -- default with inferred type
193
- |a: number, b: number = 0| { } -> $add -- mixed params
194
- |x| ($x + 1) -> $inc -- grouped expression body
195
- |x| $x -> $identity -- postfix expression body
196
-
197
- Scope rules (same as blocks):
198
- - Captures outer scope variables (read-only)
199
- - Local variables (declared inside) are mutable
200
- - Cannot modify outer scope variables
201
-
202
- Invocation:
203
- - $fn() -- call with no args
204
- - $fn("arg") -- call with args
205
- - $fn -> $() -- pipe-style invoke (equivalent)
206
- - "value" -> $fn() -- pipe value as first arg
207
- - *[1, 2] -> $fn() -- tuple unpacking at invocation *)
208
-
209
- closure-param = identifier , [ ":" , param-type ] , [ "=" , literal ]
210
- | identifier , "=" , literal ; (* type inferred from default *)
211
- param-type = "string" | "number" | "bool" ; (* primitives only for closure params *)
212
- body = block | grouped-expr | postfix-expr ;
213
- closure = "|" , [ closure-param , { "," , closure-param } ] , "|" , body ;
214
-
215
- string = '"' , { char | escape | brace-escape | interpolation } , '"'
216
- | '"""' , { char | escape | brace-escape | interpolation } , '"""' ;
217
-
218
- char = letter | digit | " " | "!" | "#" | "$" | "%" | "&" | "'" | "(" | ")"
219
- | "*" | "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | "=" | ">"
220
- | "?" | "@" | "[" | "]" | "^" | "_" | "`" | "|" | "~" | newline ;
221
- (* excludes " \ { } which have special meaning in strings *)
222
- escape = "\\" , ( "n" | "r" | "t" | "\\" | '"' ) ;
223
- brace-escape = "{{" | "}}" ; (* {{ -> literal {, }} -> literal } *)
224
-
225
- interpolation = "{" , expression , "}" ;
226
-
227
- number = [ "-" ] , digit , { digit } , [ "." , digit , { digit } ] ;
228
- bool = "true" | "false" ;
229
-
230
- list = "[" , "]"
231
- | "[" , list-element , { "," , list-element } , "]" ;
232
- list-element = "..." , expression (* spread: inline list elements *)
233
- | expression ; (* regular element *)
234
- (* List spread syntax: [1, ...$list, 2] inlines $list elements.
235
- The spread expression must evaluate to a list at runtime.
236
- Example: [1, 2] :> $a; [...$a, 3] yields [1, 2, 3]
237
- Error: spreading non-list throws RUNTIME_TYPE_ERROR *)
238
-
239
- dict = "[" , ":" , "]"
240
- | "[" , dict-entry , { "," , dict-entry } , "]" ;
241
- dict-key = identifier | number | bool
242
- | "$" , identifier (* variable key: [$keyName: value] *)
243
- | "(" , pipe-chain , ")" ; (* computed key: [($expr): value] *)
244
- dict-entry = ( dict-key | "[" , expression , { "," , expression } , "]" ) , ":" , expression ;
245
- (* Keys can be identifiers, numbers, booleans, variables, computed expressions, or multi-key lists.
246
- Variable key: [$varName: value] uses variable value as key (must be string).
247
- Computed key: [($x -> .upper): value] evaluates expression as key (must be string).
248
- Multi-key syntax [k1, k2]: expr maps multiple keys to same value.
249
- Semantic: keys "keys", "values", "entries" are reserved (cannot be used as dict keys).
250
- Semantic: closure values become "dict closures" where $ is late-bound
251
- to the containing dict at invocation time (like "this" in other languages). *)
252
-
253
- (* ============================================================ *)
254
- (* VARIABLES *)
255
- (* ============================================================ *)
256
-
257
- variable = "$" , identifier , { property-access } , [ default-value ] , [ existence-check ]
258
- | "$" , { property-access } , [ default-value ] , [ existence-check ] ;
259
- (* $ARGS and $ENV are outer-scope variables provided by the runtime,
260
- not grammar-level constructs. Same scoping rules apply. *)
261
-
262
- (* Closure calls invoke closures stored in variables:
263
- $strlen("hello") -- call closure with string arg
264
- $add(1, 2) -- call closure with multiple args
265
- $transform($data) -- call closure with variable arg
266
- In string interpolation: "Length: {$strlen("word")}" *)
267
- closure-call = "$" , identifier , "(" , [ arguments ] , ")" ;
268
-
269
- (* Property access chain supports both dot and bracket access in any order:
270
- $data.user.name -- literal fields
271
- $data.items[0] -- bracket index
272
- $data.items[-1] -- negative index (from end)
273
- $data.user.$field -- variable as key
274
- $data.items.($i + 1) -- computed expression
275
- $data.{get_key()} -- block returning key
276
- $data.user.(nick || name) -- alternatives (try left-to-right)
277
- *)
278
- property-access = field-access | bracket-access ;
279
-
280
- field-access = "." , ( identifier
281
- | "$" , identifier (* variable as key *)
282
- | "(" , pipe-chain , ")" (* computed expression *)
283
- | "{" , block , "}" (* block returning key *)
284
- | "(" , identifier , { "||" , identifier } , ")" ) ; (* alternatives *)
285
-
286
- bracket-access = "[" , pipe-chain , "]" ; (* index access, supports negative *)
287
-
288
- (* Default value if property is missing: $data.name ?? "Anonymous" *)
289
- default-value = "??" , body ;
290
-
291
- (* Existence check: $data.user.?email returns bool
292
- Type narrowing uses & (not :) because the expression returns bool.
293
- Using :type would suggest type assertion on a non-bool value.
294
- $data.?field&string means "does field exist and is it a string?" -> bool
295
- Variable existence: $data.?$keyName checks existence using variable value.
296
- Computed existence: $data.?($expr) checks existence using expression result. *)
297
- existence-check = ".?" , ( identifier
298
- | "$" , identifier (* variable key *)
299
- | "(" , pipe-chain , ")" ) (* computed expression *)
300
- , [ "&" , type-name ] ;
301
-
302
- digits = digit , { digit } ;
303
-
304
- (* Capture with optional type annotation.
305
- Variables are type-locked after first assignment.
306
-
307
- Capture arrow (:>) captures value into variable:
308
- "hello" :> $name -- capture, chain continues with "hello"
309
- "hello" :> $name -> .upper -- capture, then transform (result: "HELLO")
310
- "hello" :> $name:string -- explicit type (validated on assignment)
311
-
312
- Pipe arrow (-> $var) invokes closure stored in $var:
313
- "hello" -> $fn -- invoke $fn("hello") if $fn is closure
314
- "hello" -> $fn -> .len -- invoke, then continue chain
315
-
316
- Type locking:
317
- "hello" :> $name -- $name is now type-locked to string
318
- 5 :> $name -- ERROR: $name was previously string *)
319
- capture = "$" , identifier , [ ":" , capture-type ] ;
320
- capture-type = "string" | "number" | "bool" | "closure" | "list" | "dict" | "tuple" ;
321
-
322
- (* ============================================================ *)
323
- (* HOST FUNCTIONS *)
324
- (* ============================================================ *)
325
-
326
- host-call = host-name , "(" , [ arguments ] , ")" ;
327
-
328
- host-name = identifier , { "::" , identifier } ;
329
- (* Namespaced functions use :: separator: math::add, io::file::read
330
- rill is a vanilla language. The host provides all domain functions.
331
-
332
- Core global functions (built-in):
333
- - type($value) -- returns type name: "string", "number", "bool", "closure", "list", "dict", "tuple"
334
- - log($value) -- logs value using .str, returns value unchanged (passthrough)
335
- - json($value) -- converts any value to JSON string
336
- - parse_json($text) -- parses JSON string to value (with repair)
337
- - identity($value) -- returns value unchanged
338
- - range(start, end, step?) -- iterator from start (inclusive) to end (exclusive)
339
- - repeat(value, count) -- iterator repeating value count times
340
-
341
- Content parsing functions (built-in):
342
- - parse_auto($text) -- auto-detect and parse structured content
343
- - parse_xml($text, tag?) -- extract content from XML tags
344
- - parse_fence($text, lang?) -- extract fenced code block content
345
- - parse_fences($text) -- extract all fenced code blocks
346
- - parse_frontmatter($text) -- parse YAML frontmatter and body
347
- - parse_checklist($text) -- parse markdown checklist items
348
-
349
- All other functions are registered by the host via RuntimeContext.functions. *)
350
-
351
- arguments = expression , { "," , expression } ;
352
-
353
- (* ============================================================ *)
354
- (* METHODS *)
355
- (* ============================================================ *)
356
-
357
- (* Methods: ".method" or ".method()" with implied $ receiver.
358
- 0-arg methods may omit parens: .empty ≡ .empty() ≡ $.empty()
359
- In pipe: "x -> .str" passes x as receiver to str method.
360
- In conditional: ".empty ? then ! else" tests $.empty for truthiness.
361
- Pipe semantics: "$ -> fn" ≡ "fn($)" — piped value becomes first arg.
362
- Global functions (type, log, json) are not methods — use as: "x -> log" *)
363
-
364
- method-call = "." , method-name , [ "(" , [ arguments ] , ")" ] ;
365
-
366
- method-name = "contains" | "match" | "is_match" (* pattern: 1 arg *)
367
- | "starts_with" | "ends_with" (* prefix/suffix: 1 arg *)
368
- | "lower" | "upper" (* case: 0 args *)
369
- | "replace" | "replace_all" (* regex replace: 2 args *)
370
- | "index_of" (* position: 1 arg *)
371
- | "repeat" (* repeat: 1 arg *)
372
- | "pad_start" | "pad_end" (* padding: 1-2 args *)
373
- | "empty" | "lines" (* utility: 0 args *)
374
- | "eq" | "ne" | "lt" | "gt" | "le" | "ge" (* comparison: 1 arg *)
375
- | "str" | "num" | "len" | "trim" (* conversion: 0 args *)
376
- | "head" | "tail" (* element access: 0 args *)
377
- | "first" (* iterator: 0 args, returns iterator *)
378
- | "at" (* element access: 1 arg *)
379
- | "split" | "join" (* string ops: 0-1 args *)
380
- | "keys" | "values" | "entries" ; (* dict: 0 args *)
381
- (* Note: .str is built-in on ALL types including tuple.
382
- Use global log() and json() functions instead of methods. *)
383
-
384
- (* Arity constraints (semantic check, not grammar-enforced):
385
- The grammar allows [ arguments ] for flexibility, but these methods
386
- have fixed arity that parsers/interpreters must validate:
387
- 0 args: empty, lines, str, num, len, trim, head, tail, first, keys, values, entries, lower, upper
388
- 0-1 args: split, join (separator defaults to newline/comma)
389
- 1 arg: contains, match, is_match, starts_with, ends_with, index_of, repeat, eq, ne, lt, gt, le, ge, at
390
- 1-2 args: pad_start, pad_end (fill defaults to space)
391
- 2 args: replace, replace_all *)
392
-
393
- (* ============================================================ *)
394
- (* PIPES *)
395
- (* ============================================================ *)
396
-
397
- (* pipe-chain is defined in EXPRESSIONS section *)
398
-
399
- (* Capture: stores value in a variable and terminates the pipe chain.
400
- The stored value also becomes the expression result.
401
-
402
- Example: fetch("url") -> $result
403
- The value is stored in $result and returned.
404
-
405
- Note: Unlike earlier versions, capture ALWAYS terminates the chain.
406
- Use explicit chaining for multi-step operations:
407
- fetch("url") -> $result
408
- $result -> log -> ?(.contains("x")) { } *)
409
-
410
- (* Pipe targets: bare host names allowed without parens.
411
- "$ -> greet" ≡ "greet($)" — piped value becomes implicit first arg. *)
412
-
413
- pipe-target = host-call (* $ -> greet("world") — call with explicit args *)
414
- | closure-call (* $ -> $fn("arg") — invoke closure with args *)
415
- | pipe-invoke (* $ -> $() — invoke $ as closure, $ -> $(args) with args *)
416
- | variable (* $ -> $fn — invoke closure stored in $fn with $ as arg *)
417
- | host-name (* $ -> greet — bare name, $ becomes implicit first arg *)
418
- | method-call (* $ -> .len — method on $, $ -> .at(0) with args *)
419
- | string (* $ -> "hello {$}" — template with interpolation *)
420
- | conditional (* $ -> ? "yes" ! "no" — conditional on $ *)
421
- | loop (* $ -> @(cond) { body } — while loop *)
422
- | each-expr (* $ -> each { body } — sequential iteration *)
423
- | map-expr (* $ -> map { body } — parallel iteration *)
424
- | fold-expr (* $ -> fold(init) { body } — reduction *)
425
- | filter-expr (* $ -> filter { cond } — parallel filtering *)
426
- | block (* $ -> { .len } — block with implicit $ head *)
427
- | grouped-expr (* $ -> ($ + 1) — arithmetic/compound expression *)
428
- | closure-chain (* $ -> @[$f, $g] — chain: $g($f($)) *)
429
- | destructure (* $ -> *<$a, $b> — extract elements into vars *)
430
- | slice (* $ -> /<0:3> — extract portion of $ *)
431
- | type-assertion (* $ -> :string — assert $ is string type *)
432
- | type-check (* $ -> :?string — check if $ is string type *)
433
- | "*" ; (* $ -> * — convert $ to tuple for unpacking *)
434
-
435
- (* Closure chain (@): compose closures left-to-right.
436
- - Chain: x -> @[$f,$g,$h] -- $h($g($f(x)))
437
- The target must be a closure or list of closures. *)
438
-
439
- closure-chain = "@" , ( variable | list ) ; (* @$var or @[closures] *)
440
-
441
- (* Dispatch semantics (runtime behavior, not grammar):
442
- When piping to a dict or list literal, dispatch rules apply:
443
-
444
- 1. Scalar -> dict: key lookup
445
- "apple" -> [apple: "fruit", carrot: "veg"] -- "fruit"
446
-
447
- 2. Number -> list: index access
448
- 0 -> ["first", "second"] -- "first"
449
-
450
- 3. List -> dict/list: hierarchical navigation (path dispatch)
451
- ["name", "first"] -> [name: [first: "Alice"]] -- "Alice"
452
- [0, 1] -> [[1, 2], [3, 4]] -- 4
453
- ["users", 0, "name"] -> [users: [[name: "X"]]] -- "X"
454
-
455
- Path elements: strings for dict keys, numbers for list indexes.
456
- Negative indexes supported. Terminal closures receive $ = final key.
457
- See docs/04_operators.md for complete dispatch documentation. *)
458
-
459
- (* ============================================================ *)
460
- (* EXTRACTION OPERATORS *)
461
- (* ============================================================ *)
462
-
463
- (* Extraction operators transform collections by extracting elements.
464
- They return the extracted values while optionally binding to variables.
465
-
466
- Destructure: extract elements into variables
467
- - List: [1, 2, 3] -> *<$a, $b, $c>
468
- - Dict: [name: "x"] -> *<name: $n>
469
- - Skip: [1, 2, 3] -> *<$a, _, $c>
470
- - Nested: [[1, 2], 3] -> *<*<$a, $b>, $c>
471
-
472
- Slice: extract portions using Python-style start:stop:step
473
- - [1, 2, 3, 4, 5] -> /<1:4> -> [2, 3, 4]
474
- - [1, 2, 3, 4, 5] -> /<::-1> -> [5, 4, 3, 2, 1]
475
- - "hello" -> /<1:4> -> "ell"
476
-
477
- Enumerate: use global enumerate() function instead
478
- - enumerate([10, 20]) -> [[index: 0, value: 10], [index: 1, value: 20]]
479
- - enumerate([a: 1]) -> [[index: 0, key: "a", value: 1]] *)
480
-
481
- destructure = "*<" , [ destruct-pattern , { "," , destruct-pattern } ] , ">" ;
482
- destruct-pattern = "$" , identifier , [ ":" , capture-type ] (* variable *)
483
- | identifier , ":" , "$" , identifier , [ ":" , capture-type ] (* key: $var *)
484
- | "_" (* skip placeholder *)
485
- | destructure ; (* nested pattern *)
486
-
487
- slice = "/<" , [ slice-bound ] , ":" , [ slice-bound ] , [ ":" , [ slice-bound ] ] , ">" ;
488
- slice-bound = number | variable | grouped-expr ;
489
-
490
- (* Spread: convert list/dict to tuple for unpacking at closure invocation.
491
- The tuple type exists solely to signal "unpack these values as separate arguments."
492
-
493
- Syntax:
494
- *[1, 2, 3] -- positional tuple from list literal
495
- *[x: 1, y: 2] -- named tuple from dict literal
496
- *$myList -- positional tuple from list variable
497
- *$myDict -- named tuple from dict variable
498
- [1, 2, 3] -> * -- convert pipe value to tuple
499
-
500
- Key-based matching at invocation:
501
- - Numeric keys (from list): match parameters by position
502
- - String keys (from dict): match parameters by name
503
-
504
- Example:
505
- |a, b| { [$b, $a] } -> $flip
506
- *[1, 2] -> $flip() -- $a=1, $b=2, returns [2, 1]
507
- *[a: 1, b: 2] -> $flip() -- named tuple, same result
508
-
509
- Strict validation: missing or extra arguments error at invocation.
510
- Parameter defaults provide explicit opt-in leniency. *)
511
-
512
- spread = "*" , [ postfix-expr ] ;
513
- (* Bare * uses implicit $: each { * } ≡ each { $ -> * } *)
514
-
515
- (* Pipe-invoke: call the pipe value ($) as a closure
516
- ||{ "hello" } -> $() -- invoke with no args
517
- |x|$x -> $("world") -- invoke with args
518
- $closure -> $() -- invoke variable that holds a closure *)
519
- pipe-invoke = "$" , "(" , [ arguments ] , ")" ;
520
-
521
- (* Type assertion: assert value is a specific type, error if mismatch.
522
- Returns value unchanged on success. Can be used as:
523
- - Postfix: 42:number, (expr):type - binds tighter than method calls
524
- - Pipe target: -> :type - asserts on pipe value ($)
525
-
526
- Examples:
527
- 42:number -- postfix assertion
528
- (1 + 2):number -- postfix on grouped expr
529
- (($ < 5) @ ($ + 1)):number -- postfix on loop result
530
- "hello" -> :string -- pipe target assertion
531
- $val -> :dict -> .keys -- assert then continue chain *)
532
- type-assertion = ":" , type-name ;
533
-
534
- (* Type check: check if value is a specific type, returns bool.
535
- Can be used as postfix (expr:?type) or pipe target (-> :?type).
536
-
537
- Examples:
538
- 42:?number -- true (postfix)
539
- "hello":?number -- false (postfix)
540
- $val -> :?list ? process() -- conditional on type *)
541
- type-check = ":" , "?" , type-name ;
542
-
543
- type-name = "string" | "number" | "bool" | "closure" | "list" | "dict" | "tuple" ;
544
-
545
- (* ============================================================ *)
546
- (* CONTROL FLOW *)
547
- (* ============================================================ *)
548
-
549
- (* Conditional syntax: condition ? then-branch ! else-branch
550
- The condition MUST evaluate to a boolean value (true or false).
551
- Non-boolean values will cause a runtime error.
552
-
553
- Examples:
554
- $ready ? "go" ! "wait" -- variable must be boolean
555
- .valid ? process() ! reject() -- method must return boolean
556
- ($x > 10) ? "big" ! "small" -- comparison returns boolean
557
- true ? "yes" ! "no" -- literal boolean
558
-
559
- Piped form (? alone uses $ as condition):
560
- true -> ? "yes" ! "no" -- pipe value must be boolean
561
- ($x > 5) -> ? "big" ! "small" -- expression evaluates to boolean
562
-
563
- Else-if chaining:
564
- .eq("A") ? 1 ! .eq("B") ? 2 ! 3 -- chained conditions *)
565
-
566
- conditional = body , "?" , body , [ "!" , else-clause ]
567
- | "?" , body , [ "!" , else-clause ] ; (* piped form: condition is $ *)
568
- else-clause = conditional | body ;
569
-
570
- (* Loop syntax: condition @ body [? condition]
571
- The @ operator is for while loops only. For iteration, use collection operators.
572
- Both while and do-while conditions MUST evaluate to boolean (true or false).
573
- Non-boolean values will cause a runtime error.
574
-
575
- - bool input: while loop (re-evaluates condition each iteration)
576
- - trailing ? condition: do-while (body first, then check)
577
-
578
- For-each iteration uses collection operators (each, map, filter, fold).
579
- See docs/collections.md for collection operators.
580
-
581
- Examples:
582
- ($x < 10) @ { ($x + 1) :> $x } -- while loop (condition is bool)
583
- @ { $ } ? ($x < 10) -- do-while (body, then check)
584
- [1, 2, 3] -> each { $ * 2 } -- for-each (use each operator)
585
- "abc" -> each { $ } -- iterate over string chars
586
- [a: 1, b: 2] -> each { $.key } -- iterate over dict entries
587
- $items -> each { process($) } -- iterate over variable *)
588
-
589
- loop = [ body ] , "@" , body , [ "?" , body ] ;
590
- (* First body: input (condition for while, iterable for for-each)
591
- Second body: loop body
592
- Third body (after ?): do-while condition *)
593
-
594
- (* ============================================================ *)
595
- (* COLLECTION OPERATORS *)
596
- (* ============================================================ *)
597
-
598
- (* Collection operators for iteration and reduction.
599
- See docs/collections.md for complete documentation.
600
-
601
- Operator comparison:
602
- - each: Sequential iteration, returns list of all results
603
- - map: Parallel iteration (Promise.all), returns list of all results
604
- - filter: Parallel filtering, returns elements where predicate is truthy
605
- - fold: Sequential reduction, returns final result only
606
-
607
- Body forms (all operators):
608
- - block: { body } -- $ is current element
609
- - grouped: ( expr ) -- $ is current element
610
- - closure: |x| body -- named parameter
611
- - variable: $fn -- pre-defined closure
612
- - identity: $ -- return elements unchanged
613
-
614
- Accumulator (each and fold only):
615
- - Block form: (init) { body } -- $@ is accumulator, $ is element
616
- - Closure: |x, acc = init| -- named accumulator parameter
617
-
618
- Dict iteration binds $ to { key, value } objects.
619
-
620
- Examples:
621
- [1, 2, 3] -> each { $ * 2 } -- [2, 4, 6]
622
- [1, 2, 3] -> map ($ + 10) -- [11, 12, 13]
623
- [1, 2, 3, 4, 5] -> filter { $ > 2 } -- [3, 4, 5]
624
- [1, 2, 3] -> fold(0) { $@ + $ } -- 6
625
- [1, 2, 3] -> each(0) { $@ + $ } -- [1, 3, 6] (running sum)
626
- [1, 2, 3] -> each |x, s = 0| ($s + $x) -- [1, 3, 6]
627
- *)
628
-
629
- iterator-body = closure | block | grouped-expr | postfix-expr | host-name ;
630
- (* Valid body forms for each, map, fold, filter operators.
631
- closure: |x| body or |x, acc = init| body
632
- block: { body } where $ is current element
633
- grouped-expr: ( expr ) where $ is current element
634
- postfix-expr: includes variables ($fn), bare $ for identity, and .method shorthand
635
- host-name: bare function name (func or ns::func), element passed as first arg
636
- Note: variable not listed separately since postfix-expr -> primary -> variable
637
- .method shorthand applies method to each element: ["a","b"] -> map .upper *)
638
-
639
- each-expr = "each" , [ "(" , expression , ")" ] , iterator-body ;
640
- (* Sequential iteration returning list of all body results.
641
- Optional (expression) provides initial accumulator value.
642
- With accumulator: $@ is accumulator, $ is current element.
643
- Supports break for early termination.
644
- [1, 2, 3] -> each { $ * 2 } -- [2, 4, 6]
645
- [1, 2, 3] -> each(0) { $@ + $ } -- [1, 3, 6] *)
646
-
647
- map-expr = "map" , iterator-body ;
648
- (* Parallel iteration returning list of all body results.
649
- Executes all iterations concurrently via Promise.all.
650
- Order is preserved despite parallel execution.
651
- No accumulator (parallel has no "previous" value).
652
- No break support.
653
- [1, 2, 3] -> map { $ * 2 } -- [2, 4, 6]
654
- $urls -> map |url| fetch($url) -- concurrent fetch *)
655
-
656
- fold-expr = "fold" , ( "(" , expression , ")" , iterator-body | closure | variable ) ;
657
- (* Sequential reduction returning final accumulated value.
658
- Requires accumulator (reduction semantics need initial value).
659
- Block form: (init) { body } where $@ is accumulator.
660
- Closure form: |x, acc = init| body.
661
- Variable form: $fn where closure defines accumulator.
662
- No break support.
663
- [1, 2, 3] -> fold(0) { $@ + $ } -- 6
664
- [1, 2, 3] -> fold |x, s = 0| ($s + $x) -- 6 *)
665
-
666
- filter-expr = "filter" , iterator-body ;
667
- (* Parallel filtering returning elements where predicate is true.
668
- Predicate MUST return boolean (true or false).
669
- Non-boolean values will cause a runtime error.
670
- Executes all predicates concurrently via Promise.all.
671
- Order is preserved despite parallel execution.
672
- No accumulator (each element evaluated independently).
673
- [1, 2, 3, 4, 5] -> filter { $ > 2 } -- [3, 4, 5]
674
- $users -> filter |u| ($u.active) -- active users (must be bool) *)
675
-
676
- block = "{" , statement , { statement } , "}" ;
677
- (* Block expression semantics depend on context:
678
-
679
- EXPRESSION POSITION (creates closure):
680
- - { body } :> $fn -- stored as closure
681
- - [key: { body }] -- dict value is closure
682
- - type({ body }) -- arg is closure
683
- Block creates ScriptCallable with implicit $ parameter.
684
-
685
- PIPE TARGET (creates then invokes):
686
- - value -> { body } -- creates closure, invokes with value
687
- Observable result identical to eager evaluation.
688
-
689
- SYNTACTIC BODY (eager evaluation):
690
- - each { } -- loop body
691
- - cond ? { } ! { } -- conditional branches
692
- - |x| { } -- closure body
693
- These use BlockNode as grammar body, not expression.
694
-
695
- VS GROUPED EXPRESSION:
696
- - ( expr ) -- eager evaluation
697
- - ( expr ) :> $x -- stores result value
698
- Parentheses always evaluate immediately.
699
-
700
- Empty blocks not allowed. Blocks return their last expression's value,
701
- enabling use in boolean contexts and as pipe sources. *)
702
-
703
- (* ============================================================ *)
704
- (* NOTE: BOOLEAN/LOGICAL OPERATORS *)
705
- (* ============================================================ *)
706
-
707
- (* Logical operators (&&, ||, !) are integrated into the main expression
708
- grammar (see value-expr above). They work everywhere expressions
709
- work — no special-casing needed.
710
-
711
- Precedence is standard: || < && < comparison < arithmetic < unary
712
-
713
- Examples:
714
- $a && $b -- logical and
715
- $x > 0 || $x < -10 -- comparison with logical or
716
- !$ready && $timeout -- negation with logical and
717
- ($a || $b) && $c -- grouped for precedence
718
- $valid && $ready ? "go" ! "wait" -- used as conditional *)
719
-
720
- (* ============================================================ *)
721
- (* ARITHMETIC EXPRESSIONS *)
722
- (* ============================================================ *)
723
-
724
- (* Arithmetic uses parenthesis grouping: ( expr )
725
- Operators follow standard precedence: * / % before + -
726
-
727
- (5 + 3) -> 8
728
- (2 + 3 * 4) -> 14
729
- ((2 + 3) * 4) -> 20
730
- 10 -> ($ + 5) -> 15 ($ is pipe value)
731
- 3 -> $x ($ * 2) -> 6 (variables in expressions)
732
- (10 / 0) -> ERROR (division by zero)
733
-
734
- Type constraint: all operands must be numbers.
735
- "5" -> ($ + 1) -> ERROR (string is not a number)
736
-
737
- Grouped expressions can appear:
738
- - As standalone expressions: (5 + 3)
739
- - As pipe targets: 5 -> ($ * 2)
740
- - Nested: ((1 + 2) * 3)
741
- - As closure bodies: |x| ($x + 1)
742
-
743
- Note: Arithmetic works everywhere now, not just in grouped expressions.
744
- 5 + 3, { 5 + 3 }, and (5 + 3) all work. Grouped expressions provide
745
- explicit scoping for captures. *)
746
-
747
- grouped-expr = "(" , pipe-chain , ")" ;
748
- (* Single-expression block with () delimiters.
749
- Uses the same parsing as any expression. *)
750
-
751
- (* ============================================================ *)
752
- (* LEXICAL *)
753
- (* ============================================================ *)
754
-
755
- identifier = ( letter | "_" ) , { letter | digit | "_" } ;
756
- (* Style: snake_case is idiomatic for identifiers.
757
- Functions: parse_json, replace_all, pad_start
758
- Variables: $user_name, $total_count, $api_response
759
- Methods: .starts_with, .ends_with, .index_of *)
760
-
761
- letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
762
- | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
763
- | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
764
- | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" ;
765
- digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
766
- newline = "\n" | "\r\n" ;
767
-
768
- (* ============================================================ *)
769
- (* WHITESPACE & COMMENTS *)
770
- (* ============================================================ *)
771
-
772
- (* Whitespace (spaces, tabs, newlines) between tokens is insignificant.
773
- Comments start with # and extend to end of line. *)