zuzu-js 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 (167) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +113 -0
  3. package/bin/zuzu +17 -0
  4. package/bin/zuzu-build-browser-bundle +57 -0
  5. package/bin/zuzu-generate-browser-stdlib +584 -0
  6. package/bin/zuzu-js +23 -0
  7. package/bin/zuzu-js-compile +152 -0
  8. package/bin/zuzu-js-electron +19 -0
  9. package/dist/zuzu-browser-worker.js +45574 -0
  10. package/dist/zuzu-browser.js +45362 -0
  11. package/lib/browser-bundle-entry.js +160 -0
  12. package/lib/browser-gui-renderer.js +387 -0
  13. package/lib/browser-runtime.js +167 -0
  14. package/lib/browser-worker-entry.js +413 -0
  15. package/lib/browser-ztests/runner.html +103 -0
  16. package/lib/browser-ztests/runner.js +369 -0
  17. package/lib/cli.js +350 -0
  18. package/lib/collections.js +367 -0
  19. package/lib/compiler.js +303 -0
  20. package/lib/electron/launcher.js +70 -0
  21. package/lib/electron/main.js +956 -0
  22. package/lib/electron/preload.js +80 -0
  23. package/lib/electron/renderer.html +122 -0
  24. package/lib/electron/renderer.js +24 -0
  25. package/lib/execution-metadata.js +18 -0
  26. package/lib/gui/dom-renderer.js +778 -0
  27. package/lib/host/browser-host.js +278 -0
  28. package/lib/host/capabilities.js +47 -0
  29. package/lib/host/electron-host.js +15 -0
  30. package/lib/host/node-host.js +74 -0
  31. package/lib/paths.js +150 -0
  32. package/lib/runtime-entrypoints.js +60 -0
  33. package/lib/runtime-helpers.js +886 -0
  34. package/lib/runtime.js +3529 -0
  35. package/lib/tap.js +37 -0
  36. package/lib/transpiler-new/ast.js +23 -0
  37. package/lib/transpiler-new/codegen.js +2455 -0
  38. package/lib/transpiler-new/errors.js +28 -0
  39. package/lib/transpiler-new/index.js +26 -0
  40. package/lib/transpiler-new/lexer.js +834 -0
  41. package/lib/transpiler-new/parser.js +2332 -0
  42. package/lib/transpiler-new/validate-bindings.js +326 -0
  43. package/lib/transpiler-utils.js +95 -0
  44. package/lib/transpiler.js +33 -0
  45. package/lib/zuzu.js +53 -0
  46. package/modules/javascript.js +193 -0
  47. package/modules/std/archive.js +603 -0
  48. package/modules/std/clib.js +338 -0
  49. package/modules/std/data/csv.js +1331 -0
  50. package/modules/std/data/json.js +531 -0
  51. package/modules/std/data/xml.js +441 -0
  52. package/modules/std/data/yaml.js +256 -0
  53. package/modules/std/db-worker.js +250 -0
  54. package/modules/std/db.js +664 -0
  55. package/modules/std/digest/_hash.js +443 -0
  56. package/modules/std/digest/md5.js +26 -0
  57. package/modules/std/digest/sha.js +72 -0
  58. package/modules/std/eval.js +10 -0
  59. package/modules/std/gui/objects.js +1519 -0
  60. package/modules/std/internals.js +571 -0
  61. package/modules/std/io/socks-worker.js +318 -0
  62. package/modules/std/io/socks.js +186 -0
  63. package/modules/std/io.js +475 -0
  64. package/modules/std/marshal/cbor.js +463 -0
  65. package/modules/std/marshal/graph.js +1624 -0
  66. package/modules/std/marshal.js +87 -0
  67. package/modules/std/math/bignum.js +91 -0
  68. package/modules/std/math.js +79 -0
  69. package/modules/std/net/dns.js +306 -0
  70. package/modules/std/net/http.js +820 -0
  71. package/modules/std/net/smtp.js +943 -0
  72. package/modules/std/net/url.js +109 -0
  73. package/modules/std/proc.js +602 -0
  74. package/modules/std/secure.js +3724 -0
  75. package/modules/std/string/base64.js +138 -0
  76. package/modules/std/string.js +299 -0
  77. package/modules/std/task.js +914 -0
  78. package/modules/std/time.js +579 -0
  79. package/modules/std/tui.js +188 -0
  80. package/modules/std/worker-thread.js +246 -0
  81. package/modules/std/worker.js +790 -0
  82. package/package.json +67 -0
  83. package/stdlib/modules/javascript.zzm +99 -0
  84. package/stdlib/modules/perl.zzm +105 -0
  85. package/stdlib/modules/std/archive.zzm +132 -0
  86. package/stdlib/modules/std/cache/lru.zzm +174 -0
  87. package/stdlib/modules/std/clib.zzm +112 -0
  88. package/stdlib/modules/std/colour.zzm +220 -0
  89. package/stdlib/modules/std/config.zzm +818 -0
  90. package/stdlib/modules/std/data/cbor.zzm +497 -0
  91. package/stdlib/modules/std/data/csv.zzm +285 -0
  92. package/stdlib/modules/std/data/ini.zzm +472 -0
  93. package/stdlib/modules/std/data/json/schema/core.zzm +573 -0
  94. package/stdlib/modules/std/data/json/schema/format.zzm +581 -0
  95. package/stdlib/modules/std/data/json/schema/model.zzm +255 -0
  96. package/stdlib/modules/std/data/json/schema/output.zzm +272 -0
  97. package/stdlib/modules/std/data/json/schema/relative_pointer.zzm +299 -0
  98. package/stdlib/modules/std/data/json/schema/validation.zzm +1503 -0
  99. package/stdlib/modules/std/data/json/schema.zzm +306 -0
  100. package/stdlib/modules/std/data/json.zzm +102 -0
  101. package/stdlib/modules/std/data/kdl/json.zzm +460 -0
  102. package/stdlib/modules/std/data/kdl/xml.zzm +387 -0
  103. package/stdlib/modules/std/data/kdl.zzm +1631 -0
  104. package/stdlib/modules/std/data/toml.zzm +756 -0
  105. package/stdlib/modules/std/data/toon.zzm +1017 -0
  106. package/stdlib/modules/std/data/xml/escape.zzm +156 -0
  107. package/stdlib/modules/std/data/xml.zzm +276 -0
  108. package/stdlib/modules/std/data/yaml.zzm +94 -0
  109. package/stdlib/modules/std/db.zzm +173 -0
  110. package/stdlib/modules/std/defer.zzm +75 -0
  111. package/stdlib/modules/std/digest/crc32.zzm +196 -0
  112. package/stdlib/modules/std/digest/md5.zzm +54 -0
  113. package/stdlib/modules/std/digest/sha.zzm +83 -0
  114. package/stdlib/modules/std/dump.zzm +317 -0
  115. package/stdlib/modules/std/eval.zzm +63 -0
  116. package/stdlib/modules/std/getopt.zzm +432 -0
  117. package/stdlib/modules/std/gui/dialogue.zzm +592 -0
  118. package/stdlib/modules/std/gui/objects.zzm +123 -0
  119. package/stdlib/modules/std/gui.zzm +1914 -0
  120. package/stdlib/modules/std/internals.zzm +139 -0
  121. package/stdlib/modules/std/io/socks.zzm +139 -0
  122. package/stdlib/modules/std/io.zzm +157 -0
  123. package/stdlib/modules/std/lingua/en.zzm +347 -0
  124. package/stdlib/modules/std/log.zzm +169 -0
  125. package/stdlib/modules/std/mail.zzm +2726 -0
  126. package/stdlib/modules/std/marshal.zzm +138 -0
  127. package/stdlib/modules/std/math/bignum.zzm +98 -0
  128. package/stdlib/modules/std/math/range.zzm +116 -0
  129. package/stdlib/modules/std/math/roman.zzm +156 -0
  130. package/stdlib/modules/std/math.zzm +141 -0
  131. package/stdlib/modules/std/net/dns.zzm +93 -0
  132. package/stdlib/modules/std/net/http.zzm +278 -0
  133. package/stdlib/modules/std/net/smtp.zzm +257 -0
  134. package/stdlib/modules/std/net/url.zzm +69 -0
  135. package/stdlib/modules/std/path/jsonpointer.zzm +526 -0
  136. package/stdlib/modules/std/path/kdl.zzm +1003 -0
  137. package/stdlib/modules/std/path/simple.zzm +520 -0
  138. package/stdlib/modules/std/path/z/context.zzm +147 -0
  139. package/stdlib/modules/std/path/z/evaluate.zzm +549 -0
  140. package/stdlib/modules/std/path/z/functions.zzm +874 -0
  141. package/stdlib/modules/std/path/z/lexer.zzm +490 -0
  142. package/stdlib/modules/std/path/z/node.zzm +1455 -0
  143. package/stdlib/modules/std/path/z/operators.zzm +445 -0
  144. package/stdlib/modules/std/path/z/parser.zzm +359 -0
  145. package/stdlib/modules/std/path/z.zzm +403 -0
  146. package/stdlib/modules/std/path/zz/functions.zzm +828 -0
  147. package/stdlib/modules/std/path/zz/operators.zzm +1036 -0
  148. package/stdlib/modules/std/path/zz.zzm +100 -0
  149. package/stdlib/modules/std/proc.zzm +155 -0
  150. package/stdlib/modules/std/result.zzm +149 -0
  151. package/stdlib/modules/std/secure.zzm +606 -0
  152. package/stdlib/modules/std/string/base64.zzm +66 -0
  153. package/stdlib/modules/std/string/quoted_printable.zzm +485 -0
  154. package/stdlib/modules/std/string.zzm +179 -0
  155. package/stdlib/modules/std/task.zzm +221 -0
  156. package/stdlib/modules/std/template/z.zzm +531 -0
  157. package/stdlib/modules/std/template/zz.zzm +62 -0
  158. package/stdlib/modules/std/time.zzm +188 -0
  159. package/stdlib/modules/std/tui.zzm +89 -0
  160. package/stdlib/modules/std/uuid.zzm +223 -0
  161. package/stdlib/modules/std/web/session.zzm +388 -0
  162. package/stdlib/modules/std/web/static.zzm +329 -0
  163. package/stdlib/modules/std/web.zzm +1942 -0
  164. package/stdlib/modules/std/worker.zzm +202 -0
  165. package/stdlib/modules/std/zuzuzoo.zzm +3960 -0
  166. package/stdlib/modules/test/more.zzm +528 -0
  167. package/stdlib/modules/test/parser.zzm +209 -0
