@rcrsr/rill 0.1.0 → 0.2.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 (260) hide show
  1. package/README.md +16 -8
  2. package/dist/check/config.d.ts +20 -0
  3. package/dist/check/config.d.ts.map +1 -0
  4. package/dist/check/config.js +151 -0
  5. package/dist/check/config.js.map +1 -0
  6. package/dist/check/fixer.d.ts +39 -0
  7. package/dist/check/fixer.d.ts.map +1 -0
  8. package/dist/check/fixer.js +119 -0
  9. package/dist/check/fixer.js.map +1 -0
  10. package/dist/check/index.d.ts +10 -0
  11. package/dist/check/index.d.ts.map +1 -0
  12. package/dist/check/index.js +21 -0
  13. package/dist/check/index.js.map +1 -0
  14. package/dist/check/rules/anti-patterns.d.ts +65 -0
  15. package/dist/check/rules/anti-patterns.d.ts.map +1 -0
  16. package/dist/check/rules/anti-patterns.js +427 -0
  17. package/dist/check/rules/anti-patterns.js.map +1 -0
  18. package/dist/check/rules/closures.d.ts +66 -0
  19. package/dist/check/rules/closures.d.ts.map +1 -0
  20. package/dist/check/rules/closures.js +373 -0
  21. package/dist/check/rules/closures.js.map +1 -0
  22. package/dist/check/rules/collections.d.ts +90 -0
  23. package/dist/check/rules/collections.d.ts.map +1 -0
  24. package/dist/check/rules/collections.js +373 -0
  25. package/dist/check/rules/collections.js.map +1 -0
  26. package/dist/check/rules/conditionals.d.ts +41 -0
  27. package/dist/check/rules/conditionals.d.ts.map +1 -0
  28. package/dist/check/rules/conditionals.js +106 -0
  29. package/dist/check/rules/conditionals.js.map +1 -0
  30. package/dist/check/rules/flow.d.ts +46 -0
  31. package/dist/check/rules/flow.d.ts.map +1 -0
  32. package/dist/check/rules/flow.js +206 -0
  33. package/dist/check/rules/flow.js.map +1 -0
  34. package/dist/check/rules/formatting.d.ts +133 -0
  35. package/dist/check/rules/formatting.d.ts.map +1 -0
  36. package/dist/check/rules/formatting.js +639 -0
  37. package/dist/check/rules/formatting.js.map +1 -0
  38. package/dist/check/rules/helpers.d.ts +26 -0
  39. package/dist/check/rules/helpers.d.ts.map +1 -0
  40. package/dist/check/rules/helpers.js +66 -0
  41. package/dist/check/rules/helpers.js.map +1 -0
  42. package/dist/check/rules/index.d.ts +21 -0
  43. package/dist/check/rules/index.d.ts.map +1 -0
  44. package/dist/check/rules/index.js +78 -0
  45. package/dist/check/rules/index.js.map +1 -0
  46. package/dist/check/rules/loops.d.ts +70 -0
  47. package/dist/check/rules/loops.d.ts.map +1 -0
  48. package/dist/check/rules/loops.js +227 -0
  49. package/dist/check/rules/loops.js.map +1 -0
  50. package/dist/check/rules/naming.d.ts +21 -0
  51. package/dist/check/rules/naming.d.ts.map +1 -0
  52. package/dist/check/rules/naming.js +167 -0
  53. package/dist/check/rules/naming.js.map +1 -0
  54. package/dist/check/rules/strings.d.ts +28 -0
  55. package/dist/check/rules/strings.d.ts.map +1 -0
  56. package/dist/check/rules/strings.js +80 -0
  57. package/dist/check/rules/strings.js.map +1 -0
  58. package/dist/check/rules/types.d.ts +41 -0
  59. package/dist/check/rules/types.d.ts.map +1 -0
  60. package/dist/check/rules/types.js +162 -0
  61. package/dist/check/rules/types.js.map +1 -0
  62. package/dist/check/types.d.ts +106 -0
  63. package/dist/check/types.d.ts.map +1 -0
  64. package/dist/check/types.js +6 -0
  65. package/dist/check/types.js.map +1 -0
  66. package/dist/check/validator.d.ts +18 -0
  67. package/dist/check/validator.d.ts.map +1 -0
  68. package/dist/check/validator.js +88 -0
  69. package/dist/check/validator.js.map +1 -0
  70. package/dist/check/visitor.d.ts +33 -0
  71. package/dist/check/visitor.d.ts.map +1 -0
  72. package/dist/check/visitor.js +243 -0
  73. package/dist/check/visitor.js.map +1 -0
  74. package/dist/cli-check.d.ts +43 -0
  75. package/dist/cli-check.d.ts.map +1 -0
  76. package/dist/cli-check.js +356 -0
  77. package/dist/cli-check.js.map +1 -0
  78. package/dist/cli-eval.d.ts +15 -0
  79. package/dist/cli-eval.d.ts.map +1 -0
  80. package/dist/cli-eval.js +120 -0
  81. package/dist/cli-eval.js.map +1 -0
  82. package/dist/cli-exec.d.ts +49 -0
  83. package/dist/cli-exec.d.ts.map +1 -0
  84. package/dist/cli-exec.js +191 -0
  85. package/dist/cli-exec.js.map +1 -0
  86. package/dist/cli-module-loader.d.ts +19 -0
  87. package/dist/cli-module-loader.d.ts.map +1 -0
  88. package/dist/cli-module-loader.js +83 -0
  89. package/dist/cli-module-loader.js.map +1 -0
  90. package/dist/cli-shared.d.ts +36 -0
  91. package/dist/cli-shared.d.ts.map +1 -0
  92. package/dist/cli-shared.js +101 -0
  93. package/dist/cli-shared.js.map +1 -0
  94. package/dist/cli.d.ts +2 -0
  95. package/dist/cli.d.ts.map +1 -1
  96. package/dist/cli.js +4 -11
  97. package/dist/cli.js.map +1 -1
  98. package/dist/index.d.ts +1 -1
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +1 -1
  101. package/dist/index.js.map +1 -1
  102. package/dist/lexer/readers.d.ts +1 -1
  103. package/dist/lexer/readers.d.ts.map +1 -1
  104. package/dist/lexer/readers.js +62 -32
  105. package/dist/lexer/readers.js.map +1 -1
  106. package/dist/lexer/tokenizer.d.ts.map +1 -1
  107. package/dist/lexer/tokenizer.js +5 -6
  108. package/dist/lexer/tokenizer.js.map +1 -1
  109. package/dist/parser/index.js +1 -1
  110. package/dist/parser/index.js.map +1 -1
  111. package/dist/parser/parser-expr.js +23 -5
  112. package/dist/parser/parser-expr.js.map +1 -1
  113. package/dist/parser/parser-functions.d.ts +2 -2
  114. package/dist/parser/parser-functions.d.ts.map +1 -1
  115. package/dist/parser/parser-functions.js +2 -1
  116. package/dist/parser/parser-functions.js.map +1 -1
  117. package/dist/parser/parser-literals.js +2 -2
  118. package/dist/parser/parser-literals.js.map +1 -1
  119. package/dist/parser/parser-script.js +9 -7
  120. package/dist/parser/parser-script.js.map +1 -1
  121. package/dist/parser/parser-variables.js +4 -3
  122. package/dist/parser/parser-variables.js.map +1 -1
  123. package/dist/runtime/core/callable.d.ts +5 -6
  124. package/dist/runtime/core/callable.d.ts.map +1 -1
  125. package/dist/runtime/core/callable.js.map +1 -1
  126. package/dist/runtime/core/context.d.ts.map +1 -1
  127. package/dist/runtime/core/context.js +19 -32
  128. package/dist/runtime/core/context.js.map +1 -1
  129. package/dist/runtime/core/equals.js +1 -1
  130. package/dist/runtime/core/equals.js.map +1 -1
  131. package/dist/runtime/core/eval/evaluator.d.ts +78 -0
  132. package/dist/runtime/core/eval/evaluator.d.ts.map +1 -1
  133. package/dist/runtime/core/eval/evaluator.js +78 -0
  134. package/dist/runtime/core/eval/evaluator.js.map +1 -1
  135. package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -1
  136. package/dist/runtime/core/eval/mixins/closures.js +9 -1
  137. package/dist/runtime/core/eval/mixins/closures.js.map +1 -1
  138. package/dist/runtime/core/eval/mixins/variables.d.ts.map +1 -1
  139. package/dist/runtime/core/eval/mixins/variables.js +143 -2
  140. package/dist/runtime/core/eval/mixins/variables.js.map +1 -1
  141. package/dist/runtime/core/types.d.ts +15 -2
  142. package/dist/runtime/core/types.d.ts.map +1 -1
  143. package/dist/runtime/core/types.js.map +1 -1
  144. package/dist/runtime/ext/extensions.d.ts +51 -0
  145. package/dist/runtime/ext/extensions.d.ts.map +1 -0
  146. package/dist/runtime/ext/extensions.js +67 -0
  147. package/dist/runtime/ext/extensions.js.map +1 -0
  148. package/dist/runtime/index.d.ts +3 -0
  149. package/dist/runtime/index.d.ts.map +1 -1
  150. package/dist/runtime/index.js +1 -0
  151. package/dist/runtime/index.js.map +1 -1
  152. package/dist/types.d.ts +8 -4
  153. package/dist/types.d.ts.map +1 -1
  154. package/dist/types.js +5 -4
  155. package/dist/types.js.map +1 -1
  156. package/docs/00_INDEX.md +1 -0
  157. package/docs/01_guide.md +3 -3
  158. package/docs/02_types.md +8 -10
  159. package/docs/03_variables.md +10 -0
  160. package/docs/04_operators.md +3 -3
  161. package/docs/05_control-flow.md +21 -0
  162. package/docs/07_collections.md +2 -0
  163. package/docs/10_parsing.md +9 -9
  164. package/docs/11_reference.md +1 -1
  165. package/docs/12_examples.md +36 -62
  166. package/docs/14_host-integration.md +116 -111
  167. package/docs/15_grammar.ebnf +1 -5
  168. package/docs/16_conventions.md +3 -4
  169. package/docs/17_cli-tools.md +184 -0
  170. package/docs/99_llm-reference.txt +46 -5
  171. package/package.json +13 -4
  172. package/dist/demo.d.ts +0 -6
  173. package/dist/demo.d.ts.map +0 -1
  174. package/dist/demo.js +0 -121
  175. package/dist/demo.js.map +0 -1
  176. package/dist/lexer.d.ts +0 -19
  177. package/dist/lexer.d.ts.map +0 -1
  178. package/dist/lexer.js +0 -344
  179. package/dist/lexer.js.map +0 -1
  180. package/dist/parser/arithmetic.d.ts +0 -16
  181. package/dist/parser/arithmetic.d.ts.map +0 -1
  182. package/dist/parser/arithmetic.js +0 -128
  183. package/dist/parser/arithmetic.js.map +0 -1
  184. package/dist/parser/boolean.d.ts +0 -15
  185. package/dist/parser/boolean.d.ts.map +0 -1
  186. package/dist/parser/boolean.js +0 -20
  187. package/dist/parser/boolean.js.map +0 -1
  188. package/dist/parser/control-flow.d.ts +0 -56
  189. package/dist/parser/control-flow.d.ts.map +0 -1
  190. package/dist/parser/control-flow.js +0 -167
  191. package/dist/parser/control-flow.js.map +0 -1
  192. package/dist/parser/expressions.d.ts +0 -23
  193. package/dist/parser/expressions.d.ts.map +0 -1
  194. package/dist/parser/expressions.js +0 -950
  195. package/dist/parser/expressions.js.map +0 -1
  196. package/dist/parser/extraction.d.ts +0 -48
  197. package/dist/parser/extraction.d.ts.map +0 -1
  198. package/dist/parser/extraction.js +0 -279
  199. package/dist/parser/extraction.js.map +0 -1
  200. package/dist/parser/functions.d.ts +0 -20
  201. package/dist/parser/functions.d.ts.map +0 -1
  202. package/dist/parser/functions.js +0 -96
  203. package/dist/parser/functions.js.map +0 -1
  204. package/dist/parser/literals.d.ts +0 -37
  205. package/dist/parser/literals.d.ts.map +0 -1
  206. package/dist/parser/literals.js +0 -373
  207. package/dist/parser/literals.js.map +0 -1
  208. package/dist/parser/script.d.ts +0 -14
  209. package/dist/parser/script.d.ts.map +0 -1
  210. package/dist/parser/script.js +0 -196
  211. package/dist/parser/script.js.map +0 -1
  212. package/dist/parser/variables.d.ts +0 -10
  213. package/dist/parser/variables.d.ts.map +0 -1
  214. package/dist/parser/variables.js +0 -215
  215. package/dist/parser/variables.js.map +0 -1
  216. package/dist/runtime/ast-equals.d.ts +0 -13
  217. package/dist/runtime/ast-equals.d.ts.map +0 -1
  218. package/dist/runtime/ast-equals.js +0 -447
  219. package/dist/runtime/ast-equals.js.map +0 -1
  220. package/dist/runtime/builtins.d.ts +0 -13
  221. package/dist/runtime/builtins.d.ts.map +0 -1
  222. package/dist/runtime/builtins.js +0 -180
  223. package/dist/runtime/builtins.js.map +0 -1
  224. package/dist/runtime/callable.d.ts +0 -88
  225. package/dist/runtime/callable.d.ts.map +0 -1
  226. package/dist/runtime/callable.js +0 -98
  227. package/dist/runtime/callable.js.map +0 -1
  228. package/dist/runtime/context.d.ts +0 -13
  229. package/dist/runtime/context.d.ts.map +0 -1
  230. package/dist/runtime/context.js +0 -73
  231. package/dist/runtime/context.js.map +0 -1
  232. package/dist/runtime/core/evaluate.d.ts +0 -42
  233. package/dist/runtime/core/evaluate.d.ts.map +0 -1
  234. package/dist/runtime/core/evaluate.debug.js +0 -1251
  235. package/dist/runtime/core/evaluate.js +0 -1913
  236. package/dist/runtime/core/evaluate.js.map +0 -1
  237. package/dist/runtime/evaluate.d.ts +0 -32
  238. package/dist/runtime/evaluate.d.ts.map +0 -1
  239. package/dist/runtime/evaluate.js +0 -1111
  240. package/dist/runtime/evaluate.js.map +0 -1
  241. package/dist/runtime/execute.d.ts +0 -26
  242. package/dist/runtime/execute.d.ts.map +0 -1
  243. package/dist/runtime/execute.js +0 -121
  244. package/dist/runtime/execute.js.map +0 -1
  245. package/dist/runtime/signals.d.ts +0 -19
  246. package/dist/runtime/signals.d.ts.map +0 -1
  247. package/dist/runtime/signals.js +0 -26
  248. package/dist/runtime/signals.js.map +0 -1
  249. package/dist/runtime/types.d.ts +0 -169
  250. package/dist/runtime/types.d.ts.map +0 -1
  251. package/dist/runtime/types.js +0 -50
  252. package/dist/runtime/types.js.map +0 -1
  253. package/dist/runtime/values.d.ts +0 -50
  254. package/dist/runtime/values.d.ts.map +0 -1
  255. package/dist/runtime/values.js +0 -209
  256. package/dist/runtime/values.js.map +0 -1
  257. package/dist/runtime.d.ts +0 -254
  258. package/dist/runtime.d.ts.map +0 -1
  259. package/dist/runtime.js +0 -2014
  260. package/dist/runtime.js.map +0 -1
