@rcrsr/rill 0.16.0 → 0.17.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 (57) hide show
  1. package/README.md +37 -21
  2. package/dist/ext/crypto/index.d.ts +3 -3
  3. package/dist/ext/crypto/index.js +61 -58
  4. package/dist/ext/exec/index.d.ts +3 -3
  5. package/dist/ext/exec/index.js +14 -8
  6. package/dist/ext/fetch/index.d.ts +3 -3
  7. package/dist/ext/fetch/index.js +16 -11
  8. package/dist/ext/fs/index.d.ts +3 -3
  9. package/dist/ext/fs/index.js +242 -239
  10. package/dist/ext/kv/index.d.ts +3 -3
  11. package/dist/ext/kv/index.js +197 -195
  12. package/dist/ext/kv/store.js +2 -1
  13. package/dist/ext-parse-bridge.d.ts +10 -0
  14. package/dist/ext-parse-bridge.js +10 -0
  15. package/dist/generated/introspection-data.d.ts +1 -1
  16. package/dist/generated/introspection-data.js +385 -296
  17. package/dist/generated/version-data.d.ts +1 -1
  18. package/dist/generated/version-data.js +2 -2
  19. package/dist/index.d.ts +15 -4
  20. package/dist/index.js +14 -5
  21. package/dist/parser/parser-types.js +12 -0
  22. package/dist/parser/parser-use.js +7 -1
  23. package/dist/runtime/core/callable.d.ts +20 -8
  24. package/dist/runtime/core/callable.js +63 -23
  25. package/dist/runtime/core/context.d.ts +0 -11
  26. package/dist/runtime/core/context.js +76 -75
  27. package/dist/runtime/core/eval/index.d.ts +2 -2
  28. package/dist/runtime/core/eval/index.js +11 -0
  29. package/dist/runtime/core/eval/mixins/closures.js +15 -15
  30. package/dist/runtime/core/eval/mixins/conversion.js +51 -110
  31. package/dist/runtime/core/eval/mixins/core.js +2 -2
  32. package/dist/runtime/core/eval/mixins/expressions.js +35 -27
  33. package/dist/runtime/core/eval/mixins/literals.js +3 -3
  34. package/dist/runtime/core/eval/mixins/types.js +44 -54
  35. package/dist/runtime/core/eval/mixins/variables.js +10 -8
  36. package/dist/runtime/core/field-descriptor.d.ts +3 -3
  37. package/dist/runtime/core/field-descriptor.js +2 -1
  38. package/dist/runtime/core/introspection.js +6 -6
  39. package/dist/runtime/core/markers.d.ts +12 -0
  40. package/dist/runtime/core/markers.js +7 -0
  41. package/dist/runtime/core/type-registrations.d.ts +136 -0
  42. package/dist/runtime/core/type-registrations.js +749 -0
  43. package/dist/runtime/core/type-structures.d.ts +128 -0
  44. package/dist/runtime/core/type-structures.js +12 -0
  45. package/dist/runtime/core/types.d.ts +15 -3
  46. package/dist/runtime/core/values.d.ts +62 -153
  47. package/dist/runtime/core/values.js +308 -524
  48. package/dist/runtime/ext/builtins.js +83 -64
  49. package/dist/runtime/ext/extensions.d.ts +30 -124
  50. package/dist/runtime/ext/extensions.js +0 -93
  51. package/dist/runtime/ext/test-context.d.ts +28 -0
  52. package/dist/runtime/ext/test-context.js +154 -0
  53. package/dist/runtime/index.d.ts +22 -8
  54. package/dist/runtime/index.js +18 -4
  55. package/dist/signature-parser.d.ts +2 -2
  56. package/dist/signature-parser.js +14 -14
  57. package/package.json +1 -1
@@ -85,7 +85,7 @@ Pattern summary:
85
85
  ### 6. No Exceptions
86
86
 
87
87
  Errors halt execution. No try/catch. Use conditionals for error handling.
