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,549 @@
1
+ =encoding utf8
2
+
3
+ =head1 NAME
4
+
5
+ std/path/z/evaluate - Pure Zuzu evaluator for ZPath expressions.
6
+
7
+ =head1 IMPLEMENTATION SUPPORT
8
+
9
+ This module is supported by all implementations of ZuzuScript.
10
+
11
+ =head1 DESCRIPTION
12
+
13
+ This module provides the pure-Zuzu evaluator used by ZPath.
14
+
15
+ =head1 EXPORTS
16
+
17
+ =head2 Classes
18
+
19
+ =over
20
+
21
+ =item C<Evaluator>
22
+
23
+ Pure-Zuzu evaluator for parsed ZPath AST values.
24
+
25
+ =over
26
+
27
+ =item C<< evaluator.operator_definitions() >>
28
+
29
+ Parameters: none. Returns: C<Array>. Returns the active operator
30
+ definition table.
31
+
32
+ =item C<< evaluator.function_definitions() >>
33
+
34
+ Parameters: none. Returns: C<Array>. Returns the active function
35
+ definition table.
36
+
37
+ =item C<< evaluator.eval_expr_wrap(ast, ctx) >>
38
+
39
+ Parameters: C<ast> is an AST node and C<ctx> is a C<Ctx>. Returns:
40
+ C<Array>. Evaluates with optional debug dumping.
41
+
42
+ =item C<< evaluator.eval_expr(ast, ctx) >>
43
+
44
+ Parameters: C<ast> is an AST node and C<ctx> is a C<Ctx>. Returns:
45
+ C<Array>. Evaluates an expression to a node set.
46
+
47
+ =item C<< evaluator.nested_ctx(ctx, ... PairList extras) >>
48
+
49
+ Parameters: C<ctx> is a C<Ctx> or C<null> and C<extras> are metadata.
50
+ Returns: C<Ctx> or C<null>. Creates a nested evaluation context.
51
+
52
+ =item C<< evaluator.eval_binop(ast, ctx) >>, C<< evaluator.eval_unop(ast, ctx) >>
53
+
54
+ Parameters: C<ast> is an operator AST node and C<ctx> is a C<Ctx>.
55
+ Returns: C<Array>. Evaluates a binary or unary operator.
56
+
57
+ =item C<< evaluator.eval_path(path_ast, ctx) >>
58
+
59
+ Parameters: C<path_ast> is a path AST node and C<ctx> is a C<Ctx>.
60
+ Returns: C<Array>. Evaluates a path expression to selected nodes.
61
+
62
+ =item C<< evaluator.maybe_apply_action(node, ctx) >>
63
+
64
+ Parameters: C<node> is a C<Node> and C<ctx> is a C<Ctx>. Returns:
65
+ C<Node>. Applies any pending path action.
66
+
67
+ =item C<< evaluator.eval_fn(fn_ast, ctx) >>
68
+
69
+ Parameters: C<fn_ast> is a function-call AST node and C<ctx> is a
70
+ C<Ctx>. Returns: C<Array>. Evaluates a ZPath function call.
71
+
72
+ =item C<< evaluator.string_replace(string, pattern, replacement) >>
73
+
74
+ Parameters: all arguments are strings or regex-like values. Returns:
75
+ C<String>. Applies ZPath string replacement.
76
+
77
+ =item C<< evaluator.dedup_nodes(nodes) >>
78
+
79
+ Parameters: C<nodes> is an array of nodes. Returns: C<Array>. Removes
80
+ duplicate nodes while preserving order.
81
+
82
+ =item C<< evaluator.truthy(n) >>
83
+
84
+ Parameters: C<n> is a node or value. Returns: C<Boolean>. Applies ZPath
85
+ truthiness.
86
+
87
+ =item C<< evaluator.to_number(n) >>, C<< evaluator.to_string(n) >>
88
+
89
+ Parameters: C<n> is a node or value. Returns: C<Number> or C<String>.
90
+ Coerces a ZPath value.
91
+
92
+ =item C<< evaluator.equals(a, b) >>
93
+
94
+ Parameters: C<a> and C<b> are nodes or values. Returns: C<Boolean>.
95
+ Compares two ZPath values.
96
+
97
+ =back
98
+
99
+ =back
100
+
101
+ =head1 COPYRIGHT AND LICENCE
102
+
103
+ B<< std/path/z/evaluate >> is copyright Toby Inkster.
104
+
105
+ It is free software; you may redistribute it and/or modify it under
106
+ the terms of either the Artistic License 1.0 or the GNU General Public
107
+ License version 2.
108
+
109
+ =cut
110
+
111
+ from std/string import replace, index, substr;
112
+ from std/path/z/node import Node;
113
+ from std/path/z/functions import STANDARD_FUNCTIONS;
114
+ from std/path/z/operators import STANDARD_OPERATORS;
115
+
116
+ let _do_dump := false;
117
+
118
+ class Evaluator {
119
+ let _operator_definitions;
120
+ let _function_definitions;
121
+
122
+ let ε := 0.000000001;
123
+
124
+ method operator_definitions () {
125
+ _operator_definitions ?:= STANDARD_OPERATORS;
126
+ return _operator_definitions;
127
+ }
128
+
129
+ method function_definitions () {
130
+ _function_definitions ?:= STANDARD_FUNCTIONS;
131
+ return _function_definitions;
132
+ }
133
+
134
+ method eval_expr_wrap ( ast, ctx ) {
135
+ from std/dump import Dumper;
136
+ let got := self._real_eval_expr ( ast, ctx );
137
+ say `AST ${Dumper.dump(ast)} CTX ${Dumper.dump(ctx)} ⇒ GOT ${Dumper.dump(got)}` if _do_dump;
138
+ return got;
139
+ }
140
+
141
+ method eval_expr ( ast, ctx ) {
142
+
143
+ switch ( ast{t} : eq ) {
144
+ case "num":
145
+ return [ Node.wrap( ast{v} ) ];
146
+ case "str":
147
+ return [ Node.wrap( ast{v} ) ];
148
+ case "path":
149
+ return self.eval_path( ast, ctx );
150
+ case "fn":
151
+ return self.eval_fn( ast, ctx );
152
+ case "un":
153
+ return self.eval_unop( ast, ctx );
154
+ case "bin":
155
+ return self.eval_binop( ast, ctx );
156
+ case "ternary":
157
+ let c := self.eval_expr( ast{c}, ctx );
158
+ let ab := self.truthy( c.get(0) ) ? "a" : "b";
159
+ return self.eval_expr( ast{(ab)}, ctx );
160
+ case "elvis":
161
+ let left := self.eval_expr( ast{c}, ctx );
162
+ return left if self.truthy( left.get(0) );
163
+ return self.eval_expr( ast{b}, ctx );
164
+ default:
165
+ die "Panic! Unknown AST node type!";
166
+ }
167
+ }
168
+
169
+ method nested_ctx ( ctx, ... PairList extras ) {
170
+ return null if ctx ≡ null;
171
+ return ctx.nested( extras );
172
+ }
173
+
174
+ method _hold_parent ( node, parent ) {
175
+ if ( node ≢ null and parent ≢ null ) {
176
+ node.hold_parent(parent);
177
+ }
178
+ return node;
179
+ }
180
+
181
+ method _children ( node ) {
182
+ let out := [];
183
+ for ( let child in node.children() ) {
184
+ out.push( self._hold_parent( child, node ) );
185
+ }
186
+ return out;
187
+ }
188
+
189
+ method _attributes ( node ) {
190
+ let out := [];
191
+ for ( let attr in node.attributes() ) {
192
+ out.push( self._hold_parent( attr, node ) );
193
+ }
194
+ return out;
195
+ }
196
+
197
+ method _indexed_child ( node, i ) {
198
+ return self._hold_parent( node.indexed_child(i), node );
199
+ }
200
+
201
+ method _named_child ( node, name ) {
202
+ return self._hold_parent( node.named_child(name), node );
203
+ }
204
+
205
+ method _named_indexed_child ( node, name, i ) {
206
+ return self._hold_parent( node.named_indexed_child( name, i ), node );
207
+ }
208
+
209
+ method eval_binop ( ast, ctx ) {
210
+ const op := ast{op};
211
+ const op_def := self.operator_definitions().first(
212
+ fn x → x.get_spelling eq op and x.is_binary()
213
+ );
214
+ if ( op_def and op_def{f} ) {
215
+ const implementation := op_def{f};
216
+ return implementation( op_def, self, ast, ctx, ast{l}, ast{r} );
217
+ }
218
+ return [];
219
+ }
220
+
221
+ method eval_unop ( ast, ctx ) {
222
+ const op := ast{op};
223
+ const op_def := self.operator_definitions().first(
224
+ fn x → x.get_spelling eq op and x.is_unary()
225
+ );
226
+ if ( op_def and op_def{f} ) {
227
+ const implementation := op_def{f};
228
+ return implementation( op_def, self, ast, ctx, ast{e} );
229
+ }
230
+ return [];
231
+ }
232
+
233
+ method eval_path ( path_ast, ctx ) {
234
+ let current := [];
235
+ for ( let n in ctx.nodeset() ) {
236
+ current.push(n);
237
+ }
238
+
239
+ let parentset := ctx.parentset();
240
+
241
+ for ( let seg in path_ast{s} ) {
242
+ let next_nodes := [];
243
+
244
+ if ( seg{k} ≡ "root" ) {
245
+ next_nodes := [ ctx.root() ];
246
+ }
247
+ else if ( seg{k} ≡ "dot" ) {
248
+ next_nodes := current;
249
+ }
250
+ else if ( seg{k} ≡ "parent" ) {
251
+ for ( let n in current ) {
252
+ let p := n.parent();
253
+ if ( p ≢ null ) {
254
+ next_nodes.push(p);
255
+ }
256
+ }
257
+ next_nodes := self.dedup_nodes(next_nodes);
258
+ }
259
+ else if ( seg{k} ≡ "ancestors" ) {
260
+ let anc := [];
261
+ for ( let n in current ) {
262
+ let p := n.parent();
263
+ while ( p ≢ null ) {
264
+ anc.push(p);
265
+ p := p.parent();
266
+ }
267
+ }
268
+ next_nodes := self.dedup_nodes(anc);
269
+ }
270
+ else if ( seg{k} ≡ "star" ) {
271
+ let kids := [];
272
+ for ( let n in current ) {
273
+ for ( let child in self._children(n) ) {
274
+ if ( child.type() ≢ "attr" ) {
275
+ kids.push(child);
276
+ }
277
+ }
278
+ }
279
+ next_nodes := self.dedup_nodes(kids);
280
+ }
281
+ else if ( seg{k} ≡ "desc" ) {
282
+ let acc := [];
283
+ let stack := [];
284
+ for ( let n in current ) {
285
+ stack.push(n);
286
+ }
287
+
288
+ while ( stack.length() > 0 ) {
289
+ let n := stack.shift();
290
+ acc.push(n);
291
+ for ( let child in self._children(n) ) {
292
+ if ( child.type() ≢ "attr" ) {
293
+ stack.push(child);
294
+ }
295
+ }
296
+ }
297
+ next_nodes := self.dedup_nodes(acc);
298
+ }
299
+ else if ( seg{k} ≡ "index" ) {
300
+ let idx := seg{i};
301
+ let kids := [];
302
+ for ( let n in current ) {
303
+ let c := self._indexed_child( n, idx );
304
+ kids.push(c) if c ≢ null;
305
+ }
306
+ next_nodes := self.dedup_nodes(kids);
307
+ }
308
+ else if ( seg{k} ≡ "fnseg" ) {
309
+ let out := [];
310
+ for ( let n in current ) {
311
+ let seg_ctx := ctx.with_nodeset( [ n ], current );
312
+ let fn_ast := { t: "fn", n: seg{n}, a: seg{a} };
313
+ let res := self.eval_fn( fn_ast, seg_ctx );
314
+ for ( let x in res ) {
315
+ out.push(x);
316
+ }
317
+ }
318
+ next_nodes := out;
319
+ }
320
+ else if ( seg{k} ≡ "name" ) {
321
+ let name := seg{n};
322
+
323
+ if ( name ~ /^\@/ ) {
324
+ if ( name ≡ "@*" ) {
325
+ let attrs := [];
326
+ for ( let n in current ) {
327
+ for ( let a in self._attributes(n) ) {
328
+ attrs.push(a);
329
+ }
330
+ }
331
+ next_nodes := self.dedup_nodes(attrs);
332
+ }
333
+ else {
334
+ let attrs := [];
335
+ for ( let n in current ) {
336
+ for ( let a in self._attributes(n) ) {
337
+ if ( a.name() ≡ name ) {
338
+ attrs.push(a);
339
+ }
340
+ }
341
+ }
342
+ next_nodes := self.dedup_nodes(attrs);
343
+ }
344
+ }
345
+ else {
346
+ let kids := [];
347
+ for ( let n in current ) {
348
+ if ( n.can_have_named_indexed_children() ) {
349
+ let idx := 0;
350
+ while ( true ) {
351
+ let c := self._named_indexed_child( n, name, idx );
352
+ last if c ≡ null;
353
+ kids.push(c);
354
+ idx++;
355
+ }
356
+ }
357
+ else {
358
+ let c := self._named_child( n, name );
359
+ kids.push(c) if c ≢ null;
360
+ }
361
+ }
362
+ next_nodes := self.dedup_nodes(kids);
363
+ }
364
+
365
+ if ( seg.exists("i") and seg{i} ≢ null ) {
366
+ let idx := seg{i};
367
+ let picked := [];
368
+ for ( let n in current ) {
369
+ let c := self._named_indexed_child( n, name, idx );
370
+ picked.push(c) if c ≢ null;
371
+ }
372
+ next_nodes := self.dedup_nodes(picked);
373
+ }
374
+ }
375
+ else {
376
+ die "Unknown path segment kind: " _ seg{k};
377
+ }
378
+
379
+ if ( seg.exists("q") and seg{q}.length() > 0 ) {
380
+ for ( let q in seg{q} ) {
381
+ if ( q.exists("t") and q{t} ≡ "num" and q{v} ~ /^\d+$/ ) {
382
+ let idx := 0 + q{v};
383
+
384
+ if ( next_nodes.length() > 0 and self._node_is_xml(next_nodes[0]) ) {
385
+ next_nodes := ( idx ≥ 0 and idx < next_nodes.length() ) ? [ next_nodes[idx] ] : [];
386
+ }
387
+ else {
388
+ let picked := [];
389
+ for ( let node in next_nodes ) {
390
+ let ch := self._children(node)
391
+ .grep( fn c → c.type() ≢ "attr" );
392
+ if ( idx ≥ 0 and idx < ch.length() ) {
393
+ picked.push( ch[idx] );
394
+ }
395
+ }
396
+ next_nodes := picked;
397
+ }
398
+
399
+ next;
400
+ }
401
+
402
+ let filtered := [];
403
+ let i := 0;
404
+ while ( i < next_nodes.length() ) {
405
+ let node := next_nodes[i];
406
+ let ns_ctx := ctx.with_nodeset( next_nodes, current );
407
+ let filter_ctx := ns_ctx.with_nodeset( [ node ], next_nodes );
408
+ let r := self.eval_expr( q, self.nested_ctx( filter_ctx, want: "exists" ) );
409
+
410
+ let ok := false;
411
+ if ( q.exists("t") and q{t} ≡ "path" ) {
412
+ ok := r.length() > 0;
413
+ }
414
+ else {
415
+ ok := self.truthy( r.length() > 0 ? r[0] : null );
416
+ }
417
+
418
+ if ( ok ) {
419
+ filtered.push(node);
420
+ }
421
+ i++;
422
+ }
423
+ next_nodes := filtered;
424
+ }
425
+ }
426
+
427
+ parentset := current;
428
+ current := next_nodes;
429
+ }
430
+
431
+ return current;
432
+ }
433
+
434
+ method maybe_apply_action ( node, ctx ) {
435
+ const meta := ctx.meta();
436
+ return node if not meta.exists( "action" );
437
+ const action := meta{action};
438
+ return node if action ≡ null;
439
+ return node if not action.exists( "op" );
440
+ node.do_action(action);
441
+ return node;
442
+ }
443
+
444
+ method eval_fn ( fn_ast, ctx ) {
445
+ const name := fn_ast{n};
446
+ const fn_def := self.function_definitions().first( fn f → f.has_name(name) );
447
+ return fn_def{f}( fn_def, self, fn_ast, ctx, fn_ast{a} ?: [] ) if fn_def;
448
+ return [];
449
+ }
450
+
451
+ method string_replace ( string, pattern, replacement ) {
452
+ let text := string ≡ null ? "" : "" _ string;
453
+ let rep := replacement ≡ null ? "" : "" _ replacement;
454
+
455
+ try {
456
+ return replace( text, pattern, rep, "g" );
457
+ }
458
+ catch {
459
+ return replace( text, "" _ pattern, rep, "g" );
460
+ }
461
+ }
462
+
463
+ method dedup_nodes ( nodes ) {
464
+ let seen := {};
465
+ let out := [];
466
+ for ( let n in nodes ) {
467
+ let key := n.id ?: ( "anon:" _ out.length() _ ":" _ ( "" _ n.raw() ) );
468
+ if ( not seen.exists(key) ) {
469
+ seen.set( key, true );
470
+ out.push(n);
471
+ }
472
+ }
473
+ return out;
474
+ }
475
+
476
+ method truthy ( n ) {
477
+ return false if n ≡ null;
478
+ let pv := n.primitive_value();
479
+ return pv ? true : false;
480
+ }
481
+
482
+ method to_number ( n ) {
483
+ return null if n ≡ null;
484
+ return n.number_value();
485
+ }
486
+
487
+ method to_string ( n ) {
488
+ return null if n ≡ null;
489
+ return n.string_value();
490
+ }
491
+
492
+ method equals ( a, b ) {
493
+ return false if a ≡ null or b ≡ null;
494
+
495
+ let a_type := a.type();
496
+ let b_type := b.type();
497
+
498
+ if ( b_type eq "null" ) {
499
+ return a_type eq "null";
500
+ }
501
+ if ( a_type eq "null" ) {
502
+ return b_type eq "null";
503
+ }
504
+
505
+ if ( a_type eq "boolean" and b_type eq "boolean" ) {
506
+ let av := a.primitive_value() ? true : false;
507
+ let bv := b.primitive_value() ? true : false;
508
+ return av ≡ bv;
509
+ }
510
+
511
+ if ( a_type ≡ "number" and b_type ≡ "number" ) {
512
+ let av := a.number_value();
513
+ let bv := b.number_value();
514
+ return false if av ≡ null or bv ≡ null;
515
+
516
+ if ( ( "" _ av ) ~ /\./ or ( "" _ bv ) ~ /\./ ) {
517
+ return abs( av - bv ) < ε;
518
+ }
519
+
520
+ return av = bv;
521
+ }
522
+
523
+ let string_like := [ "string", "text", "attr", "comment", "element" ];
524
+ if ( a_type in string_like and b_type in string_like ) {
525
+ let av := a.string_value();
526
+ let bv := b.string_value();
527
+ return av eq bv;
528
+ }
529
+
530
+ let aid := a.id();
531
+ let bid := b.id();
532
+ return false if aid ≡ null or bid ≡ null;
533
+ return aid eq bid;
534
+ }
535
+
536
+ method _node_is_xml ( n ) {
537
+ if ( n ≡ null ) {
538
+ return false;
539
+ }
540
+ let raw := n.raw();
541
+ try {
542
+ let node_type := raw.nodeType();
543
+ return node_type ≢ null;
544
+ }
545
+ catch {
546
+ return false;
547
+ }
548
+ }
549
+ }