@rcrsr/rill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +69 -0
- package/dist/cli.js.map +1 -0
- package/dist/demo.d.ts +6 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +121 -0
- package/dist/demo.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/errors.d.ts +9 -0
- package/dist/lexer/errors.d.ts.map +1 -0
- package/dist/lexer/errors.js +12 -0
- package/dist/lexer/errors.js.map +1 -0
- package/dist/lexer/helpers.d.ts +14 -0
- package/dist/lexer/helpers.d.ts.map +1 -0
- package/dist/lexer/helpers.js +30 -0
- package/dist/lexer/helpers.js.map +1 -0
- package/dist/lexer/index.d.ts +8 -0
- package/dist/lexer/index.d.ts.map +1 -0
- package/dist/lexer/index.js +8 -0
- package/dist/lexer/index.js.map +1 -0
- package/dist/lexer/operators.d.ts +11 -0
- package/dist/lexer/operators.d.ts.map +1 -0
- package/dist/lexer/operators.js +58 -0
- package/dist/lexer/operators.js.map +1 -0
- package/dist/lexer/readers.d.ts +12 -0
- package/dist/lexer/readers.d.ts.map +1 -0
- package/dist/lexer/readers.js +144 -0
- package/dist/lexer/readers.js.map +1 -0
- package/dist/lexer/state.d.ts +18 -0
- package/dist/lexer/state.d.ts.map +1 -0
- package/dist/lexer/state.js +37 -0
- package/dist/lexer/state.js.map +1 -0
- package/dist/lexer/tokenizer.d.ts +9 -0
- package/dist/lexer/tokenizer.d.ts.map +1 -0
- package/dist/lexer/tokenizer.js +100 -0
- package/dist/lexer/tokenizer.js.map +1 -0
- package/dist/lexer.d.ts +19 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +344 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser/arithmetic.d.ts +16 -0
- package/dist/parser/arithmetic.d.ts.map +1 -0
- package/dist/parser/arithmetic.js +128 -0
- package/dist/parser/arithmetic.js.map +1 -0
- package/dist/parser/boolean.d.ts +15 -0
- package/dist/parser/boolean.d.ts.map +1 -0
- package/dist/parser/boolean.js +20 -0
- package/dist/parser/boolean.js.map +1 -0
- package/dist/parser/control-flow.d.ts +56 -0
- package/dist/parser/control-flow.d.ts.map +1 -0
- package/dist/parser/control-flow.js +167 -0
- package/dist/parser/control-flow.js.map +1 -0
- package/dist/parser/expressions.d.ts +23 -0
- package/dist/parser/expressions.d.ts.map +1 -0
- package/dist/parser/expressions.js +950 -0
- package/dist/parser/expressions.js.map +1 -0
- package/dist/parser/extraction.d.ts +48 -0
- package/dist/parser/extraction.d.ts.map +1 -0
- package/dist/parser/extraction.js +279 -0
- package/dist/parser/extraction.js.map +1 -0
- package/dist/parser/functions.d.ts +20 -0
- package/dist/parser/functions.d.ts.map +1 -0
- package/dist/parser/functions.js +96 -0
- package/dist/parser/functions.js.map +1 -0
- package/dist/parser/helpers.d.ts +94 -0
- package/dist/parser/helpers.d.ts.map +1 -0
- package/dist/parser/helpers.js +225 -0
- package/dist/parser/helpers.js.map +1 -0
- package/dist/parser/index.d.ts +49 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +73 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/literals.d.ts +37 -0
- package/dist/parser/literals.d.ts.map +1 -0
- package/dist/parser/literals.js +373 -0
- package/dist/parser/literals.js.map +1 -0
- package/dist/parser/parser-collect.d.ts +16 -0
- package/dist/parser/parser-collect.d.ts.map +1 -0
- package/dist/parser/parser-collect.js +125 -0
- package/dist/parser/parser-collect.js.map +1 -0
- package/dist/parser/parser-control.d.ts +20 -0
- package/dist/parser/parser-control.d.ts.map +1 -0
- package/dist/parser/parser-control.js +120 -0
- package/dist/parser/parser-control.js.map +1 -0
- package/dist/parser/parser-expr.d.ts +37 -0
- package/dist/parser/parser-expr.d.ts.map +1 -0
- package/dist/parser/parser-expr.js +639 -0
- package/dist/parser/parser-expr.js.map +1 -0
- package/dist/parser/parser-extract.d.ts +17 -0
- package/dist/parser/parser-extract.d.ts.map +1 -0
- package/dist/parser/parser-extract.js +222 -0
- package/dist/parser/parser-extract.js.map +1 -0
- package/dist/parser/parser-functions.d.ts +21 -0
- package/dist/parser/parser-functions.d.ts.map +1 -0
- package/dist/parser/parser-functions.js +155 -0
- package/dist/parser/parser-functions.js.map +1 -0
- package/dist/parser/parser-literals.d.ts +22 -0
- package/dist/parser/parser-literals.d.ts.map +1 -0
- package/dist/parser/parser-literals.js +288 -0
- package/dist/parser/parser-literals.js.map +1 -0
- package/dist/parser/parser-script.d.ts +21 -0
- package/dist/parser/parser-script.d.ts.map +1 -0
- package/dist/parser/parser-script.js +174 -0
- package/dist/parser/parser-script.js.map +1 -0
- package/dist/parser/parser-variables.d.ts +20 -0
- package/dist/parser/parser-variables.d.ts.map +1 -0
- package/dist/parser/parser-variables.js +146 -0
- package/dist/parser/parser-variables.js.map +1 -0
- package/dist/parser/parser.d.ts +49 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +54 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/script.d.ts +14 -0
- package/dist/parser/script.d.ts.map +1 -0
- package/dist/parser/script.js +196 -0
- package/dist/parser/script.js.map +1 -0
- package/dist/parser/state.d.ts +40 -0
- package/dist/parser/state.d.ts.map +1 -0
- package/dist/parser/state.js +129 -0
- package/dist/parser/state.js.map +1 -0
- package/dist/parser/variables.d.ts +10 -0
- package/dist/parser/variables.d.ts.map +1 -0
- package/dist/parser/variables.js +215 -0
- package/dist/parser/variables.js.map +1 -0
- package/dist/runtime/ast-equals.d.ts +13 -0
- package/dist/runtime/ast-equals.d.ts.map +1 -0
- package/dist/runtime/ast-equals.js +447 -0
- package/dist/runtime/ast-equals.js.map +1 -0
- package/dist/runtime/builtins.d.ts +13 -0
- package/dist/runtime/builtins.d.ts.map +1 -0
- package/dist/runtime/builtins.js +180 -0
- package/dist/runtime/builtins.js.map +1 -0
- package/dist/runtime/callable.d.ts +88 -0
- package/dist/runtime/callable.d.ts.map +1 -0
- package/dist/runtime/callable.js +98 -0
- package/dist/runtime/callable.js.map +1 -0
- package/dist/runtime/context.d.ts +13 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +73 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/core/callable.d.ts +171 -0
- package/dist/runtime/core/callable.d.ts.map +1 -0
- package/dist/runtime/core/callable.js +246 -0
- package/dist/runtime/core/callable.js.map +1 -0
- package/dist/runtime/core/context.d.ts +29 -0
- package/dist/runtime/core/context.d.ts.map +1 -0
- package/dist/runtime/core/context.js +154 -0
- package/dist/runtime/core/context.js.map +1 -0
- package/dist/runtime/core/equals.d.ts +9 -0
- package/dist/runtime/core/equals.d.ts.map +1 -0
- package/dist/runtime/core/equals.js +381 -0
- package/dist/runtime/core/equals.js.map +1 -0
- package/dist/runtime/core/eval/base.d.ts +65 -0
- package/dist/runtime/core/eval/base.d.ts.map +1 -0
- package/dist/runtime/core/eval/base.js +112 -0
- package/dist/runtime/core/eval/base.js.map +1 -0
- package/dist/runtime/core/eval/evaluator.d.ts +47 -0
- package/dist/runtime/core/eval/evaluator.d.ts.map +1 -0
- package/dist/runtime/core/eval/evaluator.js +73 -0
- package/dist/runtime/core/eval/evaluator.js.map +1 -0
- package/dist/runtime/core/eval/index.d.ts +57 -0
- package/dist/runtime/core/eval/index.d.ts.map +1 -0
- package/dist/runtime/core/eval/index.js +95 -0
- package/dist/runtime/core/eval/index.js.map +1 -0
- package/dist/runtime/core/eval/mixins/annotations.d.ts +19 -0
- package/dist/runtime/core/eval/mixins/annotations.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/annotations.js +146 -0
- package/dist/runtime/core/eval/mixins/annotations.js.map +1 -0
- package/dist/runtime/core/eval/mixins/closures.d.ts +49 -0
- package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/closures.js +479 -0
- package/dist/runtime/core/eval/mixins/closures.js.map +1 -0
- package/dist/runtime/core/eval/mixins/collections.d.ts +24 -0
- package/dist/runtime/core/eval/mixins/collections.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/collections.js +466 -0
- package/dist/runtime/core/eval/mixins/collections.js.map +1 -0
- package/dist/runtime/core/eval/mixins/control-flow.d.ts +27 -0
- package/dist/runtime/core/eval/mixins/control-flow.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/control-flow.js +369 -0
- package/dist/runtime/core/eval/mixins/control-flow.js.map +1 -0
- package/dist/runtime/core/eval/mixins/core.d.ts +24 -0
- package/dist/runtime/core/eval/mixins/core.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/core.js +335 -0
- package/dist/runtime/core/eval/mixins/core.js.map +1 -0
- package/dist/runtime/core/eval/mixins/expressions.d.ts +19 -0
- package/dist/runtime/core/eval/mixins/expressions.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/expressions.js +202 -0
- package/dist/runtime/core/eval/mixins/expressions.js.map +1 -0
- package/dist/runtime/core/eval/mixins/extraction.d.ts +10 -0
- package/dist/runtime/core/eval/mixins/extraction.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/extraction.js +250 -0
- package/dist/runtime/core/eval/mixins/extraction.js.map +1 -0
- package/dist/runtime/core/eval/mixins/literals.d.ts +23 -0
- package/dist/runtime/core/eval/mixins/literals.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/literals.js +180 -0
- package/dist/runtime/core/eval/mixins/literals.js.map +1 -0
- package/dist/runtime/core/eval/mixins/types.d.ts +20 -0
- package/dist/runtime/core/eval/mixins/types.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/types.js +109 -0
- package/dist/runtime/core/eval/mixins/types.js.map +1 -0
- package/dist/runtime/core/eval/mixins/variables.d.ts +34 -0
- package/dist/runtime/core/eval/mixins/variables.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/variables.js +247 -0
- package/dist/runtime/core/eval/mixins/variables.js.map +1 -0
- package/dist/runtime/core/eval/types.d.ts +41 -0
- package/dist/runtime/core/eval/types.d.ts.map +1 -0
- package/dist/runtime/core/eval/types.js +10 -0
- package/dist/runtime/core/eval/types.js.map +1 -0
- package/dist/runtime/core/evaluate.d.ts +42 -0
- package/dist/runtime/core/evaluate.d.ts.map +1 -0
- package/dist/runtime/core/evaluate.debug.js +1251 -0
- package/dist/runtime/core/evaluate.js +1913 -0
- package/dist/runtime/core/evaluate.js.map +1 -0
- package/dist/runtime/core/execute.d.ts +26 -0
- package/dist/runtime/core/execute.d.ts.map +1 -0
- package/dist/runtime/core/execute.js +177 -0
- package/dist/runtime/core/execute.js.map +1 -0
- package/dist/runtime/core/signals.d.ts +19 -0
- package/dist/runtime/core/signals.d.ts.map +1 -0
- package/dist/runtime/core/signals.js +26 -0
- package/dist/runtime/core/signals.js.map +1 -0
- package/dist/runtime/core/types.d.ts +177 -0
- package/dist/runtime/core/types.d.ts.map +1 -0
- package/dist/runtime/core/types.js +50 -0
- package/dist/runtime/core/types.js.map +1 -0
- package/dist/runtime/core/values.d.ts +66 -0
- package/dist/runtime/core/values.d.ts.map +1 -0
- package/dist/runtime/core/values.js +240 -0
- package/dist/runtime/core/values.js.map +1 -0
- package/dist/runtime/evaluate.d.ts +32 -0
- package/dist/runtime/evaluate.d.ts.map +1 -0
- package/dist/runtime/evaluate.js +1111 -0
- package/dist/runtime/evaluate.js.map +1 -0
- package/dist/runtime/execute.d.ts +26 -0
- package/dist/runtime/execute.d.ts.map +1 -0
- package/dist/runtime/execute.js +121 -0
- package/dist/runtime/execute.js.map +1 -0
- package/dist/runtime/ext/builtins.d.ts +16 -0
- package/dist/runtime/ext/builtins.d.ts.map +1 -0
- package/dist/runtime/ext/builtins.js +528 -0
- package/dist/runtime/ext/builtins.js.map +1 -0
- package/dist/runtime/ext/content-parser.d.ts +83 -0
- package/dist/runtime/ext/content-parser.d.ts.map +1 -0
- package/dist/runtime/ext/content-parser.js +536 -0
- package/dist/runtime/ext/content-parser.js.map +1 -0
- package/dist/runtime/index.d.ts +28 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +34 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/signals.d.ts +19 -0
- package/dist/runtime/signals.d.ts.map +1 -0
- package/dist/runtime/signals.js +26 -0
- package/dist/runtime/signals.js.map +1 -0
- package/dist/runtime/types.d.ts +169 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +50 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/values.d.ts +50 -0
- package/dist/runtime/values.d.ts.map +1 -0
- package/dist/runtime/values.js +209 -0
- package/dist/runtime/values.js.map +1 -0
- package/dist/runtime.d.ts +254 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +2014 -0
- package/dist/runtime.js.map +1 -0
- package/dist/types.d.ts +752 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +189 -0
- package/dist/types.js.map +1 -0
- package/docs/00_INDEX.md +65 -0
- package/docs/01_guide.md +390 -0
- package/docs/02_types.md +399 -0
- package/docs/03_variables.md +314 -0
- package/docs/04_operators.md +551 -0
- package/docs/05_control-flow.md +350 -0
- package/docs/06_closures.md +353 -0
- package/docs/07_collections.md +686 -0
- package/docs/08_iterators.md +330 -0
- package/docs/09_strings.md +205 -0
- package/docs/10_parsing.md +366 -0
- package/docs/11_reference.md +350 -0
- package/docs/12_examples.md +771 -0
- package/docs/13_modules.md +519 -0
- package/docs/14_host-integration.md +826 -0
- package/docs/15_grammar.ebnf +693 -0
- package/docs/16_conventions.md +696 -0
- package/docs/99_llm-reference.txt +300 -0
- package/docs/assets/logo.png +0 -0
- package/package.json +70 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# Module Convention
|
|
2
|
+
|
|
3
|
+
This guide describes a convention for hosts implementing module support in Rill scripts. Modules enable code reuse across scripts while preserving Rill's core principle: **frontmatter is opaque to Rill**.
|
|
4
|
+
|
|
5
|
+
The convention defines:
|
|
6
|
+
- Frontmatter keys (`use`, `export`) for declaring dependencies and public APIs
|
|
7
|
+
- Path prefixes (`@core/`, `@host/`, relative) for module resolution
|
|
8
|
+
- Host responsibilities for loading, caching, and binding modules
|
|
9
|
+
|
|
10
|
+
Rill itself does not interpret these keys. The host parses frontmatter and provides resolved modules via the `variables` option.
|
|
11
|
+
|
|
12
|
+
## Design Principles
|
|
13
|
+
|
|
14
|
+
1. **Modules are dicts** — No new type; exports are dict members
|
|
15
|
+
2. **Host-provided resolution** — Host parses frontmatter and resolves paths
|
|
16
|
+
3. **Pipe-compatible** — Modules export values (closures, literals), not side effects
|
|
17
|
+
4. **Explicit over implicit** — No auto-imports or magic globals
|
|
18
|
+
5. **Namespace alignment** — Uses existing `$namespace.member` pattern
|
|
19
|
+
|
|
20
|
+
## Syntax Overview
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
---
|
|
24
|
+
use:
|
|
25
|
+
- math: "./utils/math.rill"
|
|
26
|
+
- str: "@core/string"
|
|
27
|
+
- http: "@host/http"
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
5 :> $math.double()
|
|
31
|
+
"hello" :> $str.reverse()
|
|
32
|
+
$http.get("https://api.example.com")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The host:
|
|
36
|
+
1. Parses frontmatter YAML
|
|
37
|
+
2. Extracts `use` declarations
|
|
38
|
+
3. Resolves each specifier to a module
|
|
39
|
+
4. Passes resolved modules via `createRuntimeContext({ variables: { math, str, http } })`
|
|
40
|
+
|
|
41
|
+
Rill sees `$math`, `$str`, `$http` as regular variables containing dicts.
|
|
42
|
+
|
|
43
|
+
## Import Declaration
|
|
44
|
+
|
|
45
|
+
Imports appear in frontmatter under the `use` key:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
---
|
|
49
|
+
use:
|
|
50
|
+
- math: "./utils/math.rill"
|
|
51
|
+
- m: "./utils/math.rill" # Same module, different name
|
|
52
|
+
---
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
All imports require a name (the key before the colon). This ensures:
|
|
56
|
+
- Clear origin of every symbol (`$math.double` vs bare `$double`)
|
|
57
|
+
- No hidden name collisions
|
|
58
|
+
- Grep-friendly code (search for `$math.` finds all usages)
|
|
59
|
+
|
|
60
|
+
### Path Prefixes
|
|
61
|
+
|
|
62
|
+
| Prefix | Meaning | Resolution |
|
|
63
|
+
|--------|---------|------------|
|
|
64
|
+
| `./` or `../` | Relative path | Host filesystem |
|
|
65
|
+
| `@core/` | Core modules | Host-provided standard library |
|
|
66
|
+
| `@host/` | Host modules | Host-specific functionality |
|
|
67
|
+
| `name` (bare) | Registry package | Host package resolver |
|
|
68
|
+
|
|
69
|
+
## Export Declaration
|
|
70
|
+
|
|
71
|
+
Scripts export values via the `export` frontmatter key:
|
|
72
|
+
|
|
73
|
+
```rill
|
|
74
|
+
---
|
|
75
|
+
export:
|
|
76
|
+
- double
|
|
77
|
+
- triple
|
|
78
|
+
- constants
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
|x|($x * 2) :> $double
|
|
82
|
+
|x|($x * 3) :> $triple
|
|
83
|
+
[pi: 3.14159, e: 2.71828] :> $constants
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The host:
|
|
87
|
+
1. Executes the script
|
|
88
|
+
2. Reads the `export` list from frontmatter
|
|
89
|
+
3. Extracts named variables from the execution result
|
|
90
|
+
4. Returns them as a dict
|
|
91
|
+
|
|
92
|
+
## Module Structure
|
|
93
|
+
|
|
94
|
+
A module is a Rill script with frontmatter declaring exports:
|
|
95
|
+
|
|
96
|
+
```rill
|
|
97
|
+
# utils/math.rill
|
|
98
|
+
---
|
|
99
|
+
export:
|
|
100
|
+
- double
|
|
101
|
+
- triple
|
|
102
|
+
- clamp
|
|
103
|
+
- constants
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
# Closure exports
|
|
107
|
+
|x|($x * 2) :> $double
|
|
108
|
+
|x|($x * 3) :> $triple
|
|
109
|
+
|x, min, max|{
|
|
110
|
+
($x < $min) ? $min ! ($x > $max) ? $max ! $x
|
|
111
|
+
} :> $clamp
|
|
112
|
+
|
|
113
|
+
# Literal exports (dicts, lists, numbers, strings)
|
|
114
|
+
[pi: 3.14159, e: 2.71828, phi: 1.61803] :> $constants
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Usage:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
---
|
|
121
|
+
use:
|
|
122
|
+
- math: "./utils/math.rill"
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
$math.double(5) # 10
|
|
126
|
+
$math.constants.pi # 3.14159
|
|
127
|
+
$math.constants -> .keys # ["pi", "e", "phi"]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Import Binding
|
|
131
|
+
|
|
132
|
+
A module's exports form a dict. The host binds this dict to the import name:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
---
|
|
136
|
+
use:
|
|
137
|
+
- math: "./utils/math.rill"
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
# $math is a dict: [double: closure, triple: closure, clamp: closure, constants: dict]
|
|
141
|
+
$math.double(5) # 10
|
|
142
|
+
$math.clamp(15, 0, 10) # 10
|
|
143
|
+
$math.constants.pi # 3.14159
|
|
144
|
+
|
|
145
|
+
# Standard dict operations work
|
|
146
|
+
$math -> .keys # ["double", "triple", "clamp", "constants"]
|
|
147
|
+
$math -> type # "dict"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
This aligns with Rill's existing patterns:
|
|
151
|
+
- Dict callables with `$obj.method()` syntax
|
|
152
|
+
- Host variables via `createRuntimeContext({ variables: { namespace: { fn: callable(...) } } })`
|
|
153
|
+
- No new concepts—modules are dicts, exports are members
|
|
154
|
+
|
|
155
|
+
## Host Implementation
|
|
156
|
+
|
|
157
|
+
### Core Types
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
type ModuleResolver = (
|
|
161
|
+
specifier: string,
|
|
162
|
+
fromPath: string
|
|
163
|
+
) => Promise<ModuleResult>;
|
|
164
|
+
|
|
165
|
+
interface ModuleResult {
|
|
166
|
+
exports: Record<string, RillValue>;
|
|
167
|
+
path: string; // Canonical path for caching
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Minimal Implementation
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { parse, execute, createRuntimeContext, callable } from '@rcrsr/rill';
|
|
175
|
+
import * as fs from 'fs/promises';
|
|
176
|
+
import * as path from 'path';
|
|
177
|
+
import * as yaml from 'yaml';
|
|
178
|
+
|
|
179
|
+
async function loadModule(
|
|
180
|
+
specifier: string,
|
|
181
|
+
fromPath: string,
|
|
182
|
+
cache: Map<string, Record<string, RillValue>>
|
|
183
|
+
): Promise<Record<string, RillValue>> {
|
|
184
|
+
// Resolve path
|
|
185
|
+
const absolutePath = path.resolve(path.dirname(fromPath), specifier);
|
|
186
|
+
|
|
187
|
+
// Check cache
|
|
188
|
+
if (cache.has(absolutePath)) {
|
|
189
|
+
return cache.get(absolutePath)!;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Load and parse
|
|
193
|
+
const source = await fs.readFile(absolutePath, 'utf-8');
|
|
194
|
+
const ast = parse(source);
|
|
195
|
+
|
|
196
|
+
// Extract frontmatter
|
|
197
|
+
const frontmatter = ast.frontmatter
|
|
198
|
+
? yaml.parse(ast.frontmatter.content)
|
|
199
|
+
: {};
|
|
200
|
+
|
|
201
|
+
// Resolve dependencies first
|
|
202
|
+
const imports: Record<string, RillValue> = {};
|
|
203
|
+
if (frontmatter.use) {
|
|
204
|
+
for (const entry of frontmatter.use) {
|
|
205
|
+
const [name, depPath] = Object.entries(entry)[0] as [string, string];
|
|
206
|
+
imports[name] = await loadModule(depPath, absolutePath, cache);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Execute module
|
|
211
|
+
const ctx = createRuntimeContext({ variables: imports });
|
|
212
|
+
const result = await execute(ast, ctx);
|
|
213
|
+
|
|
214
|
+
// Extract exports
|
|
215
|
+
const exports: Record<string, RillValue> = {};
|
|
216
|
+
const exportList: string[] = frontmatter.export ?? [];
|
|
217
|
+
for (const name of exportList) {
|
|
218
|
+
if (result.variables[name] !== undefined) {
|
|
219
|
+
exports[name] = result.variables[name];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
cache.set(absolutePath, exports);
|
|
224
|
+
return exports;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Usage
|
|
228
|
+
async function runScript(entryPath: string) {
|
|
229
|
+
const cache = new Map();
|
|
230
|
+
const source = await fs.readFile(entryPath, 'utf-8');
|
|
231
|
+
const ast = parse(source);
|
|
232
|
+
|
|
233
|
+
const frontmatter = ast.frontmatter
|
|
234
|
+
? yaml.parse(ast.frontmatter.content)
|
|
235
|
+
: {};
|
|
236
|
+
|
|
237
|
+
// Load imports
|
|
238
|
+
const variables: Record<string, RillValue> = {};
|
|
239
|
+
if (frontmatter.use) {
|
|
240
|
+
for (const entry of frontmatter.use) {
|
|
241
|
+
const [name, specifier] = Object.entries(entry)[0] as [string, string];
|
|
242
|
+
variables[name] = await loadModule(specifier, entryPath, cache);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const ctx = createRuntimeContext({ variables });
|
|
247
|
+
return execute(ast, ctx);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Circular Import Detection
|
|
252
|
+
|
|
253
|
+
Track the import chain to detect cycles:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
async function loadModule(
|
|
257
|
+
specifier: string,
|
|
258
|
+
fromPath: string,
|
|
259
|
+
cache: Map<string, Record<string, RillValue>>,
|
|
260
|
+
chain: Set<string> = new Set()
|
|
261
|
+
): Promise<Record<string, RillValue>> {
|
|
262
|
+
const absolutePath = path.resolve(path.dirname(fromPath), specifier);
|
|
263
|
+
|
|
264
|
+
if (chain.has(absolutePath)) {
|
|
265
|
+
const cycle = [...chain, absolutePath].join(' -> ');
|
|
266
|
+
throw new Error(`Circular dependency detected: ${cycle}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (cache.has(absolutePath)) {
|
|
270
|
+
return cache.get(absolutePath)!;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
chain.add(absolutePath);
|
|
274
|
+
// ... load and execute ...
|
|
275
|
+
chain.delete(absolutePath);
|
|
276
|
+
|
|
277
|
+
cache.set(absolutePath, exports);
|
|
278
|
+
return exports;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Host Functions with Namespaces
|
|
283
|
+
|
|
284
|
+
For host-specific functionality, you can use namespaced functions with `::` syntax instead of the module convention. This is simpler when you don't need the full module system:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const ctx = createRuntimeContext({
|
|
288
|
+
functions: {
|
|
289
|
+
'http::get': async (args) => {
|
|
290
|
+
const response = await fetch(String(args[0]));
|
|
291
|
+
return response.text();
|
|
292
|
+
},
|
|
293
|
+
'http::post': async (args) => {
|
|
294
|
+
const response = await fetch(String(args[0]), {
|
|
295
|
+
method: 'POST',
|
|
296
|
+
body: String(args[1]),
|
|
297
|
+
});
|
|
298
|
+
return response.text();
|
|
299
|
+
},
|
|
300
|
+
'fs::read': async (args) => fs.readFile(String(args[0]), 'utf-8'),
|
|
301
|
+
'fs::write': async (args) => {
|
|
302
|
+
await fs.writeFile(String(args[0]), String(args[1]));
|
|
303
|
+
return true;
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Scripts call these directly:
|
|
310
|
+
|
|
311
|
+
```rill
|
|
312
|
+
http::get("https://api.example.com") -> parse_json
|
|
313
|
+
fs::read("config.json") -> parse_json :> $config
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Host Modules (@host/) — Alternative
|
|
317
|
+
|
|
318
|
+
For more complex scenarios, register host modules as dicts with callable members:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { callable } from '@rcrsr/rill';
|
|
322
|
+
|
|
323
|
+
const hostModules: Record<string, Record<string, RillValue>> = {
|
|
324
|
+
'@host/http': {
|
|
325
|
+
get: callable(async (args) => {
|
|
326
|
+
const response = await fetch(String(args[0]));
|
|
327
|
+
return response.text();
|
|
328
|
+
}),
|
|
329
|
+
post: callable(async (args) => {
|
|
330
|
+
const response = await fetch(String(args[0]), {
|
|
331
|
+
method: 'POST',
|
|
332
|
+
body: String(args[1]),
|
|
333
|
+
});
|
|
334
|
+
return response.text();
|
|
335
|
+
}),
|
|
336
|
+
},
|
|
337
|
+
'@host/fs': {
|
|
338
|
+
read: callable(async (args) => fs.readFile(String(args[0]), 'utf-8')),
|
|
339
|
+
write: callable(async (args) => {
|
|
340
|
+
await fs.writeFile(String(args[0]), String(args[1]));
|
|
341
|
+
return true;
|
|
342
|
+
}),
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// In resolver
|
|
347
|
+
if (specifier.startsWith('@host/')) {
|
|
348
|
+
return hostModules[specifier] ?? {};
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
This approach requires the `$` prefix (`$http.get()`) but allows passing modules as values.
|
|
353
|
+
|
|
354
|
+
### Core Modules (@core/)
|
|
355
|
+
|
|
356
|
+
Suggested standard library modules (host-provided):
|
|
357
|
+
|
|
358
|
+
| Module | Exports |
|
|
359
|
+
|--------|---------|
|
|
360
|
+
| `@core/string` | `reverse`, `capitalize`, `words`, `lines` |
|
|
361
|
+
| `@core/list` | `sort`, `reverse`, `unique`, `flatten` |
|
|
362
|
+
| `@core/math` | `abs`, `min`, `max`, `floor`, `ceil`, `round` |
|
|
363
|
+
| `@core/json` | `parse`, `stringify`, `pretty` |
|
|
364
|
+
|
|
365
|
+
Hosts can implement these in Rill or TypeScript. Consistency across hosts improves script portability.
|
|
366
|
+
|
|
367
|
+
## Examples
|
|
368
|
+
|
|
369
|
+
### Basic Module
|
|
370
|
+
|
|
371
|
+
```rill
|
|
372
|
+
# greet.rill
|
|
373
|
+
---
|
|
374
|
+
export:
|
|
375
|
+
- hello
|
|
376
|
+
- goodbye
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
|name|"Hello, {$name}!" :> $hello
|
|
380
|
+
|name|"Goodbye, {$name}!" :> $goodbye
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Using a Module
|
|
384
|
+
|
|
385
|
+
```text
|
|
386
|
+
---
|
|
387
|
+
use:
|
|
388
|
+
- greet: "./greet.rill"
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
"World" :> $greet.hello() -> log
|
|
392
|
+
# Output: Hello, World!
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Re-exporting
|
|
396
|
+
|
|
397
|
+
Imported modules can be re-exported:
|
|
398
|
+
|
|
399
|
+
```text
|
|
400
|
+
# utils/index.rill
|
|
401
|
+
---
|
|
402
|
+
use:
|
|
403
|
+
- math: "./math.rill"
|
|
404
|
+
- str: "./string.rill"
|
|
405
|
+
export:
|
|
406
|
+
- math
|
|
407
|
+
- str
|
|
408
|
+
---
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
The importing script sees nested namespaces:
|
|
412
|
+
|
|
413
|
+
```text
|
|
414
|
+
---
|
|
415
|
+
use:
|
|
416
|
+
- utils: "./utils/index.rill"
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
5 :> $utils.math.double()
|
|
420
|
+
"hello" :> $utils.str.reverse()
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Private Helpers
|
|
424
|
+
|
|
425
|
+
Non-exported variables remain private:
|
|
426
|
+
|
|
427
|
+
```rill
|
|
428
|
+
---
|
|
429
|
+
export:
|
|
430
|
+
- processAll
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
# Private helper (not exported)
|
|
434
|
+
|item|{
|
|
435
|
+
$item -> .upper -> .trim
|
|
436
|
+
} :> $normalizeItem
|
|
437
|
+
|
|
438
|
+
# Public function using private helper
|
|
439
|
+
|items|{
|
|
440
|
+
$items -> map $normalizeItem
|
|
441
|
+
} :> $processAll
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Module Loading Phases
|
|
445
|
+
|
|
446
|
+
1. **Parse** — Parse source, extract frontmatter as raw YAML
|
|
447
|
+
2. **Resolve** — Host resolves import specifiers to paths
|
|
448
|
+
3. **Load** — Recursively load dependencies (detect cycles)
|
|
449
|
+
4. **Execute** — Execute module body, collect variables
|
|
450
|
+
5. **Extract** — Build export dict from `export` list
|
|
451
|
+
6. **Bind** — Pass to importing script via `variables` option
|
|
452
|
+
|
|
453
|
+
### Caching
|
|
454
|
+
|
|
455
|
+
Cache modules by canonical path. Same module imported multiple times shares the same export object:
|
|
456
|
+
|
|
457
|
+
```rill
|
|
458
|
+
# ...
|
|
459
|
+
# Both reference the same loaded module
|
|
460
|
+
# ---
|
|
461
|
+
# use:
|
|
462
|
+
# - a: "./utils/math.rill"
|
|
463
|
+
# - b: "../project/utils/math.rill" # Same canonical path
|
|
464
|
+
# ---
|
|
465
|
+
# $a == $b # true (same export object)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Design Rationale
|
|
469
|
+
|
|
470
|
+
### Why Frontmatter?
|
|
471
|
+
|
|
472
|
+
Alternatives considered:
|
|
473
|
+
|
|
474
|
+
**Import statements** (`import math from "./math.rill"`)
|
|
475
|
+
- Rejected: Introduces new statement type. Frontmatter keeps imports declarative and separate from executable code.
|
|
476
|
+
|
|
477
|
+
**Pipe-based imports** (`"./math.rill" -> import :> $math`)
|
|
478
|
+
- Rejected: Imports are static metadata, not runtime operations.
|
|
479
|
+
|
|
480
|
+
**Global registry** (`$modules.math.double(5)`)
|
|
481
|
+
- Rejected: Creates implicit global state. Explicit imports are clearer.
|
|
482
|
+
|
|
483
|
+
### Why Host-Managed?
|
|
484
|
+
|
|
485
|
+
Keeping module resolution in the host:
|
|
486
|
+
- Preserves "frontmatter is opaque" principle
|
|
487
|
+
- Allows hosts to customize resolution (virtual modules, access control, caching)
|
|
488
|
+
- Keeps Rill runtime dependency-free
|
|
489
|
+
- Enables different hosts to support different module ecosystems
|
|
490
|
+
|
|
491
|
+
## Open Questions
|
|
492
|
+
|
|
493
|
+
### Selective Imports
|
|
494
|
+
|
|
495
|
+
Should scripts import specific members?
|
|
496
|
+
|
|
497
|
+
```yaml
|
|
498
|
+
use:
|
|
499
|
+
- double: "./math.rill".double
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Recommendation:** Defer. Full-module imports are simpler and grep-friendly.
|
|
503
|
+
|
|
504
|
+
### Version Constraints
|
|
505
|
+
|
|
506
|
+
Should specifiers support versions?
|
|
507
|
+
|
|
508
|
+
```yaml
|
|
509
|
+
use:
|
|
510
|
+
- lodash: "lodash@^4.0.0"
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Recommendation:** Leave to host/registry. Specifiers pass through verbatim.
|
|
514
|
+
|
|
515
|
+
### Type Exports
|
|
516
|
+
|
|
517
|
+
Should modules export type information?
|
|
518
|
+
|
|
519
|
+
**Recommendation:** Defer. Types are not enforced at runtime; tooling can infer from execution.
|