@@ -0,0 +1,403 @@
1
+ =encoding utf8
2
+
3
+ =head1 NAME
4
+
5
+ std/path/z - Pure Zuzu implementation of ZPath selectors.
6
+
7
+ =head1 SYNOPSIS
8
+
9
+ from std/path/z import ZPath;
10
+ from std/time import Time;
11
+
12
+ let data := {
13
+ users: [
14
+ { name: "Ada", age: 32, updated: new Time() },
15
+ { name: "Bob", age: 27 },
16
+ ],
17
+ };
18
+
19
+ let names := query( data, "/users/*/name" );
20
+ let zp := new ZPath( path: "/users/#0/name" );
21
+ say( zp.first( data, "n/a" ) );
22
+ say( exists( data, "/users/#9/name" ) );
23
+ say( first( data, "/users/#0/updated/@year" ) );
24
+ say( zp.assign_first( data, "Adele" ) );
25
+
26
+ =head1 IMPLEMENTATION SUPPORT
27
+
28
+ This module is supported by all implementations of ZuzuScript.
29
+
30
+ =head1 DESCRIPTION
31
+
32
+ Native (pure-Zuzu) path traversal for structured values.
33
+
34
+ =head1 EXPORTS
35
+
36
+ =head2 Classes
37
+
38
+ =over
39
+
40
+ =item C<< ZPath({ path: String, ast? }) >>
41
+
42
+ Constructs a compiled ZPath selector. Returns: C<ZPath>.
43
+
44
+ =over
45
+
46
+ =item C<< ZPath.use() >>
47
+
48
+ Parameters: none. Returns: C<null>. Makes this path class the lexical
49
+ implementation for C<@>, C<@@>, and C<@?>.
50
+
51
+ =item C<< path.get_evaluator() >>
52
+
53
+ Parameters: none. Returns: C<Evaluator>. Returns the evaluator used for
54
+ this path.
55
+
56
+ =item C<< path.evaluate(raw, meta := {}) >>
57
+
58
+ Parameters: C<raw> is the query root and C<meta> is optional evaluation
59
+ metadata. Returns: C<Array>. Evaluates the path and returns selected
60
+ nodes.
61
+
62
+ =item C<< path.get(raw) >>, C<< path.select(raw) >>, C<< path.query(raw) >>
63
+
64
+ Parameters: C<raw> is the query root. Returns: C<Array>. Evaluates the
65
+ path and returns selected primitive values.
66
+
67
+ =item C<< path.first(raw, fallback?) >>
68
+
69
+ Parameters: C<raw> is the query root and C<fallback> is optional.
70
+ Returns: value. Returns the first selected value or C<fallback>/C<null>.
71
+
72
+ =item C<< path.exists(raw) >>
73
+
74
+ Parameters: C<raw> is the query root. Returns: C<Boolean>. Returns true
75
+ when the path selects at least one value.
76
+
77
+ =item C<< path.assign_first(raw, value, op := ":=", weak := false) >>
78
+
79
+ Parameters: C<raw> is the query root, C<value> is the assignment value,
80
+ C<op> is an assignment operator, and C<weak> requests weak assignment.
81
+ Returns: value. Updates the first selected node or throws if none match.
82
+
83
+ =item C<< path.assign_all(raw, value, op := ":=", weak := false) >>
84
+
85
+ Parameters: same as C<assign_first>. Returns: value. Updates every
86
+ selected node.
87
+
88
+ =item C<< path.assign_maybe(raw, value, op := ":=", weak := false) >>
89
+
90
+ Parameters: same as C<assign_first>. Returns: C<Boolean>. Updates the
91
+ first selected node when one exists.
92
+
93
+ =item C<< path.ref_first(raw) >>
94
+
95
+ Parameters: C<raw> is the query root. Returns: C<Function>. Returns a
96
+ reference-like getter/setter for the first selected node.
97
+
98
+ =item C<< path.ref_all(raw) >>
99
+
100
+ Parameters: C<raw> is the query root. Returns: C<Array>. Returns
101
+ reference-like getter/setters for all selected nodes.
102
+
103
+ =item C<< path.ref_maybe(raw) >>
104
+
105
+ Parameters: C<raw> is the query root. Returns: C<Function> or C<null>.
106
+ Returns a reference-like getter/setter for the first selected node when
107
+ one exists.
108
+
109
+ =back
110
+
111
+ =back
112
+
113
+ =head1 USE WITH PATH OPERATORS
114
+
115
+ The path operators C<@>, C<@@>, and C<@?> can be set to use this module
116
+ in a lexical scope.
117
+
118
+ from std/path/z import ZPath;
119
+
120
+ function find_usernames (data) {
121
+ ZPath.use();
122
+ return data @@ "/users/*/name";
123
+ }
124
+
125
+ However, for repeatedly used paths it may be more efficient to compile the
126
+ path once and use many times:
127
+
128
+ let _usernames_zpath;
129
+ function find_usernames (data) {
130
+ from std/path/z import ZPath;
131
+ _usernames_zpath ?:= new ZPath( path: "/users/*/name" );
132
+ return data @@ _usernames_zpath;
133
+ }
134
+
135
+ =head1 SUPPORTED TYPES
136
+
137
+ =over
138
+
139
+ =item B<Null>, B<Boolean>, B<Number>, B<String>, B<BinaryString>, B<Regexp>
140
+
141
+ Treated as terminal nodes. These objects cannot have child objects.
142
+
143
+ =item B<Array>
144
+
145
+ Array items can be indexed by number.
146
+
147
+ =item B<Bag>, B<Set>
148
+
149
+ Items cannot be indexed by number, but can be returned by "*".
150
+
151
+ =item B<Dict>
152
+
153
+ Values are named by their key.
154
+
155
+ =item B<PairList>
156
+
157
+ Pairs can be indexed by number, named by their key, or use a combination of
158
+ both.
159
+
160
+ {{ foo: 11, bar: -1, foo: 22, foo: 33 }}
161
+
162
+ C<< /#2 >> (0-based index) will retrieve C<< foo: 22 >>.
163
+ C<< /foo >> will retrieve C<< foo: 11 >>, C<< foo: 22 >>, and C<< foo: 33 >>.
164
+ C<< /foo#2 >> (0-based index on just values with key "foo") will retrieve C<< foo: 33 >>.
165
+
166
+ Note that rather than just retrieving the value, a Pair object is retrieved.
167
+ The selected Pair exposes C<< @key >> and C<< @value >> attributes. Path
168
+ assignment to a selected Pair, or to its C<< @value >> attribute, replaces
169
+ that pair entry's value while preserving pair order and duplicate keys.
170
+
171
+ =item B<< Pair >>
172
+
173
+ Pair objects do not have child objects but do have C<< @key >> and
174
+ C<< @value >> attributes.
175
+
176
+ let pairlist := {{ foo: 11, bar: -1, foo: 22, foo: 33 }};
177
+ say( first( pairlist, "/#2/@key" ) ); // "foo"
178
+ say( first( pairlist, "/#2/@value" ) ); // 22
179
+
180
+ =item B<< Time >>
181
+
182
+ Time is treated as a terminal node with attributes C<< @year >>,
183
+ C<< @month >>, C<< @day >>, C<< @hour >>, C<< @min >>, and C<< @sec >>.
184
+
185
+ See C<< std/time >>.
186
+
187
+ =item B<< Path >>
188
+
189
+ Paths representing files are treated as terminal nodes with attributes
190
+ corresponding to the values from the C<stat> system call: C<< @dev >>,
191
+ C<< @ino >>, C<< @mode >>, C<< @nlink >>, C<< @uid >>, C<< @gid >>,
192
+ C<< @rdev >>, C<< @size >>, C<< @atime >>, C<< @mtime >>, C<< @ctime >>,
193
+ C<< @blksize >>, and C<< @blocks >>.
194
+
195
+ See C<< std/io >>.
196
+
197
+ =item B<< XMLDocument >>, B<< XMLNode >>, etc.
198
+
199
+ Are treated roughly how the ZPath specification suggests.
200
+
201
+ /html/body/table/tbody/tr // all rows in the tbody
202
+ /html/body/table/tbody/tr#0 // the first row in the tbody
203
+ /html/body/table/tbody/#0 // the child element in the tbody
204
+ /html/body/table[@id] // all tables that have an id attribute
205
+
206
+ See C<< std/data/xml >>.
207
+
208
+ =back
209
+
210
+ =head1 SEE ALSO
211
+
212
+ Specification: L<https://zpath.me>.
213
+
214
+ ZPath specification: L<https://zpath.me>.
215
+
216
+ =head1 COPYRIGHT AND LICENCE
217
+
218
+ B<< std/path/z >> is copyright Toby Inkster.
219
+
220
+ It is free software; you may redistribute it and/or modify it under
221
+ the terms of either the Artistic License 1.0 or the GNU General Public
222
+ License version 2.
223
+
224
+ =cut
225
+
226
+ from std/path/z/parser import Parser;
227
+ from std/path/z/evaluate import Evaluator;
228
+ from std/path/z/context import Ctx;
229
+
230
+ let _cache;
231
+ do {
232
+ from std/cache/lru try import Cache;
233
+ if ( Cache ) {
234
+ _cache := new Cache( capacity: 16 );
235
+ }
236
+ };
237
+
238
+ class ZPath {
239
+ let String path;
240
+ let ast;
241
+ let ev;
242
+
243
+ static method use () {
244
+ from std/internals import setupperprop;
245
+ setupperprop( 1, "paths", self );
246
+ }
247
+
248
+ method __build__ () {
249
+ ev := self.get_evaluator;
250
+ const p := new Parser( allowed_operators: ev.operator_definitions() );
251
+ ast ?:= _cache
252
+ ? _cache.get( path, fn x → p.parse_top_level_terms(x) )
253
+ : p.parse_top_level_terms(path);
254
+ }
255
+
256
+ method get_evaluator () {
257
+ return new Evaluator();
258
+ }
259
+
260
+ method evaluate ( raw, meta := {} ) {
261
+
262
+ meta.set( "level", 0 ) unless meta.defined( "level" );
263
+ const ctx := new Ctx(
264
+ root: raw,
265
+ nodeset: meta.get( "nodeset", null ),
266
+ parentset: meta.get( "parentset", null ),
267
+ meta: meta,
268
+ );
269
+
270
+ const short_circuit := ( meta.get( "want", "all" ) in [ "first", "exists" ] );
271
+
272
+ let results := [];
273
+ for ( let term in ast ) {
274
+ for ( let node in ev.eval_expr( term, ctx ) ) {
275
+ let next_node := ev.maybe_apply_action( node, ctx );
276
+ results.push(next_node);
277
+ return results if short_circuit;
278
+ }
279
+ }
280
+
281
+ return results;
282
+ }
283
+
284
+ method get ( raw ) {
285
+ return self.evaluate(raw).map( fn r → r.primitive_value );
286
+ }
287
+
288
+ method select ( raw ) {
289
+ return self.evaluate(raw).map( fn r → r.primitive_value );
290
+ }
291
+
292
+ method query ( raw ) {
293
+ return self.evaluate(raw).map( fn r → r.primitive_value );
294
+ }
295
+
296
+ method first ( raw, fallback? ) {
297
+ let got := self.evaluate( raw, { want: "first" } );
298
+ return got.empty ? fallback : got[0].primitive_value;
299
+ }
300
+
301
+ method exists ( raw ) {
302
+ let got := self.evaluate( raw, { want: "exists" } );
303
+ return not got.empty;
304
+ }
305
+
306
+ method _apply_assignment_ref ( ref, value, op := ":=", weak := false ) {
307
+ if ( op ≡ ":=" ) {
308
+ return weak ? ref( value, true ) : ref(value);
309
+ }
310
+
311
+ let current := ref();
312
+
313
+ if ( op ≡ "+=" ) {
314
+ current += value;
315
+ }
316
+ else if ( op ≡ "-=" ) {
317
+ current -= value;
318
+ }
319
+ else if ( op ≡ "*=" or op ≡ "×=" ) {
320
+ current *= value;
321
+ }
322
+ else if ( op ≡ "/=" or op ≡ "÷=" ) {
323
+ current /= value;
324
+ }
325
+ else if ( op ≡ "**=" ) {
326
+ current **= value;
327
+ }
328
+ else if ( op ≡ "_=" ) {
329
+ current _= value;
330
+ }
331
+ else if ( op ≡ "?:=" ) {
332
+ current ?:= value;
333
+ }
334
+ else if ( op ≡ "~=" ) {
335
+ current ~= value[0] -> value[1](m);
336
+ }
337
+ else {
338
+ die `Unsupported path assignment operator '${op}'`;
339
+ }
340
+
341
+ ref(current);
342
+ return current;
343
+ }
344
+
345
+ method _assign_all_result ( value, op, last_result ) {
346
+ return op ≡ "~=" ? last_result : value;
347
+ }
348
+
349
+ method assign_first ( raw, value, op := ":=", weak := false ) {
350
+ let got := self.evaluate( raw, { want: "first" } );
351
+ die "Path assignment (@) found no matches" if got.empty;
352
+ return self._apply_assignment_ref(
353
+ got[0].ref(),
354
+ value,
355
+ op,
356
+ weak,
357
+ );
358
+ }
359
+
360
+ method assign_all ( raw, value, op := ":=", weak := false ) {
361
+ let got := self.evaluate(raw);
362
+ if ( got.empty ) {
363
+ return self._assign_all_result( value, op, value );
364
+ }
365
+
366
+ let last_result := value;
367
+ for ( let node in got ) {
368
+ last_result := self._apply_assignment_ref(
369
+ node.ref(),
370
+ value,
371
+ op,
372
+ weak,
373
+ );
374
+ }
375
+
376
+ return self._assign_all_result( value, op, last_result );
377
+ }
378
+
379
+ method assign_maybe ( raw, value, op := ":=", weak := false ) {
380
+ let got := self.evaluate( raw, { want: "first" } );
381
+ if ( got.empty ) {
382
+ return false;
383
+ }
384
+
385
+ self._apply_assignment_ref( got[0].ref(), value, op, weak );
386
+ return true;
387
+ }
388
+
389
+ method ref_first ( raw ) {
390
+ let got := self.evaluate( raw, { want: "first" } );
391
+ die "Path assignment (@) found no matches" if got.empty;
392
+ return got[0].ref();
393
+ }
394
+
395
+ method ref_all ( raw ) {
396
+ return self.evaluate(raw).map( fn n → n.ref );
397
+ }
398
+
399
+ method ref_maybe ( raw ) {
400
+ let got := self.evaluate( raw, { want: "first" } );
401
+ return got.empty ? null : got[0].ref();
402
+ }
403
+ }