@@ -15,9 +15,11 @@ const source = `
15
15
  const ast = parse(source);
16
16
  const ctx = createRuntimeContext({
17
17
  functions: {
18
- prompt: async (args) => {
19
- const text = String(args[0]);
20
- return await callYourLLM(text);
18
+ prompt: {
19
+ params: [{ name: 'text', type: 'string' }],
20
+ fn: async (args) => {
21
+ return await callYourLLM(args[0]);
22
+ },
21
23
  },
22
24
  },
23
25
  });
@@ -33,7 +35,7 @@ The `createRuntimeContext()` function accepts these options:
33
35
  | Option | Type | Description |
34
36
  |--------|------|-------------|
35
37
  | `variables` | `Record<string, RillValue>` | Initial variables accessible as `$name` |
36
- | `functions` | `Record<string, CallableFn \| HostFunctionDefinition>` | Custom functions callable as `name()` |
38
+ | `functions` | `Record<string, HostFunctionDefinition>` | Custom functions callable as `name()` |
37
39
  | `callbacks` | `Partial<RuntimeCallbacks>` | I/O callbacks (e.g., `onLog`) |
38
40
  | `observability` | `ObservabilityCallbacks` | Execution monitoring hooks |
39
41
  | `timeout` | `number` | Timeout in ms for async functions |
@@ -51,18 +53,24 @@ Host functions must follow these rules to ensure correct script behavior:
51
53
  ```typescript
