@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.
Files changed (295) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -0
  3. package/dist/cli.d.ts +11 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +69 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/demo.d.ts +6 -0
  8. package/dist/demo.d.ts.map +1 -0
  9. package/dist/demo.js +121 -0
  10. package/dist/demo.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +9 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/lexer/errors.d.ts +9 -0
  16. package/dist/lexer/errors.d.ts.map +1 -0
  17. package/dist/lexer/errors.js +12 -0
  18. package/dist/lexer/errors.js.map +1 -0
  19. package/dist/lexer/helpers.d.ts +14 -0
  20. package/dist/lexer/helpers.d.ts.map +1 -0
  21. package/dist/lexer/helpers.js +30 -0
  22. package/dist/lexer/helpers.js.map +1 -0
  23. package/dist/lexer/index.d.ts +8 -0
  24. package/dist/lexer/index.d.ts.map +1 -0
  25. package/dist/lexer/index.js +8 -0
  26. package/dist/lexer/index.js.map +1 -0
  27. package/dist/lexer/operators.d.ts +11 -0
  28. package/dist/lexer/operators.d.ts.map +1 -0
  29. package/dist/lexer/operators.js +58 -0
  30. package/dist/lexer/operators.js.map +1 -0
  31. package/dist/lexer/readers.d.ts +12 -0
  32. package/dist/lexer/readers.d.ts.map +1 -0
  33. package/dist/lexer/readers.js +144 -0
  34. package/dist/lexer/readers.js.map +1 -0
  35. package/dist/lexer/state.d.ts +18 -0
  36. package/dist/lexer/state.d.ts.map +1 -0
  37. package/dist/lexer/state.js +37 -0
  38. package/dist/lexer/state.js.map +1 -0
  39. package/dist/lexer/tokenizer.d.ts +9 -0
  40. package/dist/lexer/tokenizer.d.ts.map +1 -0
  41. package/dist/lexer/tokenizer.js +100 -0
  42. package/dist/lexer/tokenizer.js.map +1 -0
  43. package/dist/lexer.d.ts +19 -0
  44. package/dist/lexer.d.ts.map +1 -0
  45. package/dist/lexer.js +344 -0
  46. package/dist/lexer.js.map +1 -0
  47. package/dist/parser/arithmetic.d.ts +16 -0
  48. package/dist/parser/arithmetic.d.ts.map +1 -0
  49. package/dist/parser/arithmetic.js +128 -0
  50. package/dist/parser/arithmetic.js.map +1 -0
  51. package/dist/parser/boolean.d.ts +15 -0
  52. package/dist/parser/boolean.d.ts.map +1 -0
  53. package/dist/parser/boolean.js +20 -0
  54. package/dist/parser/boolean.js.map +1 -0
  55. package/dist/parser/control-flow.d.ts +56 -0
  56. package/dist/parser/control-flow.d.ts.map +1 -0
  57. package/dist/parser/control-flow.js +167 -0
  58. package/dist/parser/control-flow.js.map +1 -0
  59. package/dist/parser/expressions.d.ts +23 -0
  60. package/dist/parser/expressions.d.ts.map +1 -0
  61. package/dist/parser/expressions.js +950 -0
  62. package/dist/parser/expressions.js.map +1 -0
  63. package/dist/parser/extraction.d.ts +48 -0
  64. package/dist/parser/extraction.d.ts.map +1 -0
  65. package/dist/parser/extraction.js +279 -0
  66. package/dist/parser/extraction.js.map +1 -0
  67. package/dist/parser/functions.d.ts +20 -0
  68. package/dist/parser/functions.d.ts.map +1 -0
  69. package/dist/parser/functions.js +96 -0
  70. package/dist/parser/functions.js.map +1 -0
  71. package/dist/parser/helpers.d.ts +94 -0
  72. package/dist/parser/helpers.d.ts.map +1 -0
  73. package/dist/parser/helpers.js +225 -0
  74. package/dist/parser/helpers.js.map +1 -0
  75. package/dist/parser/index.d.ts +49 -0
  76. package/dist/parser/index.d.ts.map +1 -0
  77. package/dist/parser/index.js +73 -0
  78. package/dist/parser/index.js.map +1 -0
  79. package/dist/parser/literals.d.ts +37 -0
  80. package/dist/parser/literals.d.ts.map +1 -0
  81. package/dist/parser/literals.js +373 -0
  82. package/dist/parser/literals.js.map +1 -0
  83. package/dist/parser/parser-collect.d.ts +16 -0
  84. package/dist/parser/parser-collect.d.ts.map +1 -0
  85. package/dist/parser/parser-collect.js +125 -0
  86. package/dist/parser/parser-collect.js.map +1 -0
  87. package/dist/parser/parser-control.d.ts +20 -0
  88. package/dist/parser/parser-control.d.ts.map +1 -0
  89. package/dist/parser/parser-control.js +120 -0
  90. package/dist/parser/parser-control.js.map +1 -0
  91. package/dist/parser/parser-expr.d.ts +37 -0
  92. package/dist/parser/parser-expr.d.ts.map +1 -0
  93. package/dist/parser/parser-expr.js +639 -0
  94. package/dist/parser/parser-expr.js.map +1 -0
  95. package/dist/parser/parser-extract.d.ts +17 -0
  96. package/dist/parser/parser-extract.d.ts.map +1 -0
  97. package/dist/parser/parser-extract.js +222 -0
  98. package/dist/parser/parser-extract.js.map +1 -0
  99. package/dist/parser/parser-functions.d.ts +21 -0
  100. package/dist/parser/parser-functions.d.ts.map +1 -0
  101. package/dist/parser/parser-functions.js +155 -0
  102. package/dist/parser/parser-functions.js.map +1 -0
  103. package/dist/parser/parser-literals.d.ts +22 -0
  104. package/dist/parser/parser-literals.d.ts.map +1 -0
  105. package/dist/parser/parser-literals.js +288 -0
  106. package/dist/parser/parser-literals.js.map +1 -0
  107. package/dist/parser/parser-script.d.ts +21 -0
  108. package/dist/parser/parser-script.d.ts.map +1 -0
  109. package/dist/parser/parser-script.js +174 -0
  110. package/dist/parser/parser-script.js.map +1 -0
  111. package/dist/parser/parser-variables.d.ts +20 -0
  112. package/dist/parser/parser-variables.d.ts.map +1 -0
  113. package/dist/parser/parser-variables.js +146 -0
  114. package/dist/parser/parser-variables.js.map +1 -0
  115. package/dist/parser/parser.d.ts +49 -0
  116. package/dist/parser/parser.d.ts.map +1 -0
  117. package/dist/parser/parser.js +54 -0
  118. package/dist/parser/parser.js.map +1 -0
  119. package/dist/parser/script.d.ts +14 -0
  120. package/dist/parser/script.d.ts.map +1 -0
  121. package/dist/parser/script.js +196 -0
  122. package/dist/parser/script.js.map +1 -0
  123. package/dist/parser/state.d.ts +40 -0
  124. package/dist/parser/state.d.ts.map +1 -0
  125. package/dist/parser/state.js +129 -0
  126. package/dist/parser/state.js.map +1 -0
  127. package/dist/parser/variables.d.ts +10 -0
  128. package/dist/parser/variables.d.ts.map +1 -0
  129. package/dist/parser/variables.js +215 -0
  130. package/dist/parser/variables.js.map +1 -0
  131. package/dist/runtime/ast-equals.d.ts +13 -0
  132. package/dist/runtime/ast-equals.d.ts.map +1 -0
  133. package/dist/runtime/ast-equals.js +447 -0
  134. package/dist/runtime/ast-equals.js.map +1 -0
  135. package/dist/runtime/builtins.d.ts +13 -0
  136. package/dist/runtime/builtins.d.ts.map +1 -0
  137. package/dist/runtime/builtins.js +180 -0
  138. package/dist/runtime/builtins.js.map +1 -0
  139. package/dist/runtime/callable.d.ts +88 -0
  140. package/dist/runtime/callable.d.ts.map +1 -0
  141. package/dist/runtime/callable.js +98 -0
  142. package/dist/runtime/callable.js.map +1 -0
  143. package/dist/runtime/context.d.ts +13 -0
  144. package/dist/runtime/context.d.ts.map +1 -0
  145. package/dist/runtime/context.js +73 -0
  146. package/dist/runtime/context.js.map +1 -0
  147. package/dist/runtime/core/callable.d.ts +171 -0
  148. package/dist/runtime/core/callable.d.ts.map +1 -0
  149. package/dist/runtime/core/callable.js +246 -0
  150. package/dist/runtime/core/callable.js.map +1 -0
  151. package/dist/runtime/core/context.d.ts +29 -0
  152. package/dist/runtime/core/context.d.ts.map +1 -0
  153. package/dist/runtime/core/context.js +154 -0
  154. package/dist/runtime/core/context.js.map +1 -0
  155. package/dist/runtime/core/equals.d.ts +9 -0
  156. package/dist/runtime/core/equals.d.ts.map +1 -0
  157. package/dist/runtime/core/equals.js +381 -0
  158. package/dist/runtime/core/equals.js.map +1 -0
  159. package/dist/runtime/core/eval/base.d.ts +65 -0
  160. package/dist/runtime/core/eval/base.d.ts.map +1 -0
  161. package/dist/runtime/core/eval/base.js +112 -0
  162. package/dist/runtime/core/eval/base.js.map +1 -0
  163. package/dist/runtime/core/eval/evaluator.d.ts +47 -0
  164. package/dist/runtime/core/eval/evaluator.d.ts.map +1 -0
  165. package/dist/runtime/core/eval/evaluator.js +73 -0
  166. package/dist/runtime/core/eval/evaluator.js.map +1 -0
  167. package/dist/runtime/core/eval/index.d.ts +57 -0
  168. package/dist/runtime/core/eval/index.d.ts.map +1 -0
  169. package/dist/runtime/core/eval/index.js +95 -0
  170. package/dist/runtime/core/eval/index.js.map +1 -0
  171. package/dist/runtime/core/eval/mixins/annotations.d.ts +19 -0
  172. package/dist/runtime/core/eval/mixins/annotations.d.ts.map +1 -0
  173. package/dist/runtime/core/eval/mixins/annotations.js +146 -0
  174. package/dist/runtime/core/eval/mixins/annotations.js.map +1 -0
  175. package/dist/runtime/core/eval/mixins/closures.d.ts +49 -0
  176. package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -0
  177. package/dist/runtime/core/eval/mixins/closures.js +479 -0
  178. package/dist/runtime/core/eval/mixins/closures.js.map +1 -0
  179. package/dist/runtime/core/eval/mixins/collections.d.ts +24 -0
  180. package/dist/runtime/core/eval/mixins/collections.d.ts.map +1 -0
  181. package/dist/runtime/core/eval/mixins/collections.js +466 -0
  182. package/dist/runtime/core/eval/mixins/collections.js.map +1 -0
  183. package/dist/runtime/core/eval/mixins/control-flow.d.ts +27 -0
  184. package/dist/runtime/core/eval/mixins/control-flow.d.ts.map +1 -0
  185. package/dist/runtime/core/eval/mixins/control-flow.js +369 -0
  186. package/dist/runtime/core/eval/mixins/control-flow.js.map +1 -0
  187. package/dist/runtime/core/eval/mixins/core.d.ts +24 -0
  188. package/dist/runtime/core/eval/mixins/core.d.ts.map +1 -0
  189. package/dist/runtime/core/eval/mixins/core.js +335 -0
  190. package/dist/runtime/core/eval/mixins/core.js.map +1 -0
  191. package/dist/runtime/core/eval/mixins/expressions.d.ts +19 -0
  192. package/dist/runtime/core/eval/mixins/expressions.d.ts.map +1 -0
  193. package/dist/runtime/core/eval/mixins/expressions.js +202 -0
  194. package/dist/runtime/core/eval/mixins/expressions.js.map +1 -0
  195. package/dist/runtime/core/eval/mixins/extraction.d.ts +10 -0
  196. package/dist/runtime/core/eval/mixins/extraction.d.ts.map +1 -0
  197. package/dist/runtime/core/eval/mixins/extraction.js +250 -0
  198. package/dist/runtime/core/eval/mixins/extraction.js.map +1 -0
  199. package/dist/runtime/core/eval/mixins/literals.d.ts +23 -0
  200. package/dist/runtime/core/eval/mixins/literals.d.ts.map +1 -0
  201. package/dist/runtime/core/eval/mixins/literals.js +180 -0
  202. package/dist/runtime/core/eval/mixins/literals.js.map +1 -0
  203. package/dist/runtime/core/eval/mixins/types.d.ts +20 -0
  204. package/dist/runtime/core/eval/mixins/types.d.ts.map +1 -0
  205. package/dist/runtime/core/eval/mixins/types.js +109 -0
  206. package/dist/runtime/core/eval/mixins/types.js.map +1 -0
  207. package/dist/runtime/core/eval/mixins/variables.d.ts +34 -0
  208. package/dist/runtime/core/eval/mixins/variables.d.ts.map +1 -0
  209. package/dist/runtime/core/eval/mixins/variables.js +247 -0
  210. package/dist/runtime/core/eval/mixins/variables.js.map +1 -0
  211. package/dist/runtime/core/eval/types.d.ts +41 -0
  212. package/dist/runtime/core/eval/types.d.ts.map +1 -0
  213. package/dist/runtime/core/eval/types.js +10 -0
  214. package/dist/runtime/core/eval/types.js.map +1 -0
  215. package/dist/runtime/core/evaluate.d.ts +42 -0
  216. package/dist/runtime/core/evaluate.d.ts.map +1 -0
  217. package/dist/runtime/core/evaluate.debug.js +1251 -0
  218. package/dist/runtime/core/evaluate.js +1913 -0
  219. package/dist/runtime/core/evaluate.js.map +1 -0
  220. package/dist/runtime/core/execute.d.ts +26 -0
  221. package/dist/runtime/core/execute.d.ts.map +1 -0
  222. package/dist/runtime/core/execute.js +177 -0
  223. package/dist/runtime/core/execute.js.map +1 -0
  224. package/dist/runtime/core/signals.d.ts +19 -0
  225. package/dist/runtime/core/signals.d.ts.map +1 -0
  226. package/dist/runtime/core/signals.js +26 -0
  227. package/dist/runtime/core/signals.js.map +1 -0
  228. package/dist/runtime/core/types.d.ts +177 -0
  229. package/dist/runtime/core/types.d.ts.map +1 -0
  230. package/dist/runtime/core/types.js +50 -0
  231. package/dist/runtime/core/types.js.map +1 -0
  232. package/dist/runtime/core/values.d.ts +66 -0
  233. package/dist/runtime/core/values.d.ts.map +1 -0
  234. package/dist/runtime/core/values.js +240 -0
  235. package/dist/runtime/core/values.js.map +1 -0
  236. package/dist/runtime/evaluate.d.ts +32 -0
  237. package/dist/runtime/evaluate.d.ts.map +1 -0
  238. package/dist/runtime/evaluate.js +1111 -0
  239. package/dist/runtime/evaluate.js.map +1 -0
  240. package/dist/runtime/execute.d.ts +26 -0
  241. package/dist/runtime/execute.d.ts.map +1 -0
  242. package/dist/runtime/execute.js +121 -0
  243. package/dist/runtime/execute.js.map +1 -0
  244. package/dist/runtime/ext/builtins.d.ts +16 -0
  245. package/dist/runtime/ext/builtins.d.ts.map +1 -0
  246. package/dist/runtime/ext/builtins.js +528 -0
  247. package/dist/runtime/ext/builtins.js.map +1 -0
  248. package/dist/runtime/ext/content-parser.d.ts +83 -0
  249. package/dist/runtime/ext/content-parser.d.ts.map +1 -0
  250. package/dist/runtime/ext/content-parser.js +536 -0
  251. package/dist/runtime/ext/content-parser.js.map +1 -0
  252. package/dist/runtime/index.d.ts +28 -0
  253. package/dist/runtime/index.d.ts.map +1 -0
  254. package/dist/runtime/index.js +34 -0
  255. package/dist/runtime/index.js.map +1 -0
  256. package/dist/runtime/signals.d.ts +19 -0
  257. package/dist/runtime/signals.d.ts.map +1 -0
  258. package/dist/runtime/signals.js +26 -0
  259. package/dist/runtime/signals.js.map +1 -0
  260. package/dist/runtime/types.d.ts +169 -0
  261. package/dist/runtime/types.d.ts.map +1 -0
  262. package/dist/runtime/types.js +50 -0
  263. package/dist/runtime/types.js.map +1 -0
  264. package/dist/runtime/values.d.ts +50 -0
  265. package/dist/runtime/values.d.ts.map +1 -0
  266. package/dist/runtime/values.js +209 -0
  267. package/dist/runtime/values.js.map +1 -0
  268. package/dist/runtime.d.ts +254 -0
  269. package/dist/runtime.d.ts.map +1 -0
  270. package/dist/runtime.js +2014 -0
  271. package/dist/runtime.js.map +1 -0
  272. package/dist/types.d.ts +752 -0
  273. package/dist/types.d.ts.map +1 -0
  274. package/dist/types.js +189 -0
  275. package/dist/types.js.map +1 -0
  276. package/docs/00_INDEX.md +65 -0
  277. package/docs/01_guide.md +390 -0
  278. package/docs/02_types.md +399 -0
  279. package/docs/03_variables.md +314 -0
  280. package/docs/04_operators.md +551 -0
  281. package/docs/05_control-flow.md +350 -0
  282. package/docs/06_closures.md +353 -0
  283. package/docs/07_collections.md +686 -0
  284. package/docs/08_iterators.md +330 -0
  285. package/docs/09_strings.md +205 -0
  286. package/docs/10_parsing.md +366 -0
  287. package/docs/11_reference.md +350 -0
  288. package/docs/12_examples.md +771 -0
  289. package/docs/13_modules.md +519 -0
  290. package/docs/14_host-integration.md +826 -0
  291. package/docs/15_grammar.ebnf +693 -0
  292. package/docs/16_conventions.md +696 -0
  293. package/docs/99_llm-reference.txt +300 -0
  294. package/docs/assets/logo.png +0 -0
  295. 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.