88
- Built-in: \`assert\` (validate condition), \`error\` (halt with message).
88
+ Keywords: \`assert\` (validate condition), \`error\` (halt with message).
89
89
 
90
90
  ### 7. Value Semantics (No References)
91
91
 
@@ -95,45 +95,6 @@ All copies are deep. All comparisons are by value. No object identity.
95
95
  list[1, 2] => \$a
96
96
  \$a => \$b # \$b is an independent deep copy
97
97
 
98
- ## Grammar Patterns
99
-
100
- rill uses two bracket forms as grammar constructs (not function calls):
101
-
102
- **\`keyword[...]\`** — collection literals. Keyword and \`[\` must be adjacent (no space):
103
-
104
- list[1, 2, 3] # list
105
- dict[name: "alice", age: 30] # dict (key: value pairs)
106
- tuple[1, 2, 3] # positional argument unpacking
107
- ordered[a: 1, b: 2] # named argument unpacking
108
-
109
- list [1, 2] # ERROR: RILL-P007 (space before bracket)
110
-
111
- **\`keyword<...>\`** — extraction operators. Custom grammar with \`,\` or \`:\` separators:
112
-
113
- list[1, 2, 3] -> destruct<\$a, \$b, \$c> # destructure into variables
114
- list[0,1,2,3] -> slice<1:3> # slice with start:stop:step
115
-
116
- These are NOT function calls. They are parsed as special syntax by the compiler.
117
-
118
- ## Callable Types
119
-
120
- rill has three callable types:
121
-
122
- | Type | Source | Examples |
123
- |------|--------|---------|
124
- | Closure | Script-defined | \`\\|x\\| { \$x * 2 }\`, \`{ \$ + 1 }\` |
125
- | Built-in function | Shipped with runtime | \`log\`, \`json\`, \`range\`, \`chain\`, \`error\` |
126
- | Host function | Registered by host app | \`app::fetch()\`, \`app::prompt()\` |
127
-
128
- The \`\$\` prefix disambiguates callables from variables and keys:
129
-
130
- name() -> built-in or host function call
131
- \$name() -> closure invocation
132
- \$name -> variable reference
133
- name -> dict key literal
134
-
135
- Without \`\$\`, \`process(data)\` is ambiguous: is \`process\` a function or stored closure? Is \`data\` a variable or key? This would require tracking all declarations.
136
-
137
98
  ## Syntax Quick Reference
138
99
 
139
100
  Variables: \$name (always prefixed with \$)
@@ -153,8 +114,28 @@ Without \`\$\`, \`process(data)\` is ambiguous: is \`process\` a function or sto
153
114
  Ordered: ordered[a: 1, b: 2] # named argument unpacking (preserves order)
154
115
  Closures: |x|(\$x + 1) # like lambda/arrow functions
155
116
  Type annot: "hi" => \$x:string # lock type on capture
117
+ Modules: use<module:greetings> => \$g # host-provided imports
156
118
  Comments: # single line only
157
119
 
120
+ CRITICAL: \`keyword[\` must have NO space before \`[\`. \`list [1]\` is ERROR (RILL-P007).
121
+
122
+ ## Callable Types
123
+
124
+ rill has three callable types:
125
+
126
+ | Type | Source | Examples |
127
+ |------|--------|---------|
128
+ | Closure | Script-defined | \`\\|x\\| { \$x * 2 }\`, \`{ \$ + 1 }\` |
129
+ | Built-in function | Shipped with runtime | \`log\`, \`json\`, \`range\`, \`repeat\`, \`chain\` |
130
+ | Host function | Registered by host app | \`app::fetch()\`, \`app::prompt()\` |
131
+
132
+ The \`\$\` prefix disambiguates callables from variables and keys:
133
+
134
+ name() -> built-in or host function call
135
+ \$name() -> closure invocation
136
+ \$name -> variable reference
137
+ name -> dict key literal
138
+
158
139
  ## Pipes and \$ Binding
159
140
 
160
141
  \`\$\` is the current piped value. Its meaning depends on context:
@@ -173,12 +154,27 @@ Without \`\$\`, \`process(data)\` is ambiguous: is \`process\` a function or sto
173
154
  Implied \`\$\`: bare \`.method()\` means \`\$ -> .method()\`.
174
155
  Example: \`"hello" -> .upper\` is the same as \`"hello" -> \$.upper()\`.
175
156
 
157
+ Always prefer implicit \`\$\` shorthand:
158
+
159
+ \$.method() -> .method "x" -> .upper (not \$.upper())
160
+ func(\$) -> func "x" -> log (not log(\$))
161
+ \$fn(\$) -> \$fn 5 -> \$double (not \$double(\$))
162
+
163
+ Don't capture just to continue — use line continuation instead:
164
+
165
+ # avoid # good
166
+ "x" => \$a "x"
167
+ \$a -> .upper => \$b -> .upper
168
+ \$b -> .len -> .len
169
+
170
+ Only capture when the variable is reused later in the code.
171
+
176
172
  ## Control Flow
177
173
 
178
174
  Conditional (if-else):
179
175
 
180
176
  cond ? then_expr ! else_expr
181
- cond ? then_expr # else returns null
177
+ cond ? then_expr # else returns empty string
182
178
 
183
179
  Piped conditional (\`\$\` becomes condition):
184
180
 
@@ -240,8 +236,52 @@ Pass (returns \`\$\` unchanged, explicit no-op):
240
236
 
241
237
  Note: \`pass\` requires pipe context. Using \`pass\` without \`\$\` throws error.
242
238
 
239
+ ## Closures
240
+
241
+ **Block-closures:** \`{ body }\` in expression position
242
+
243
+ { \$ + 1 } => \$inc # implicit \$ parameter
244
+ \$inc(5) # 6
245
+ 5 -> \$inc # 6 (pipe invocation)
246
+ dict[x: { \$ * 2 }] # dict value is closure
247
+
248
+ **Explicit closures:** \`|params| body\`
249
+
250
+ |x|(\$x + 1) => \$inc # named parameter
251
+ |a, b|(\$a + \$b) => \$add # multiple params
252
+ |x = 0|(\$x + 1) => \$inc_or_one # default value
253
+ |x: number|(\$x + 1) => \$typed # type annotation
254
+
255
+ \`{ body }\` vs \`( expr )\` distinction:
256
+
257
+ | Syntax | Semantics | Example |
258
+ |---|---|---|
259
+ | \`{ body }\` | Deferred (closure) | \`{ \$ + 1 } => \$fn\` |
260
+ | \`( expr )\` | Eager (immediate eval) | \`( 5 + 1 ) => \$x\` gives 6 |
261
+
262
+ LATE BINDING: closures capture scope, not values. Variables resolve at call time.
263
+
264
+ \`\$\` vs named params:
265
+ - Use \`\$\` in inline pipes and loops: \`"hello" -> { .upper }\`
266
+ - Use named params in stored closures: \`|x| (\$x * 2) => \$double\`
267
+ - \`\$\` is undefined when a stored closure is called later — always use params.
268
+
269
+ Zero-param dict closures (methods):
270
+
271
+ dict[count: 3, double: ||{ \$.count * 2 }] => \$obj
272
+ \$obj.double # 6 (\$ is bound to dict)
273
+
243
274
  ## Collection Operators
244
275
 
276
+ Choose the right operator:
277
+
278
+ Transform each item? -> map { }
279
+ Side effects per item? -> each { }
280
+ Single result from list? -> fold(init) { \$@ + \$ }
281
+ Filter items? -> filter { }
282
+ Results + running total? -> each(init) { ... \$@ ... }
283
+ Loop with state? -> (cond) @ { } (see Control Flow)
284
+
245
285
  | Operator | Execution | Returns | Break? |
246
286
  |---|---|---|---|
247
287
  | \`-> each { }\` | sequential | all body results | yes |
@@ -278,24 +318,232 @@ String iteration (iterates over characters):
278
318
  "abc" -> each { "{\$}!" } # list["a!", "b!", "c!"]
279
319
  "hello" -> filter { \$ != "l" } # list["h", "e", "o"]
280
320
 
281
- ## Closures
321
+ ## Property Access
282
322
 
283
- Two ways to create closures:
323
+ \$data.field # dict field
324
+ \$data[0], \$data[-1] # list index (negative from end)
325
+ \$data.\$key # variable as key
326
+ \$data.(\$i + 1) # computed key
327
+ \$data.(a || b) # try keys left-to-right
328
+ \$data.field ?? "default" # default if missing
329
+ \$data.?field # existence check (boolean)
330
+ \$data.?\$keyVar # variable existence check
331
+ \$data.?(\$expr) # computed existence check
332
+ \$data.?field&string # existence AND type check
333
+ \$data.?\$key&number # variable existence + type check
334
+ \$data.?(\$a -> "{\$}_b")&list # computed existence + type check
284
335
 
285
- **Block-closures:** \`{ body }\` in expression position
336
+ Dict keys take priority over built-in methods. A dict with key \`len\` returns that key's value, not the built-in \`.len\` method.
286
337
 
287
- { \$ + 1 } => \$inc # implicit \$ parameter
288
- \$inc(5) # 6
289
- 5 -> \$inc # 6 (pipe invocation)
290
- dict[x: { \$ * 2 }] # dict value is closure
291
- { "hi" }:?closure # true
338
+ ## Types and Assertions
292
339
 
293
- **Explicit closures:** \`|params| body\`
340
+ **Type names:**
294
341
 
295
- |x|(\$x + 1) => \$inc # named parameter
296
- |a, b|(\$a + \$b) => \$add # multiple params
297
- |x = 0|(\$x + 1) => \$inc_or_one # default value
298
- |x: number|(\$x + 1) => \$typed # type annotation
342
+ string number bool list dict ordered
343
+ tuple closure vector iterator any type
344
+
345
+ **Type assertion** \`:type\` errors if value does not match:
346
+
347
+ 42:number # passes
348
+ "x":number # RILL-R002: expected number, got string
349
+ \$val:list(string) # assert parameterized type
350
+
351
+ **Type check** \`:?type\` — returns boolean, never errors:
352
+
353
+ 42:?number # true
354
+ "x":?number # false
355
+
356
+ Both forms work as pipe targets:
357
+
358
+ \$val -> :number # assert in pipe
359
+ \$val -> :?number # check in pipe
360
+
361
+ **Union types** (\`T1|T2\`) — match any member type:
362
+
363
+ 42 -> :string|number # passes (number matches)
364
+ 42:?string|number # true
365
+ "hello" => \$x:string|number # capture with union type
366
+
367
+ **Enforcing dict shapes** with \`dict(field: type, ...)\`:
368
+
369
+ \$data -> :dict(name: string, age: number) # assert exact fields and types
370
+ \$data:?dict(name: string, age: number) # check without error
371
+
372
+ Use in closure params to validate input structure:
373
+
374
+ |d: dict(name: string, age: number)| {
375
+ "{\$d.name} is {\$d.age}"
376
+ } => \$format_person
377
+
378
+ **Parameterized type constructors** (structural types):
379
+
380
+ list(string) # list of strings
381
+ dict(name: string, age: number) # dict with named fields
382
+ tuple(number, string) # positional tuple
383
+ ordered(x: number, y: number) # named ordered args
384
+
385
+ **Type conversion** \`:>type\` — explicit conversion between compatible types:
386
+
387
+ 42 -> :>string # "42"
388
+ "3.14" -> :>number # 3.14
389
+ list[1, 2] -> :>tuple # tuple[1, 2]
390
+ tuple[1, 2] -> :>list # list[1, 2]
391
+ ordered[a: 1] -> :>dict # dict[a: 1]
392
+
393
+ ## Common Patterns
394
+
395
+ **Process an API response:**
396
+
397
+ app::fetch("https://api.example.com/users") => \$response
398
+ \$response.data
399
+ -> filter { \$.active }
400
+ -> map { dict[name: \$.name, email: \$.email] }
401
+ -> each {
402
+ "Processing {\$.name}" -> log
403
+ app::send_welcome(\$.email)
404
+ }
405
+
406
+ **Build a result from a list:**
407
+
408
+ list["alice", "bob", "charlie"]
409
+ -> map { dict[name: \$, greeting: "Hello, {\$}!"] }
410
+ -> fold(dict[]) {
411
+ dict[...\$@, \$.name: \$.greeting]
412
+ }
413
+ # dict[alice: "Hello, alice!", bob: "Hello, bob!", charlie: "Hello, charlie!"]
414
+
415
+ **Validate and transform input:**
416
+
417
+ \$input -> assert \$:?string "Expected string input"
418
+ \$input
419
+ -> .trim
420
+ -> assert !.empty "Input cannot be empty"
421
+ -> .split(",")
422
+ -> map .trim
423
+ -> filter (!.empty)
424
+
425
+ **Stateful retry loop:**
426
+
427
+ dict[attempts: 0, max: 3, result: "", done: false]
428
+ -> (!\$.done && \$.attempts < \$.max) @ {
429
+ \$.attempts + 1 => \$try
430
+ app::fetch(\$url) => \$res
431
+ \$res.?status&number
432
+ ? (\$res.status == 200)
433
+ ? dict[attempts: \$try, max: \$.max, result: \$res.body, done: true]
434
+ ! dict[attempts: \$try, max: \$.max, result: "", done: false]
435
+ ! dict[attempts: \$try, max: \$.max, result: "", done: false]
436
+ }
437
+ -> \$.done ? \$.result ! error "Failed after {\$.max} attempts"
438
+
439
+ **Reduce with running state:**
440
+
441
+ list[10, 25, 5, 40, 15]
442
+ -> each(dict[sum: 0, max: 0]) {
443
+ \$.sum + \$@ => \$new_sum
444
+ (\$ > \$@.max) ? \$ ! \$@.max => \$new_max
445
+ dict[sum: \$new_sum, max: \$new_max]
446
+ }
447
+ # each item produces the running state; last element is final
448
+
449
+ ## Method Reference
450
+
451
+ ### String Methods
452
+
453
+ | Method | Description |
454
+ |---|---|
455
+ | \`.len\` | length |
456
+ | \`.empty\` | is empty string |
457
+ | \`.trim\` | remove whitespace |
458
+ | \`.upper\` | uppercase |
459
+ | \`.lower\` | lowercase |
460
+ | \`.head\` | first character |
461
+ | \`.tail\` | last character |
462
+ | \`.at(i)\` | character at index |
463
+ | \`.first\` | iterator at first character |
464
+ | \`.split(sep)\` | split into list (default: newline) |
465
+ | \`.lines\` | split on newlines |
466
+ | \`.contains(s)\` | substring check |
467
+ | \`.starts_with(s)\` | prefix check |
468
+ | \`.ends_with(s)\` | suffix check |
469
+ | \`.index_of(s)\` | first match position (-1 if none) |
470
+ | \`.replace(p, r)\` | replace first regex match |
471
+ | \`.replace_all(p, r)\` | replace all regex matches |
472
+ | \`.match(regex)\` | first match info (dict with matched, index, groups) |
473
+ | \`.is_match(regex)\` | boolean regex check |
474
+ | \`.repeat(n)\` | repeat n times |
475
+ | \`.pad_start(n, f)\` | pad start |
476
+ | \`.pad_end(n, f)\` | pad end |
477
+
478
+ ### List Methods
479
+
480
+ | Method | Description |
481
+ |---|---|
482
+ | \`.len\` | length |
483
+ | \`.empty\` | is empty |
484
+ | \`.head\` | first element |
485
+ | \`.tail\` | last element |
486
+ | \`.first\` | iterator at first element |
487
+ | \`.at(i)\` | element at index |
488
+ | \`.join(sep)\` | join elements with separator |
489
+ | \`.has(val)\` | contains value (deep equality) |
490
+ | \`.has_any(list)\` | contains any value from candidates |
491
+ | \`.has_all(list)\` | contains all values from candidates |
492
+
493
+ ### Dict Methods
494
+
495
+ | Method | Description |
496
+ |---|---|
497
+ | \`.len\` | number of entries |
498
+ | \`.empty\` | is empty |
499
+ | \`.first\` | iterator at first entry |
500
+ | \`.keys\` | keys as list |
501
+ | \`.values\` | values as list |
502
+ | \`.entries\` | list of \`tuple[k, v]\` pairs |
503
+
504
+ ### Comparison Methods
505
+
506
+ .eq(val) == .ne(val) != .lt(val) < .gt(val) > .le(val) <= .ge(val) >=
507
+ Example: \$age -> .ge(18) ? "adult" ! "minor"
508
+
509
+ ### Built-in Functions
510
+
511
+ | Function | Description |
512
+ |---|---|
513
+ | \`log(val)\` | print and pass through |
514
+ | \`json(val)\` | convert to JSON string |
515
+ | \`identity(val)\` | returns input unchanged |
516
+ | \`range(start, end, step?)\` | number sequence (iterator) |
517
+ | \`repeat(val, count)\` | repeat value n times (iterator) |
518
+ | \`enumerate(coll)\` | lists: \`tuple[index, value]\`; dicts: \`tuple[index, key, value]\` |
519
+ | \`chain(\$fn)\` | apply single closure; chain(val, list[\$f, \$g]) applies in sequence |
520
+
521
+ ## Style
522
+
523
+ ### Naming: snake_case
524
+
525
+ \$user_name, \$item_list, \$is_valid # variables
526
+ \$double_value, \$cleanup_text # closures
527
+ dict[first_name: "x", last_name: "y"] # dict keys
528
+
529
+ ### Spacing
530
+
531
+ Operators: space both sides 5 + 3, \$x -> .upper, "a" => \$b
532
+ Parentheses: no inner space (\$x + 1), (\$ > 3) ? "yes"
533
+ Braces: space inside { \$x + 1 }, each { \$ * 2 }
534
+ Brackets: no inner space \$list[0], \$dict.items[1]
535
+ Literals: space after , and : list[1, 2, 3], dict[name: "x", age: 30]
536
+ Keyword+[ NO space between list[1, 2] (not list [1, 2] — RILL-P007)
537
+ Closures: space after params |x| (\$x * 2), |a, b| { \$a + \$b }
538
+ Methods: no space before . or ( \$str.upper(), \$list.join(", ")
539
+ Pipes: space both sides "x" -> .upper -> .len
540
+ Continuations: indent 2 spaces \$data
541
+ -> .filter { \$.active }
542
+ -> map { \$.name }
543
+
544
+ ## Extended Reference
545
+
546
+ ### Typed Closures
299
547
 
300
548
  **Anonymous typed closures:** \`|type|{ body }\` — reserved type keyword as param, \`\$\` is type-checked piped input
301
549
 
@@ -305,7 +553,7 @@ Two ways to create closures:
305
553
  5 -> \$double # 10
306
554
  "hi" -> \$double # RILL-R001: expected number, got string
307
555
 
308
- All 11 type names valid: string, number, bool, list, dict, ordered, tuple, closure, vector, any, type
556
+ All 12 type names valid: string, number, bool, list, dict, ordered, tuple, closure, vector, iterator, any, type
309
557
  Parameterized: |list(number)|{ \$ -> each { \$ * 2 } }
310
558
 
311
559
  **Return type assertions** (enforced at runtime — RILL-R004 on mismatch):
@@ -316,10 +564,20 @@ Parameterized: |list(number)|{ \$ -> each { \$ * 2 } }
316
564
  |x: number| { \$x * 2 }:string => \$fn # mismatch halts with RILL-R004
317
565
  \$fn(5) # RILL-R004: expected string, got number
318
566
 
319
- Valid return type targets: any type name (string, number, bool, closure, list, dict, tuple,
320
- ordered, vector, any, type), or a parameterized type constructor (list(string),
321
- dict(a: number, b: string)).
322
- Declared return type accessible via \$fn.^output annotation.
567
+ ### Callable Reflection
568
+
569
+ \`.^input\` and \`.^output\` work on ALL callable types:
570
+
571
+ |x: number, y: string|(\$x) => \$fn
572
+ \$fn.^input # ordered(x: number, y: string)
573
+ \$fn.^output # any (no return type declared)
574
+
575
+ |x: number|{ \$x * 2 }:number => \$fn2
576
+ \$fn2.^output # number
577
+
578
+ \`.^input\` returns an ordered dict mapping parameter names to type values.
579
+ \`.^output\` returns the declared return type (\`any\` if undeclared).
580
+ Both work on script closures, built-in functions, and host functions.
323
581
 
324
582
  **Description shorthand** (bare string in \`^(...)\` expands to \`description: <string>\`):
325
583
 
@@ -330,41 +588,7 @@ Declared return type accessible via \$fn.^output annotation.
330
588
  \$get_user.^description # "Fetch profile"
331
589
  \$get_user.^cache # true
332
590
 
333
- \`{ body }\` vs \`( expr )\` distinction:
334
-
335
- | Syntax | Semantics | Example |
336
- |---|---|---|
337
- | \`{ body }\` | Deferred (closure) | \`{ \$ + 1 } => \$fn\` |
338
- | \`( expr )\` | Eager (immediate eval) | \`( 5 + 1 ) => \$x\` gives 6 |
339
-
340
- LATE BINDING: closures capture scope, not values. Variables resolve at call time.
341
-
342
- \`\$\` vs named params:
343
- - Use \`\$\` in inline pipes and loops: \`"hello" -> { .upper }\`
344
- - Use named params in stored closures: \`|x| (\$x * 2) => \$double\`
345
- - \`\$\` is undefined when a stored closure is called later — always use params.
346
-
347
- Zero-param dict closures (methods):
348
-
349
- dict[count: 3, double: ||{ \$.count * 2 }] => \$obj
350
- \$obj.double # 6 (\$ is bound to dict)
351
-
352
- ## Property Access
353
-
354
- \$data.field # dict field
355
- \$data[0], \$data[-1] # list index (negative from end)
356
- \$data.\$key # variable as key
357
- \$data.(\$i + 1) # computed key
358
- \$data.(a || b) # try keys left-to-right
359
- \$data.field ?? "default" # default if missing
360
- \$data.?field # existence check (boolean)
361
- \$data.?\$keyVar # variable existence check
362
- \$data.?(\$expr) # computed existence check
363
- \$data.?field&string # existence AND type check
364
- \$data.?\$key&number # variable existence + type check
365
- \$data.?(\$a -> "{\$}_b")&list # computed existence + type check
366
-
367
- ## Dispatch Operators
591
+ ### Dispatch Operators
368
592
 
369
593
  **Dict dispatch** (single key) — pipe a value to a dict to match keys:
370
594
 
@@ -396,82 +620,41 @@ Note: lists require same-type elements. For mixed paths (strings and numbers), u
396
620
  dict[users: list[dict[name: "Alice"]]] => \$d
397
621
  \$d.users[0].name # "Alice"
398
622
 
399
- ## Types and Type Expressions
400
-
401
- **Type names:**
402
-
403
- string number bool list dict ordered
404
- tuple closure vector any type
405
-
406
- **Type names are first-class values** — assign to variables, use in dynamic expressions:
407
-
408
- number => \$t # store a type value
409
- 42 -> :\$t # dynamic assertion (same as :number)
410
- 42 -> :?\$t # dynamic check: true
411
- 42 -> :>\$t # dynamic conversion (same as :>number)
412
- \$val.^type == \$t # compare against stored type
413
-
414
- **Parameterized type constructors** (structural types):
415
-
416
- list(string) # list of strings
417
- dict(name: string, age: number) # dict with named fields
418
- tuple(number, string) # positional tuple
419
- ordered(x: number, y: number) # named ordered args
420
- |x: number, y: string| :any # closure signature type
421
-
422
- Closure signatures include parameter names — \`|x: number|\` and \`|y: number|\` are different types.
423
-
424
- **Type assertion** \`:type\` — errors if value does not match:
425
-
426
- 42:number # passes
427
- "x":number # RILL-R002: expected number, got string
428
- \$val:list(string) # assert parameterized type
429
-
430
- **Enforcing dict shapes** with \`dict(field: type, ...)\`:
431
-
432
- \$data -> :dict(name: string, age: number) # assert exact fields and types
433
- \$data:?dict(name: string, age: number) # check without error
434
- \$data:dict(name: string) -> .name # assert then access
435
-
436
- Use in closure params to validate input structure:
437
-
438
- |d: dict(name: string, age: number)| {
439
- "{\$d.name} is {\$d.age}"
440
- } => \$format_person
623
+ ### Extraction Operators
441
624
 
442
- **Type check** \`:?type\` — returns boolean, never errors:
443
-
444
- 42:?number # true
445
- "x":?number # false
446
- \$val:?list(string) # structural check
625
+ Destructure (\`destruct<>\`):
447
626
 
448
- Both forms work as pipe targets:
627
+ list[1, 2, 3] -> destruct<\$a, \$b, \$c> # \$a=1, \$b=2, \$c=3
628
+ dict[x: 1, y: 2] -> destruct<x: \$a, y: \$b> # \$a=1, \$b=2
629
+ list[1, 2, 3] -> destruct<\$first, _, \$third> # _ skips element
449
630
 
450
- \$val -> :number # assert in pipe
451
- \$val -> :?number # check in pipe
631
+ Slice (\`slice<start:stop:step>\`):
452
632
 
453
- **\`^type\` operator** returns a type value for inspection:
633
+ list[0,1,2,3,4] -> slice<1:3> # list[1, 2]
634
+ list[0,1,2,3,4] -> slice<-2:> # list[3, 4]
635
+ list[0,1,2,3,4] -> slice<::-1> # list[4,3,2,1,0] (reverse)
636
+ "hello" -> slice<1:4> # "ell"
454
637
 
455
- 42.^type # type value for number
456
- 42.^type.name # "number"
457
- list[1, 2].^type.name # "list"
458
- list[1, 2].^type.signature # "list(number)"
638
+ ### List Spread
459
639
 
460
- **Type value equality:**
640
+ list[1, 2] => \$a
641
+ list[...\$a, 3] # list[1, 2, 3]
642
+ list[...\$a, ...\$b] # concatenate lists
643
+ list[...(\$nums -> map {\$ * 2})] # spread expression result
461
644
 
462
- \$val.^type == list(number) # true if \$val is list(number)
463
- \$val.^type == \$other.^type # compare structural types
645
+ ### Argument Unpacking
464
646
 
465
- **Type conversion** \`:>type\` explicit conversion between compatible types:
647
+ tuple[1, 2, 3] -> \$fn(...) # positional spread: \$fn(1, 2, 3)
648
+ ordered[a: 10, b: 2] -> \$fn(...) # named spread: keys must match parameter order
649
+ tuple[...\$list, 3] -> \$fn(...) # spread in tuple then spread into call
650
+ tuple[1, 2, 3] => \$args
651
+ \$args -> \$fn(...) # spread stored tuple
652
+ ordered[a: 1, b: 2] => \$named
653
+ \$named -> \$fn(...) # spread stored ordered
466
654
 
467
- 42 -> :>string # "42"
468
- "3.14" -> :>number # 3.14
469
- list[1, 2] -> :>tuple # tuple[1, 2]
470
- tuple[1, 2] -> :>list # list[1, 2]
471
- ordered[a: 1] -> :>dict # dict[a: 1]
472
- dict[x: 1, y: 2] -> :>ordered(x: number, y: number) # structural
655
+ ### Type Conversion Matrix
473
656
 
474
- Compatibility matrix (error = halts with RILL-R036, no-op = same-type identity):
657
+ \`:>type\` compatibility (error = halts with RILL-R036, no-op = same-type identity):
475
658
 
476
659
  Source | :>list | :>dict | :>tuple | :>ordered(sig) | :>number | :>string | :>bool
477
660
  ---------|---------|---------|---------|----------------|----------|----------|-------
@@ -489,115 +672,43 @@ Compatibility matrix (error = halts with RILL-R036, no-op = same-type identity):
489
672
  ⁴ true maps to 1, false maps to 0
490
673
  ⁵ 0 maps to false, 1 maps to true; all other values halt with RILL-R036
491
674
 
492
- ## Comparison Methods
493
-
494
- .eq(val) == .ne(val) != .lt(val) < .gt(val) > .le(val) <= .ge(val) >=
495
- Example: \$age -> .ge(18) ? "adult" ! "minor"
496
-
497
- ## Operator Precedence (Highest to Lowest)
498
-
499
- 1. Member access: \`.field\`, \`[index]\`
500
- 2. Type operators: \`:type\`, \`:?type\`
501
- 3. Unary: \`-\`, \`!\`
502
- 4. Multiplicative: \`*\`, \`/\`, \`%\`
503
- 5. Additive: \`+\`, \`-\`
504
- 6. Comparison: \`==\`, \`!=\`, \`<\`, \`>\`, \`<=\`, \`>=\`
505
- 7. Logical AND: \`&&\`
506
- 8. Logical OR: \`||\`
507
- 9. Default: \`??\`
508
- 10. Pipe: \`->\`
509
- 11. Capture: \`=>\`
510
-
511
- Use parentheses to override: \`(2 + 3) * 4\`
512
-
513
- ## Extraction Operators
514
-
515
- Destructure (\`destruct<>\`):
516
-
517
- list[1, 2, 3] -> destruct<\$a, \$b, \$c> # \$a=1, \$b=2, \$c=3
518
- dict[x: 1, y: 2] -> destruct<x: \$a, y: \$b> # \$a=1, \$b=2
519
- list[1, 2, 3] -> destruct<\$first, _, \$third> # _ skips element
675
+ **Default values in type constructors** (used with \`:>\` conversion):
520
676
 
521
- Slice (\`slice<start:stop:step>\`):
677
+ dict(a: string, b: string = "x") # field b defaults to "x"
678
+ ordered(x: number, y: number = 0) # field y defaults to 0
679
+ tuple(string, number = 0) # trailing element defaults to 0
522
680
 
523
- list[0,1,2,3,4] -> slice<1:3> # list[1, 2]
524
- list[0,1,2,3,4] -> slice<-2:> # list[3, 4]
525
- list[0,1,2,3,4] -> slice<::-1> # list[4,3,2,1,0] (reverse)
526
- "hello" -> slice<1:4> # "ell"
681
+ dict[b: "b"] -> :>dict(b: string, a: string = "a") # dict[a: "a", b: "b"]
527
682
 
528
- ## List Spread in Literals
683
+ Only \`:>\` conversion fills defaults. \`:\` assertion does NOT hydrate defaults.
684
+ \`:>dict(...)\` and \`:>ordered(...)\` fill missing fields from type constructor defaults.
685
+ Nested fields are recursively hydrated.
529
686
 
530
- list[1, 2] => \$a
531
- list[...\$a, 3] # list[1, 2, 3]
532
- list[...\$a, ...\$b] # concatenate lists
533
- list[...(\$nums -> map {\$ * 2})] # spread expression result
687
+ ### Type Inspection
534
688
 
535
- ## Tuples and Ordered for Argument Unpacking
689
+ **\`^type\` operator** returns a type value for inspection:
536
690
 
537
- tuple[1, 2, 3] -> \$fn(...) # positional spread: \$fn(1, 2, 3)
538
- ordered[a: 10, b: 2] -> \$fn(...) # named spread: keys must match parameter order
539
- tuple[...\$list, 3] -> \$fn(...) # spread in tuple then spread into call
540
- tuple[1, 2, 3] => \$args
541
- \$args -> \$fn(...) # spread stored tuple
542
- ordered[a: 1, b: 2] => \$named
543
- \$named -> \$fn(...) # spread stored ordered
691
+ 42.^type # type value for number
692
+ 42.^type.name # "number"
693
+ list[1, 2].^type.name # "list"
694
+ list[1, 2].^type.signature # "list(number)"
544
695
 
545
- ## String Methods
696
+ **Type value equality:**
546
697
 
547
- | Method | Description |
548
- |---|---|
549
- | \`.len\` | length |
550
- | \`.empty\` | is empty string |
551
- | \`.trim\` | remove whitespace |
552
- | \`.upper\` | uppercase |
553
- | \`.lower\` | lowercase |
554
- | \`.head\` | first character |
555
- | \`.tail\` | last character |
556
- | \`.at(i)\` | character at index |
557
- | \`.split(sep)\` | split into list (default: newline) |
558
- | \`.lines\` | split on newlines |
559
- | \`.join(sep)\` | join list with separator |
560
- | \`.contains(s)\` | substring check |
561
- | \`.starts_with(s)\` | prefix check |
562
- | \`.ends_with(s)\` | suffix check |
563
- | \`.index_of(s)\` | first match position (-1 if none) |
564
- | \`.replace(p, r)\` | replace first regex match |
565
- | \`.replace_all(p, r)\` | replace all regex matches |
566
- | \`.match(regex)\` | first match info (dict with matched, index, groups) |
567
- | \`.is_match(regex)\` | boolean regex check |
568
- | \`.repeat(n)\` | repeat n times |
569
- | \`.pad_start(n, f)\` | pad start |
570
- | \`.pad_end(n, f)\` | pad end |
698
+ \$val.^type == list(number) # true if \$val is list(number)
699
+ \$val.^type == \$other.^type # compare structural types
571
700
 
572
- ## List/Dict Methods
701
+ ### Modules
573
702
 
574
- | Method | Description |
575
- |---|---|
576
- | \`.len\` | length |
577
- | \`.empty\` | is empty |
578
- | \`.head\` | first element |
579
- | \`.tail\` | last element |
580
- | \`.at(i)\` | element at index |
581
- | \`.keys\` | dict keys as list |
582
- | \`.values\` | dict values as list |
583
- | \`.entries\` | dict as list of \`tuple[k, v]\` pairs |
584
- | \`.has(val)\` | list contains value (deep equality) |
585
- | \`.has_any(list)\` | list contains any value from candidates |
586
- | \`.has_all(list)\` | list contains all values from candidates |
703
+ \`use<scheme:resource>\` resolves a named resource through a host-registered scheme resolver:
587
704
 
588
- ## Built-in Functions
705
+ use<module:greetings> => \$g # load module, capture as \$g
706
+ \$g.hello("World") # call module member
707
+ use<ext:qdrant.search> # load extension member
589
708
 
590
- | Function | Description |
591
- |---|---|
592
- | \`log(val)\` | print and pass through |
593
- | \`json(val)\` | convert to JSON string |
594
- | \`identity(val)\` | returns input unchanged |
595
- | \`range(start, end, step?)\` | number sequence (iterator) |
596
- | \`repeat(val, count)\` | repeat value n times (iterator) |
597
- | \`enumerate(coll)\` | lists: \`tuple[index, value]\`; dicts: \`tuple[index, key, value]\` |
598
- | \`chain(\$fn)\` | apply single closure; chain(val, list[\$f, \$g]) applies in sequence |
709
+ \`use<>\` returns the resolved value (dict, closure, or any rill type). The host registers resolvers for each scheme. Scripts cannot define resolvers.
599
710
 
600
- ## Iterators
711
+ ### Iterators
601
712
 
602
713
  Lazy sequence generation. Collection operators auto-expand iterators.
603
714
 
@@ -615,7 +726,7 @@ Iterator protocol (dict with \`value\`, \`done\`, \`next\`):
615
726
  \$it.value # current element
616
727
  \$it.next() # returns new iterator at next position
617
728
 
618
- ## Iteration Limits
729
+ ### Iteration Limits
619
730
 
620
731
  Default: 10,000 iterations max for loops.
621
732
  Override: \`^(limit: N)\` at operator level (between operator name and body).
@@ -626,7 +737,23 @@ Override: \`^(limit: N)\` at operator level (between operator name and body).
626
737
 
627
738
  Invalid annotation keys produce a runtime error.
628
739
 
629
- ## Script Return Values
740
+ ### Operator Precedence (Highest to Lowest)
741
+
742
+ 1. Member access: \`.field\`, \`[index]\`
743
+ 2. Type operators: \`:type\`, \`:?type\`
744
+ 3. Unary: \`-\`, \`!\`
745
+ 4. Multiplicative: \`*\`, \`/\`, \`%\`
746
+ 5. Additive: \`+\`, \`-\`
747
+ 6. Comparison: \`==\`, \`!=\`, \`<\`, \`>\`, \`<=\`, \`>=\`
748
+ 7. Logical AND: \`&&\`
749
+ 8. Logical OR: \`||\`
750
+ 9. Default: \`??\`
751
+ 10. Pipe: \`->\`
752
+ 11. Capture: \`=>\`
753
+
754
+ Use parentheses to override: \`(2 + 3) * 4\`
755
+
756
+ ### Script Return Values
630
757
 
631
758
  | Return value | Exit code |
632
759
  |---|---|
@@ -634,42 +761,4 @@ Invalid annotation keys produce a runtime error.
634
761
  | \`false\` / empty string | 1 |
635
762
  | \`list[0, "message"]\` | 0 with message |
636
763
  | \`list[1, "message"]\` | 1 with message |
637
-
638
- ## Style
639
-
640
- ### Naming: snake_case
641
-
642
- \$user_name, \$item_list, \$is_valid # variables
643
- \$double_value, \$cleanup_text # closures
644
- dict[first_name: "x", last_name: "y"] # dict keys
645
-
646
- ### Spacing
647
-
648
- Operators: space both sides 5 + 3, \$x -> .upper, "a" => \$b
649
- Parentheses: no inner space (\$x + 1), (\$ > 3) ? "yes"
650
- Braces: space inside { \$x + 1 }, each { \$ * 2 }
651
- Brackets: no inner space \$list[0], \$dict.items[1]
652
- Literals: space after , and : list[1, 2, 3], dict[name: "x", age: 30]
653
- Keyword+[ NO space between list[1, 2] (not list [1, 2] — RILL-P007)
654
- Closures: space after params |x| (\$x * 2), |a, b| { \$a + \$b }
655
- Methods: no space before . or ( \$str.upper(), \$list.join(", ")
656
- Pipes: space both sides "x" -> .upper -> .len
657
- Continuations: indent 2 spaces \$data
658
- -> .filter { \$.active }
659
- -> map { \$.name }
660
-
661
- ### Implicit \$ Shorthand (Always Prefer)
662
-
663
- \$.method() -> .method "x" -> .upper (not \$.upper())
664
- func(\$) -> func "x" -> log (not log(\$))
665
- \$fn(\$) -> \$fn 5 -> \$double (not \$double(\$))
666
-
667
- Don't capture just to continue — use line continuation instead:
668
-
669
- # avoid # good
670
- "x" => \$a "x"
671
- \$a -> .upper => \$b -> .upper
672
- \$b -> .len -> .len
673
-
674
- Only capture when the variable is reused later in the code.
675
764
  `;