52
54
  // WRONG: Mutates input array
53
55
  functions: {
54
- addItem: (args) => {
55
- const list = args[0] as unknown[];
56
- list.push('new'); // DON'T DO THIS
57
- return list;
56
+ addItem: {
57
+ params: [{ name: 'list', type: 'list' }],
58
+ fn: (args) => {
59
+ const list = args[0] as unknown[];
60
+ list.push('new'); // DON'T DO THIS
61
+ return list;
62
+ },
58
63
  },
59
64
  }
60
65
 
61
66
  // CORRECT: Return new value
62
67
  functions: {
63
- addItem: (args) => {
64
- const list = args[0] as unknown[];
65
- return [...list, 'new']; // Create new array
68
+ addItem: {
69
+ params: [{ name: 'list', type: 'list' }],
70
+ fn: (args) => {
71
+ const list = args[0] as unknown[];
72
+ return [...list, 'new']; // Create new array
73
+ },
66
74
  },
67
75
  }
68
76
  ```
@@ -75,9 +83,12 @@ For maximum safety, consider freezing values passed to host functions:
75
83
  import { deepFreeze } from './utils'; // Your utility
76
84
 
77
85
  functions: {
78
- process: (args) => {
79
- const frozen = deepFreeze(args[0]);
80
- return transform(frozen); // Any mutation throws
86
+ process: {
87
+ params: [{ name: 'input', type: 'string' }],
88
+ fn: (args) => {
89
+ const frozen = deepFreeze(args[0]);
90
+ return transform(frozen); // Any mutation throws
91
+ },
81
92
  },
82
93
  }
83
94
  ```
@@ -96,31 +107,40 @@ Functions are called by name: `functionName(arg1, arg2)`.
96
107
  const ctx = createRuntimeContext({
97
108
  functions: {
98
109
  // Sync function
99
- add: (args) => {
100
- const a = typeof args[0] === 'number' ? args[0] : 0;
101
- const b = typeof args[1] === 'number' ? args[1] : 0;
102
- return a + b;
110
+ add: {
111
+ params: [
112
+ { name: 'a', type: 'number' },
113
+ { name: 'b', type: 'number' },
114
+ ],
115
+ fn: (args) => args[0] + args[1],
103
116
  },
104
117
 
105
118
  // Async function
106
- fetch: async (args, ctx, location) => {
107
- const url = String(args[0]);
108
- const response = await fetch(url);
109
- return await response.text();
119
+ fetch: {
120
+ params: [{ name: 'url', type: 'string' }],
121
+ fn: async (args, ctx, location) => {
122
+ const response = await fetch(args[0]);
123
+ return await response.text();
124
+ },
110
125
  },
111
126
 
112
127
  // Function with context access
113
- getVar: (args, ctx) => {
114
- const name = String(args[0]);
115
- return ctx.variables.get(name) ?? null;
128
+ getVar: {
129
+ params: [{ name: 'name', type: 'string' }],
130
+ fn: (args, ctx) => {
131
+ return ctx.variables.get(args[0]) ?? null;
132
+ },
116
133
  },
117
134
 
118
135
  // Function with location for error reporting
119
- validate: (args, ctx, location) => {
120
- if (!args[0]) {
121
- throw new Error(`Validation failed at line ${location?.line}`);
122
- }
123
- return args[0];
136
+ validate: {
137
+ params: [{ name: 'value', type: 'string' }],
138
+ fn: (args, ctx, location) => {
139
+ if (!args[0]) {
140
+ throw new Error(`Validation failed at line ${location?.line}`);
141
+ }
142
+ return args[0];
143
+ },
124
144
  },
125
145
  },
126
146
  });
@@ -134,14 +154,41 @@ Use `::` to organize functions into namespaces:
134
154
  const ctx = createRuntimeContext({
135
155
  functions: {
136
156
  // Namespaced functions use :: separator
137
- 'math::add': (args) => (args[0] as number) + (args[1] as number),
138
- 'math::multiply': (args) => (args[0] as number) * (args[1] as number),
139
- 'str::upper': (args) => String(args[0]).toUpperCase(),
140
- 'str::lower': (args) => String(args[0]).toLowerCase(),
157
+ 'math::add': {
158
+ params: [
159
+ { name: 'a', type: 'number' },
160
+ { name: 'b', type: 'number' },
161
+ ],
162
+ fn: (args) => args[0] + args[1],
163
+ },
164
+ 'math::multiply': {
165
+ params: [
166
+ { name: 'a', type: 'number' },
167
+ { name: 'b', type: 'number' },
168
+ ],
169
+ fn: (args) => args[0] * args[1],
170
+ },
171
+ 'str::upper': {
172
+ params: [{ name: 'text', type: 'string' }],
173
+ fn: (args) => args[0].toUpperCase(),
174
+ },
175
+ 'str::lower': {
176
+ params: [{ name: 'text', type: 'string' }],
177
+ fn: (args) => args[0].toLowerCase(),
178
+ },
141
179
 
142
180
  // Multi-level namespaces
143
- 'io::file::read': async (args) => fs.readFile(String(args[0]), 'utf-8'),
144
- 'io::file::write': async (args) => fs.writeFile(String(args[0]), String(args[1])),
181
+ 'io::file::read': {
182
+ params: [{ name: 'path', type: 'string' }],
183
+ fn: async (args) => fs.readFile(args[0], 'utf-8'),
184
+ },
185
+ 'io::file::write': {
186
+ params: [
187
+ { name: 'path', type: 'string' },
188
+ { name: 'content', type: 'string' },
189
+ ],
190
+ fn: async (args) => fs.writeFile(args[0], args[1]),
191
+ },
145
192
  },
146
193
  });
147
194
  ```
@@ -159,6 +206,8 @@ Namespaces help organize host APIs and avoid name collisions without requiring t
159
206
 
160
207
  ### CallableFn Signature
161
208
 
209
+ The `fn` property in `HostFunctionDefinition` uses the `CallableFn` type:
210
+
162
211
  ```typescript
163
212
  type CallableFn = (
164
213
  args: RillValue[],
@@ -169,17 +218,17 @@ type CallableFn = (
169
218
 
170
219
  | Parameter | Description |
171
220
  |-----------|-------------|
172
- | `args` | Positional arguments passed to the function |
221
+ | `args` | Positional arguments passed to the function (already validated against `params`) |
173
222
  | `ctx` | Runtime context with variables, pipeValue, etc. |
174
223
  | `location` | Source location of the call site (for error reporting) |
175
224
 
176
- ## Typed Host Functions
225
+ ## Host Function Type Declarations
177
226
 
178
- Host functions can declare parameter types and defaults. The runtime validates arguments before calling your function, eliminating manual type checking.
227
+ All host functions must declare parameter types and optional defaults using the `HostFunctionDefinition` interface. The runtime validates arguments before calling your function, eliminating manual type checking.
179
228
 
180
- ### Basic Type Declarations
229
+ ### Parameter Type Declarations
181
230
 
182
- Declare parameter types using the `HostFunctionDefinition` interface:
231
+ Declare parameter types in the `params` array:
183
232
 
184
233
  ```typescript
185
234
  const ctx = createRuntimeContext({
@@ -254,62 +303,6 @@ Error details include:
254
303
  - Expected type
255
304
  - Actual type received
256
305
 
257
- ### Migration from Untyped Functions
258
-
259
- Typed function declarations are optional. Existing untyped functions continue working without changes.
260
-
261
- **Before (manual validation):**
262
-
263
- ```typescript
264
- functions: {
265
- repeat: (args) => {
266
- const str = typeof args[0] === 'string' ? args[0] : '';
267
- const count = typeof args[1] === 'number' ? args[1] : 1;
268
- return str.repeat(count);
269
- },
270
- }
271
- ```
272
-
273
- **After (declarative validation):**
274
-
275
- ```typescript
276
- functions: {
277
- repeat: {
278
- params: [
279
- { name: 'str', type: 'string' },
280
- { name: 'count', type: 'number', defaultValue: 1 },
281
- ],
282
- fn: (args) => args[0].repeat(args[1]), // Types guaranteed
283
- },
284
- }
285
- ```
286
-
287
- Benefits of migration:
288
-
289
- - Eliminates manual type checking code
290
- - Provides clear error messages to script authors
291
- - Documents expected types in function signature
292
- - Reduces bugs from incorrect type coercion
293
-
294
- ### Mixed Function Definitions
295
-
296
- You can mix typed and untyped functions in the same context:
297
-
298
- ```typescript
299
- const ctx = createRuntimeContext({
300
- functions: {
301
- // Untyped (legacy)
302
- 'legacy::func': (args) => args[0],
303
-
304
- // Typed (new)
305
- 'typed::func': {
306
- params: [{ name: 'x', type: 'string' }],
307
- fn: (args) => args[0],
308
- },
309
- },
310
- });
311
- ```
312
-
313
306
  ### HostFunctionDefinition Interface
314
307
 
315
308
  ```typescript
@@ -439,9 +432,12 @@ const controller = new AbortController();
439
432
  const ctx = createRuntimeContext({
440
433
  signal: controller.signal,
441
434
  functions: {
442
- longTask: async () => {
443
- await new Promise((r) => setTimeout(r, 10000));
444
- return 'done';
435
+ longTask: {
436
+ params: [],
437
+ fn: async () => {
438
+ await new Promise((r) => setTimeout(r, 10000));
439
+ return 'done';
440
+ },
445
441
  },
446
442
  },
447
443
  });
@@ -620,10 +616,13 @@ Set a timeout for async operations:
620
616
  const ctx = createRuntimeContext({
621
617
  timeout: 30000, // 30 seconds
622
618
  functions: {
623
- slowOperation: async () => {
624
- // Will throw TimeoutError if exceeds 30s
625
- await longRunningTask();
626
- return 'done';
619
+ slowOperation: {
620
+ params: [],
621
+ fn: async () => {
622
+ // Will throw TimeoutError if exceeds 30s
623
+ await longRunningTask();
624
+ return 'done';
625
+ },
627
626
  },
628
627
  },
629
628
  });
@@ -640,10 +639,13 @@ const ctx = createRuntimeContext({
640
639
  'FATAL', // Matches "FATAL" anywhere
641
640
  ],
642
641
  functions: {
643
- process: (args) => {
644
- // If this returns "error: invalid input",
645
- // execution halts with AutoExceptionError
646
- return externalProcess(args[0]);
642
+ process: {
643
+ params: [{ name: 'input', type: 'string' }],
644
+ fn: (args) => {
645
+ // If this returns "error: invalid input",
646
+ // execution halts with AutoExceptionError
647
+ return externalProcess(args[0]);
648
+ },
647
649
  },
648
650
  },
649
651
  });
@@ -749,9 +751,12 @@ const ctx = createRuntimeContext({
749
751
  },
750
752
 
751
753
  functions: {
752
- prompt: async (args, ctx, location) => {
753
- console.log(`[prompt at line ${location?.line}]`);
754
- return await callLLM(String(args[0]));
754
+ prompt: {
755
+ params: [{ name: 'text', type: 'string' }],
756
+ fn: async (args, ctx, location) => {
757
+ console.log(`[prompt at line ${location?.line}]`);
758
+ return await callLLM(args[0]);
759
+ },
755
760
  },
756
761
  },
757
762
 
@@ -188,7 +188,7 @@ body = block | grouped-expr | postfix-expr ;
188
188
  closure = "|" , [ closure-param , { "," , closure-param } ] , "|" , body ;
189
189
 
190
190
  string = '"' , { char | escape | brace-escape | interpolation } , '"'
191
- | "<<" , delimiter , newline , heredoc-body , delimiter ;
191
+ | '"""' , { char | escape | brace-escape | interpolation } , '"""' ;
192
192
 
193
193
  char = letter | digit | " " | "!" | "#" | "$" | "%" | "&" | "'" | "(" | ")"
194
194
  | "*" | "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | "=" | ">"
@@ -196,10 +196,6 @@ char = letter | digit | " " | "!" | "#" | "$" | "%" | "&" | "'" | "(" |
196
196
  (* excludes " \ { } which have special meaning in strings *)
197
197
  escape = "\\" , ( "n" | "r" | "t" | "\\" | '"' ) ;
198
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
199
 
204
200
  interpolation = "{" , expression , "}" ;
205
201
 
@@ -318,16 +318,15 @@ parseJson($input):dict :> $data
318
318
 
319
319
  ## String Handling
320
320
 
321
- ### Use heredocs for multiline content
321
+ ### Use triple-quotes for multiline content
322
322
 
323
323
  ```text
324
- prompt(<<EOF
324
+ """
325
325
  Analyze this content:
326
326
  {$content}
327
327
 
328
328
  Provide a summary.
329
- EOF
330
- )
329
+ """
331
330
  ```
332
331
 
333
332
  ### Use .empty for emptiness checks
@@ -0,0 +1,184 @@
1
+ # CLI Tools
2
+
3
+ rill ships three command-line tools for running and validating scripts.
4
+
5
+ ## rill-exec
6
+
7
+ Execute a rill script file with arguments.
8
+
9
+ ```text
10
+ rill-exec <script.rill> [args...]
11
+ rill-exec - # Read from stdin
12
+ rill-exec --help
13
+ rill-exec --version
14
+ ```
15
+
16
+ ### Arguments
17
+
18
+ Positional arguments pass to the script as a string list in `$` (pipe value).
19
+
20
+ ```bash
21
+ rill-exec greet.rill alice bob
22
+ # Inside script: $ == ["alice", "bob"]
23
+ ```
24
+
25
+ ### Stdin
26
+
27
+ Use `-` to read a script from standard input:
28
+
29
+ ```bash
30
+ echo 'log("hello")' | rill-exec -
31
+ ```
32
+
33
+ ### Frontmatter Modules
34
+
35
+ Scripts with `use:` frontmatter load modules before execution:
36
+
37
+ ```text
38
+ ---
39
+ use:
40
+ - utils: ./lib/utils.rill
41
+ ---
42
+
43
+ $utils.helper("input")
44
+ ```
45
+
46
+ ### Exit Codes
47
+
48
+ | Return Value | Exit Code |
49
+ |-------------|-----------|
50
+ | `true` or non-empty string | 0 |
51
+ | `false` or empty string | 1 |
52
+ | `[0, "message"]` | 0 (prints message) |
53
+ | `[1, "message"]` | 1 (prints message) |
54
+
55
+ ## rill-eval
56
+
57
+ Evaluate a single rill expression. No file context or module loading.
58
+
59
+ ```text
60
+ rill-eval <expression>
61
+ rill-eval --help
62
+ rill-eval --version
63
+ ```
64
+
65
+ ### Examples
66
+
67
+ ```bash
68
+ rill-eval '"hello".len'
69
+ # 5
70
+
71
+ rill-eval '5 + 3'
72
+ # 8
73
+
74
+ rill-eval '[1, 2, 3] -> map |x|($x * 2)'
75
+ # [2, 4, 6]
76
+ ```
77
+
78
+ ## rill-check
79
+
80
+ Static analysis tool that validates rill scripts against lint rules.
81
+
82
+ ```text
83
+ rill-check [options] <file>
84
+ ```
85
+
86
+ ### Options
87
+
88
+ | Flag | Description |
89
+ |------|-------------|
90
+ | `--fix` | Apply automatic fixes to the source file |
91
+ | `--format text` | Human-readable output (default) |
92
+ | `--format json` | Machine-readable JSON output |
93
+ | `--verbose` | Include rule category in JSON output |
94
+ | `-h`, `--help` | Show help |
95
+ | `-v`, `--version` | Show version |
96
+
97
+ ### Exit Codes
98
+
99
+ | Code | Meaning |
100
+ |------|---------|
101
+ | 0 | No issues found |
102
+ | 1 | Diagnostics reported (or argument error) |
103
+ | 2 | File not found or unreadable |
104
+ | 3 | Parse error in source file |
105
+
106
+ ### Output Formats
107
+
108
+ **Text** (default): one line per diagnostic.
109
+
110
+ ```text
111
+ script.rill:5:3: warning: message (RULE_CODE)
112
+ ```
113
+
114
+ **JSON**: structured output with summary.
115
+
116
+ ```json
117
+ {
118
+ "file": "script.rill",
119
+ "errors": [
120
+ {
121
+ "location": { "line": 5, "column": 3, "offset": 42 },
122
+ "severity": "warning",
123
+ "code": "RULE_CODE",
124
+ "message": "description"
125
+ }
126
+ ],
127
+ "summary": { "total": 1, "errors": 0, "warnings": 1, "info": 0 }
128
+ }
129
+ ```
130
+
131
+ ### Configuration
132
+
133
+ Place a `.rill-check.json` file in the project root to configure rules:
134
+
135
+ ```json
136
+ {
137
+ "rules": {
138
+ "NAMING_SNAKE_CASE": "on",
139
+ "SPACING_OPERATOR": "off",
140
+ "COMPLEX_CONDITION": "warn"
141
+ },
142
+ "severity": {
143
+ "AVOID_REASSIGNMENT": "error"
144
+ }
145
+ }
146
+ ```
147
+
148
+ Rule states: `"on"` (enabled), `"off"` (disabled), `"warn"` (downgrade to warning).
149
+
150
+ ### Lint Rules
151
+
152
+ | Code | Category | Default | Description |
153
+ |------|----------|---------|-------------|
154
+ | `NAMING_SNAKE_CASE` | naming | error | Variable names must use snake_case |
155
+ | `AVOID_REASSIGNMENT` | anti-patterns | warning | Avoid reassigning captured variables |
156
+ | `COMPLEX_CONDITION` | anti-patterns | info | Condition expression is complex |
157
+ | `LOOP_OUTER_CAPTURE` | anti-patterns | warning | Loop body captures to outer variable |
158
+ | `USE_EMPTY_METHOD` | strings | warning | Use `.empty` instead of `.len == 0` |
159
+ | `UNNECESSARY_ASSERTION` | types | info | Type assertion on a literal value |
160
+ | `VALIDATE_EXTERNAL` | types | info | External data lacks type validation |
161
+ | `CAPTURE_INLINE_CHAIN` | flow | info | Capture breaks a pipe chain |
162
+ | `CAPTURE_BEFORE_BRANCH` | flow | info | Capture value before branching |
163
+ | `LOOP_ACCUMULATOR` | loops | info | Use accumulator `$@` pattern |
164
+ | `PREFER_DO_WHILE` | loops | info | Consider do-while for init-then-loop |
165
+ | `USE_EACH` | loops | info | Use `each` instead of while loop |
166
+ | `BREAK_IN_PARALLEL` | collections | error | `break` inside `map` or `filter` |
167
+ | `PREFER_MAP` | collections | info | Use `map` when body has no side effects |
168
+ | `FOLD_INTERMEDIATES` | collections | info | `fold` discards intermediate results |
169
+ | `FILTER_NEGATION` | collections | warning | Negated filter condition |
170
+ | `METHOD_SHORTHAND` | collections | info | Use method reference shorthand |
171
+ | `USE_DEFAULT_OPERATOR` | conditionals | info | Use `??` instead of conditional |
172
+ | `CONDITION_TYPE` | conditionals | warning | Condition not boolean |
173
+ | `CLOSURE_BARE_DOLLAR` | closures | warning | Stored closure uses bare `$` |
174
+ | `CLOSURE_BRACES` | closures | info | Multi-statement closure needs braces |
175
+ | `CLOSURE_LATE_BINDING` | closures | warning | Closure captures late-bound variable |
176
+ | `SPACING_OPERATOR` | formatting | info | Operators need surrounding spaces |
177
+ | `SPACING_BRACES` | formatting | info | Braces need inner spacing |
178
+ | `SPACING_BRACKETS` | formatting | info | Brackets need consistent spacing |
179
+ | `SPACING_CLOSURE` | formatting | info | Closure params need spacing |
180
+ | `INDENT_CONTINUATION` | formatting | info | Continuation line indentation |
181
+ | `IMPLICIT_DOLLAR_METHOD` | formatting | info | Prefer implicit `$` for methods |
182
+ | `IMPLICIT_DOLLAR_FUNCTION` | formatting | info | Prefer implicit `$` for functions |
183
+ | `IMPLICIT_DOLLAR_CLOSURE` | formatting | info | Prefer implicit `$` for closures |
184
+ | `THROWAWAY_CAPTURE` | formatting | info | Captured variable never used |
@@ -67,10 +67,17 @@ CRITICAL DIFFERENCES FROM MAINSTREAM LANGUAGES
67
67
  "hello" :> $x
68
68
  42 :> $x # ERROR: cannot assign number to string variable
69
69
 
70
- 5. NO VARIABLE SHADOWING
70
+ 5. NO VARIABLE SHADOWING (CRITICAL FOR LOOPS)
71
71
  Child scopes can READ parent variables but cannot WRITE or redeclare them.
72
72
  Variables created inside blocks/loops do NOT leak out.
73
73
 
74
+ WRONG - this pattern NEVER works:
75
+ 0 :> $count
76
+ [1, 2, 3] -> each { $count + 1 :> $count } # creates LOCAL $count
77
+ $count # still 0!
78
+
79
+ RIGHT - use $ or $@ as state carrier (see LOOP STATE PATTERNS below)
80
+
74
81
  6. NO EXCEPTIONS
75
82
  Errors halt execution. No try/catch. Use conditionals for error handling.
76
83
  Script-level exit functions (error, stop) must be host-provided.
@@ -80,7 +87,7 @@ SYNTAX QUICK REFERENCE
80
87
 
81
88
  Variables: $name (always prefixed with $)
82
89
  Strings: "hello {$var}" # interpolation with {}
83
- <<EOF ... EOF # heredoc (also interpolates)
90
+ """...""" # multiline (also interpolates)
84
91
  Numbers: 42, 3.14, -7
85
92
  Booleans: true, false
86
93
  Lists: [1, 2, 3]
@@ -118,10 +125,10 @@ Conditional (if-else):
118
125
  Piped conditional ($ becomes condition):
119
126
  value -> ? then_expr ! else_expr
120
127
 
121
- While loop:
128
+ Condition loop (NO "while" keyword - use @ operator):
122
129
  init_value -> ($ < 10) @ { $ + 1 } # $ is accumulator
123
130
 
124
- Do-while loop (body runs at least once):
131
+ Do-condition loop (body runs at least once):
125
132
  init_value -> @ { $ + 1 } ? ($ < 10)
126
133
 
127
134
  Break (exits loop, returns collected results before break):
@@ -131,6 +138,39 @@ Return (exits block or script with value):
131
138
  { 5 :> $x; ($x > 3) ? ("big" -> return); "small" } # returns "big"
132
139
  "done" -> return # exits script with "done"
133
140
 
141
+ LOOP STATE PATTERNS (CRITICAL)
142
+ ------------------------------
143
+ Rill loops CANNOT modify outer variables. All state must flow through $ or $@.
144
+
145
+ WRONG - outer variable modification (NEVER works):
146
+ 0 :> $sum
147
+ [1, 2, 3] -> each { $sum + $ :> $sum } # $sum unchanged!
148
+
149
+ WRONG - "while" keyword does not exist:
150
+ while ($i < 10) { $i + 1 :> $i } # SYNTAX ERROR
151
+
152
+ RIGHT - use fold for reduction:
153
+ [1, 2, 3] -> fold(0) { $@ + $ } # 6 ($@ is accumulator)
154
+
155
+ RIGHT - use each(init) when you need both results AND accumulator:
156
+ [1, 2, 3] -> each(0) { $@ + $ } # [1, 3, 6] (running totals)
157
+
158
+ RIGHT - use (cond) @ { } with $ as state dict for multiple values:
159
+ [iter: 0, max: 3, text: $input, done: false]
160
+ -> (!$.done && $.iter < $.max) @ {
161
+ $.iter + 1 :> $i
162
+ process($.text) :> $result
163
+ $result.finished ? [iter: $i, max: $.max, text: $.text, done: true]
164
+ ! [iter: $i, max: $.max, text: $result.text, done: false]
165
+ }
166
+ # Access final state: $.text, $.iter
167
+
168
+ Pattern summary:
169
+ - Single value accumulation -> fold(init) { $@ + $ }
170
+ - Per-item results + running -> each(init) { ... $@ ... }
171
+ - Multiple state values / while -> (cond) @ { } with $ as state dict
172
+ - "while" and "for" keywords -> DO NOT EXIST
173
+
134
174
  COLLECTION OPERATORS
135
175
  --------------------
136
176
 
@@ -286,10 +326,11 @@ COMMON MISTAKES
286
326
  2. Using || for defaults -> use ?? instead
287
327
  3. Assuming truthiness -> explicit boolean checks required
288
328
  4. Breaking from map/filter -> only works in each/fold
289
- 5. Expecting variable shadowing -> not allowed
329
+ 5. Modifying outer vars in loops -> use fold/$@ or $ as state dict (see LOOP STATE PATTERNS)
290
330
  6. Expecting variables to leak -> block scope is strict
291
331
  7. Forgetting () on methods -> .upper() not .upper (unless property)
292
332
  8. Reassigning different type -> variables lock to first type
333
+ 9. Using while/for keywords -> use (cond) @ { } or -> each { } instead
293
334
 
294
335
  SCRIPT RETURN VALUES
295
336
  --------------------