zuzu-js 0.4.0 → 0.6.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.
@@ -228,6 +228,9 @@
228
228
  function makeSet(values) {
229
229
  return runtimeHelpers().makeSet(values);
230
230
  }
231
+ function operatorString(value) {
232
+ return runtimeHelpers().operatorString(value);
233
+ }
231
234
  function stringSortComparator(a, b) {
232
235
  return runtimeHelpers().stringSortComparator(a, b);
233
236
  }
@@ -278,7 +281,13 @@
278
281
  return this.add(...values);
279
282
  }
280
283
  remove(value) {
281
- return this.remove_first(value);
284
+ for (let idx = this.items.length - 1; idx >= 0; idx--) {
285
+ if (this.items[idx] === value) {
286
+ releaseCollectionValue(this, this.items[idx]);
287
+ this.items.splice(idx, 1);
288
+ }
289
+ }
290
+ return this;
282
291
  }
283
292
  remove_first(value) {
284
293
  const idx = this.items.indexOf(value);
@@ -298,9 +307,6 @@
298
307
  });
299
308
  return this;
300
309
  }
301
- get(idx, fallback = null) {
302
- return idx >= 0 && idx < this.items.length ? this.items[idx] : fallback;
303
- }
304
310
  map(fn2) {
305
311
  return new _ZuzuBag(this.items.map(fn2));
306
312
  }
@@ -437,6 +443,9 @@
437
443
  empty() {
438
444
  return this.list.length === 0 ? 1 : 0;
439
445
  }
446
+ is_empty() {
447
+ return this.empty();
448
+ }
440
449
  keys() {
441
450
  return this.list.map((pair) => pair[0]);
442
451
  }
@@ -543,6 +552,7 @@
543
552
  configurable: true
544
553
  });
545
554
  function withArrayMethods() {
555
+ const nativeJoin = Array.prototype.join;
546
556
  const define = (name2, fn2) => {
547
557
  if (!Object.prototype.hasOwnProperty.call(Array.prototype, name2)) {
548
558
  const desc = /* @__PURE__ */ Object.create(null);
@@ -551,6 +561,38 @@
551
561
  Object.defineProperty(Array.prototype, name2, desc);
552
562
  }
553
563
  };
564
+ if (Array.prototype.join.__zuzu_array_join !== true) {
565
+ const desc = /* @__PURE__ */ Object.create(null);
566
+ desc.value = function _join(separator, fallback) {
567
+ const hasFallback = arguments.length > 1;
568
+ let fallbackString;
569
+ const sep = operatorString(separator);
570
+ const out = [];
571
+ for (const value of this) {
572
+ try {
573
+ out.push(operatorString(value));
574
+ } catch (err) {
575
+ if (!hasFallback) {
576
+ throw err;
577
+ }
578
+ if (typeof fallback === "function") {
579
+ out.push(operatorString(fallback(value)));
580
+ } else {
581
+ if (fallbackString === void 0) {
582
+ fallbackString = operatorString(fallback);
583
+ }
584
+ out.push(fallbackString);
585
+ }
586
+ }
587
+ }
588
+ return nativeJoin.call(out, sep);
589
+ };
590
+ desc.value.__zuzu_array_join = true;
591
+ desc.enumerable = false;
592
+ desc.configurable = true;
593
+ desc.writable = true;
594
+ Object.defineProperty(Array.prototype, "join", desc);
595
+ }
554
596
  define("count", function _count() {
555
597
  return this.length;
556
598
  });
@@ -798,6 +840,14 @@
798
840
  }
799
841
  return resolved;
800
842
  }
843
+ function strongReferenceCount(value) {
844
+ const resolved = resolveWeakValue(value);
845
+ if (!isWeakableValue(resolved)) {
846
+ return 0;
847
+ }
848
+ const state = weakStates.get(resolved);
849
+ return state ? state.strong : 0;
850
+ }
801
851
  function retainedValuesForCollection(collection, create = false) {
802
852
  const resolved = resolveWeakValue(collection);
803
853
  if (!isWeakableValue(resolved)) {
@@ -849,7 +899,7 @@
849
899
  }
850
900
  return null;
851
901
  }
852
- function releaseValue(value) {
902
+ function releaseValue(value, options = {}) {
853
903
  if (isWeakCell(value)) {
854
904
  return null;
855
905
  }
@@ -860,7 +910,12 @@
860
910
  const wasStrong = state.strong;
861
911
  state.strong = Math.max(0, state.strong - 1);
862
912
  if (wasStrong > 0 && state.strong === 0) {
863
- releaseCollectionValues(resolved);
913
+ if (!options.preserveCollectionValues) {
914
+ releaseCollectionValues(resolved);
915
+ }
916
+ if (!options.suppressDemolish && typeof resolved.__demolish__ === "function") {
917
+ resolved.__demolish__();
918
+ }
864
919
  }
865
920
  }
866
921
  }
@@ -1197,6 +1252,9 @@
1197
1252
  if (name2 === "eq") {
1198
1253
  return (left, right) => String(left) === String(right);
1199
1254
  }
1255
+ if (name2 === "eqi") {
1256
+ return (left, right) => String(left).toLowerCase() === String(right).toLowerCase();
1257
+ }
1200
1258
  if (name2 === "~") {
1201
1259
  return (left, right) => {
1202
1260
  if (right && typeof right.test === "function") {
@@ -1207,11 +1265,19 @@
1207
1265
  }
1208
1266
  return (left, right) => left == right;
1209
1267
  }
1268
+ function switchTruthy(value) {
1269
+ return !!value;
1270
+ }
1210
1271
  function runSwitch(value, cmpName, cases, defaultBody) {
1211
1272
  const cmp = buildComparator(cmpName);
1212
1273
  let runNext = false;
1213
1274
  for (const section of cases) {
1214
- const matched = section.values.some((item) => cmp(value, item));
1275
+ let matched = false;
1276
+ if (Array.isArray(section.tests)) {
1277
+ matched = section.tests.some((test) => switchTruthy(test(value)));
1278
+ } else {
1279
+ matched = section.values.some((item) => cmp(value, item));
1280
+ }
1215
1281
  if (matched || runNext) {
1216
1282
  const result = section.body();
1217
1283
  runNext = result === true;
@@ -1554,6 +1620,7 @@
1554
1620
  resolveWeakValue,
1555
1621
  retainValue,
1556
1622
  releaseValue,
1623
+ strongReferenceCount,
1557
1624
  retainCollectionValue,
1558
1625
  releaseCollectionValue,
1559
1626
  releaseCollectionValues,
@@ -4432,6 +4499,33 @@
4432
4499
  return `
4433
4500
  //# sourceURL=${filename.replace(/[\r\n]/gu, "_")}`;
4434
4501
  }
4502
+ var browserModuleUrls = /* @__PURE__ */ new Map();
4503
+ function _normalizeModuleName(value) {
4504
+ const name2 = String(value ?? "").trim().replace(/\\/gu, "/");
4505
+ if (name2 === "" || name2.startsWith("/") || name2.endsWith("/") || /(^|\/)\.\.?(\/|$)/u.test(name2)) {
4506
+ throw new Error(`Invalid browser module name: ${value}`);
4507
+ }
4508
+ return name2.replace(/\.zzm$/u, "");
4509
+ }
4510
+ function _moduleNameFromPath(filename) {
4511
+ const path = _resolvePath(filename);
4512
+ if (!path.startsWith("/modules/") || !path.endsWith(".zzm")) {
4513
+ return null;
4514
+ }
4515
+ return path.slice("/modules/".length, -".zzm".length);
4516
+ }
4517
+ function addBrowserModule(moduleName, url) {
4518
+ const logicalName = _normalizeModuleName(moduleName);
4519
+ const moduleUrl = String(url ?? "").trim();
4520
+ if (moduleUrl === "") {
4521
+ throw new Error(`Invalid URL for browser module '${logicalName}'`);
4522
+ }
4523
+ browserModuleUrls.set(logicalName, moduleUrl);
4524
+ return logicalName;
4525
+ }
4526
+ function hasBrowserModules() {
4527
+ return browserModuleUrls.size > 0;
4528
+ }
4435
4529
  function _browserEval(source, context, runOptions = {}) {
4436
4530
  context.globalThis = context;
4437
4531
  const sourceUrl = _sourceUrlComment(runOptions);
@@ -4530,6 +4624,11 @@
4530
4624
  const virtualFiles = new Map(Object.entries(options.virtualFiles || {}).map(([filename, source]) => [_resolvePath(filename), String(source)]));
4531
4625
  const jsModules = new Map(Object.entries(options.jsModules || {}).map(([filename, loaded]) => [_resolvePath(filename), loaded]));
4532
4626
  const fetchedFiles = /* @__PURE__ */ new Map();
4627
+ const fetchingFiles = /* @__PURE__ */ new Map();
4628
+ const optionModuleUrls = new Map(
4629
+ Object.entries(options.remoteModules || {}).map(([name2, url]) => [_normalizeModuleName(name2), String(url)])
4630
+ );
4631
+ const fetchText = typeof options.fetch === "function" ? options.fetch : typeof fetch === "function" ? fetch : null;
4533
4632
  const fetchModule = typeof options.fetchModule === "function" ? options.fetchModule : null;
4534
4633
  const runInContext = typeof options.evaluate === "function" ? options.evaluate : _browserEval;
4535
4634
  const capabilities2 = /* @__PURE__ */ new Set([
@@ -4549,6 +4648,56 @@
4549
4648
  capabilities2.add("worker");
4550
4649
  }
4551
4650
  const gui = _defaultGuiBridge(options);
4651
+ function browserModuleUrlForPath(filename) {
4652
+ const logicalName = _moduleNameFromPath(filename);
4653
+ if (!logicalName) {
4654
+ return null;
4655
+ }
4656
+ if (optionModuleUrls.has(logicalName)) {
4657
+ return optionModuleUrls.get(logicalName);
4658
+ }
4659
+ return browserModuleUrls.get(logicalName) || null;
4660
+ }
4661
+ function hasAsyncModules() {
4662
+ return optionModuleUrls.size > 0 || browserModuleUrls.size > 0;
4663
+ }
4664
+ async function fetchBrowserModule(key, url) {
4665
+ if (fetchedFiles.has(key)) {
4666
+ return fetchedFiles.get(key);
4667
+ }
4668
+ if (fetchingFiles.has(key)) {
4669
+ return fetchingFiles.get(key);
4670
+ }
4671
+ if (!fetchText) {
4672
+ throw new Error(`Exception: Browser host cannot fetch module '${key}'`);
4673
+ }
4674
+ const pending = Promise.resolve(fetchText(url)).then(async (response) => {
4675
+ if (typeof response === "string") {
4676
+ return response;
4677
+ }
4678
+ if (response && response.ok === false) {
4679
+ throw new Error(
4680
+ `Exception: Fetch failed for browser module '${key}' (${response.status})`
4681
+ );
4682
+ }
4683
+ if (!response || typeof response.text !== "function") {
4684
+ throw new Error(
4685
+ `Exception: Fetch did not return text for browser module '${key}'`
4686
+ );
4687
+ }
4688
+ return response.text();
4689
+ }).then((source) => {
4690
+ const text = String(source);
4691
+ fetchedFiles.set(key, text);
4692
+ fetchingFiles.delete(key);
4693
+ return text;
4694
+ }, (err) => {
4695
+ fetchingFiles.delete(key);
4696
+ throw err;
4697
+ });
4698
+ fetchingFiles.set(key, pending);
4699
+ return pending;
4700
+ }
4552
4701
  return {
4553
4702
  name: "browser",
4554
4703
  repoRoot,
@@ -4585,13 +4734,33 @@
4585
4734
  return fetched;
4586
4735
  }
4587
4736
  }
4737
+ if (browserModuleUrlForPath(key)) {
4738
+ throw new Error(`Exception: Browser module '${filename}' requires asynchronous loading`);
4739
+ }
4588
4740
  throw new Error(`Exception: Browser host cannot read file '${filename}'`);
4589
4741
  },
4742
+ async readFileTextAsync(filename) {
4743
+ const key = _resolvePath(filename);
4744
+ if (virtualFiles.has(key)) {
4745
+ return virtualFiles.get(key);
4746
+ }
4747
+ if (fetchedFiles.has(key)) {
4748
+ return fetchedFiles.get(key);
4749
+ }
4750
+ const browserModuleUrl = browserModuleUrlForPath(key);
4751
+ if (browserModuleUrl) {
4752
+ return fetchBrowserModule(key, browserModuleUrl);
4753
+ }
4754
+ return this.readFileText(key);
4755
+ },
4590
4756
  fileExists(filename) {
4591
4757
  const key = _resolvePath(filename);
4592
4758
  if (virtualFiles.has(key) || jsModules.has(key) || fetchedFiles.has(key)) {
4593
4759
  return true;
4594
4760
  }
4761
+ if (browserModuleUrlForPath(key)) {
4762
+ return true;
4763
+ }
4595
4764
  if (fetchModule) {
4596
4765
  const fetched = fetchModule(key);
4597
4766
  if (typeof fetched === "string") {
@@ -4625,11 +4794,14 @@
4625
4794
  throw new Error(`Exception: Browser host has no JS module for '${filename}'`);
4626
4795
  }
4627
4796
  return jsModules.get(key);
4628
- }
4797
+ },
4798
+ hasAsyncModules
4629
4799
  };
4630
4800
  }
4631
4801
  module2.exports = {
4632
- createBrowserHost
4802
+ addBrowserModule,
4803
+ createBrowserHost,
4804
+ hasBrowserModules
4633
4805
  };
4634
4806
  }
4635
4807
  });
@@ -4782,6 +4954,10 @@
4782
4954
  "or",
4783
4955
  "xor",
4784
4956
  "nand",
4957
+ "nor",
4958
+ "xnor",
4959
+ "onlyif",
4960
+ "butnot",
4785
4961
  "not",
4786
4962
  // TODO: Remove stale legacy JS-only words that are not current
4787
4963
  // ZuzuScript keywords: contains, difference, elsif, export, foreach,
@@ -4866,6 +5042,31 @@
4866
5042
  "contains"
4867
5043
  ]);
4868
5044
  var SIMPLE_PUNCTUATION = /* @__PURE__ */ new Set(["(", ")", "{", "}", "[", "]", ",", ";", "."]);
5045
+ var VALUE_PRESERVING_LOGICAL_OPERATORS = /* @__PURE__ */ new Set([
5046
+ "and",
5047
+ "\u22C0",
5048
+ "or",
5049
+ "\u22C1",
5050
+ "xor",
5051
+ "\u22BB",
5052
+ "xnor",
5053
+ "\u2194",
5054
+ "nand",
5055
+ "\u22BC",
5056
+ "nor",
5057
+ "\u22BD",
5058
+ "onlyif",
5059
+ "\u22A8",
5060
+ "butnot",
5061
+ "\u22AD"
5062
+ ]);
5063
+ function maybeValuePreservingOperator(value, peek, advance) {
5064
+ if (VALUE_PRESERVING_LOGICAL_OPERATORS.has(value) && peek() === "?") {
5065
+ advance();
5066
+ return `${value}?`;
5067
+ }
5068
+ return value;
5069
+ }
4869
5070
  function decodeSimpleEscape(esc) {
4870
5071
  switch (esc) {
4871
5072
  case "n":
@@ -5137,7 +5338,30 @@
5137
5338
  if (lastToken.type === "operator") {
5138
5339
  return true;
5139
5340
  }
5140
- if (lastToken.type === "keyword" && ["return", "case", "if", "while", "throw", "and", "or", "xor", "nand", "not"].includes(lastToken.value)) {
5341
+ if (lastToken.type === "keyword" && [
5342
+ "return",
5343
+ "case",
5344
+ "if",
5345
+ "while",
5346
+ "throw",
5347
+ "and",
5348
+ "and?",
5349
+ "or",
5350
+ "or?",
5351
+ "xor",
5352
+ "xor?",
5353
+ "xnor",
5354
+ "xnor?",
5355
+ "nand",
5356
+ "nand?",
5357
+ "nor",
5358
+ "nor?",
5359
+ "onlyif",
5360
+ "onlyif?",
5361
+ "butnot",
5362
+ "butnot?",
5363
+ "not"
5364
+ ].includes(lastToken.value)) {
5141
5365
  return true;
5142
5366
  }
5143
5367
  return false;
@@ -5507,12 +5731,18 @@
5507
5731
  addToken("number", value, start, cursor());
5508
5732
  continue;
5509
5733
  }
5734
+ if (ch === "\u22A4" || ch === "\u22A5") {
5735
+ advance();
5736
+ addToken("keyword", ch === "\u22A4" ? "true" : "false", start, cursor());
5737
+ continue;
5738
+ }
5510
5739
  if (isIdentifierStart(ch)) {
5511
5740
  let value = "";
5512
5741
  while (isIdentifierPart(peek())) {
5513
5742
  value += advance();
5514
5743
  }
5515
5744
  if (KEYWORDS.has(value)) {
5745
+ value = maybeValuePreservingOperator(value, peek, advance);
5516
5746
  addToken("keyword", value, start, cursor());
5517
5747
  } else {
5518
5748
  addToken("identifier", value, start, cursor());
@@ -5524,9 +5754,10 @@
5524
5754
  addToken("punctuation", ch, start, cursor());
5525
5755
  continue;
5526
5756
  }
5527
- if (["+", "-", "*", "/", "%", "<", ">", "=", "_", "?", ":", "~", "!", "\\", "&", "|", "^", "@", "\xD7", "\xF7", "\xAC", "\u22C0", "\u22C1", "\u22BB", "\u22BC"].includes(ch)) {
5757
+ if (["+", "-", "*", "/", "%", "<", ">", "=", "_", "?", ":", "~", "!", "\\", "&", "|", "^", "@", "\xD7", "\xF7", "\xAC", "\u22C0", "\u22C1", "\u22BB", "\u22BC", "\u22BD", "\u2194", "\u22A8", "\u22AD"].includes(ch)) {
5528
5758
  advance();
5529
- addToken("operator", ch, start, cursor());
5759
+ const value = maybeValuePreservingOperator(ch, peek, advance);
5760
+ addToken("operator", value, start, cursor());
5530
5761
  continue;
5531
5762
  }
5532
5763
  if (["\u2261", "\u2262", "\u2260", "\u2264", "\u2265", "\u2192", "\u221A", "\u230A", "\u230B", "\u2308", "\u2309", "\u2208", "\u2209", "\u22C3", "\u22C2", "\u2216", "\u2282", "\u2283", "\xAB", "\xBB", "\u2276", "\u2277", "\u25B7", "\u25C1", "\u2223", "\u2224"].includes(ch)) {
@@ -6517,11 +6748,11 @@
6517
6748
  let comparator = "==";
6518
6749
  if (match("operator", ":")) {
6519
6750
  const token = current();
6520
- if (!["identifier", "keyword", "operator", "string"].includes(token.type)) {
6751
+ const operator = parseSwitchComparator();
6752
+ if (!operator) {
6521
6753
  throw new TranspilerSyntaxError("Expected switch comparator after :", token);
6522
6754
  }
6523
- index++;
6524
- comparator = token.value;
6755
+ comparator = operator;
6525
6756
  }
6526
6757
  expect("punctuation", ")", "Expected ) after switch header");
6527
6758
  expect("punctuation", "{", "Expected { to start switch body");
@@ -6555,18 +6786,97 @@
6555
6786
  }
6556
6787
  function parseSwitchCase() {
6557
6788
  const start = expect("keyword", "case");
6558
- const values = [parseExpression()];
6789
+ const first = parseSwitchCaseValue();
6790
+ const values = [first.value];
6791
+ const operators = [first.operator];
6559
6792
  while (match("punctuation", ",")) {
6560
- values.push(parseExpression());
6793
+ const item = parseSwitchCaseValue();
6794
+ values.push(item.value);
6795
+ operators.push(item.operator);
6561
6796
  }
6562
6797
  expect("operator", ":", "Expected : after switch case");
6563
6798
  const consequent = parseSwitchConsequent(start);
6564
6799
  return withLoc({
6565
6800
  type: "SwitchCase",
6566
6801
  values,
6802
+ operators,
6567
6803
  consequent
6568
6804
  }, start, endLocFromNode(consequent));
6569
6805
  }
6806
+ function parseSwitchCaseValue() {
6807
+ const operator = parseSwitchComparator();
6808
+ return {
6809
+ operator,
6810
+ value: parseExpression()
6811
+ };
6812
+ }
6813
+ function parseSwitchComparator() {
6814
+ if (!isSwitchComparatorToken(current())) {
6815
+ return null;
6816
+ }
6817
+ const operator = current().value;
6818
+ index++;
6819
+ return operator;
6820
+ }
6821
+ function isSwitchComparatorToken(token) {
6822
+ if (!token) {
6823
+ return false;
6824
+ }
6825
+ if (token.type === "identifier" && token.value === "instanceof") {
6826
+ return true;
6827
+ }
6828
+ if (token.type !== "operator" && token.type !== "keyword") {
6829
+ return false;
6830
+ }
6831
+ return [
6832
+ ">",
6833
+ "<",
6834
+ ">=",
6835
+ "\u2264",
6836
+ "<=",
6837
+ "\u2265",
6838
+ "=",
6839
+ "!=",
6840
+ "\u2260",
6841
+ "==",
6842
+ "\u2261",
6843
+ "\u2262",
6844
+ "<=>",
6845
+ "\u2276",
6846
+ "\u2277",
6847
+ "eq",
6848
+ "ne",
6849
+ "gt",
6850
+ "ge",
6851
+ "lt",
6852
+ "le",
6853
+ "cmp",
6854
+ "eqi",
6855
+ "nei",
6856
+ "gti",
6857
+ "gei",
6858
+ "lti",
6859
+ "lei",
6860
+ "cmpi",
6861
+ "in",
6862
+ "\u2208",
6863
+ "not_in",
6864
+ "\u2209",
6865
+ "subsetof",
6866
+ "\u2282",
6867
+ "supersetof",
6868
+ "\u2283",
6869
+ "equivalentof",
6870
+ "\u2282\u2283",
6871
+ "does",
6872
+ "can",
6873
+ "~",
6874
+ "@?",
6875
+ "\u2223",
6876
+ "divides",
6877
+ "\u2224"
6878
+ ].includes(token.value);
6879
+ }
6570
6880
  function parseSwitchDefaultCase() {
6571
6881
  const start = expect("keyword", "default");
6572
6882
  expect("operator", ":", "Expected : after default");
@@ -6953,15 +7263,27 @@
6953
7263
  }, startLocFromNode(left), endLocFromNode(right));
6954
7264
  }
6955
7265
  function parseLogicalOr() {
7266
+ return parseLeftAssociative(
7267
+ parseOnlyif,
7268
+ (token) => token.type === "keyword" && ["or", "or?"].includes(token.value) || token.type === "operator" && ["\u22C1", "\u22C1?"].includes(token.value)
7269
+ );
7270
+ }
7271
+ function parseOnlyif() {
7272
+ return parseRightAssociative(
7273
+ parseLogicalXor,
7274
+ (token) => token.type === "keyword" && ["onlyif", "onlyif?"].includes(token.value) || token.type === "operator" && ["\u22A8", "\u22A8?"].includes(token.value)
7275
+ );
7276
+ }
7277
+ function parseLogicalXor() {
6956
7278
  return parseLeftAssociative(
6957
7279
  parseLogicalAnd,
6958
- (token) => token.type === "keyword" && ["or", "xor", "nand"].includes(token.value) || token.type === "operator" && ["\u22C1", "\u22BB", "\u22BC"].includes(token.value)
7280
+ (token) => token.type === "keyword" && ["xor", "xor?", "nor", "nor?", "xnor", "xnor?"].includes(token.value) || token.type === "operator" && ["\u22BB", "\u22BB?", "\u22BD", "\u22BD?", "\u2194", "\u2194?"].includes(token.value)
6959
7281
  );
6960
7282
  }
6961
7283
  function parseLogicalAnd() {
6962
7284
  return parseLeftAssociative(
6963
7285
  parseEquality,
6964
- (token) => token.type === "keyword" && token.value === "and" || token.type === "operator" && token.value === "\u22C0"
7286
+ (token) => token.type === "keyword" && ["and", "and?", "nand", "nand?", "butnot", "butnot?"].includes(token.value) || token.type === "operator" && ["\u22C0", "\u22C0?", "\u22BC", "\u22BC?", "\u22AD", "\u22AD?"].includes(token.value)
6965
7287
  );
6966
7288
  }
6967
7289
  function parseEquality() {
@@ -7021,6 +7343,21 @@
7021
7343
  }
7022
7344
  return expr;
7023
7345
  }
7346
+ function parseRightAssociative(nextFn, predicate) {
7347
+ const left = nextFn();
7348
+ if (!predicate(current())) {
7349
+ return left;
7350
+ }
7351
+ const op = current();
7352
+ index++;
7353
+ const right = parseRightAssociative(nextFn, predicate);
7354
+ return withLoc({
7355
+ type: "BinaryExpression",
7356
+ operator: op.value,
7357
+ left,
7358
+ right
7359
+ }, startLocFromNode(left), endLocFromNode(right));
7360
+ }
7024
7361
  function parseUnary() {
7025
7362
  if (current().type === "operator" && ["-", "+", "++", "--", "!", "~", "\\", "\u221A", "\xAC"].includes(current().value) || current().type === "keyword" && ["not", "typeof", "abs", "sqrt", "floor", "ceil", "round", "int", "length", "uc", "lc"].includes(current().value)) {
7026
7363
  const op = current();
@@ -7204,6 +7541,17 @@
7204
7541
  argument
7205
7542
  }, start, endLocFromNode(argument));
7206
7543
  }
7544
+ if (startsNamedKeyBeforeColon()) {
7545
+ const normalized = parseNamedKeyBeforeColon();
7546
+ expect("operator", ":", "Expected : in named argument");
7547
+ const value = parseExpression();
7548
+ return withLoc({
7549
+ type: "NamedArgument",
7550
+ key: normalized.key,
7551
+ keyExpr: normalized.keyExpr,
7552
+ value
7553
+ }, startLocFromNode(normalized.keyExpr), previous());
7554
+ }
7207
7555
  const expr = parseExpression();
7208
7556
  if (current().type === "keyword" && current().value === "but") {
7209
7557
  throw new TranspilerSyntaxError(
@@ -7224,6 +7572,20 @@
7224
7572
  }
7225
7573
  return expr;
7226
7574
  }
7575
+ function startsNamedKeyBeforeColon() {
7576
+ return ["identifier", "keyword", "string"].includes(current().type) && peekToken().type === "operator" && peekToken().value === ":";
7577
+ }
7578
+ function parseNamedKeyBeforeColon() {
7579
+ const token = current();
7580
+ index++;
7581
+ return {
7582
+ key: token.value,
7583
+ keyExpr: withLoc({
7584
+ type: "StringLiteral",
7585
+ value: token.value
7586
+ }, token, token)
7587
+ };
7588
+ }
7227
7589
  function normalizeNamedArgumentKey(expr) {
7228
7590
  if (expr.type === "Identifier") {
7229
7591
  return {
@@ -7492,9 +7854,10 @@
7492
7854
  break;
7493
7855
  }
7494
7856
  const start = current();
7495
- const keyExpr = parseExpression();
7857
+ const literalKey = startsNamedKeyBeforeColon() ? parseNamedKeyBeforeColon() : null;
7858
+ const keyExpr = literalKey ? literalKey.keyExpr : parseExpression();
7496
7859
  if (current().type === "operator" && current().value === ":") {
7497
- const normalized = normalizeNamedArgumentKey(keyExpr);
7860
+ const normalized = literalKey || normalizeNamedArgumentKey(keyExpr);
7498
7861
  expect("operator", ":", "Expected : in pairlist literal");
7499
7862
  const value = parseExpression();
7500
7863
  entries.push(withLoc({
@@ -7725,8 +8088,8 @@
7725
8088
  function emitProgram(ast, options = {}) {
7726
8089
  loopCounter = 0;
7727
8090
  chainCounter = 0;
7728
- const needsAsyncWrapper = programNeedsAsyncWrapper(ast);
7729
8091
  const syncEval = options.syncEval === true;
8092
+ const needsAsyncWrapper = programNeedsAsyncWrapper(ast) || options.asyncImports === true && !syncEval && programContainsImport(ast);
7730
8093
  const expressionDeclaredNames = collectExpressionDeclaredNames(ast.body);
7731
8094
  const sharedOptions = {
7732
8095
  ...options,
@@ -7752,7 +8115,7 @@ ${emitExpressionBlock({ body: ast.body }, sharedOptions)}
7752
8115
  })).join("\n");
7753
8116
  const prelude = emitPredeclaredNames(expressionDeclaredNames);
7754
8117
  const source = [prelude, body].filter(Boolean).join("\n");
7755
- if (needsAsyncWrapper && !syncEval) {
8118
+ if (needsAsyncWrapper && !syncEval && options.deferAsyncWrapper !== true) {
7756
8119
  return `( async () => {
7757
8120
  ${source}
7758
8121
  } )()`;
@@ -7963,6 +8326,9 @@ globalThis[${JSON.stringify(node.id.name)}] = ${name2};` : declaration;
7963
8326
  if (options.inSwitchSection) {
7964
8327
  return `return { __zuzu_return: true, value: ${value} };`;
7965
8328
  }
8329
+ if (options.returnSlotName) {
8330
+ return `return (${options.returnSlotName} = ${value});`;
8331
+ }
7966
8332
  return node.argument ? `return ${value};` : `return ${value};`;
7967
8333
  }
7968
8334
  case "IfStatement":
@@ -8047,18 +8413,23 @@ globalThis[${JSON.stringify(node.id.name)}] = ${name2};` : declaration;
8047
8413
  }
8048
8414
  function emitFunctionLike(node, header, options = {}) {
8049
8415
  const signature = analyzeSpecialSignature(node.params);
8050
- const functionOptions = createFunctionOptions(node, options);
8051
8416
  const cleanupNames = collectCleanupNames(node.body);
8417
+ const returnSlotName = cleanupNames.length > 0 ? "__zuzu_returning" : null;
8418
+ const functionOptions = {
8419
+ ...createFunctionOptions(node, options),
8420
+ returnSlotName
8421
+ };
8052
8422
  const bodySource = withEmitContext(
8053
8423
  functionOptions,
8054
8424
  () => emitFunctionBlock(node.body, cleanupNames, functionOptions)
8055
8425
  );
8056
- const cleanup = cleanupNames.map((name2) => `__zuzu_maybe_demolish( ${name2} );`).join("\n");
8426
+ const cleanup = cleanupNames.map((name2) => `__zuzu_maybe_demolish( ${name2}, ${returnSlotName} );`).join("\n");
8057
8427
  const bodyWrapper = [
8428
+ returnSlotName ? `let ${returnSlotName} = null;` : "",
8058
8429
  "try {",
8059
8430
  bodySource,
8060
8431
  "} catch ( __zuzu_nonlocal ) {",
8061
- "if ( __zuzu_nonlocal && __zuzu_nonlocal.__zuzu_nonlocal_return ) { return __zuzu_nonlocal.value; }",
8432
+ returnSlotName ? `if ( __zuzu_nonlocal && __zuzu_nonlocal.__zuzu_nonlocal_return ) { ${returnSlotName} = __zuzu_nonlocal.value; return ${returnSlotName}; }` : "if ( __zuzu_nonlocal && __zuzu_nonlocal.__zuzu_nonlocal_return ) { return __zuzu_nonlocal.value; }",
8062
8433
  "throw __zuzu_nonlocal;",
8063
8434
  "}",
8064
8435
  cleanupNames.length > 0 ? "finally {" : "",
@@ -8208,7 +8579,8 @@ globalThis[${JSON.stringify(node.id.name)}] = ${name2};` : declaration;
8208
8579
  ...expressionDeclaredNames
8209
8580
  ]),
8210
8581
  bodylessFunctionNames: /* @__PURE__ */ new Set(),
8211
- chainHereName: hereParamName || options.chainHereName
8582
+ chainHereName: hereParamName || options.chainHereName,
8583
+ returnSlotName: null
8212
8584
  };
8213
8585
  }
8214
8586
  function extractDeclaredParamNames(params) {
@@ -8509,14 +8881,19 @@ globalThis[${JSON.stringify(node.id.name)}] = ${name2};` : declaration;
8509
8881
  return `try ${emitBlock(node.block, options)} catch ( __zuzu_err ) { ${catchBody} }`;
8510
8882
  }
8511
8883
  function emitImportDeclaration(node, options = {}) {
8884
+ const asyncImport = options.asyncImports === true && options.syncEval !== true;
8885
+ const importModule = () => {
8886
+ const call = `__zuzu_import( ${JSON.stringify(node.source)} )`;
8887
+ return asyncImport ? `await ${call}` : call;
8888
+ };
8512
8889
  if (node.importAll) {
8513
8890
  if (options.syncEval) {
8514
8891
  return `{ const __zuzu_star = __zuzu_import( ${JSON.stringify(node.source)} ); }`;
8515
8892
  }
8516
8893
  if (node.source === "std/eval") {
8517
- return `{ const __zuzu_star = __zuzu_import( ${JSON.stringify(node.source)} ); for ( const __zuzu_key of Object.keys( __zuzu_star ) ) { if ( __zuzu_key === "eval" ) { continue; } const __zuzu_desc = Object.create( null ); __zuzu_desc.configurable = true; __zuzu_desc.enumerable = true; __zuzu_desc.get = function() { return __zuzu_star[ __zuzu_key ] ?? null; }; __zuzu_desc.set = function( value ) { __zuzu_star[ __zuzu_key ] = value; }; Object.defineProperty( globalThis, __zuzu_key, __zuzu_desc ); } }`;
8894
+ return `{ const __zuzu_star = ${importModule()}; for ( const __zuzu_key of Object.keys( __zuzu_star ) ) { if ( __zuzu_key === "eval" ) { continue; } const __zuzu_desc = Object.create( null ); __zuzu_desc.configurable = true; __zuzu_desc.enumerable = true; __zuzu_desc.get = function() { return __zuzu_star[ __zuzu_key ] ?? null; }; __zuzu_desc.set = function( value ) { __zuzu_star[ __zuzu_key ] = value; }; Object.defineProperty( globalThis, __zuzu_key, __zuzu_desc ); } }`;
8518
8895
  }
8519
- return `{ const __zuzu_star = __zuzu_import( ${JSON.stringify(node.source)} ); if ( Object.prototype.hasOwnProperty.call( __zuzu_star, "done_testing" ) ) { var done_testing = __zuzu_star.done_testing ?? null; } for ( const __zuzu_key of Object.keys( __zuzu_star ) ) { if ( __zuzu_key === "done_testing" ) { continue; } const __zuzu_desc = Object.create( null ); __zuzu_desc.configurable = true; __zuzu_desc.enumerable = true; __zuzu_desc.get = function() { return __zuzu_star[ __zuzu_key ] ?? null; }; __zuzu_desc.set = function( value ) { __zuzu_star[ __zuzu_key ] = value; }; Object.defineProperty( globalThis, __zuzu_key, __zuzu_desc ); } }`;
8896
+ return `{ const __zuzu_star = ${importModule()}; if ( Object.prototype.hasOwnProperty.call( __zuzu_star, "done_testing" ) ) { var done_testing = __zuzu_star.done_testing ?? null; } for ( const __zuzu_key of Object.keys( __zuzu_star ) ) { if ( __zuzu_key === "done_testing" ) { continue; } const __zuzu_desc = Object.create( null ); __zuzu_desc.configurable = true; __zuzu_desc.enumerable = true; __zuzu_desc.get = function() { return __zuzu_star[ __zuzu_key ] ?? null; }; __zuzu_desc.set = function( value ) { __zuzu_star[ __zuzu_key ] = value; }; Object.defineProperty( globalThis, __zuzu_key, __zuzu_desc ); } }`;
8520
8897
  }
8521
8898
  const moduleName = `__zuzu_imported_${Math.abs(hashString(node.source))}_${node.specifiers.length}_${options.syncEval ? loopCounter++ : 0}`;
8522
8899
  const defineImports = node.specifiers.map((specifier) => {
@@ -8565,6 +8942,22 @@ globalThis[${JSON.stringify(node.id.name)}] = ${name2};` : declaration;
8565
8942
  return `const ${moduleName} = __zuzu_import( ${JSON.stringify(node.source)} ); ${defineImports.join(" ")}`;
8566
8943
  }
8567
8944
  if (node.tryMode) {
8945
+ if (asyncImport) {
8946
+ return [
8947
+ `{ ${legacyShape} const ${moduleName} = await ( async () => {`,
8948
+ "try {",
8949
+ importCondition ? `if ( !( ${importCondition} ) ) { return {}; }` : "",
8950
+ `return ${importModule()};`,
8951
+ "}",
8952
+ "catch ( __zuzu_err ) {",
8953
+ "if ( __zuzu_err && __zuzu_err.__zuzu_nonlocal_return ) { throw __zuzu_err; }",
8954
+ "return {};",
8955
+ "}",
8956
+ "} )();",
8957
+ ...defineImports,
8958
+ "}"
8959
+ ].filter(Boolean).join(" ");
8960
+ }
8568
8961
  return [
8569
8962
  `{ ${legacyShape} const ${moduleName} = ( () => {`,
8570
8963
  "try {",
@@ -8581,6 +8974,16 @@ globalThis[${JSON.stringify(node.id.name)}] = ${name2};` : declaration;
8581
8974
  ].filter(Boolean).join(" ");
8582
8975
  }
8583
8976
  if (importCondition) {
8977
+ if (asyncImport) {
8978
+ return [
8979
+ `{ ${legacyShape} const ${moduleName} = await ( async () => {`,
8980
+ `if ( !( ${importCondition} ) ) { return ${disabledImports}; }`,
8981
+ `return ${importModule()};`,
8982
+ "} )();",
8983
+ ...defineImports,
8984
+ "}"
8985
+ ].join(" ");
8986
+ }
8584
8987
  return [
8585
8988
  `{ ${legacyShape} const ${moduleName} = ( () => {`,
8586
8989
  `if ( !( ${importCondition} ) ) { return ${disabledImports}; }`,
@@ -8590,7 +8993,7 @@ globalThis[${JSON.stringify(node.id.name)}] = ${name2};` : declaration;
8590
8993
  "}"
8591
8994
  ].join(" ");
8592
8995
  }
8593
- return `{ ${legacyShape} const ${moduleName} = __zuzu_import( ${JSON.stringify(node.source)} ); ${defineImports.join(" ")} }`;
8996
+ return `{ ${legacyShape} const ${moduleName} = ${importModule()}; ${defineImports.join(" ")} }`;
8594
8997
  }
8595
8998
  function normalizeImportedName(source, imported) {
8596
8999
  return source === "std/math" && imported === "pi" ? "\u03C0" : imported;
@@ -8646,7 +9049,7 @@ globalThis[${JSON.stringify(node.id.name)}] = ${name2};` : declaration;
8646
9049
  ${body}
8647
9050
  }`;
8648
9051
  }
8649
- const cleanup = cleanupNames.map((name2) => `__zuzu_maybe_demolish( ${name2} );`).join("\n");
9052
+ const cleanup = cleanupNames.map((name2) => options.returnSlotName ? `__zuzu_maybe_demolish( ${name2}, ${options.returnSlotName} );` : `__zuzu_maybe_demolish( ${name2} );`).join("\n");
8650
9053
  return `{
8651
9054
  ${cleanupNames.map((name2) => `let ${name2} = null;`).join("\n")}
8652
9055
  try {
@@ -8889,7 +9292,7 @@ ${cleanup}
8889
9292
  }
8890
9293
  function emitSwitchStatement(node, options = {}) {
8891
9294
  if (options.asyncContext && !options.syncEval) {
8892
- const cases2 = node.cases.map((section) => `{ values: [ ${section.values.map(emitExpression).join(", ")} ], body: async function() ${emitBlock(section.consequent, {
9295
+ const cases2 = node.cases.map((section) => `{ tests: [ ${section.values.map((value, index) => `async function( __zuzu_switch_value ) { return __zuzu_truthy( ${emitSwitchTestExpression(section, index, node.comparator)} ); }`).join(", ")} ], body: async function() ${emitBlock(section.consequent, {
8893
9296
  ...options,
8894
9297
  inSwitchSection: true,
8895
9298
  loopDepth: 0
@@ -8902,7 +9305,7 @@ ${cleanup}
8902
9305
  const tail2 = switchStatementContainsReturn(node) ? options.inSwitchSection ? "if ( __zuzu_switch_result && __zuzu_switch_result.__zuzu_return ) { return __zuzu_switch_result; }" : "if ( __zuzu_switch_result && __zuzu_switch_result.__zuzu_return ) { return __zuzu_switch_result.value; }" : "";
8903
9306
  return `{ let __zuzu_switch_result = await __zuzu_switch_async( ${emitExpression(node.discriminant)}, ${JSON.stringify(node.comparator)}, [ ${cases2.join(", ")} ], ${defaultBody2} ); ${tail2} }`;
8904
9307
  }
8905
- const cases = node.cases.map((section) => `{ values: [ ${section.values.map(emitExpression).join(", ")} ], body: function() ${emitBlock(section.consequent, {
9308
+ const cases = node.cases.map((section) => `{ tests: [ ${section.values.map((value, index) => `function( __zuzu_switch_value ) { return __zuzu_truthy( ${emitSwitchTestExpression(section, index, node.comparator)} ); }`).join(", ")} ], body: function() ${emitBlock(section.consequent, {
8906
9309
  ...options,
8907
9310
  inSwitchSection: true,
8908
9311
  loopDepth: 0
@@ -8915,6 +9318,19 @@ ${cleanup}
8915
9318
  const tail = switchStatementContainsReturn(node) ? options.inSwitchSection ? "if ( __zuzu_switch_result && __zuzu_switch_result.__zuzu_return ) { return __zuzu_switch_result; }" : "if ( __zuzu_switch_result && __zuzu_switch_result.__zuzu_return ) { return __zuzu_switch_result.value; }" : "";
8916
9319
  return `{ let __zuzu_switch_result = __zuzu_switch( ${emitExpression(node.discriminant)}, ${JSON.stringify(node.comparator)}, [ ${cases.join(", ")} ], ${defaultBody} ); ${tail} }`;
8917
9320
  }
9321
+ function emitSwitchTestExpression(section, index, defaultComparator) {
9322
+ const right = section.values[index];
9323
+ const operator = section.operators && section.operators[index] || defaultComparator || "==";
9324
+ return emitBinaryExpression({
9325
+ type: "BinaryExpression",
9326
+ operator,
9327
+ left: {
9328
+ type: "Identifier",
9329
+ name: "__zuzu_switch_value"
9330
+ },
9331
+ right
9332
+ });
9333
+ }
8918
9334
  function emitTraitDeclaration(node, options = {}) {
8919
9335
  const methods = completeMethodDeclarations(node.body || []).map((method) => {
8920
9336
  const value = emitMethodFunction(method, {
@@ -9116,7 +9532,7 @@ ${cleanup}
9116
9532
  case "ConditionalExpression":
9117
9533
  return `( __zuzu_truthy( ${emitExpression(node.test)} ) ? ${emitExpression(node.consequent)} : ${emitExpression(node.alternate)} )`;
9118
9534
  case "ShortTernaryExpression":
9119
- return `( __zuzu_truthy( ${emitExpression(node.test)} ) ? ${emitExpression(node.test)} : ${emitExpression(node.alternate)} )`;
9535
+ return `( () => { const __zuzu_left = ${emitExpression(node.test)}; return __zuzu_resolve_weak_value( __zuzu_left ) != null ? __zuzu_left : ${emitExpression(node.alternate)}; } )()`;
9120
9536
  case "TryExpression":
9121
9537
  return emitTryExpression(node);
9122
9538
  case "DoExpression":
@@ -9216,13 +9632,17 @@ ${cleanup}
9216
9632
  source = `await __zuzu_collection_${method}_async( ${object}, ${args} )`;
9217
9633
  } else if (node.arguments.length === 0) {
9218
9634
  const object = emitExpression(node.callee.object);
9219
- const property = node.callee.computed ? node.callee.property.type === "BraceIdentifier" ? `__zuzu_resolve_brace_key( ${object}, ${JSON.stringify(node.callee.property.name)}, () => ${node.callee.property.name} )` : emitExpression(node.callee.property) : JSON.stringify(node.callee.property.name);
9220
- source = `__zuzu_call_member( ${object}, ${property} )`;
9635
+ if (node.callee.computed) {
9636
+ const property = node.callee.property.type === "BraceIdentifier" ? `__zuzu_resolve_brace_key( __zuzu_receiver, ${JSON.stringify(node.callee.property.name)}, () => ${node.callee.property.name} )` : emitExpression(node.callee.property);
9637
+ source = `( () => { const __zuzu_receiver = ${object}; return __zuzu_call_member( __zuzu_receiver, ${property} ); } )()`;
9638
+ } else {
9639
+ source = `__zuzu_call_member( ${object}, ${JSON.stringify(node.callee.property.name)} )`;
9640
+ }
9221
9641
  } else {
9222
9642
  const object = emitExpression(node.callee.object);
9223
9643
  if (node.callee.computed) {
9224
- const property = node.callee.property.type === "BraceIdentifier" ? `__zuzu_resolve_brace_key( ${object}, ${JSON.stringify(node.callee.property.name)}, () => ${node.callee.property.name} )` : emitExpression(node.callee.property);
9225
- source = `__zuzu_call_member( ${object}, ${property}, ${args} )`;
9644
+ const property = node.callee.property.type === "BraceIdentifier" ? `__zuzu_resolve_brace_key( __zuzu_receiver, ${JSON.stringify(node.callee.property.name)}, () => ${node.callee.property.name} )` : emitExpression(node.callee.property);
9645
+ source = `( () => { const __zuzu_receiver = ${object}; return __zuzu_call_member( __zuzu_receiver, ${property}, ${args} ); } )()`;
9226
9646
  } else {
9227
9647
  source = `__zuzu_call_member( ${object}, ${JSON.stringify(node.callee.property.name)}, ${args} )`;
9228
9648
  }
@@ -9405,16 +9825,52 @@ ${cleanup}
9405
9825
  switch (node.operator) {
9406
9826
  case "and":
9407
9827
  case "\u22C0":
9408
- return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? ( __zuzu_truthy( ${right} ) ? 1 : 0 ) : 0; } )()`;
9828
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? __zuzu_truthy( ${right} ) : false; } )()`;
9829
+ case "and?":
9830
+ case "\u22C0?":
9831
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? ${right} : __zuzu_left; } )()`;
9409
9832
  case "or":
9410
9833
  case "\u22C1":
9411
- return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? 1 : ( __zuzu_truthy( ${right} ) ? 1 : 0 ); } )()`;
9834
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? true : __zuzu_truthy( ${right} ); } )()`;
9835
+ case "or?":
9836
+ case "\u22C1?":
9837
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? __zuzu_left : ${right}; } )()`;
9838
+ case "nor":
9839
+ case "\u22BD":
9840
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? false : !__zuzu_truthy( ${right} ); } )()`;
9841
+ case "nor?":
9842
+ case "\u22BD?":
9843
+ return `( () => { const __zuzu_left = ${left}; const __zuzu_right = ${right}; return __zuzu_truthy( __zuzu_left ) ? ( __zuzu_truthy( __zuzu_right ) ? false : __zuzu_right ) : ( __zuzu_truthy( __zuzu_right ) ? __zuzu_left : true ); } )()`;
9412
9844
  case "xor":
9413
9845
  case "\u22BB":
9414
9846
  return `__zuzu_xor( ${left}, ${right} )`;
9847
+ case "xor?":
9848
+ case "\u22BB?":
9849
+ return `( () => { const __zuzu_left = ${left}; const __zuzu_right = ${right}; return __zuzu_truthy( __zuzu_left ) ? ( __zuzu_truthy( __zuzu_right ) ? false : __zuzu_left ) : ( __zuzu_truthy( __zuzu_right ) ? __zuzu_right : false ); } )()`;
9850
+ case "xnor":
9851
+ case "\u2194":
9852
+ return `( __zuzu_truthy( ${left} ) === __zuzu_truthy( ${right} ) )`;
9853
+ case "xnor?":
9854
+ case "\u2194?":
9855
+ return `( () => { const __zuzu_left = ${left}; const __zuzu_right = ${right}; return __zuzu_truthy( __zuzu_left ) ? __zuzu_right : ( __zuzu_truthy( __zuzu_right ) ? __zuzu_left : true ); } )()`;
9415
9856
  case "nand":
9416
9857
  case "\u22BC":
9417
9858
  return `__zuzu_nand( ${left}, ${right} )`;
9859
+ case "nand?":
9860
+ case "\u22BC?":
9861
+ return `( () => { const __zuzu_left = ${left}; const __zuzu_right = ${right}; return __zuzu_truthy( __zuzu_left ) ? ( __zuzu_truthy( __zuzu_right ) ? false : true ) : ( __zuzu_truthy( __zuzu_right ) ? __zuzu_right : true ); } )()`;
9862
+ case "onlyif":
9863
+ case "\u22A8":
9864
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? __zuzu_truthy( ${right} ) : true; } )()`;
9865
+ case "onlyif?":
9866
+ case "\u22A8?":
9867
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? ${right} : true; } )()`;
9868
+ case "butnot":
9869
+ case "\u22AD":
9870
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? !__zuzu_truthy( ${right} ) : false; } )()`;
9871
+ case "butnot?":
9872
+ case "\u22AD?":
9873
+ return `( () => { const __zuzu_left = ${left}; return __zuzu_truthy( __zuzu_left ) ? ( __zuzu_truthy( ${right} ) ? false : __zuzu_left ) : __zuzu_left; } )()`;
9418
9874
  case "eq":
9419
9875
  return `__zuzu_str_eq( ${left}, ${right} )`;
9420
9876
  case "ne":
@@ -9817,6 +10273,9 @@ ${cleanup}
9817
10273
  function programNeedsAsyncWrapper(ast) {
9818
10274
  return ast.body.some((stmt) => nodeContainsAsyncControl(stmt));
9819
10275
  }
10276
+ function programContainsImport(ast) {
10277
+ return ast.body.some((stmt) => stmt && stmt.type === "ImportDeclaration");
10278
+ }
9820
10279
  function nodeContainsAsyncControl(node) {
9821
10280
  if (!node || typeof node !== "object") {
9822
10281
  return false;
@@ -11104,7 +11563,7 @@ ${cleanup}
11104
11563
  "package.json"(exports2, module2) {
11105
11564
  module2.exports = {
11106
11565
  name: "zuzu-js",
11107
- version: "0.4.0",
11566
+ version: "0.6.0",
11108
11567
  description: "JavaScript runtime, compiler, and browser bundle for ZuzuScript.",
11109
11568
  main: "lib/zuzu.js",
11110
11569
  bin: {
@@ -11183,6 +11642,27 @@ ${cleanup}
11183
11642
  var { BinaryString: BinaryString2 } = require_runtime_helpers();
11184
11643
  var { Task, traceBlockingOperation } = require_task();
11185
11644
  var runtimePolicy2 = {};
11645
+ function makeTempFilePath() {
11646
+ for (let attempt = 0; attempt < 1e3; attempt++) {
11647
+ const nonce = `${process.pid}-${Date.now()}-${attempt}-${Math.random().toString(36).slice(2)}`;
11648
+ const file = path.join(os.tmpdir(), `zuzu-js-${nonce}.tmp`);
11649
+ let fd = null;
11650
+ try {
11651
+ fd = fs.openSync(file, "wx");
11652
+ return file;
11653
+ } catch (err) {
11654
+ if (err && err.code === "EEXIST") {
11655
+ continue;
11656
+ }
11657
+ throw err;
11658
+ } finally {
11659
+ if (fd !== null) {
11660
+ fs.closeSync(fd);
11661
+ }
11662
+ }
11663
+ }
11664
+ throw new Error("IOError: could not create temporary file");
11665
+ }
11186
11666
  function typeName2(value) {
11187
11667
  if (value == null) {
11188
11668
  return "Null";
@@ -11357,11 +11837,28 @@ ${cleanup}
11357
11837
  return out;
11358
11838
  }
11359
11839
  var Path = class _Path {
11360
- constructor(rawPath) {
11840
+ constructor(rawPath, options = {}) {
11361
11841
  const pathValue = rawPath && typeof rawPath === "object" && !Array.isArray(rawPath) && (Object.prototype.hasOwnProperty.call(rawPath, "path") || typeof rawPath.get === "function" && typeof rawPath.has === "function" && rawPath.has("path")) ? typeof rawPath.get === "function" ? rawPath.get("path") : rawPath.path : rawPath;
11362
11842
  this.value = String(pathValue ?? "");
11363
11843
  this._linePos = 0;
11364
11844
  this._lineMode = "text";
11845
+ this._tempCleanup = options.tempCleanup || null;
11846
+ }
11847
+ __demolish__() {
11848
+ const cleanup = this._tempCleanup;
11849
+ this._tempCleanup = null;
11850
+ if (cleanup === "dir") {
11851
+ try {
11852
+ fs.rmSync(this.value, { recursive: true, force: true });
11853
+ } catch (_err) {
11854
+ }
11855
+ } else if (cleanup === "file") {
11856
+ try {
11857
+ fs.rmSync(this.value, { force: true });
11858
+ } catch (_err) {
11859
+ }
11860
+ }
11861
+ return null;
11365
11862
  }
11366
11863
  to_String() {
11367
11864
  return this.value;
@@ -11598,11 +12095,13 @@ ${cleanup}
11598
12095
  return new _Path(process.cwd());
11599
12096
  }
11600
12097
  static tempfile() {
11601
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "zuzu-js-"));
11602
- return new _Path(path.join(dir, "tmp.txt"));
12098
+ return new _Path(makeTempFilePath(), { tempCleanup: "file" });
11603
12099
  }
11604
12100
  static tempdir() {
11605
- return new _Path(fs.mkdtempSync(path.join(os.tmpdir(), "zuzu-js-dir-")));
12101
+ return new _Path(
12102
+ fs.mkdtempSync(path.join(os.tmpdir(), "zuzu-js-dir-")),
12103
+ { tempCleanup: "dir" }
12104
+ );
11606
12105
  }
11607
12106
  static join(parts) {
11608
12107
  return new _Path(path.join(...parts.map((p) => String(p))));
@@ -11682,6 +12181,7 @@ ${cleanup}
11682
12181
  resolveWeakValue,
11683
12182
  retainValue,
11684
12183
  releaseValue,
12184
+ strongReferenceCount,
11685
12185
  retainCollectionValue,
11686
12186
  releaseCollectionValue,
11687
12187
  releaseCollectionValues,
@@ -11758,6 +12258,12 @@ ${cleanup}
11758
12258
  this.add(makeWeakValue(value));
11759
12259
  return this;
11760
12260
  });
12261
+ define(Set.prototype, "push_weak", function _pushWeak(...values) {
12262
+ for (const value of values) {
12263
+ this.add(makeWeakValue(value));
12264
+ }
12265
+ return this;
12266
+ });
11761
12267
  define(Set.prototype, "copy", function _copy() {
11762
12268
  return makeSet(this);
11763
12269
  });
@@ -12064,8 +12570,12 @@ ${cleanup}
12064
12570
  switch (comparator) {
12065
12571
  case "eq":
12066
12572
  return stringCompare(left, right) === 0;
12573
+ case "eqi":
12574
+ return stringCompare(left, right, { insensitive: true }) === 0;
12067
12575
  case "ne":
12068
12576
  return stringCompare(left, right) !== 0;
12577
+ case "nei":
12578
+ return stringCompare(left, right, { insensitive: true }) !== 0;
12069
12579
  case "=":
12070
12580
  return numericEqual(zuzuToNumber(left), zuzuToNumber(right));
12071
12581
  case "==":
@@ -12075,7 +12585,17 @@ ${cleanup}
12075
12585
  };
12076
12586
  let runNext = false;
12077
12587
  for (const section of cases) {
12078
- const matched = section.values.some((item) => cmp(value, item));
12588
+ let matched = false;
12589
+ if (Array.isArray(section.tests)) {
12590
+ for (const test of section.tests) {
12591
+ if (zuzuTruthy(await taskRuntime2.awaitValue(test(value)))) {
12592
+ matched = true;
12593
+ break;
12594
+ }
12595
+ }
12596
+ } else {
12597
+ matched = section.values.some((item) => cmp(value, item));
12598
+ }
12079
12599
  if (matched || runNext) {
12080
12600
  const result = await taskRuntime2.awaitValue(section.body());
12081
12601
  runNext = result === true;
@@ -12310,7 +12830,7 @@ ${cleanup}
12310
12830
  return Object.prototype.toString.call(value) === "[object Object]" && !(value.constructor && value.constructor.__zuzu_class_name);
12311
12831
  }
12312
12832
  function isDictMethodReceiver(value) {
12313
- return value && typeof value === "object" && !Array.isArray(value) && !isPairListLike(value) && !isSetLike(value) && !isBagLike(value);
12833
+ return isPlainObjectLike(value) && !isPairListLike(value) && !isSetLike(value) && !isBagLike(value);
12314
12834
  }
12315
12835
  function normalizeDictKey(key) {
12316
12836
  key = resolveWeakValue(key);
@@ -12328,18 +12848,350 @@ ${cleanup}
12328
12848
  }
12329
12849
  return String(key);
12330
12850
  }
12851
+ function arraySliceBounds(length, start, end = length) {
12852
+ let from = Number(start ?? 0);
12853
+ let to = Number(end ?? length);
12854
+ from = Number.isFinite(from) ? Math.trunc(from) : 0;
12855
+ to = Number.isFinite(to) ? Math.trunc(to) : length;
12856
+ if (from < 0) {
12857
+ from += length;
12858
+ }
12859
+ if (to < 0) {
12860
+ to += length;
12861
+ }
12862
+ from = Math.max(0, Math.min(length, from));
12863
+ to = Math.max(0, Math.min(length, to));
12864
+ return [Math.min(from, to), Math.max(from, to)];
12865
+ }
12866
+ function arrayMethodArity(name2, actual, min, max = min) {
12867
+ const count = actual - 1;
12868
+ if (count < min || count > max) {
12869
+ throw new Error(
12870
+ min === max ? `${name2} expects ${min} argument${min === 1 ? "" : "s"}` : `${name2} expects between ${min} and ${max} arguments`
12871
+ );
12872
+ }
12873
+ }
12874
+ function arrayIndex(length, idx) {
12875
+ let index = Number(idx);
12876
+ index = Number.isFinite(index) ? Math.trunc(index) : 0;
12877
+ if (index < 0) {
12878
+ index += length;
12879
+ }
12880
+ return index;
12881
+ }
12882
+ function shuffledArrayValues(self) {
12883
+ const out = self.slice();
12884
+ for (let idx = out.length - 1; idx > 0; idx--) {
12885
+ const swapIdx = Math.floor(Math.random() * (idx + 1));
12886
+ [out[idx], out[swapIdx]] = [out[swapIdx], out[idx]];
12887
+ }
12888
+ return out;
12889
+ }
12890
+ function arrayJoin(self, separator, fallback, hasFallback) {
12891
+ const sep = operatorString(separator);
12892
+ let fallbackString;
12893
+ const out = [];
12894
+ for (const value of self) {
12895
+ try {
12896
+ out.push(operatorString(value));
12897
+ } catch (err) {
12898
+ if (!hasFallback) {
12899
+ throw err;
12900
+ }
12901
+ if (typeof fallback === "function") {
12902
+ out.push(operatorString(fallback(value)));
12903
+ } else {
12904
+ if (fallbackString === void 0) {
12905
+ fallbackString = operatorString(fallback);
12906
+ }
12907
+ out.push(fallbackString);
12908
+ }
12909
+ }
12910
+ }
12911
+ return out.join(sep);
12912
+ }
12913
+ var ARRAY_METHODS = Object.freeze({
12914
+ append(self, ...values) {
12915
+ self.push(...values.map((value) => retainCollectionValue(self, value)));
12916
+ return self;
12917
+ },
12918
+ push(self, ...values) {
12919
+ return ARRAY_METHODS.append(self, ...values);
12920
+ },
12921
+ add(self, ...values) {
12922
+ return ARRAY_METHODS.append(self, ...values);
12923
+ },
12924
+ push_weak(self, ...values) {
12925
+ self.push(...values.map(makeWeakValue));
12926
+ return self;
12927
+ },
12928
+ pop(self) {
12929
+ return self.length ? self.pop() : null;
12930
+ },
12931
+ prepend(self, ...values) {
12932
+ self.unshift(...values.map((value) => retainCollectionValue(self, value)));
12933
+ return self;
12934
+ },
12935
+ unshift(self, ...values) {
12936
+ return ARRAY_METHODS.prepend(self, ...values);
12937
+ },
12938
+ unshift_weak(self, ...values) {
12939
+ self.unshift(...values.map(makeWeakValue));
12940
+ return self;
12941
+ },
12942
+ shift(self) {
12943
+ return self.length ? self.shift() : null;
12944
+ },
12945
+ length(self) {
12946
+ return self.length;
12947
+ },
12948
+ count(self) {
12949
+ return self.length;
12950
+ },
12951
+ empty(self) {
12952
+ return self.length === 0 ? 1 : 0;
12953
+ },
12954
+ is_empty(self) {
12955
+ return ARRAY_METHODS.empty(self);
12956
+ },
12957
+ copy(self) {
12958
+ return makeArray(self);
12959
+ },
12960
+ to_Array(self) {
12961
+ return makeArray(self);
12962
+ },
12963
+ get(self, idx, fallback = null) {
12964
+ arrayMethodArity("Array.get", arguments.length, 1, 2);
12965
+ const index = arrayIndex(self.length, idx);
12966
+ return index >= 0 && index < self.length ? self[index] : fallback;
12967
+ },
12968
+ set(self, idx, value) {
12969
+ arrayMethodArity("Array.set", arguments.length, 2);
12970
+ const index = arrayIndex(self.length, idx);
12971
+ if (index < 0) {
12972
+ throw new Error("Array index is out of range");
12973
+ }
12974
+ releaseCollectionValue(self, self[index]);
12975
+ self[index] = retainCollectionValue(self, value);
12976
+ return self;
12977
+ },
12978
+ set_weak(self, idx, value) {
12979
+ arrayMethodArity("Array.set_weak", arguments.length, 2);
12980
+ const index = arrayIndex(self.length, idx);
12981
+ if (index < 0) {
12982
+ throw new Error("Array index is out of range");
12983
+ }
12984
+ releaseCollectionValue(self, self[index]);
12985
+ self[index] = makeWeakValue(value);
12986
+ return self;
12987
+ },
12988
+ clear(self) {
12989
+ releaseCollectionValues(self);
12990
+ self.splice(0, self.length);
12991
+ return self;
12992
+ },
12993
+ join(self, separator, fallback) {
12994
+ return arrayJoin(self, separator, fallback, arguments.length > 2);
12995
+ },
12996
+ slice(self, start, end) {
12997
+ const [from, to] = arraySliceBounds(self.length, start, end);
12998
+ return makeArray(self.slice(from, to));
12999
+ },
13000
+ head(self, n = 1) {
13001
+ arrayMethodArity("Array.head", arguments.length, 0, 1);
13002
+ return makeArray(self.slice(0, Math.max(0, Number(n) || 0)));
13003
+ },
13004
+ tail(self, n = 1) {
13005
+ arrayMethodArity("Array.tail", arguments.length, 0, 1);
13006
+ const count = Math.max(0, Number(n) || 0);
13007
+ return makeArray(count === 0 ? [] : self.slice(-count));
13008
+ },
13009
+ to_Set(self) {
13010
+ return makeSet(self);
13011
+ },
13012
+ to_Bag(self) {
13013
+ return makeBag(self);
13014
+ },
13015
+ to_Iterator(self) {
13016
+ return self[Symbol.iterator]();
13017
+ },
13018
+ sort(self, fn2) {
13019
+ const out = self.slice();
13020
+ out.sort((left, right) => Number(fn2(left, right)) || 0);
13021
+ return makeArray(out);
13022
+ },
13023
+ sortstr(self) {
13024
+ return makeArray(self.slice().sort(stringSortComparator));
13025
+ },
13026
+ sortnum(self) {
13027
+ return makeArray(
13028
+ self.map((item) => Number(item)).sort((a, b) => a - b)
13029
+ );
13030
+ },
13031
+ reverse(self) {
13032
+ return makeArray(self.slice().reverse());
13033
+ },
13034
+ sum(self) {
13035
+ return self.reduce((a, b) => Number(a) + Number(b), 0);
13036
+ },
13037
+ product(self) {
13038
+ return self.reduce((a, b) => Number(a) * Number(b), 1);
13039
+ },
13040
+ shuffle(self) {
13041
+ arrayMethodArity("Array.shuffle", arguments.length, 0);
13042
+ return makeArray(shuffledArrayValues(self));
13043
+ },
13044
+ sample(self, n = 1) {
13045
+ arrayMethodArity("Array.sample", arguments.length, 0, 1);
13046
+ return makeArray(
13047
+ shuffledArrayValues(self).slice(0, Math.max(0, Number(n) || 0))
13048
+ );
13049
+ },
13050
+ contains(self, value) {
13051
+ return self.includes(value) ? 1 : 0;
13052
+ },
13053
+ map(self, fn2) {
13054
+ arrayMethodArity("Array.map", arguments.length, 1);
13055
+ return makeArray(self.map((value) => fn2(value)));
13056
+ },
13057
+ grep(self, fn2) {
13058
+ arrayMethodArity("Array.grep", arguments.length, 1);
13059
+ return makeArray(self.filter((value) => fn2(value)));
13060
+ },
13061
+ any(self, fn2) {
13062
+ arrayMethodArity("Array.any", arguments.length, 1);
13063
+ return self.some((value) => fn2(value)) ? 1 : 0;
13064
+ },
13065
+ all(self, fn2) {
13066
+ arrayMethodArity("Array.all", arguments.length, 1);
13067
+ return self.every((value) => fn2(value)) ? 1 : 0;
13068
+ },
13069
+ first(self, fn2) {
13070
+ arrayMethodArity("Array.first", arguments.length, 1);
13071
+ for (const value of self) {
13072
+ if (fn2(value)) {
13073
+ return value;
13074
+ }
13075
+ }
13076
+ return null;
13077
+ },
13078
+ remove(self, fn2) {
13079
+ for (let idx = self.length - 1; idx >= 0; idx--) {
13080
+ if (fn2(self[idx])) {
13081
+ releaseCollectionValue(self, self[idx]);
13082
+ self.splice(idx, 1);
13083
+ }
13084
+ }
13085
+ return self;
13086
+ },
13087
+ first_index(self, fn2) {
13088
+ for (let idx = 0; idx < self.length; idx++) {
13089
+ if (fn2(self[idx])) {
13090
+ return idx;
13091
+ }
13092
+ }
13093
+ return -1;
13094
+ },
13095
+ for_each_value(self, fn2) {
13096
+ for (const value of self) {
13097
+ fn2(value);
13098
+ }
13099
+ return self;
13100
+ },
13101
+ reduce(self, fn2) {
13102
+ if (self.length === 0) {
13103
+ return null;
13104
+ }
13105
+ let acc = self[0];
13106
+ for (let idx = 1; idx < self.length; idx++) {
13107
+ acc = fn2(acc, self[idx]);
13108
+ }
13109
+ return acc;
13110
+ },
13111
+ reductions(self, fn2) {
13112
+ if (self.length === 0) {
13113
+ return makeArray([]);
13114
+ }
13115
+ const out = [self[0]];
13116
+ for (let idx = 1; idx < self.length; idx++) {
13117
+ out.push(fn2(out[out.length - 1], self[idx]));
13118
+ }
13119
+ return makeArray(out);
13120
+ }
13121
+ });
13122
+ var ARRAY_ZERO_ARG_METHODS = /* @__PURE__ */ new Set([
13123
+ "copy",
13124
+ "count",
13125
+ "empty",
13126
+ "is_empty",
13127
+ "length",
13128
+ "pop",
13129
+ "reverse",
13130
+ "shift",
13131
+ "shuffle",
13132
+ "sortnum",
13133
+ "sortstr",
13134
+ "to_Array",
13135
+ "to_Bag",
13136
+ "to_Iterator",
13137
+ "to_Set"
13138
+ ]);
13139
+ function getArrayMethod(object, property) {
13140
+ if (typeof property === "symbol") {
13141
+ return null;
13142
+ }
13143
+ const name2 = String(property);
13144
+ if (!Array.isArray(object) || !Object.prototype.hasOwnProperty.call(ARRAY_METHODS, name2)) {
13145
+ return null;
13146
+ }
13147
+ return function zuzuArrayMethod(...args) {
13148
+ return ARRAY_METHODS[name2](object, ...args);
13149
+ };
13150
+ }
13151
+ var SET_METHODS = Object.freeze({
13152
+ add_weak(self, ...values) {
13153
+ for (const value of values) {
13154
+ self.add(makeWeakValue(value));
13155
+ }
13156
+ return self;
13157
+ },
13158
+ push_weak(self, ...values) {
13159
+ return SET_METHODS.add_weak(self, ...values);
13160
+ },
13161
+ clear(self) {
13162
+ releaseCollectionValues(self);
13163
+ Set.prototype.clear.call(self);
13164
+ return self;
13165
+ }
13166
+ });
13167
+ var SET_ZERO_ARG_METHODS = /* @__PURE__ */ new Set(["clear"]);
13168
+ function getSetMethod(object, property) {
13169
+ if (typeof property === "symbol") {
13170
+ return null;
13171
+ }
13172
+ const name2 = String(property);
13173
+ if (!isSetLike(object) || !Object.prototype.hasOwnProperty.call(SET_METHODS, name2)) {
13174
+ return null;
13175
+ }
13176
+ return function zuzuSetMethod(...args) {
13177
+ return SET_METHODS[name2](object, ...args);
13178
+ };
13179
+ }
13180
+ function dictSortedKeys(self) {
13181
+ return isDictMethodReceiver(self) ? Object.keys(self).sort() : [];
13182
+ }
12331
13183
  var DICT_METHODS = Object.freeze({
12332
13184
  length(self) {
12333
13185
  return isDictMethodReceiver(self) ? Object.keys(self).length : 0;
12334
13186
  },
12335
13187
  keys(self) {
12336
- return isDictMethodReceiver(self) ? Object.keys(self).sort() : [];
13188
+ return makeSet(dictSortedKeys(self));
12337
13189
  },
12338
13190
  values(self) {
12339
13191
  if (!isDictMethodReceiver(self)) {
12340
- return [];
13192
+ return makeBag([]);
12341
13193
  }
12342
- return DICT_METHODS.keys(self).map((key) => resolveWeakValue(self[key]));
13194
+ return makeBag(dictSortedKeys(self).map((key) => resolveWeakValue(self[key])));
12343
13195
  },
12344
13196
  copy(self) {
12345
13197
  if (!isDictMethodReceiver(self)) {
@@ -12353,11 +13205,11 @@ ${cleanup}
12353
13205
  },
12354
13206
  enumerate(self) {
12355
13207
  if (!isDictMethodReceiver(self)) {
12356
- return [];
13208
+ return makeBag([]);
12357
13209
  }
12358
- return DICT_METHODS.keys(self).map(
13210
+ return makeBag(dictSortedKeys(self).map(
12359
13211
  (key) => new Pair({ pair: [key, resolveWeakValue(self[key])] })
12360
- );
13212
+ ));
12361
13213
  },
12362
13214
  has(self, key) {
12363
13215
  return isDictMethodReceiver(self) && Object.prototype.hasOwnProperty.call(self, normalizeDictKey(key)) ? 1 : 0;
@@ -12405,13 +13257,13 @@ ${cleanup}
12405
13257
  return [];
12406
13258
  }
12407
13259
  const out = [];
12408
- for (const key of DICT_METHODS.keys(self)) {
13260
+ for (const key of dictSortedKeys(self)) {
12409
13261
  out.push(key, resolveWeakValue(self[key]));
12410
13262
  }
12411
13263
  return out;
12412
13264
  },
12413
13265
  sorted_keys(self) {
12414
- return DICT_METHODS.keys(self);
13266
+ return dictSortedKeys(self);
12415
13267
  },
12416
13268
  remove(self, key) {
12417
13269
  if (!isDictMethodReceiver(self)) {
@@ -12444,26 +13296,31 @@ ${cleanup}
12444
13296
  return DICT_METHODS.empty(self);
12445
13297
  },
12446
13298
  to_Array(self) {
12447
- return DICT_METHODS.enumerate(self);
13299
+ if (!isDictMethodReceiver(self)) {
13300
+ return [];
13301
+ }
13302
+ return makeArray(dictSortedKeys(self).map(
13303
+ (key) => new Pair({ pair: [key, resolveWeakValue(self[key])] })
13304
+ ));
12448
13305
  },
12449
13306
  to_Iterator(self) {
12450
- return DICT_METHODS.keys(self)[Symbol.iterator]();
13307
+ return dictSortedKeys(self)[Symbol.iterator]();
12451
13308
  },
12452
13309
  for_each_key(self, fn2) {
12453
- for (const key of DICT_METHODS.keys(self)) {
13310
+ for (const key of dictSortedKeys(self)) {
12454
13311
  fn2(key);
12455
13312
  }
12456
13313
  return self;
12457
13314
  },
12458
13315
  for_each_value(self, fn2) {
12459
- for (const value of DICT_METHODS.values(self)) {
12460
- fn2(value);
13316
+ for (const key of dictSortedKeys(self)) {
13317
+ fn2(resolveWeakValue(self[key]));
12461
13318
  }
12462
13319
  return self;
12463
13320
  },
12464
13321
  for_each_pair(self, fn2) {
12465
- for (const pair of DICT_METHODS.enumerate(self)) {
12466
- fn2(pair);
13322
+ for (const key of dictSortedKeys(self)) {
13323
+ fn2(new Pair({ pair: [key, resolveWeakValue(self[key])] }));
12467
13324
  }
12468
13325
  return self;
12469
13326
  },
@@ -12497,6 +13354,12 @@ ${cleanup}
12497
13354
  return null;
12498
13355
  }
12499
13356
  const name2 = String(property);
13357
+ for (let proto = Object.getPrototypeOf(object); proto; proto = Object.getPrototypeOf(proto)) {
13358
+ const descriptor = Object.getOwnPropertyDescriptor(proto, name2);
13359
+ if (descriptor && typeof descriptor.value === "function") {
13360
+ return null;
13361
+ }
13362
+ }
12500
13363
  if (!isDictMethodReceiver(object) || Object.prototype.hasOwnProperty.call(object, name2) || !Object.prototype.hasOwnProperty.call(DICT_METHODS, name2)) {
12501
13364
  return null;
12502
13365
  }
@@ -12536,8 +13399,8 @@ ${cleanup}
12536
13399
  enumerable: false,
12537
13400
  configurable: true
12538
13401
  });
12539
- bound.invoke = function invoke(_self, args = []) {
12540
- return fn2.apply(receiver, args);
13402
+ bound.invoke = function invoke(self, args = []) {
13403
+ return fn2.apply(self, args);
12541
13404
  };
12542
13405
  bound.to_String = function to_String() {
12543
13406
  return methodName;
@@ -12988,6 +13851,22 @@ ${cleanup}
12988
13851
  if (object == null) {
12989
13852
  return null;
12990
13853
  }
13854
+ if (property !== "length") {
13855
+ const arrayMethod = getArrayMethod(object, property);
13856
+ if (arrayMethod && ARRAY_ZERO_ARG_METHODS.has(String(property))) {
13857
+ return arrayMethod();
13858
+ }
13859
+ if (arrayMethod) {
13860
+ return arrayMethod;
13861
+ }
13862
+ }
13863
+ const setMethod = getSetMethod(object, property);
13864
+ if (setMethod && SET_ZERO_ARG_METHODS.has(String(property))) {
13865
+ return setMethod();
13866
+ }
13867
+ if (setMethod) {
13868
+ return setMethod;
13869
+ }
12991
13870
  const value = resolveWeakValue(object[property]);
12992
13871
  if (typeof value === "function" && value.length === 0 && !value.__zuzu_marshal_meta) {
12993
13872
  return value.call(object);
@@ -13020,6 +13899,18 @@ ${cleanup}
13020
13899
  if (property instanceof ZuzuMethod || property && property.__zuzu_method) {
13021
13900
  return property.invoke(object, args);
13022
13901
  }
13902
+ const arrayMethod = getArrayMethod(object, property);
13903
+ if (arrayMethod) {
13904
+ return arrayMethod(...args);
13905
+ }
13906
+ const setMethod = getSetMethod(object, property);
13907
+ if (setMethod) {
13908
+ return setMethod(...args);
13909
+ }
13910
+ const dictMethod = getDictMethod(object, property);
13911
+ if (dictMethod) {
13912
+ return dictMethod(...args);
13913
+ }
13023
13914
  const value = resolveWeakValue(object[property]);
13024
13915
  if (typeof value === "function") {
13025
13916
  return value.apply(object, args);
@@ -13032,21 +13923,20 @@ ${cleanup}
13032
13923
  }
13033
13924
  proto = Object.getPrototypeOf(proto);
13034
13925
  }
13035
- const dictMethod = getDictMethod(object, property);
13036
- if (dictMethod) {
13037
- return dictMethod(...args);
13038
- }
13039
13926
  throw new TypeError(`${String(property)} is not a function`);
13040
13927
  }
13041
- function zuzuMaybeDemolish(value) {
13928
+ function zuzuMaybeDemolish(value, preserved = null) {
13042
13929
  const original = value;
13043
13930
  value = resolveWeakValue(value);
13044
- let result = null;
13045
- if (value && typeof value.__demolish__ === "function") {
13046
- result = value.__demolish__();
13931
+ const shouldDemolish = value && value !== resolveWeakValue(preserved) && typeof value.__demolish__ === "function";
13932
+ releaseValue(original, {
13933
+ preserveCollectionValues: value === resolveWeakValue(preserved),
13934
+ suppressDemolish: true
13935
+ });
13936
+ if (shouldDemolish && strongReferenceCount(value) === 0) {
13937
+ return value.__demolish__();
13047
13938
  }
13048
- releaseValue(original);
13049
- return result;
13939
+ return null;
13050
13940
  }
13051
13941
  function zuzuCan(value, methodName) {
13052
13942
  value = resolveWeakValue(value);
@@ -13054,6 +13944,12 @@ ${cleanup}
13054
13944
  return 0;
13055
13945
  }
13056
13946
  const key = String(methodName || "");
13947
+ if (getArrayMethod(value, key)) {
13948
+ return 1;
13949
+ }
13950
+ if (getSetMethod(value, key)) {
13951
+ return 1;
13952
+ }
13057
13953
  if (getDictMethod(value, key)) {
13058
13954
  return 1;
13059
13955
  }
@@ -13488,14 +14384,19 @@ ${cleanup}
13488
14384
  this.transpiler = normalizeTranspilerName(
13489
14385
  options.transpiler || DEFAULT_TRANSPILER
13490
14386
  );
14387
+ this.asyncImports = options.asyncImports === true;
13491
14388
  if (typeof this.host.loadJsModule !== "function") {
13492
14389
  this.host.loadJsModule = (filename) => __require(filename);
13493
14390
  }
13494
14391
  }
14392
+ usesAsyncImports() {
14393
+ return this.asyncImports || typeof this.host.hasAsyncModules === "function" && this.host.hasAsyncModules();
14394
+ }
13495
14395
  transpile(source, options = {}) {
13496
14396
  return transpile(source, {
13497
14397
  ...options,
13498
- transpiler: options.transpiler || this.transpiler
14398
+ transpiler: options.transpiler || this.transpiler,
14399
+ asyncImports: options.asyncImports ?? this.usesAsyncImports()
13499
14400
  });
13500
14401
  }
13501
14402
  getModuleSearchRoots() {
@@ -13834,6 +14735,12 @@ ${cleanup}
13834
14735
  }
13835
14736
  return this;
13836
14737
  });
14738
+ defineRuntimeMethod(Set.prototype, "push_weak", function _pushWeakSet(...values) {
14739
+ for (const v of values) {
14740
+ this.add(makeWeakValue(v));
14741
+ }
14742
+ return this;
14743
+ });
13837
14744
  defineRuntimeMethod(Set.prototype, "contains", function _containsSet(value) {
13838
14745
  return contains(this, value);
13839
14746
  });
@@ -13983,6 +14890,7 @@ ${cleanup}
13983
14890
  __file__: this.fileValueForFilename(filename),
13984
14891
  __zuzu_system_seed: systemGlobalSeed,
13985
14892
  __zuzu_current_system: currentSystem,
14893
+ __zuzu_operator_string: zuzuOperatorString,
13986
14894
  DEBUG: this.debugLevel,
13987
14895
  __zuzu_host_capabilities: capabilityFlags,
13988
14896
  has_capability(name2) {
@@ -14343,13 +15251,13 @@ ${cleanup}
14343
15251
  return zuzuTruthy(value);
14344
15252
  },
14345
15253
  __zuzu_not(value) {
14346
- return zuzuTruthy(value) ? 0 : 1;
15254
+ return !zuzuTruthy(value);
14347
15255
  },
14348
15256
  __zuzu_and(left, right) {
14349
- return zuzuTruthy(left) && zuzuTruthy(right) ? 1 : 0;
15257
+ return zuzuTruthy(left) && zuzuTruthy(right);
14350
15258
  },
14351
15259
  __zuzu_or(left, right) {
14352
- return zuzuTruthy(left) || zuzuTruthy(right) ? 1 : 0;
15260
+ return zuzuTruthy(left) || zuzuTruthy(right);
14353
15261
  },
14354
15262
  __zuzu_typeof(value) {
14355
15263
  return zuzuTypeof(value);
@@ -14595,14 +15503,14 @@ ${cleanup}
14595
15503
  __zuzu_resolve_brace_key(object, literalProperty, getDynamicProperty) {
14596
15504
  return zuzuResolveBraceKey(object, literalProperty, getDynamicProperty);
14597
15505
  },
14598
- __zuzu_maybe_demolish(value) {
14599
- return zuzuMaybeDemolish(value);
15506
+ __zuzu_maybe_demolish(value, preserved = null) {
15507
+ return zuzuMaybeDemolish(value, preserved);
14600
15508
  },
14601
15509
  __zuzu_xor(left, right) {
14602
- return zuzuTruthy(left) !== zuzuTruthy(right) ? 1 : 0;
15510
+ return zuzuTruthy(left) !== zuzuTruthy(right);
14603
15511
  },
14604
15512
  __zuzu_nand(left, right) {
14605
- return zuzuTruthy(left) && zuzuTruthy(right) ? 0 : 1;
15513
+ return !(zuzuTruthy(left) && zuzuTruthy(right));
14606
15514
  },
14607
15515
  __zuzu_num_eq(left, right) {
14608
15516
  if (!isNumericComparable(left) || !isNumericComparable(right)) {
@@ -14708,6 +15616,9 @@ ${cleanup}
14708
15616
  if (name2 === "std/eval") {
14709
15617
  return { eval: context.__zuzu_native_eval };
14710
15618
  }
15619
+ if (this.usesAsyncImports()) {
15620
+ return this.loadModuleAsync(name2, filename, context);
15621
+ }
14711
15622
  return this.loadModule(name2, filename, context);
14712
15623
  };
14713
15624
  return context;
@@ -14784,6 +15695,41 @@ ${cleanup}
14784
15695
  Object.defineProperty( proto, name, desc );
14785
15696
  }
14786
15697
  };
15698
+ if ( Array.prototype.join.__zuzu_array_join !== true ) {
15699
+ const __zuzu_native_array_join = Array.prototype.join;
15700
+ const joinDesc = Object.create( null );
15701
+ joinDesc.value = function _join( separator, fallback ) {
15702
+ const hasFallback = arguments.length > 1;
15703
+ let fallbackString;
15704
+ const sep = __zuzu_operator_string( separator );
15705
+ const out = [];
15706
+ for ( const value of this ) {
15707
+ try {
15708
+ out.push( __zuzu_operator_string( value ) );
15709
+ }
15710
+ catch ( err ) {
15711
+ if ( !hasFallback ) {
15712
+ throw err;
15713
+ }
15714
+ if ( typeof fallback === 'function' ) {
15715
+ out.push( __zuzu_operator_string( fallback( value ) ) );
15716
+ }
15717
+ else {
15718
+ if ( fallbackString === undefined ) {
15719
+ fallbackString = __zuzu_operator_string( fallback );
15720
+ }
15721
+ out.push( fallbackString );
15722
+ }
15723
+ }
15724
+ }
15725
+ return __zuzu_native_array_join.call( out, sep );
15726
+ };
15727
+ joinDesc.value.__zuzu_array_join = true;
15728
+ joinDesc.enumerable = false;
15729
+ joinDesc.configurable = true;
15730
+ joinDesc.writable = true;
15731
+ Object.defineProperty( Array.prototype, 'join', joinDesc );
15732
+ }
14787
15733
  define( Array.prototype, 'length', function _length() { return this.length; } );
14788
15734
  define( Array.prototype, 'count', function _count() { return this.length; } );
14789
15735
  define( Array.prototype, 'empty', function _empty() { return this.length === 0 ? 1 : 0; } );
@@ -14826,6 +15772,7 @@ ${cleanup}
14826
15772
  define( Set.prototype, 'is_empty', function _is_empty() { return this.empty(); } );
14827
15773
  define( Set.prototype, 'push', function _push( ...values ) { for ( const v of values ) { __zuzu_add_set_value( this, v ); } return this; } );
14828
15774
  define( Set.prototype, 'add_weak', function _add_weak( value ) { this.add( __zuzu_make_weak_value( value ) ); return this; } );
15775
+ define( Set.prototype, 'push_weak', function _push_weak( ...values ) { for ( const v of values ) { this.add( __zuzu_make_weak_value( v ) ); } return this; } );
14829
15776
  define( Set.prototype, 'contains', function _contains( value ) { return __zuzu_contains( this, value ); } );
14830
15777
  define( Set.prototype, 'remove', function _remove( value ) { const resolved = __zuzu_resolve_weak_value( value ); if ( this.delete( resolved ) ) { __zuzu_release_collection_value( this, resolved ); } return this; } );
14831
15778
  define( Set.prototype, 'to_Array', function _to_array() { return __zuzu_array( this ); } );
@@ -15057,6 +16004,68 @@ ${exportBridge}
15057
16004
  }
15058
16005
  return moduleObj.exports;
15059
16006
  }
16007
+ async loadModuleAsync(moduleName, fromFile, contextForPolicy = null) {
16008
+ if (/(^|\/)\.\.(\/|$)/.test(moduleName)) {
16009
+ throw new Error("Import module path cannot contain '..' segments");
16010
+ }
16011
+ this.enforceModulePolicy(moduleName);
16012
+ const resolved = this.resolveModulePath(moduleName, fromFile);
16013
+ const cacheable = moduleName !== "test/more" && this.evalCapabilityOverrides == null;
16014
+ if (cacheable && this.moduleCache.has(resolved)) {
16015
+ return this.moduleCache.get(resolved);
16016
+ }
16017
+ if (resolved.endsWith(".js")) {
16018
+ return this.loadModule(moduleName, fromFile, contextForPolicy);
16019
+ }
16020
+ if (this.moduleLoading.has(resolved)) {
16021
+ throw new Error("Circular module loading detected");
16022
+ }
16023
+ this.moduleLoading.add(resolved);
16024
+ try {
16025
+ const source = typeof this.host.readFileTextAsync === "function" ? await this.host.readFileTextAsync(resolved) : this.host.readFileText(resolved);
16026
+ let js = this.transpile(source, {
16027
+ asyncImports: true,
16028
+ deferAsyncWrapper: true
16029
+ });
16030
+ const exportNames = resolved.endsWith(".zzm") ? collectTopLevelDeclarations(source, stripPod) : [];
16031
+ let exportBridge = "";
16032
+ if (exportNames.length > 0) {
16033
+ exportBridge = exportNames.map((name2) => {
16034
+ if (name2.startsWith("_")) {
16035
+ return `if ( typeof ${name2} !== "undefined" ) { const __zuzu_desc = Object.create( null ); __zuzu_desc.get = function() { return ${name2}; }; __zuzu_desc.set = function( value ) { ${name2} = value; }; __zuzu_desc.enumerable = false; __zuzu_desc.configurable = true; Object.defineProperty( module.exports, ${JSON.stringify(name2)}, __zuzu_desc ); }`;
16036
+ }
16037
+ return `if ( typeof ${name2} !== "undefined" ) { const __zuzu_desc = Object.create( null ); __zuzu_desc.get = function() { return ${name2}; }; __zuzu_desc.set = function( value ) { ${name2} = value; }; __zuzu_desc.enumerable = true; __zuzu_desc.configurable = true; Object.defineProperty( module.exports, ${JSON.stringify(name2)}, __zuzu_desc ); }`;
16038
+ }).join("\n");
16039
+ }
16040
+ js = `( async () => {
16041
+ ${js}
16042
+ ${exportBridge}
16043
+ } )()`;
16044
+ setCompiledSource(resolved, js);
16045
+ const moduleObj = { exports: {} };
16046
+ const context = this.buildContext({
16047
+ exports: moduleObj.exports,
16048
+ module: moduleObj,
16049
+ filename: resolved
16050
+ });
16051
+ context.__global__ = /* @__PURE__ */ Object.create(null);
16052
+ this.installCollectionMethods(context);
16053
+ const moduleRunOptions = { filename: resolved };
16054
+ if (this.executionTimeoutMs != null) {
16055
+ moduleRunOptions.timeout = this.executionTimeoutMs;
16056
+ }
16057
+ const result = this.host.runInContext(js, context, moduleRunOptions);
16058
+ if (result && typeof result.then === "function") {
16059
+ await result;
16060
+ }
16061
+ if (cacheable) {
16062
+ this.moduleCache.set(resolved, moduleObj.exports);
16063
+ }
16064
+ return moduleObj.exports;
16065
+ } finally {
16066
+ this.moduleLoading.delete(resolved);
16067
+ }
16068
+ }
15060
16069
  runSource(source, options = {}) {
15061
16070
  const filename = options.filename || this.host.join(this.repoRoot, "<inline>.zzs");
15062
16071
  let js;
@@ -15694,7 +16703,9 @@ ${" ".repeat(Math.max(0, caretColumn - 1))}^`;
15694
16703
  const bindingName = marshalBindingName(preferredName || meta.name, id);
15695
16704
  const captures = [];
15696
16705
  const dependencies = [];
15697
- for (const [name2, captured] of Object.entries(meta.captures || {}).sort()) {
16706
+ for (const [name2, captured] of Object.entries(meta.captures || {}).sort(
16707
+ (left, right) => left[0].localeCompare(right[0])
16708
+ )) {
15698
16709
  if (isFunctionValue(captured)) {
15699
16710
  dependencies.push([0, encodeFunctionCode(captured, state, name2)]);
15700
16711
  } else if (isUserClassValue(captured)) {
@@ -40602,9 +41613,9 @@ ${lines.join("\n")}
40602
41613
  }
40603
41614
  });
40604
41615
 
40605
- // ../../../../../tmp/zuzu-browser-build.fADF4k/browser-stdlib.generated.js
41616
+ // ../../../../../tmp/zuzu-browser-build.UGYrnC/browser-stdlib.generated.js
40606
41617
  var require_browser_stdlib_generated = __commonJS({
40607
- "../../../../../tmp/zuzu-browser-build.fADF4k/browser-stdlib.generated.js"(exports2, module2) {
41618
+ "../../../../../tmp/zuzu-browser-build.UGYrnC/browser-stdlib.generated.js"(exports2, module2) {
40608
41619
  "use strict";
40609
41620
  function createBrowserStdlib2() {
40610
41621
  const jsModules = /* @__PURE__ */ Object.create(null);
@@ -40635,9 +41646,9 @@ ${lines.join("\n")}
40635
41646
  virtualFiles["/modules/std/colour.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/colour - Colour parsing helpers.\n\n=head1 SYNOPSIS\n\n from std/colour import parse_colour;\n\n say( parse_colour("red") ); # #ff0000\n say( parse_colour("#abc") ); # #aabbcc\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nC<parse_colour> accepts three- and six-digit hexadecimal colours and the\nCSS3 extended colour keywords. It returns a lowercase six-digit\nhexadecimal colour string.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item C<< parse_colour(String x) >>\n\nParameters: C<x> is a CSS colour keyword or a three- or six-digit\nhexadecimal colour string. Returns: C<String>. Returns the normalized\nlowercase C<#rrggbb> colour string, or throws if C<x> is not recognised.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/colour >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/string import trim;\n\nconst _COLOUR_KEYWORDS := {\n aliceblue: "#f0f8ff",\n antiquewhite: "#faebd7",\n aqua: "#00ffff",\n aquamarine: "#7fffd4",\n azure: "#f0ffff",\n beige: "#f5f5dc",\n bisque: "#ffe4c4",\n black: "#000000",\n blanchedalmond: "#ffebcd",\n blue: "#0000ff",\n blueviolet: "#8a2be2",\n brown: "#a52a2a",\n burlywood: "#deb887",\n cadetblue: "#5f9ea0",\n chartreuse: "#7fff00",\n chocolate: "#d2691e",\n coral: "#ff7f50",\n cornflowerblue: "#6495ed",\n cornsilk: "#fff8dc",\n crimson: "#dc143c",\n cyan: "#00ffff",\n darkblue: "#00008b",\n darkcyan: "#008b8b",\n darkgoldenrod: "#b8860b",\n darkgray: "#a9a9a9",\n darkgreen: "#006400",\n darkgrey: "#a9a9a9",\n darkkhaki: "#bdb76b",\n darkmagenta: "#8b008b",\n darkolivegreen: "#556b2f",\n darkorange: "#ff8c00",\n darkorchid: "#9932cc",\n darkred: "#8b0000",\n darksalmon: "#e9967a",\n darkseagreen: "#8fbc8f",\n darkslateblue: "#483d8b",\n darkslategray: "#2f4f4f",\n darkslategrey: "#2f4f4f",\n darkturquoise: "#00ced1",\n darkviolet: "#9400d3",\n deeppink: "#ff1493",\n deepskyblue: "#00bfff",\n dimgray: "#696969",\n dimgrey: "#696969",\n dodgerblue: "#1e90ff",\n firebrick: "#b22222",\n floralwhite: "#fffaf0",\n forestgreen: "#228b22",\n fuchsia: "#ff00ff",\n gainsboro: "#dcdcdc",\n ghostwhite: "#f8f8ff",\n gold: "#ffd700",\n goldenrod: "#daa520",\n gray: "#808080",\n green: "#008000",\n greenyellow: "#adff2f",\n grey: "#808080",\n honeydew: "#f0fff0",\n hotpink: "#ff69b4",\n indianred: "#cd5c5c",\n indigo: "#4b0082",\n ivory: "#fffff0",\n khaki: "#f0e68c",\n lavender: "#e6e6fa",\n lavenderblush: "#fff0f5",\n lawngreen: "#7cfc00",\n lemonchiffon: "#fffacd",\n lightblue: "#add8e6",\n lightcoral: "#f08080",\n lightcyan: "#e0ffff",\n lightgoldenrodyellow: "#fafad2",\n lightgray: "#d3d3d3",\n lightgreen: "#90ee90",\n lightgrey: "#d3d3d3",\n lightpink: "#ffb6c1",\n lightsalmon: "#ffa07a",\n lightseagreen: "#20b2aa",\n lightskyblue: "#87cefa",\n lightslategray: "#778899",\n lightslategrey: "#778899",\n lightsteelblue: "#b0c4de",\n lightyellow: "#ffffe0",\n lime: "#00ff00",\n limegreen: "#32cd32",\n linen: "#faf0e6",\n magenta: "#ff00ff",\n maroon: "#800000",\n mediumaquamarine: "#66cdaa",\n mediumblue: "#0000cd",\n mediumorchid: "#ba55d3",\n mediumpurple: "#9370db",\n mediumseagreen: "#3cb371",\n mediumslateblue: "#7b68ee",\n mediumspringgreen: "#00fa9a",\n mediumturquoise: "#48d1cc",\n mediumvioletred: "#c71585",\n midnightblue: "#191970",\n mintcream: "#f5fffa",\n mistyrose: "#ffe4e1",\n moccasin: "#ffe4b5",\n navajowhite: "#ffdead",\n navy: "#000080",\n oldlace: "#fdf5e6",\n olive: "#808000",\n olivedrab: "#6b8e23",\n orange: "#ffa500",\n orangered: "#ff4500",\n orchid: "#da70d6",\n palegoldenrod: "#eee8aa",\n palegreen: "#98fb98",\n paleturquoise: "#afeeee",\n palevioletred: "#db7093",\n papayawhip: "#ffefd5",\n peachpuff: "#ffdab9",\n peru: "#cd853f",\n pink: "#ffc0cb",\n plum: "#dda0dd",\n powderblue: "#b0e0e6",\n purple: "#800080",\n rebeccapurple: "#663399",\n red: "#ff0000",\n rosybrown: "#bc8f8f",\n royalblue: "#4169e1",\n saddlebrown: "#8b4513",\n salmon: "#fa8072",\n sandybrown: "#f4a460",\n seagreen: "#2e8b57",\n seashell: "#fff5ee",\n sienna: "#a0522d",\n silver: "#c0c0c0",\n skyblue: "#87ceeb",\n slateblue: "#6a5acd",\n slategray: "#708090",\n slategrey: "#708090",\n snow: "#fffafa",\n springgreen: "#00ff7f",\n steelblue: "#4682b4",\n tan: "#d2b48c",\n teal: "#008080",\n thistle: "#d8bfd8",\n tomato: "#ff6347",\n turquoise: "#40e0d0",\n violet: "#ee82ee",\n wheat: "#f5deb3",\n white: "#ffffff",\n whitesmoke: "#f5f5f5",\n yellow: "#ffff00",\n yellowgreen: "#9acd32",\n};\n\nfunction parse_colour ( String x ) {\n let text := lc( trim(x) );\n\n if ( text ~ /^#[0-9a-f]{6}$/ ) {\n return text;\n }\n\n let short := text ~ /^#([0-9a-f])([0-9a-f])([0-9a-f])$/;\n if ( short ) {\n return "#"\n _ short[1] _ short[1]\n _ short[2] _ short[2]\n _ short[3] _ short[3];\n }\n\n if ( _COLOUR_KEYWORDS.exists(text) ) {\n return _COLOUR_KEYWORDS.get(text);\n }\n\n die `Invalid colour: ${x}`;\n}\n';
40636
41647
  virtualFiles["/modules/std/result.zzm"] = "=encoding utf8\n\n=head1 NAME\n\nstd/result - A simple Result object for explicit success and failure values.\n\n=head1 SYNOPSIS\n\n from std/result import Result;\n\n let r := Result.ok(42);\n\n if ( r.is_ok() ) {\n say r.unwrap();\n }\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nC<Result> is a small, subclassable object modelled on a simplified version\nof Rust's C<Result> concept. It is useful when a function, task, or worker\nwants to return an explicit success or failure value without throwing an\nexception.\n\nIt is a normal ZuzuScript class. Workers do not require C<Result>; any value\nsupported by C<std/marshal> may be returned.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<Result>\n\nContainer for either an ok value or an error value.\n\n=over\n\n=item C<< Result.ok(value) >>\n\nParameters: C<value> is any success value. Returns: C<Result>. Creates an\nok result wrapping C<value>.\n\n=item C<< Result.err(error) >>\n\nParameters: C<error> is any error value. Returns: C<Result>. Creates an\nerror result wrapping C<error>.\n\n=item C<< result.is_ok() >>\n\nParameters: none. Returns: C<Boolean>. Returns true when the result is an\nok value.\n\n=item C<< result.is_err() >>\n\nParameters: none. Returns: C<Boolean>. Returns true when the result is an\nerror value.\n\n=item C<< result.value() >>\n\nParameters: none. Returns: value. Returns the stored ok value, or\nC<null> for an error result.\n\n=item C<< result.error() >>\n\nParameters: none. Returns: value. Returns the stored error value, or\nC<null> for an ok result.\n\n=item C<< result.unwrap() >>\n\nParameters: none. Returns: value. Returns the ok value, or throws if the\nresult is an error.\n\n=item C<< result.unwrap_err() >>\n\nParameters: none. Returns: value. Returns the error value, or throws if\nthe result is ok.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/result >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nclass Result {\n let Boolean _is_ok := true;\n let _value := null;\n let _error := null;\n\n static method ok ( value ) {\n return new self(\n _is_ok: true,\n _value: value,\n _error: null,\n );\n }\n\n static method err ( error ) {\n return new self(\n _is_ok: false,\n _value: null,\n _error: error,\n );\n }\n\n method is_ok () {\n return _is_ok;\n }\n\n method is_err () {\n return not _is_ok;\n }\n\n method value () {\n return _value;\n }\n\n method error () {\n return _error;\n }\n\n method unwrap () {\n if ( _is_ok ) {\n return _value;\n }\n\n die `called Result.unwrap() on an err value: ${_error}`;\n }\n\n method unwrap_err () {\n if ( not _is_ok ) {\n return _error;\n }\n\n die `called Result.unwrap_err() on an ok value: ${_value}`;\n }\n}\n";
40637
41648
  virtualFiles["/modules/std/string/quoted_printable.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/string/quoted_printable - Quoted-printable encoders and decoders.\n\n=head1 SYNOPSIS\n\n from std/string/quoted_printable import encode, decode;\n\n let raw := to_binary( "Hello, world!\\r\\n" );\n\n let text := encode(raw);\n let bytes := decode(text);\n\n let binary_text := encode(raw, binary: true);\n let short_lines := encode(raw, line_length: 40, newline: "\\n");\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module provides quoted-printable encoding and decoding helpers for\nRFC 2045-style byte transport. Encoding returns ASCII C<String> text.\nDecoding returns a C<BinaryString>, because quoted-printable is a byte\ntransfer encoding rather than a Unicode text format.\n\nThe C<binary> option controls how input line break bytes are encoded.\nIn the default non-binary mode, CRLF, CR, and LF bytes are normalized to\nthe configured C<newline> string. In binary mode, CR and LF bytes are\nencoded as C<=0D> and C<=0A>.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item * C<encode(BinaryString bytes, ... PairList options)>\n\nParameters: C<bytes> is binary input data and C<options> controls\nencoding. Returns: C<String>. Encodes C<bytes> as quoted-printable ASCII\ntext.\n\n=item * C<decode(String text, ... PairList options)>\n\nParameters: C<text> is quoted-printable text and C<options> controls\nstrictness. Returns: C<BinaryString>. Decodes quoted-printable text into\nbytes.\n\n=back\n\n=head1 OPTIONS\n\n=over\n\n=item * C<line_length>\n\nMaximum encoded line length. Defaults to C<76> and must be at least\nC<4>.\n\n=item * C<newline>\n\nOutput newline for hard line breaks and encoded soft breaks. Defaults\nto CRLF.\n\n=item * C<binary>\n\nWhen true, encode CR and LF bytes as C<=0D> and C<=0A>. Defaults to\nfalse.\n\n=item * C<strict>\n\nWhen true, malformed quoted-printable escape sequences throw during\ndecoding. Non-strict decoding preserves malformed escape text\nliterally.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/string/quoted_printable >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\n\nlet encode := null;\nlet decode := null;\n\n{\n from std/string import chr, index, ord, substr;\n from std/string/base64 import\n encode as _base64_encode,\n decode as _base64_decode;\n\n let _B64_ALPHABET := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"\n _ "abcdefghijklmnopqrstuvwxyz0123456789+/";\n let _HEX := "0123456789ABCDEF";\n\n function _div_floor ( Number n, Number d ) {\n return floor( n / d );\n }\n\n function _mod ( Number n, Number d ) {\n return n - _div_floor( n, d ) * d;\n }\n\n function _bytes_to_binary ( Array bytes ) {\n let out := "";\n let i := 0;\n let n := bytes.length();\n\n while ( i < n ) {\n let b0 := bytes[i];\n let b1 := null;\n let b2 := null;\n if ( i + 1 < n ) {\n b1 := bytes[i + 1];\n }\n if ( i + 2 < n ) {\n b2 := bytes[i + 2];\n }\n\n let c0 := _div_floor( b0, 4 );\n let c1 := _mod( b0, 4 ) * 16;\n let c2 := 64;\n let c3 := 64;\n\n if ( not( b1 == null ) ) {\n c1 += _div_floor( b1, 16 );\n c2 := _mod( b1, 16 ) * 4;\n if ( not( b2 == null ) ) {\n c2 += _div_floor( b2, 64 );\n c3 := _mod( b2, 64 );\n }\n }\n\n out _= substr( _B64_ALPHABET, c0, 1 );\n out _= substr( _B64_ALPHABET, c1, 1 );\n out _= c2 == 64 ? "=" : substr( _B64_ALPHABET, c2, 1 );\n out _= c3 == 64 ? "=" : substr( _B64_ALPHABET, c3, 1 );\n i += 3;\n }\n\n return _base64_decode(out);\n }\n\n function _binary_to_bytes ( BinaryString raw ) {\n let b64 := _base64_encode(raw);\n let out := [];\n let i := 0;\n let n := length b64;\n\n while ( i < n ) {\n let c0 := index( _B64_ALPHABET, substr( b64, i, 1 ) );\n let c1 := index( _B64_ALPHABET, substr( b64, i + 1, 1 ) );\n let ch2 := substr( b64, i + 2, 1 );\n let ch3 := substr( b64, i + 3, 1 );\n let c2 := -1;\n let c3 := -1;\n if ( ch2 ne "=" ) {\n c2 := index( _B64_ALPHABET, ch2 );\n }\n if ( ch3 ne "=" ) {\n c3 := index( _B64_ALPHABET, ch3 );\n }\n\n out.push( c0 * 4 + _div_floor( c1, 16 ) );\n if ( c2 >= 0 ) {\n out.push( _mod( c1, 16 ) * 16 + _div_floor( c2, 4 ) );\n }\n if ( c3 >= 0 ) {\n out.push( _mod( c2, 4 ) * 64 + c3 );\n }\n\n i += 4;\n }\n\n return out;\n }\n\n function _parse_options ( PairList options ) {\n let line_length := 76;\n let newline := "\\r\\n";\n let binary := false;\n let strict := false;\n\n for ( let option in options.enumerate() ) {\n let key := option.key;\n let value := option.value;\n\n if ( key eq "line_length" ) {\n line_length := value;\n }\n else if ( key eq "newline" ) {\n newline := value;\n }\n else if ( key eq "binary" ) {\n binary := value;\n }\n else if ( key eq "strict" ) {\n strict := value;\n }\n else {\n die `quoted_printable option \'${key}\' is not supported`;\n }\n }\n\n if ( not( line_length instanceof Number ) ) {\n die "quoted_printable line_length option expects Number";\n }\n if ( line_length < 4 ) {\n die "quoted_printable line_length option must be at least 4";\n }\n if ( not( newline instanceof String ) ) {\n die "quoted_printable newline option expects String";\n }\n if ( not( binary instanceof Boolean ) ) {\n die "quoted_printable binary option expects Boolean";\n }\n if ( not( strict instanceof Boolean ) ) {\n die "quoted_printable strict option expects Boolean";\n }\n\n return {\n line_length: int(line_length),\n newline: newline,\n binary: binary,\n strict: strict,\n };\n }\n\n function _is_safe_literal ( Number b ) {\n return ( b >= 33 and b <= 60 ) or ( b >= 62 and b <= 126 );\n }\n\n function _byte_to_hex_token ( Number b ) {\n return "="\n _ substr( _HEX, _div_floor( b, 16 ), 1 )\n _ substr( _HEX, _mod( b, 16 ), 1 );\n }\n\n function _simple_token_length ( Number b, Boolean final_byte ) {\n if ( ( b == 9 or b == 32 ) and not final_byte ) {\n return 1;\n }\n if ( _is_safe_literal(b) ) {\n return 1;\n }\n return 3;\n }\n\n function _space_tab_needs_escape (\n Array bytes,\n Number i,\n Number column,\n Number line_length,\n ) {\n let n := bytes.length();\n if ( i + 1 >= n ) {\n return true;\n }\n\n let j := i + 1;\n while ( j < n and ( bytes[j] == 9 or bytes[j] == 32 ) ) {\n j++;\n }\n if ( j >= n ) {\n return true;\n }\n\n let next_is_final := i + 2 >= n;\n let next_len := _simple_token_length( bytes[i + 1], next_is_final );\n let max := next_is_final ? line_length : line_length - 1;\n return column + 1 + next_len > max;\n }\n\n function _token_for_byte (\n Array bytes,\n Number i,\n Number column,\n Number line_length,\n ) {\n let b := bytes[i];\n\n if ( b == 9 or b == 32 ) {\n if ( _space_tab_needs_escape( bytes, i, column, line_length ) ) {\n return _byte_to_hex_token(b);\n }\n return chr(b);\n }\n\n if ( _is_safe_literal(b) ) {\n return chr(b);\n }\n\n return _byte_to_hex_token(b);\n }\n\n function _emit_token (\n String out,\n Number column,\n String token,\n Boolean more_after,\n Number line_length,\n String newline,\n ) {\n let max := more_after ? line_length - 1 : line_length;\n let updated_out := out;\n let updated_column := column;\n\n if ( updated_column > 0 and updated_column + ( length token ) > max ) {\n updated_out _= "=" _ newline;\n updated_column := 0;\n }\n\n updated_out _= token;\n updated_column += length token;\n\n return [ updated_out, updated_column ];\n }\n\n function _encode_segment (\n Array bytes,\n Number line_length,\n String newline,\n ) {\n let out := "";\n let column := 0;\n let i := 0;\n let n := bytes.length();\n\n while ( i < n ) {\n let token := _token_for_byte( bytes, i, column, line_length );\n let emitted := _emit_token(\n out,\n column,\n token,\n i + 1 < n,\n line_length,\n newline,\n );\n out := emitted[0];\n column := emitted[1];\n i++;\n }\n\n return out;\n }\n\n function _encode_text_bytes (\n Array bytes,\n Number line_length,\n String newline,\n ) {\n let out := "";\n let line := [];\n let i := 0;\n let n := bytes.length();\n\n while ( i < n ) {\n let b := bytes[i];\n if ( b == 13 or b == 10 ) {\n out _= _encode_segment( line, line_length, newline );\n out _= newline;\n line := [];\n\n if ( b == 13 and i + 1 < n and bytes[i + 1] == 10 ) {\n i++;\n }\n }\n else {\n line.push(b);\n }\n\n i++;\n }\n\n out _= _encode_segment( line, line_length, newline );\n return out;\n }\n\n function _hex_value ( String ch ) {\n let cp := ord(ch);\n if ( cp >= 48 and cp <= 57 ) {\n return cp - 48;\n }\n if ( cp >= 65 and cp <= 70 ) {\n return cp - 55;\n }\n if ( cp >= 97 and cp <= 102 ) {\n return cp - 87;\n }\n return -1;\n }\n\n function _decode_text_bytes ( String text, Boolean strict ) {\n let out := [];\n let i := 0;\n let n := length text;\n\n while ( i < n ) {\n let ch := substr( text, i, 1 );\n let cp := ord( text, i );\n\n if ( cp > 127 ) {\n die "quoted_printable.decode rejects non-ASCII input";\n }\n\n if ( ch eq "=" ) {\n if ( i + 1 >= n ) {\n die "malformed quoted-printable escape" if strict;\n out.push(61);\n i++;\n next;\n }\n\n let ch1 := substr( text, i + 1, 1 );\n if ( ch1 eq "\\r" ) {\n if ( i + 2 < n and substr( text, i + 2, 1 ) eq "\\n" ) {\n i += 3;\n }\n else {\n i += 2;\n }\n next;\n }\n if ( ch1 eq "\\n" ) {\n i += 2;\n next;\n }\n\n if ( i + 2 < n ) {\n let hi := _hex_value(ch1);\n let lo := _hex_value( substr( text, i + 2, 1 ) );\n if ( hi >= 0 and lo >= 0 ) {\n out.push( hi * 16 + lo );\n i += 3;\n next;\n }\n }\n\n die "malformed quoted-printable escape" if strict;\n out.push(61);\n i++;\n next;\n }\n\n out.push(cp);\n i++;\n }\n\n return out;\n }\n\n encode := function ( BinaryString bytes, ... PairList options ) {\n let opts := _parse_options(options);\n let raw := _binary_to_bytes(bytes);\n\n if ( opts{binary} ) {\n return _encode_segment(\n raw,\n opts{line_length},\n opts{newline},\n );\n }\n\n return _encode_text_bytes(\n raw,\n opts{line_length},\n opts{newline},\n );\n };\n\n decode := function ( String text, ... PairList options ) {\n let opts := _parse_options(options);\n return _bytes_to_binary( _decode_text_bytes( text, opts{strict} ) );\n };\n}\n';
40638
- virtualFiles["/modules/std/gui.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/gui - User-facing GUI constructor helpers.\n\n=head1 SYNOPSIS\n\n from std/gui import *;\n\n let w := Window(\n title: "Demo",\n VBox(\n Label( text: "Name:" ),\n Input( id: "name" ),\n Button( text: "OK", id: "submit" ),\n ),\n );\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by zuzu.pl, zuzu-rust, and zuzu-js on Electron.\nIt is not supported by zuzu-js on Node. It is partially supported by\nzuzu-js in the browser: core GUI and widget lifecycle coverage passes,\nbut filesystem-backed file and directory dialogue coverage is unsupported.\n\n=head1 DESCRIPTION\n\nThis module provides thin constructor helpers over C<std/gui/objects>.\nHost runtimes expose the shared widget, object tree, event, and window\nlifecycle APIs when GUI support is available. It also re-exports\nbackend-native file and directory dialogue hooks for C<std/gui/dialogue>.\n\nThe module exports C<EM>, the standard UI font height in logical\npixels, derived from C<std/gui/objects> metadata.\n\n=head1 EXPORTS\n\n=head2 Constants\n\n=over\n\n=item C<EM>\n\nType: C<Number>. Standard UI font height in logical pixels.\n\n=item C<GUI_XML_NS>\n\nType: C<String>. XML namespace URI used by GUI XML serialization and\nparsing.\n\n=back\n\n=head2 Functions\n\n=over\n\n=item C<< Window(... PairList p, Array c) >>, C<< VBox(... PairList p, Array c) >>, C<< HBox(... PairList p, Array c) >>, C<< Frame(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs window and layout widgets.\n\n=item C<< Label(... PairList p, Array c) >>, C<< Text(... PairList p, Array c) >>, C<< RichText(... PairList p, Array c) >>, C<< Image(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs content widgets.\n\n=item C<< Input(... PairList p, Array c) >>, C<< DatePicker(... PairList p, Array c) >>, C<< Checkbox(... PairList p, Array c) >>, C<< Radio(... PairList p, Array c) >>, C<< RadioGroup(... PairList p, Array c) >>, C<< Select(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs input widgets.\n\n=item C<< Menu(... PairList p, Array c) >>, C<< MenuItem(... PairList p, Array c) >>, C<< Button(... PairList p, Array c) >>, C<< Separator(... PairList p, Array c) >>, C<< Slider(... PairList p, Array c) >>, C<< Progress(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs command and control widgets.\n\n=item C<< Tabs(... PairList p, Array c) >>, C<< Tab(... PairList p, Array c) >>, C<< ListView(... PairList p, Array c) >>, C<< TreeView(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs tabular and collection widgets.\n\n=item C<< unbind(BindingToken token) >>\n\nParameters: C<token> is a binding token. Returns: C<null>. Removes a GUI\nbinding.\n\n=item C<< gui_from_xml(String xml) >>, C<< gui_from_xml_file(path) >>\n\nParameters: C<xml> is GUI XML text and C<path> is a file path. Returns:\nwidget object. Builds a GUI object tree from XML.\n\n=item C<< gui_to_xml(Widget root) >>\n\nParameters: C<root> is a GUI widget. Returns: C<String>. Serializes a\nGUI object tree to XML.\n\n=back\n\n=head2 Classes\n\n=over\n\n=item C<BindingToken>\n\nRepresents a model/widget binding.\n\n=over\n\n=item C<< token.is_active() >>\n\nParameters: none. Returns: C<Boolean>. Returns true while the binding is\nactive.\n\n=item C<< token.set_listener(token) >>\n\nParameters: C<token> is a listener token. Returns: C<BindingToken>.\nStores the listener token associated with the binding.\n\n=item C<< token.sync_model_to_widget() >>, C<< token.sync_widget_to_model() >>\n\nParameters: none. Returns: C<null>. Synchronizes the bound values.\n\n=item C<< token.unbind() >>\n\nParameters: none. Returns: C<null>. Removes the binding.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/gui >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/gui/objects import\n Window as _WindowClass,\n VBox as _VBoxClass,\n HBox as _HBoxClass,\n Frame as _FrameClass,\n Label as _LabelClass,\n Text as _TextClass,\n RichText as _RichTextClass,\n Image as _ImageClass,\n Input as _InputClass,\n DatePicker as _DatePickerClass,\n Checkbox as _CheckboxClass,\n Radio as _RadioClass,\n RadioGroup as _RadioGroupClass,\n Select as _SelectClass,\n Menu as _MenuClass,\n MenuItem as _MenuItemClass,\n Button as _ButtonClass,\n Separator as _SeparatorClass,\n Slider as _SliderClass,\n Progress as _ProgressClass,\n Tabs as _TabsClass,\n Tab as _TabClass,\n ListView as _ListViewClass,\n TreeView as _TreeViewClass,\n Widget,\n Event,\n ListenerToken,\n native_file_open,\n native_file_save,\n native_directory_open,\n native_directory_save,\n native_colour_picker,\n meta as _gui_meta;\n\nfrom std/gui/objects try import\n native_alert,\n native_confirm,\n native_prompt;\n\nfrom std/data/xml import XML;\nfrom std/data/xml/escape import escape_xml;\nfrom std/internals import getupperprop;\nfrom std/path/zz import ZZPath;\nfrom std/string import join, split, substr, trim;\n\n\nconst Number EM := _gui_meta{font_size_pixels};\n\nfunction _assert_known_props ( String ctor, PairList props, Array allowed ) {\n let geometry := [\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n ];\n for ( let key in props.keys() ) {\n if ( key \u2209 allowed and key \u2209 geometry ) {\n die `GUI_PROP_UNKNOWN: ${ctor} does not accept property \'${key}\'`;\n }\n }\n}\n\nfunction _assert_prop_value ( String ctor, String prop, value, Array allowed ) {\n if ( value \u2209 allowed ) {\n die `GUI_PROP_TYPE: ${ctor} property \'${prop}\' has invalid value`;\n }\n}\n\nfunction _assert_prop_array ( String ctor, String prop, value ) {\n if ( not( value instanceof Array ) ) {\n die `GUI_PROP_TYPE: ${ctor} property \'${prop}\' expects Array`;\n }\n}\n\nfunction _with_geometry_props ( Array allowed ) {\n for ( let key in [\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n ] ) {\n allowed.push(key) unless key \u2208 allowed;\n }\n return allowed;\n}\n\nfunction _apply_geometry ( Widget widget, PairList p ) {\n for ( let key in [\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n ] ) {\n if ( p.has(key) ) {\n widget.(key)( p.get(key) );\n }\n }\n return widget;\n}\n\nfunction Window ( ... PairList p, Array c ) {\n _assert_known_props(\n "Window",\n p,\n _with_geometry_props( [\n "id",\n "title",\n "width",\n "height",\n "resizable",\n "modal",\n "visible",\n "enabled",\n "disabled",\n ] ),\n );\n\n return new _WindowClass(\n id: p.get( "id", null ),\n title: p.get( "title", "" ),\n width: p.get( "width", 800 ),\n height: p.get( "height", 600 ),\n minwidth: p.get( "minwidth", null ),\n minheight: p.get( "minheight", null ),\n maxwidth: p.get( "maxwidth", null ),\n maxheight: p.get( "maxheight", null ),\n resizable: p.get( "resizable", true ),\n modal: p.get( "modal", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n );\n}\n\nfunction VBox ( ... PairList p, Array c ) {\n _assert_known_props(\n "VBox",\n p,\n [\n "id",\n "align",\n "gap",\n "padding",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n _assert_prop_value(\n "VBox",\n "align",\n p.get( "align", "top" ),\n [ "top", "centre", "bottom", "stretch" ],\n );\n\n return _apply_geometry( new _VBoxClass(\n id: p.get( "id", null ),\n align: p.get( "align", "top" ),\n gap: p.get( "gap", 0 ),\n padding: p.get( "padding", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction HBox ( ... PairList p, Array c ) {\n _assert_known_props(\n "HBox",\n p,\n _with_geometry_props( [\n "id",\n "align",\n "gap",\n "padding",\n "visible",\n "enabled",\n "disabled",\n ] ),\n );\n\n _assert_prop_value(\n "HBox",\n "align",\n p.get( "align", "left" ),\n [ "left", "centre", "right", "stretch" ],\n );\n\n return new _HBoxClass(\n id: p.get( "id", null ),\n align: p.get( "align", "left" ),\n gap: p.get( "gap", 0 ),\n padding: p.get( "padding", 0 ),\n width: p.get( "width", null ),\n height: p.get( "height", null ),\n minwidth: p.get( "minwidth", null ),\n minheight: p.get( "minheight", null ),\n maxwidth: p.get( "maxwidth", null ),\n maxheight: p.get( "maxheight", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n );\n}\n\nfunction Frame ( ... PairList p, Array c ) {\n _assert_known_props(\n "Frame",\n p,\n [\n "id",\n "label",\n "collapsible",\n "collapsed",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _FrameClass(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n collapsible: p.get( "collapsible", false ),\n collapsed: p.get( "collapsed", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Label ( ... PairList p, Array c ) {\n _assert_known_props(\n "Label",\n p,\n [ "id", "text", "for", "visible", "enabled", "disabled" ],\n );\n\n let label := new _LabelClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n );\n if ( p.has("for") ) {\n label.set_for_id( p.get( "for", null ) );\n }\n\n return _apply_geometry( label, p );\n}\n\nfunction Text ( ... PairList p, Array c ) {\n _assert_known_props(\n "Text",\n p,\n [\n "id",\n "value",\n "multiline",\n "readonly",\n "wrap",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _TextClass(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n multiline: p.get( "multiline", false ),\n readonly: p.get( "readonly", false ),\n wrap: p.get( "wrap", true ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction RichText ( ... PairList p, Array c ) {\n _assert_known_props(\n "RichText",\n p,\n [\n "id",\n "value",\n "multiline",\n "readonly",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _RichTextClass(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n multiline: p.get( "multiline", true ),\n readonly: p.get( "readonly", true ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Image ( ... PairList p, Array c ) {\n _assert_known_props(\n "Image",\n p,\n [ "id", "src", "alt", "fit", "visible", "enabled", "disabled" ],\n );\n\n _assert_prop_value(\n "Image",\n "fit",\n p.get( "fit", "none" ),\n [ "none", "contain", "cover", "stretch" ],\n );\n\n return _apply_geometry( new _ImageClass(\n id: p.get( "id", null ),\n src: p.get( "src", "" ),\n alt: p.get( "alt", "" ),\n fit: p.get( "fit", "none" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Input ( ... PairList p, Array c ) {\n _assert_known_props(\n "Input",\n p,\n [\n "id",\n "value",\n "placeholder",\n "multiline",\n "readonly",\n "password",\n "required",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _InputClass(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n placeholder: p.get( "placeholder", "" ),\n multiline: p.get( "multiline", false ),\n readonly: p.get( "readonly", false ),\n password: p.get( "password", false ),\n required: p.get( "required", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction DatePicker ( ... PairList p, Array c ) {\n _assert_known_props(\n "DatePicker",\n p,\n [\n "id",\n "value",\n "min",\n "max",\n "first_day_of_week",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _DatePickerClass(\n id: p.get( "id", null ),\n value: p.get( "value", null ),\n min: p.get( "min", null ),\n max: p.get( "max", null ),\n first_day_of_week: p.get( "first_day_of_week", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Checkbox ( ... PairList p, Array c ) {\n _assert_known_props(\n "Checkbox",\n p,\n [\n "id",\n "label",\n "checked",\n "indeterminate",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _CheckboxClass(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n checked: p.get( "checked", false ),\n indeterminate: p.get( "indeterminate", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Radio ( ... PairList p, Array c ) {\n _assert_known_props(\n "Radio",\n p,\n [\n "id",\n "label",\n "value",\n "group",\n "checked",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _RadioClass(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n value: p.get( "value", "" ),\n group: p.get( "group", null ),\n checked: p.get( "checked", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction RadioGroup ( ... PairList p, Array c ) {\n _assert_known_props(\n "RadioGroup",\n p,\n [\n "id",\n "name",\n "value",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _RadioGroupClass(\n id: p.get( "id", null ),\n name: p.get( "name", "" ),\n value: p.get( "value", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Select ( ... PairList p, Array c ) {\n _assert_known_props(\n "Select",\n p,\n [\n "id",\n "value",\n "options",\n "multiple",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n if ( p.has("options") ) {\n _assert_prop_array( "Select", "options", p.get("options") );\n }\n\n return _apply_geometry( new _SelectClass(\n id: p.get( "id", null ),\n value: p.get( "value", null ),\n options: p.get( "options", [] ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Menu ( ... PairList p, Array c ) {\n _assert_known_props(\n "Menu",\n p,\n [ "id", "text", "visible", "enabled", "disabled" ],\n );\n\n return _apply_geometry( new _MenuClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction MenuItem ( ... PairList p, Array c ) {\n _assert_known_props(\n "MenuItem",\n p,\n [ "id", "text", "disabled", "visible", "enabled" ],\n );\n\n return _apply_geometry( new _MenuItemClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Button ( ... PairList p, Array c ) {\n _assert_known_props(\n "Button",\n p,\n _with_geometry_props( [\n "id",\n "text",\n "variant",\n "visible",\n "enabled",\n "disabled",\n ] ),\n );\n\n _assert_prop_value(\n "Button",\n "variant",\n p.get( "variant", "default" ),\n [ "default", "primary", "danger" ],\n );\n\n return new _ButtonClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n variant: p.get( "variant", "default" ),\n width: p.get( "width", null ),\n height: p.get( "height", null ),\n minwidth: p.get( "minwidth", null ),\n minheight: p.get( "minheight", null ),\n maxwidth: p.get( "maxwidth", null ),\n maxheight: p.get( "maxheight", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n );\n}\n\nfunction Separator ( ... PairList p, Array c ) {\n _assert_known_props(\n "Separator",\n p,\n [ "id", "orientation", "visible", "enabled", "disabled" ],\n );\n\n _assert_prop_value(\n "Separator",\n "orientation",\n p.get( "orientation", "horizontal" ),\n [ "horizontal", "vertical" ],\n );\n\n return _apply_geometry( new _SeparatorClass(\n id: p.get( "id", null ),\n orientation: p.get( "orientation", "horizontal" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Slider ( ... PairList p, Array c ) {\n _assert_known_props(\n "Slider",\n p,\n [\n "id",\n "value",\n "min",\n "max",\n "step",\n "orientation",\n "readonly",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n _assert_prop_value(\n "Slider",\n "orientation",\n p.get( "orientation", "horizontal" ),\n [ "horizontal", "vertical" ],\n );\n\n return _apply_geometry( new _SliderClass(\n id: p.get( "id", null ),\n value: p.get( "value", 0 ),\n min: p.get( "min", 0 ),\n max: p.get( "max", 100 ),\n step: p.get( "step", 1 ),\n orientation: p.get( "orientation", "horizontal" ),\n readonly: p.get( "readonly", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Progress ( ... PairList p, Array c ) {\n _assert_known_props(\n "Progress",\n p,\n [\n "id",\n "value",\n "min",\n "max",\n "indeterminate",\n "show_text",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _ProgressClass(\n id: p.get( "id", null ),\n value: p.get( "value", 0 ),\n min: p.get( "min", 0 ),\n max: p.get( "max", 100 ),\n indeterminate: p.get( "indeterminate", false ),\n show_text: p.get( "show_text", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Tabs ( ... PairList p, Array c ) {\n _assert_known_props(\n "Tabs",\n p,\n [\n "id",\n "selected",\n "placement",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n _assert_prop_value(\n "Tabs",\n "placement",\n p.get( "placement", "top" ),\n [ "top", "bottom", "left", "right" ],\n );\n\n return _apply_geometry( new _TabsClass(\n id: p.get( "id", null ),\n selected: p.get( "selected", null ),\n placement: p.get( "placement", "top" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Tab ( ... PairList p, Array c ) {\n _assert_known_props(\n "Tab",\n p,\n [\n "id",\n "title",\n "value",\n "selected",\n "closable",\n "icon",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _TabClass(\n id: p.get( "id", null ),\n title: p.get( "title", "" ),\n value: p.get( "value", "" ),\n selected: p.get( "selected", false ),\n closable: p.get( "closable", false ),\n icon: p.get( "icon", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction ListView ( ... PairList p, Array c ) {\n _assert_known_props(\n "ListView",\n p,\n [\n "id",\n "items",\n "selected_index",\n "multiple",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n if ( p.has("items") ) {\n _assert_prop_array( "ListView", "items", p.get("items") );\n }\n\n return _apply_geometry( new _ListViewClass(\n id: p.get( "id", null ),\n items: p.get( "items", [] ),\n selected_index: p.get( "selected_index", null ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction TreeView ( ... PairList p, Array c ) {\n _assert_known_props(\n "TreeView",\n p,\n [\n "id",\n "items",\n "selected_path",\n "multiple",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n if ( p.has("items") ) {\n _assert_prop_array( "TreeView", "items", p.get("items") );\n }\n if ( p.has("selected_path") ) {\n _assert_prop_array( "TreeView", "selected_path", p.get("selected_path") );\n }\n\n return _apply_geometry( new _TreeViewClass(\n id: p.get( "id", null ),\n items: p.get( "items", [] ),\n selected_path: p.get( "selected_path", [] ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction _binding_path_class ( path_class ) {\n if ( path_class \u2261 null ) {\n return ZZPath;\n }\n if ( not( path_class instanceof Class ) ) {\n die "GUI_BIND_PATH: paths special property must be Class or null";\n }\n return path_class;\n}\n\nfunction _binding_compile_path ( String path_text, path_class ) {\n if ( path_class \u2261 null ) {\n try {\n return new ZZPath( path: path_text );\n }\n catch ( Exception e ) {\n die `GUI_BIND_PATH: ${e{message}}`;\n }\n }\n\n let path_type := _binding_path_class(path_class);\n try {\n return new path_type( path: path_text );\n }\n catch ( Exception e ) {\n die `GUI_BIND_PATH: ${e{message}}`;\n }\n}\n\nfunction _binding_path ( pathish, path_class ) {\n if ( pathish instanceof String ) {\n return _binding_compile_path( pathish, path_class );\n }\n if (\n pathish can first\n and ( pathish can assign_first or pathish can ref_first )\n ) {\n return pathish;\n }\n\n die "GUI_BIND_PATH: binding path must be String or path-like Object";\n}\n\nfunction _binding_get ( model, path ) {\n return path.first( model, null );\n}\n\nfunction _binding_set ( model, path, value ) {\n if ( path can assign_first ) {\n return path.assign_first( model, value );\n }\n\n let ref := path.ref_first(model);\n return ref(value);\n}\n\nclass BindingToken {\n let Widget widget but weak;\n let String widget_prop;\n let model;\n let model_path;\n let String event := "change";\n let listener := null;\n let Boolean _active := true;\n\n method is_active () {\n return _active;\n }\n\n method set_listener ( token ) {\n listener := token;\n return self;\n }\n\n method sync_model_to_widget () {\n widget.(widget_prop)( _binding_get( model, model_path ) );\n return self;\n }\n\n method sync_widget_to_model () {\n _binding_set( model, model_path, widget.(widget_prop)() );\n return self;\n }\n\n method unbind () {\n if ( _active and listener \u2262 null ) {\n widget.off(listener);\n }\n _active := false;\n return self;\n }\n}\n\nfunction bind (\n Widget widget,\n String widget_prop,\n model,\n model_path,\n ... PairList p\n) {\n let path := _binding_path( model_path, getupperprop( 1, "paths" ) );\n\n let token := new BindingToken(\n widget: widget,\n widget_prop: widget_prop,\n model: model,\n model_path: path,\n event: p.get( "event", "change" ),\n );\n let event_name := p.get( "event", "change" );\n\n switch ( p.get( "initial", "model" ): eq ) {\n case "model":\n token.sync_model_to_widget();\n case "widget":\n token.sync_widget_to_model();\n case "none":\n // No initial sync.\n default:\n die "GUI_BIND_INITIAL: initial must be model, widget, or none";\n }\n\n token.set_listener(\n widget.on( event_name, function () {\n if ( token.is_active() ) {\n token.sync_widget_to_model();\n }\n } ),\n );\n return token;\n}\n\nfunction unbind ( BindingToken token ) {\n return token.unbind();\n}\n\nlet GUI_XML_NS := "https://zuzulang.org/ns/std/gui";\n\nfunction _xml_error ( String code, String message ) {\n die `${code}: ${message}`;\n}\n\nfunction _xml_tag_name ( node ) {\n return node.localName() ?: node.nodeName();\n}\n\nfunction _xml_assert_namespace ( node ) {\n let ns := node.namespaceURI();\n if ( ns \u2262 null and ns \u2262 "" and ns \u2262 GUI_XML_NS ) {\n _xml_error(\n "GUI_XML_STRUCTURE",\n `unsupported GUI XML namespace \'${ns}\'`,\n );\n }\n}\n\nfunction _xml_common_allowed ( Array extra ) {\n let out := [\n "id",\n "visible",\n "enabled",\n "disabled",\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n ];\n for ( let attr in extra ) {\n out.push(attr);\n }\n return out;\n}\n\nfunction _xml_allowed_attrs ( String tag ) {\n switch ( tag: eq ) {\n case "Window":\n return _xml_common_allowed(\n [ "title", "width", "height", "resizable", "modal" ],\n );\n case "VBox", "HBox":\n return _xml_common_allowed( [ "align", "gap", "padding" ] );\n case "Frame":\n return _xml_common_allowed( [ "label", "collapsible", "collapsed" ] );\n case "Label":\n return _xml_common_allowed( [ "text", "for" ] );\n case "Text":\n return _xml_common_allowed(\n [ "value", "multiline", "readonly", "wrap" ],\n );\n case "RichText":\n return _xml_common_allowed(\n [ "value", "multiline", "readonly" ],\n );\n case "Image":\n return _xml_common_allowed( [ "src", "alt", "fit" ] );\n case "Input":\n return _xml_common_allowed( [\n "value",\n "placeholder",\n "multiline",\n "readonly",\n "password",\n "required",\n ] );\n case "DatePicker":\n return _xml_common_allowed(\n [ "value", "min", "max", "first_day_of_week" ],\n );\n case "Checkbox":\n return _xml_common_allowed( [ "label", "checked", "indeterminate" ] );\n case "Radio":\n return _xml_common_allowed( [ "label", "value", "group", "checked" ] );\n case "RadioGroup":\n return _xml_common_allowed( [ "name", "value" ] );\n case "Select":\n return _xml_common_allowed( [ "value", "multiple" ] );\n case "Menu":\n return _xml_common_allowed( [ "text" ] );\n case "MenuItem":\n return _xml_common_allowed( [ "text" ] );\n case "Button":\n return _xml_common_allowed( [ "text", "variant" ] );\n case "Separator":\n return _xml_common_allowed( [ "orientation" ] );\n case "Slider":\n return _xml_common_allowed( [\n "value",\n "min",\n "max",\n "step",\n "orientation",\n "readonly",\n ] );\n case "Progress":\n return _xml_common_allowed( [\n "value",\n "min",\n "max",\n "indeterminate",\n "show_text",\n ] );\n case "Tabs":\n return _xml_common_allowed( [ "selected", "placement" ] );\n case "Tab":\n return _xml_common_allowed(\n [ "title", "value", "selected", "closable", "icon" ],\n );\n case "ListView":\n return _xml_common_allowed( [ "selected_index", "multiple" ] );\n case "TreeView":\n return _xml_common_allowed( [ "selected_path", "multiple" ] );\n }\n\n _xml_error( "GUI_XML_STRUCTURE", `unsupported GUI XML element \'${tag}\'` );\n}\n\nfunction _xml_bool_attrs ( String tag ) {\n return [\n "visible",\n "enabled",\n "disabled",\n "resizable",\n "modal",\n "collapsible",\n "collapsed",\n "multiline",\n "readonly",\n "wrap",\n "password",\n "required",\n "checked",\n "indeterminate",\n "multiple",\n "show_text",\n "selected",\n "closable",\n ];\n}\n\nfunction _xml_number_attrs ( String tag ) {\n return [\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n "gap",\n "padding",\n "first_day_of_week",\n "selected_index",\n "value",\n "min",\n "max",\n "step",\n ];\n}\n\nfunction _xml_bool ( String tag, String attr, String raw ) {\n switch ( lc(raw): eq ) {\n case "true", "1", "yes", "on": return true;\n case "false", "0", "no", "off": return false;\n }\n _xml_error(\n "GUI_XML_ATTR_TYPE",\n `${tag}.${attr} expects a boolean XML attribute`,\n );\n}\n\nfunction _xml_number ( String tag, String attr, String raw ) {\n if ( not ( raw ~ /^-?[0-9]+(\\.[0-9]+)?$/ ) ) {\n _xml_error(\n "GUI_XML_ATTR_TYPE",\n `${tag}.${attr} expects a numeric XML attribute`,\n );\n }\n return 0 + raw;\n}\n\nfunction _xml_int_list ( String tag, String attr, String raw ) {\n let out := [];\n return out if raw eq "";\n for ( let part in split( raw, "," ) ) {\n let item := trim(part);\n if ( not ( item ~ /^-?[0-9]+$/ ) ) {\n _xml_error(\n "GUI_XML_ATTR_TYPE",\n `${tag}.${attr} expects comma-separated integers`,\n );\n }\n out.push( 0 + item );\n }\n return out;\n}\n\nfunction _xml_coerce_attr ( String tag, String attr, String raw ) {\n if ( attr eq "selected" and tag eq "Tabs" ) {\n return raw;\n }\n if ( attr eq "value" and tag \u2209 [ "Slider", "Progress" ] ) {\n return raw;\n }\n if ( attr eq "min" and tag eq "DatePicker" ) {\n return raw;\n }\n if ( attr eq "max" and tag eq "DatePicker" ) {\n return raw;\n }\n if ( attr eq "selected_path" ) {\n return _xml_int_list( tag, attr, raw );\n }\n if ( attr \u2208 _xml_bool_attrs(tag) ) {\n return _xml_bool( tag, attr, raw );\n }\n if ( attr \u2208 _xml_number_attrs(tag) ) {\n return _xml_number( tag, attr, raw );\n }\n return raw;\n}\n\nfunction _xml_attrs ( node ) {\n let tag := _xml_tag_name(node);\n let props := {};\n let meta := {};\n let meta_keys := [];\n let style := {};\n let style_keys := [];\n let allowed := _xml_allowed_attrs(tag);\n\n for ( let attr in node.attributeNames() ) {\n next if attr eq "xmlns";\n next if substr( attr, 0, 6 ) eq "xmlns:";\n let value := node.getAttribute(attr);\n if ( substr( attr, 0, 5 ) eq "meta." ) {\n let key := substr( attr, 5 );\n _xml_error( "GUI_XML_ATTR_UNKNOWN", "empty meta attribute name" )\n if key eq "";\n meta{(key)} := value;\n meta_keys.push(key);\n next;\n }\n if ( substr( attr, 0, 6 ) eq "style." ) {\n let key := substr( attr, 6 );\n _xml_error( "GUI_XML_ATTR_UNKNOWN", "empty style attribute name" )\n if key eq "";\n style{(key)} := value;\n style_keys.push(key);\n next;\n }\n if ( attr \u2209 allowed ) {\n _xml_error(\n "GUI_XML_ATTR_UNKNOWN",\n `${tag} does not accept XML attribute \'${attr}\'`,\n );\n }\n props{(attr)} := _xml_coerce_attr( tag, attr, value );\n }\n\n return {\n props: props,\n meta: meta,\n meta_keys: meta_keys,\n style: style,\n style_keys: style_keys,\n };\n}\n\nfunction _xml_apply_meta ( Widget widget, Dict meta, Array keys ) {\n for ( let key in keys ) {\n widget.meta( key, meta{(key)} );\n }\n if ( keys.length() > 0 ) {\n widget.meta( "__xml_meta_keys", keys );\n }\n return widget;\n}\n\nfunction _xml_apply_style ( Widget widget, Dict style, Array keys ) {\n for ( let key in keys ) {\n widget.style( key, style{(key)} );\n }\n if ( keys.length() > 0 ) {\n widget.meta( "__xml_style_keys", keys );\n }\n return widget;\n}\n\nfunction _xml_widget_from_node ( node ) {\n _xml_assert_namespace(node);\n\n let tag := _xml_tag_name(node);\n let child_widgets := [];\n for ( let child in node.children() ) {\n child_widgets.push( _xml_widget_from_node(child) );\n }\n let parsed := _xml_attrs(node);\n let p := parsed{props};\n let widget;\n\n switch ( tag: eq ) {\n case "Window":\n widget := new _WindowClass(\n id: p.get( "id", null ),\n title: p.get( "title", "" ),\n width: p.get( "width", 800 ),\n height: p.get( "height", 600 ),\n resizable: p.get( "resizable", true ),\n modal: p.get( "modal", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "VBox":\n widget := new _VBoxClass(\n id: p.get( "id", null ),\n align: p.get( "align", "top" ),\n gap: p.get( "gap", 0 ),\n padding: p.get( "padding", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "HBox":\n widget := new _HBoxClass(\n id: p.get( "id", null ),\n align: p.get( "align", "left" ),\n gap: p.get( "gap", 0 ),\n padding: p.get( "padding", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "Frame":\n widget := new _FrameClass(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n collapsible: p.get( "collapsible", false ),\n collapsed: p.get( "collapsed", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "Label":\n widget := Label(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n ("for"): p.get( "for", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Text":\n widget := Text(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n multiline: p.get( "multiline", false ),\n readonly: p.get( "readonly", false ),\n wrap: p.get( "wrap", true ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "RichText":\n widget := RichText(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n multiline: p.get( "multiline", true ),\n readonly: p.get( "readonly", true ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Image":\n widget := Image(\n id: p.get( "id", null ),\n src: p.get( "src", "" ),\n alt: p.get( "alt", "" ),\n fit: p.get( "fit", "none" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Input":\n widget := Input(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n placeholder: p.get( "placeholder", "" ),\n multiline: p.get( "multiline", false ),\n readonly: p.get( "readonly", false ),\n password: p.get( "password", false ),\n required: p.get( "required", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "DatePicker":\n widget := DatePicker(\n id: p.get( "id", null ),\n value: p.get( "value", null ),\n min: p.get( "min", null ),\n max: p.get( "max", null ),\n first_day_of_week: p.get( "first_day_of_week", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Checkbox":\n widget := Checkbox(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n checked: p.get( "checked", false ),\n indeterminate: p.get( "indeterminate", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Radio":\n widget := Radio(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n value: p.get( "value", "" ),\n group: p.get( "group", null ),\n checked: p.get( "checked", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "RadioGroup":\n widget := new _RadioGroupClass(\n id: p.get( "id", null ),\n name: p.get( "name", "" ),\n value: p.get( "value", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "Select":\n widget := Select(\n id: p.get( "id", null ),\n value: p.get( "value", null ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Menu":\n widget := new _MenuClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "MenuItem":\n widget := MenuItem(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Button":\n widget := Button(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n variant: p.get( "variant", "default" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Separator":\n widget := Separator(\n id: p.get( "id", null ),\n orientation: p.get( "orientation", "horizontal" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Slider":\n widget := Slider(\n id: p.get( "id", null ),\n value: p.get( "value", 0 ),\n min: p.get( "min", 0 ),\n max: p.get( "max", 100 ),\n step: p.get( "step", 1 ),\n orientation: p.get( "orientation", "horizontal" ),\n readonly: p.get( "readonly", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Progress":\n widget := Progress(\n id: p.get( "id", null ),\n value: p.get( "value", 0 ),\n min: p.get( "min", 0 ),\n max: p.get( "max", 100 ),\n indeterminate: p.get( "indeterminate", false ),\n show_text: p.get( "show_text", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Tabs":\n widget := new _TabsClass(\n id: p.get( "id", null ),\n selected: p.get( "selected", null ),\n placement: p.get( "placement", "top" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "Tab":\n widget := new _TabClass(\n id: p.get( "id", null ),\n title: p.get( "title", "" ),\n value: p.get( "value", "" ),\n selected: p.get( "selected", false ),\n closable: p.get( "closable", false ),\n icon: p.get( "icon", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "ListView":\n widget := ListView(\n id: p.get( "id", null ),\n selected_index: p.get( "selected_index", null ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "TreeView":\n widget := TreeView(\n id: p.get( "id", null ),\n selected_path: p.get( "selected_path", [] ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n default:\n _xml_error(\n "GUI_XML_STRUCTURE",\n `unsupported GUI XML element \'${tag}\'`,\n );\n }\n\n _xml_apply_meta( widget, parsed{meta}, parsed{meta_keys} );\n return _xml_apply_style( widget, parsed{style}, parsed{style_keys} );\n}\n\nfunction gui_from_xml ( String xml ) {\n return _xml_widget_from_node( XML.parse(xml).documentElement() );\n}\n\nfunction gui_from_xml_file ( path ) {\n die "XML.load is denied by runtime policy" if __system__{deny_fs};\n from std/io import Path;\n let file := path instanceof Path ? path : new Path(path);\n return _xml_widget_from_node( XML.load(file).documentElement() );\n}\n\nfunction _xml_tag_for_widget ( Widget widget ) {\n if ( widget instanceof _WindowClass ) { return "Window"; }\n if ( widget instanceof _VBoxClass ) { return "VBox"; }\n if ( widget instanceof _HBoxClass ) { return "HBox"; }\n if ( widget instanceof _FrameClass ) { return "Frame"; }\n if ( widget instanceof _LabelClass ) { return "Label"; }\n if ( widget instanceof _TextClass ) { return "Text"; }\n if ( widget instanceof _RichTextClass ) { return "RichText"; }\n if ( widget instanceof _ImageClass ) { return "Image"; }\n if ( widget instanceof _InputClass ) { return "Input"; }\n if ( widget instanceof _DatePickerClass ) { return "DatePicker"; }\n if ( widget instanceof _CheckboxClass ) { return "Checkbox"; }\n if ( widget instanceof _RadioClass ) { return "Radio"; }\n if ( widget instanceof _RadioGroupClass ) { return "RadioGroup"; }\n if ( widget instanceof _SelectClass ) { return "Select"; }\n if ( widget instanceof _MenuClass ) { return "Menu"; }\n if ( widget instanceof _MenuItemClass ) { return "MenuItem"; }\n if ( widget instanceof _ButtonClass ) { return "Button"; }\n if ( widget instanceof _SeparatorClass ) { return "Separator"; }\n if ( widget instanceof _SliderClass ) { return "Slider"; }\n if ( widget instanceof _ProgressClass ) { return "Progress"; }\n if ( widget instanceof _TabsClass ) { return "Tabs"; }\n if ( widget instanceof _TabClass ) { return "Tab"; }\n if ( widget instanceof _ListViewClass ) { return "ListView"; }\n if ( widget instanceof _TreeViewClass ) { return "TreeView"; }\n _xml_error( "GUI_XML_STRUCTURE", "unsupported widget for gui_to_xml" );\n}\n\nfunction _xml_attr ( String name, value ) {\n return "" if value \u2261 null;\n return ` ${name}="${escape_xml(value)}"`;\n}\n\nfunction _xml_attr_if ( String name, value, fallback ) {\n return "" if value \u2261 fallback;\n return _xml_attr( name, value );\n}\n\nfunction _xml_bool_attr_if ( String name, value, fallback ) {\n let normalized := value ? true : false;\n let normalized_fallback := fallback ? true : false;\n return "" if normalized \u2261 normalized_fallback;\n return _xml_attr( name, normalized ? "true" : "false" );\n}\n\nfunction _xml_attr_nonempty ( String name, value ) {\n return "" if value \u2261 null or value eq "";\n return _xml_attr( name, value );\n}\n\nfunction _xml_join_ints ( Array values ) {\n let out := [];\n for ( let value in values ) {\n out.push( "" _ value );\n }\n return join( ",", out );\n}\n\nfunction _xml_meta_attrs ( Widget widget ) {\n let keys := widget.meta( "__xml_meta_keys" ) ?: [];\n let out := "";\n for ( let key in keys ) {\n out _= _xml_attr( "meta." _ key, widget.meta(key) );\n }\n return out;\n}\n\nfunction _xml_style_attrs ( Widget widget ) {\n let keys := widget.meta( "__xml_style_keys" ) ?: [];\n let out := "";\n for ( let key in keys ) {\n out _= _xml_attr( "style." _ key, widget.style(key) );\n }\n return out;\n}\n\nfunction _xml_common_attrs ( Widget widget ) {\n let out := "";\n out _= _xml_attr( "id", widget.id() ) if widget.id() \u2262 null;\n out _= _xml_bool_attr_if( "visible", widget.visible(), true );\n out _= _xml_bool_attr_if( "disabled", not widget.enabled(), false );\n out _= _xml_meta_attrs(widget);\n out _= _xml_style_attrs(widget);\n return out;\n}\n\nfunction _xml_widget_attrs ( Widget widget ) {\n let out := _xml_common_attrs(widget);\n if ( widget instanceof _WindowClass ) {\n out _= _xml_attr_if( "title", widget.title(), "" );\n }\n else if ( widget instanceof _VBoxClass or widget instanceof _HBoxClass ) {\n let default_align := widget instanceof _VBoxClass ? "top" : "left";\n out _= _xml_attr_if( "align", widget.align(), default_align );\n out _= _xml_attr_if( "gap", widget.gap(), 0 );\n out _= _xml_attr_if( "padding", widget.padding(), 0 )\n unless widget.padding() instanceof Array;\n }\n else if ( widget instanceof _FrameClass ) {\n out _= _xml_attr_if( "label", widget.label(), "" );\n out _= _xml_bool_attr_if( "collapsible", widget.collapsible(), false );\n out _= _xml_bool_attr_if( "collapsed", widget.collapsed(), false );\n }\n else if ( widget instanceof _LabelClass ) {\n out _= _xml_attr_if( "text", widget.text(), "" );\n out _= _xml_attr_nonempty( "for", widget.for_id() );\n }\n else if ( widget instanceof _TextClass ) {\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_bool_attr_if( "multiline", widget.multiline(), false );\n out _= _xml_bool_attr_if( "readonly", widget.readonly(), false );\n out _= _xml_bool_attr_if( "wrap", widget.wrap(), true );\n }\n else if ( widget instanceof _RichTextClass ) {\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_bool_attr_if( "multiline", widget.multiline(), true );\n out _= _xml_bool_attr_if( "readonly", widget.readonly(), true );\n }\n else if ( widget instanceof _ImageClass ) {\n out _= _xml_attr_if( "src", widget.src(), "" );\n out _= _xml_attr_if( "alt", widget.alt(), "" );\n out _= _xml_attr_if( "fit", widget.fit(), "none" );\n }\n else if ( widget instanceof _InputClass ) {\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_attr_if( "placeholder", widget.placeholder(), "" );\n out _= _xml_bool_attr_if( "multiline", widget.multiline(), false );\n out _= _xml_bool_attr_if( "readonly", widget.readonly(), false );\n out _= _xml_bool_attr_if( "password", widget.password(), false );\n out _= _xml_bool_attr_if( "required", widget.required(), false );\n }\n else if ( widget instanceof _DatePickerClass ) {\n out _= _xml_attr( "value", widget.value() );\n out _= _xml_attr( "min", widget.min() ) if widget.min() \u2262 null;\n out _= _xml_attr( "max", widget.max() ) if widget.max() \u2262 null;\n out _= _xml_attr_if(\n "first_day_of_week",\n widget.first_day_of_week(),\n 0,\n );\n }\n else if ( widget instanceof _CheckboxClass ) {\n out _= _xml_attr_if( "label", widget.label(), "" );\n out _= _xml_bool_attr_if( "checked", widget.checked(), false );\n out _= _xml_bool_attr_if( "indeterminate", widget.indeterminate(), false );\n }\n else if ( widget instanceof _RadioClass ) {\n out _= _xml_attr_if( "label", widget.label(), "" );\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_attr_nonempty( "group", widget.group() );\n out _= _xml_bool_attr_if( "checked", widget.checked(), false );\n }\n else if ( widget instanceof _RadioGroupClass ) {\n out _= _xml_attr_if( "name", widget.name(), "" );\n out _= _xml_attr( "value", widget.value() ) if widget.value() \u2262 null;\n }\n else if ( widget instanceof _SelectClass ) {\n out _= _xml_attr( "value", widget.value() ) if widget.value() \u2262 null;\n out _= _xml_bool_attr_if( "multiple", widget.multiple(), false );\n }\n else if ( widget instanceof _MenuClass ) {\n out _= _xml_attr_if( "text", widget.text(), "" );\n }\n else if ( widget instanceof _MenuItemClass ) {\n out _= _xml_attr_if( "text", widget.text(), "" );\n }\n else if ( widget instanceof _ButtonClass ) {\n out _= _xml_attr_if( "text", widget.text(), "" );\n out _= _xml_attr_if( "variant", widget.variant(), "default" );\n }\n else if ( widget instanceof _SeparatorClass ) {\n out _= _xml_attr_if( "orientation", widget.orientation(), "horizontal" );\n }\n else if ( widget instanceof _SliderClass ) {\n out _= _xml_attr_if( "value", widget.value(), 0 );\n out _= _xml_attr_if( "min", widget.min(), 0 );\n out _= _xml_attr_if( "max", widget.max(), 100 );\n out _= _xml_attr_if( "step", widget.step(), 1 );\n out _= _xml_attr_if( "orientation", widget.orientation(), "horizontal" );\n out _= _xml_bool_attr_if( "readonly", widget.readonly(), false );\n }\n else if ( widget instanceof _ProgressClass ) {\n out _= _xml_attr_if( "value", widget.value(), 0 );\n out _= _xml_attr_if( "min", widget.min(), 0 );\n out _= _xml_attr_if( "max", widget.max(), 100 );\n out _= _xml_bool_attr_if( "indeterminate", widget.indeterminate(), false );\n out _= _xml_bool_attr_if( "show_text", widget.show_text(), false );\n }\n else if ( widget instanceof _TabsClass ) {\n out _= _xml_attr( "selected", widget.selected() )\n if widget.selected() \u2262 null;\n out _= _xml_attr_if( "placement", widget.placement(), "top" );\n }\n else if ( widget instanceof _TabClass ) {\n out _= _xml_attr_if( "title", widget.title(), "" );\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_attr( "icon", widget.icon() ) if widget.icon() \u2262 null;\n out _= _xml_bool_attr_if( "selected", widget.selected(), false );\n out _= _xml_bool_attr_if( "closable", widget.closable(), false );\n }\n else if ( widget instanceof _ListViewClass ) {\n out _= _xml_attr( "selected_index", widget.selected_index() )\n if widget.selected_index() \u2262 null;\n out _= _xml_bool_attr_if( "multiple", widget.multiple(), false );\n }\n else if ( widget instanceof _TreeViewClass ) {\n let selected_path := widget.selected_path();\n out _= _xml_attr( "selected_path", _xml_join_ints(selected_path) )\n if selected_path.length() > 0;\n out _= _xml_bool_attr_if( "multiple", widget.multiple(), false );\n }\n return out;\n}\n\nfunction _xml_serialize_widget ( Widget widget, Number depth ) {\n let indent := "";\n let i := 0;\n while ( i < depth ) {\n indent _= "\\t";\n i++;\n }\n\n let tag := _xml_tag_for_widget(widget);\n let attrs := _xml_widget_attrs(widget);\n attrs _= _xml_attr( "xmlns", GUI_XML_NS ) if depth = 0;\n let children := widget.children();\n if ( children.length() = 0 ) {\n return indent _ "<" _ tag _ attrs _ " />";\n }\n\n let parts := [];\n for ( let child in children ) {\n parts.push( _xml_serialize_widget( child, depth + 1 ) );\n }\n\n return indent _ "<" _ tag _ attrs _ ">\\n"\n _ join( "\\n", parts )\n _ "\\n" _ indent _ "</" _ tag _ ">";\n}\n\nfunction gui_to_xml ( Widget root ) {\n return _xml_serialize_widget( root, 0 ) _ "\\n";\n}\n';
40639
- virtualFiles["/modules/std/gui/dialogue.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/gui/dialogue - Dialogue helpers.\n\n=head1 SYNOPSIS\n\n from std/gui/dialogue import *;\n\n alert("Saved");\n let ok := confirm("Continue?");\n let name := prompt("Name:", value: "Ada");\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by zuzu.pl, zuzu-rust, and zuzu-js on Electron\nand Browser. It is not supported by zuzu-js on Node.\n\n=head1 DESCRIPTION\n\nThis module provides convenience dialogue helpers layered on top of the\nregular C<std/gui> widgets.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item C<< alert(String message, ... PairList p) >>, C<< alert_window(String message, ... PairList p) >>\n\nParameters: C<message> is display text and C<p> contains options.\nReturns: C<null> for C<alert> or C<Window> for C<alert_window>. Shows or\nbuilds an alert dialogue.\n\n=item C<< confirm(String message, ... PairList p) >>, C<< confirm_window(String message, ... PairList p) >>\n\nParameters: C<message> is display text and C<p> contains options.\nReturns: C<Boolean> for C<confirm> or C<Window> for\nC<confirm_window>. Shows or builds a confirmation dialogue.\n\n=item C<< prompt(String message, ... PairList p) >>, C<< prompt_window(String message, ... PairList p) >>\n\nParameters: C<message> is prompt text and C<p> contains options such as\nC<value>. Returns: C<String> or C<null> for C<prompt>, or C<Window> for\nC<prompt_window>. Shows or builds a text prompt.\n\n=item C<< file_open(... PairList p) >>, C<< file_save(... PairList p) >>\n\nParameters: C<p> contains dialogue options. Returns: path value or\nC<null>. Opens a file selection dialogue.\n\n=item C<< directory_open(... PairList p) >>, C<< directory_save(... PairList p) >>\n\nParameters: C<p> contains dialogue options. Returns: path value or\nC<null>. Opens a directory selection dialogue.\n\n=item C<< colour_picker(... PairList p) >>\n\nParameters: C<p> contains dialogue options. Returns: C<String> or\nC<null>. Opens a colour picker and returns a normalized colour string.\n\n=item C<< file_open_window(... PairList p) >>, C<< file_save_window(... PairList p) >>, C<< directory_open_window(... PairList p) >>, C<< directory_save_window(... PairList p) >>, C<< colour_picker_window(... PairList p) >>\n\nParameters: C<p> contains dialogue options. Returns: C<Window>. Builds\nthe corresponding GUI dialogue window.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/gui/dialogue >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/gui try import\n EM,\n Window,\n VBox,\n HBox,\n Label,\n Text,\n Input,\n Button,\n Widget,\n native_file_open,\n native_file_save,\n native_directory_open,\n native_directory_save,\n native_colour_picker;\nfrom std/gui/objects try import\n meta as _objects_meta,\n native_alert,\n native_confirm,\n native_prompt;\nfrom std/colour import parse_colour;\nfrom std/string import split;\nfrom std/tui import\n colour_text,\n directory_completions,\n filename_completions,\n readline;\n\nfunction _tui_string ( value ) {\n return value \u2261 null ? "" : "" _ value;\n}\n\nfunction _gui_backend () {\n if ( _objects_meta \u2262 null ) {\n return _objects_meta{backend};\n }\n return "";\n}\n\nfunction _path_dialogue_unsupported () {\n if ( __system__{deny_fs} ) {\n die "GUI_DIALOGUE_FS_DENIED: file and directory dialogues require filesystem capability";\n }\n if ( _gui_backend() eq "browser-dom" ) {\n die "GUI_DIALOGUE_UNSUPPORTED: file and directory dialogues are unsupported in JS/Browser";\n }\n}\n\nfunction _tui_prompt ( String message, PairList p, String default_value ) {\n return readline(\n colour_text( message _ " ", "cyan" ),\n default_value,\n null,\n );\n}\n\nfunction _tui_path_dialog (\n String label,\n String default_value,\n completion,\n PairList p\n) {\n let answer := readline(\n colour_text( p.get( "label", label ) _ " ", "cyan" ),\n _tui_string( p.get( "value", default_value ) ),\n completion,\n );\n if ( p.get( "multiple", false ) ) {\n return split( answer, p.get( "separator", "\\n" ) );\n }\n return answer;\n}\n\nfunction _parse_colour_or_null ( value ) {\n return try {\n parse_colour( _tui_string(value) );\n }\n catch {\n null;\n };\n}\n\nfunction _colour_initial_value ( PairList p ) {\n let raw := _tui_string( p.get( "value", "#000000" ) );\n let parsed := _parse_colour_or_null(raw);\n return parsed \u2262 null ? parsed : raw;\n}\n\nfunction _colour_default_value ( PairList p ) {\n return _parse_colour_or_null( p.get( "value", "#000000" ) ) ?: "#000000";\n}\n\nfunction _tui_colour_dialog ( PairList p ) {\n let default_value := _colour_default_value(p);\n while ( true ) {\n let answer := readline(\n colour_text( p.get( "label", "Colour:" ) _ " ", "cyan" ),\n default_value,\n null,\n );\n let parsed := _parse_colour_or_null(answer);\n if ( parsed \u2262 null ) {\n return parsed;\n }\n say( colour_text( "Invalid colour; try #336699 or red.", "yellow" ) );\n }\n}\n\nfunction _dialogue_window (\n String kind,\n String title,\n Widget body,\n Array buttons,\n PairList p\n) {\n let button_row := HBox(\n id: "buttons",\n align: "right",\n gap: 6,\n height: p.get( "button_row_height", 2.125 \xD7 EM ),\n maxheight: p.get( "button_row_height", 2.125 \xD7 EM ),\n );\n for ( let button in buttons ) {\n button_row.add_child(button);\n }\n let w := Window(\n title: title,\n width: p.get( "width", 360 ),\n height: p.get( "height", 180 ),\n resizable: p.get( "resizable", false ),\n modal: true,\n VBox(\n id: p.get( "id", null ),\n gap: p.get( "gap", 8 ),\n padding: p.get( "padding", 12 ),\n body,\n button_row,\n ),\n );\n w.meta( "dialogue.kind", kind );\n return w;\n}\n\nfunction _message_body ( String message, PairList p ) {\n return Text(\n id: p.get( "message_id", "message" ),\n value: message,\n readonly: true,\n wrap: true,\n );\n}\n\nfunction _primary_button ( PairList p, String fallback ) {\n return Button(\n id: p.get( "ok_id", "ok" ),\n text: p.get( "ok_text", fallback ),\n variant: "primary",\n width: p.get( "button_width", 5.5 \xD7 EM ),\n height: p.get( "button_height", 1.75 \xD7 EM ),\n maxheight: p.get( "button_height", 1.75 \xD7 EM ),\n );\n}\n\nfunction _cancel_button ( PairList p ) {\n return Button(\n id: p.get( "cancel_id", "cancel" ),\n text: p.get( "cancel_text", "Cancel" ),\n width: p.get( "button_width", 5.5 \xD7 EM ),\n height: p.get( "button_height", 1.75 \xD7 EM ),\n maxheight: p.get( "button_height", 1.75 \xD7 EM ),\n );\n}\n\nfunction _alert_window ( String message, PairList p ) {\n let ok := _primary_button( p, "OK" );\n let w := _dialogue_window(\n "alert",\n p.get( "title", "Alert" ),\n _message_body( message, p ),\n [ ok ],\n p,\n );\n ok.click( function () {\n w.close(null);\n } );\n return w;\n}\n\nfunction alert_window ( String message, ... PairList p ) {\n return _alert_window( message, p );\n}\n\nfunction alert ( String message, ... PairList p ) {\n if ( p.has("auto_result") ) {\n return null;\n }\n if ( __system__{deny_gui} ) {\n say( colour_text( message, "yellow" ) );\n return null;\n }\n if ( native_alert \u2262 null ) {\n let native_result := native_alert( message, p );\n if ( native_result ) {\n return null;\n }\n }\n return _alert_window( message, p ).call();\n}\n\nfunction _confirm_window ( String message, PairList p ) {\n let ok := _primary_button( p, p.get( "ok_text", "OK" ) );\n let cancel := _cancel_button(p);\n let w := _dialogue_window(\n "confirm",\n p.get( "title", "Confirm" ),\n _message_body( message, p ),\n [ cancel, ok ],\n p,\n );\n ok.click( function () {\n w.close(true);\n } );\n cancel.click( function () {\n w.close(false);\n } );\n return w;\n}\n\nfunction confirm_window ( String message, ... PairList p ) {\n return _confirm_window( message, p );\n}\n\nfunction confirm ( String message, ... PairList p ) {\n if ( p.has("auto_result") ) {\n return p.get("auto_result") ? true : false;\n }\n if ( __system__{deny_gui} ) {\n let yes_default := p.get( "value", p.get( "default", false ) );\n let suffix := yes_default ? " [Y/n] " : " [y/N] ";\n let answer := readline(\n colour_text( message _ suffix, "cyan" ),\n yes_default ? "y" : "n",\n null,\n );\n return ( answer ~ /^(?:y|yes|1|true)$/i ) ? true : false;\n }\n if ( native_confirm \u2262 null ) {\n let native_result := native_confirm( message, p );\n if ( native_result \u2262 null ) {\n return native_result ? true : false;\n }\n }\n return _confirm_window( message, p ).call() ? true : false;\n}\n\nfunction _prompt_window ( String message, PairList p ) {\n let input := Input(\n id: p.get( "input_id", "value" ),\n value: p.get( "value", "" ),\n placeholder: p.get( "placeholder", "" ),\n );\n let ok := _primary_button( p, p.get( "ok_text", "OK" ) );\n let cancel := _cancel_button(p);\n let w := _dialogue_window(\n "prompt",\n p.get( "title", "Prompt" ),\n VBox(\n gap: 6,\n _message_body( message, p ),\n input,\n ),\n [ cancel, ok ],\n p,\n );\n ok.click( function () {\n w.close( input.value() );\n } );\n cancel.click( function () {\n w.close(null);\n } );\n return w;\n}\n\nfunction prompt_window ( String message, ... PairList p ) {\n return _prompt_window( message, p );\n}\n\nfunction prompt ( String message, ... PairList p ) {\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_prompt(\n message,\n p,\n _tui_string( p.get( "value", "" ) ),\n );\n }\n if ( native_prompt \u2262 null ) {\n let native_result := native_prompt( message, p );\n if ( _gui_backend() eq "browser-dom" ) {\n return native_result;\n }\n if ( native_result \u2262 null ) {\n return native_result;\n }\n }\n return _prompt_window( message, p ).call();\n}\n\nfunction _path_dialog_window (\n String kind,\n String title,\n String label,\n String default_value,\n PairList p\n) {\n let input := Input(\n id: p.get( "input_id", "path" ),\n value: p.get( "value", default_value ),\n placeholder: p.get( "placeholder", "" ),\n multiline: p.get( "multiple", false ),\n );\n let ok := _primary_button( p, p.get( "ok_text", "OK" ) );\n let cancel := _cancel_button(p);\n let w := _dialogue_window(\n kind,\n p.get( "title", title ),\n VBox(\n gap: 6,\n Label( text: p.get( "label", label ), ("for"): input.id() ),\n input,\n ),\n [ cancel, ok ],\n p,\n );\n ok.click( function () {\n if ( p.get( "multiple", false ) ) {\n w.close( split( input.value(), p.get( "separator", "\\n" ) ) );\n }\n else {\n w.close( input.value() );\n }\n } );\n cancel.click( function () {\n w.close(null);\n } );\n return w;\n}\n\nfunction file_open_window ( ... PairList p ) {\n return _path_dialog_window( "file_open", "Open File", "File:", "", p );\n}\n\nfunction file_save_window ( ... PairList p ) {\n return _path_dialog_window( "file_save", "Save File", "File:", "", p );\n}\n\nfunction directory_open_window ( ... PairList p ) {\n return _path_dialog_window(\n "directory_open",\n "Open Directory",\n "Directory:",\n "",\n p,\n );\n}\n\nfunction directory_save_window ( ... PairList p ) {\n return _path_dialog_window(\n "directory_save",\n "Save Directory",\n "Directory:",\n "",\n p,\n );\n}\n\nfunction _colour_picker_window ( PairList p ) {\n let input := Input(\n id: p.get( "input_id", "path" ),\n value: _colour_initial_value(p),\n placeholder: p.get( "placeholder", "#336699" ),\n );\n let ok := _primary_button( p, p.get( "ok_text", "OK" ) );\n let cancel := _cancel_button(p);\n let default_value := _colour_default_value(p);\n let sync_ok := function () {\n ok.set_enabled( _parse_colour_or_null( input.value() ) \u2262 null );\n };\n let w := _dialogue_window(\n "colour_picker",\n p.get( "title", "Choose Colour" ),\n VBox(\n gap: 6,\n Label(\n text: p.get( "label", "Colour:" ),\n ("for"): input.id(),\n ),\n input,\n ),\n [ cancel, ok ],\n p,\n );\n sync_ok();\n input.on( "change", sync_ok );\n ok.click( function () {\n let parsed := _parse_colour_or_null( input.value() );\n if ( parsed \u2262 null ) {\n w.close(parsed);\n }\n } );\n cancel.click( function () {\n w.close(default_value);\n } );\n return w;\n}\n\nfunction colour_picker_window ( ... PairList p ) {\n return _colour_picker_window(p);\n}\n\nfunction file_open ( ... PairList p ) {\n _path_dialogue_unsupported();\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_path_dialog( "File:", "", filename_completions, p );\n }\n return native_file_open(p);\n}\n\nfunction file_save ( ... PairList p ) {\n _path_dialogue_unsupported();\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_path_dialog( "File:", "", filename_completions, p );\n }\n return native_file_save(p);\n}\n\nfunction directory_open ( ... PairList p ) {\n _path_dialogue_unsupported();\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_path_dialog(\n "Directory:",\n "",\n directory_completions,\n p,\n );\n }\n return native_directory_open(p);\n}\n\nfunction directory_save ( ... PairList p ) {\n _path_dialogue_unsupported();\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_path_dialog(\n "Directory:",\n "",\n directory_completions,\n p,\n );\n }\n if ( native_directory_save \u2262 null ) {\n let native_result := native_directory_save(p);\n if ( native_result \u2262 null ) {\n return native_result;\n }\n if ( _gui_backend() eq "electron-dom" ) {\n return null;\n }\n }\n return _path_dialog_window(\n "directory_save",\n "Save Directory",\n "Directory:",\n "",\n p,\n ).call();\n}\n\nfunction colour_picker ( ... PairList p ) {\n if ( p.has("auto_result") ) {\n return parse_colour( _tui_string( p.get("auto_result") ) );\n }\n if ( __system__{deny_gui} ) {\n return _tui_colour_dialog(p);\n }\n if ( native_colour_picker \u2262 null ) {\n let native_result := native_colour_picker(p);\n if ( native_result \u2262 null ) {\n return parse_colour(native_result);\n }\n if ( _gui_backend() eq "browser-dom" ) {\n return null;\n }\n }\n if ( _gui_backend() eq "electron-dom" ) {\n return _colour_picker_window(p).call();\n }\n return parse_colour( _colour_picker_window(p).call() );\n}\n';
40640
- virtualFiles["/modules/std/uuid.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/uuid - Pure ZuzuScript UUID v1 generator.\n\n=head1 SYNOPSIS\n\n from std/uuid import create_uuid, create_uuid_binary;\n\n let text := create_uuid();\n let raw := create_uuid_binary();\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module implements UUID version 1 generation using only\nZuzuScript code.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item * C<create_uuid_binary()>\n\nParameters: none. Returns: C<BinaryString>. Returns a single UUID as 16\nraw bytes.\n\n=item * C<create_uuid()>\n\nParameters: none. Returns: C<String>. Returns a single UUID as\nlowercase hexadecimal text with hyphens in the usual C<8-4-4-4-12>\nlayout.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/uuid >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/math import Math;\nfrom std/string import substr;\nfrom std/string/base64 import decode;\nfrom std/time import Time;\n\nlet _B64_ALPHABET := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";\nlet _HEX_ALPHABET := "0123456789abcdef";\n\nfunction _div_floor ( Number n, Number d ) {\n return floor( n / d );\n}\n\nfunction _mod ( Number n, Number d ) {\n return n - _div_floor( n, d ) * d;\n}\n\nfunction _rand_int ( Number max ) {\n return floor( Math.rand(max) );\n}\n\nfunction _bytes_to_binary ( Array bytes ) {\n let out := "";\n let i := 0;\n let n := bytes.length();\n\n while ( i < n ) {\n let b0 := bytes[i];\n let b1 := null;\n let b2 := null;\n if ( i + 1 < n ) {\n b1 := bytes[i + 1];\n }\n if ( i + 2 < n ) {\n b2 := bytes[i + 2];\n }\n\n let c0 := _div_floor( b0, 4 );\n let c1 := _mod( b0, 4 ) * 16;\n let c2 := 64;\n let c3 := 64;\n\n if ( b1 \u2262 null ) {\n c1 += _div_floor( b1, 16 );\n c2 := _mod( b1, 16 ) * 4;\n if ( b2 \u2262 null ) {\n c2 += _div_floor( b2, 64 );\n c3 := _mod( b2, 64 );\n }\n }\n\n out _= substr( _B64_ALPHABET, c0, 1 );\n out _= substr( _B64_ALPHABET, c1, 1 );\n if ( c2 \u2261 64 ) {\n out _= "=";\n }\n else {\n out _= substr( _B64_ALPHABET, c2, 1 );\n }\n if ( c3 \u2261 64 ) {\n out _= "=";\n }\n else {\n out _= substr( _B64_ALPHABET, c3, 1 );\n }\n\n i += 3;\n }\n\n return decode(out);\n}\n\nfunction _timestamp_words () {\n // Seconds between 1582-10-15 and 1970-01-01.\n let epoch_offset := 12219292800;\n let seconds := new Time().epoch() + epoch_offset;\n let ticks := _rand_int(10000000);\n\n let words := [\n _mod( seconds, 65536 ),\n _mod( _div_floor( seconds, 65536 ), 65536 ),\n _mod( _div_floor( seconds, 4294967296 ), 65536 ),\n 0,\n 0,\n ];\n\n let scale := 10000000;\n let i := 0;\n let carry := 0;\n while ( i < words.length() ) {\n let product := words[i] * scale + carry;\n words[i] := _mod( product, 65536 );\n carry := _div_floor( product, 65536 );\n i++;\n }\n\n let add_i := 0;\n let add_carry := ticks;\n while ( add_carry > 0 and add_i < words.length() ) {\n let sum := words[add_i] + _mod( add_carry, 65536 );\n words[add_i] := _mod( sum, 65536 );\n add_carry := _div_floor( add_carry, 65536 ) + _div_floor( sum, 65536 );\n add_i++;\n }\n\n return words;\n}\n\nfunction _create_uuid_bytes () {\n let words := _timestamp_words();\n let time_low := words[0] + words[1] * 65536;\n let time_mid := words[2];\n let time_hi_and_version := _mod( words[3], 4096 ) + 4096;\n\n let clock_seq := _rand_int(16384);\n let clock_seq_hi_and_reserved := _mod( _div_floor( clock_seq, 256 ), 64 ) + 128;\n let clock_seq_low := _mod( clock_seq, 256 );\n\n let node := [];\n let j := 0;\n while ( j < 6 ) {\n node.push( _rand_int(256) );\n j++;\n }\n // Multicast bit set means this is not an IEEE MAC address.\n node[0] := node[0] | 1;\n\n return [\n _mod( _div_floor( time_low, 16777216 ), 256 ),\n _mod( _div_floor( time_low, 65536 ), 256 ),\n _mod( _div_floor( time_low, 256 ), 256 ),\n _mod( time_low, 256 ),\n _mod( _div_floor( time_mid, 256 ), 256 ),\n _mod( time_mid, 256 ),\n _mod( _div_floor( time_hi_and_version, 256 ), 256 ),\n _mod( time_hi_and_version, 256 ),\n clock_seq_hi_and_reserved,\n clock_seq_low,\n node[0],\n node[1],\n node[2],\n node[3],\n node[4],\n node[5],\n ];\n}\n\nfunction _byte_to_hex ( Number b ) {\n let hi := _div_floor( b, 16 );\n let lo := _mod( b, 16 );\n return substr( _HEX_ALPHABET, hi, 1 ) _ substr( _HEX_ALPHABET, lo, 1 );\n}\n\nfunction _bytes_to_uuid_text ( Array bytes ) {\n let out := "";\n let i := 0;\n while ( i < 16 ) {\n out _= _byte_to_hex( bytes[i] );\n if ( i \u2261 3 or i \u2261 5 or i \u2261 7 or i \u2261 9 ) {\n out _= "-";\n }\n i++;\n }\n return out;\n}\n\nfunction create_uuid_binary () {\n return _bytes_to_binary( _create_uuid_bytes() );\n}\n\nfunction create_uuid () {\n return _bytes_to_uuid_text( _create_uuid_bytes() );\n}\n';
41649
+ virtualFiles["/modules/std/gui.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/gui - User-facing GUI constructor helpers.\n\n=head1 SYNOPSIS\n\n from std/gui import *;\n\n let w := Window(\n title: "Demo",\n VBox(\n Label( text: "Name:" ),\n Input( id: "name" ),\n Button( text: "OK", id: "submit" ),\n ),\n );\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by zuzu.pl, zuzu-rust, and zuzu-js on Electron.\nIt is not supported by zuzu-js on Node. It is partially supported by\nzuzu-js in the browser: core GUI and widget lifecycle coverage passes,\nbut filesystem-backed file and directory dialogue coverage is unsupported.\n\n=head1 DESCRIPTION\n\nThis module provides thin constructor helpers over C<std/gui/objects>.\nHost runtimes expose the shared widget, object tree, event, and window\nlifecycle APIs when GUI support is available. It also re-exports\nbackend-native file and directory dialogue hooks for C<std/gui/dialogue>.\n\nThe module exports C<EM>, the standard UI font height in logical\npixels, derived from C<std/gui/objects> metadata.\n\n=head1 EXPORTS\n\n=head2 Constants\n\n=over\n\n=item C<EM>\n\nType: C<Number>. Standard UI font height in logical pixels.\n\n=item C<GUI_XML_NS>\n\nType: C<String>. XML namespace URI used by GUI XML serialization and\nparsing.\n\n=back\n\n=head2 Functions\n\n=over\n\n=item C<< Window(... PairList p, Array c) >>, C<< VBox(... PairList p, Array c) >>, C<< HBox(... PairList p, Array c) >>, C<< Frame(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs window and layout widgets.\n\n=item C<< Label(... PairList p, Array c) >>, C<< Text(... PairList p, Array c) >>, C<< RichText(... PairList p, Array c) >>, C<< Image(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs content widgets.\n\n=item C<< Input(... PairList p, Array c) >>, C<< DatePicker(... PairList p, Array c) >>, C<< Checkbox(... PairList p, Array c) >>, C<< Radio(... PairList p, Array c) >>, C<< RadioGroup(... PairList p, Array c) >>, C<< Select(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs input widgets.\n\n=item C<< Menu(... PairList p, Array c) >>, C<< MenuItem(... PairList p, Array c) >>, C<< Button(... PairList p, Array c) >>, C<< Separator(... PairList p, Array c) >>, C<< Slider(... PairList p, Array c) >>, C<< Progress(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs command and control widgets.\n\n=item C<< Tabs(... PairList p, Array c) >>, C<< Tab(... PairList p, Array c) >>, C<< ListView(... PairList p, Array c) >>, C<< TreeView(... PairList p, Array c) >>\n\nParameters: C<p> are widget properties and C<c> contains child widgets.\nReturns: widget object. Constructs tabular and collection widgets.\n\n=item C<< unbind(BindingToken token) >>\n\nParameters: C<token> is a binding token. Returns: C<null>. Removes a GUI\nbinding.\n\n=item C<< gui_from_xml(String xml) >>, C<< gui_from_xml_file(path) >>\n\nParameters: C<xml> is GUI XML text and C<path> is a file path. Returns:\nwidget object. Builds a GUI object tree from XML.\n\n=item C<< gui_to_xml(Widget root) >>\n\nParameters: C<root> is a GUI widget. Returns: C<String>. Serializes a\nGUI object tree to XML.\n\n=back\n\n=head2 Classes\n\n=over\n\n=item C<BindingToken>\n\nRepresents a model/widget binding.\n\n=over\n\n=item C<< token.is_active() >>\n\nParameters: none. Returns: C<Boolean>. Returns true while the binding is\nactive.\n\n=item C<< token.set_listener(token) >>\n\nParameters: C<token> is a listener token. Returns: C<BindingToken>.\nStores the listener token associated with the binding.\n\n=item C<< token.sync_model_to_widget() >>, C<< token.sync_widget_to_model() >>\n\nParameters: none. Returns: C<null>. Synchronizes the bound values.\n\n=item C<< token.unbind() >>\n\nParameters: none. Returns: C<null>. Removes the binding.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/gui >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/gui/objects import\n Window as _WindowClass,\n VBox as _VBoxClass,\n HBox as _HBoxClass,\n Frame as _FrameClass,\n Label as _LabelClass,\n Text as _TextClass,\n RichText as _RichTextClass,\n Image as _ImageClass,\n Input as _InputClass,\n DatePicker as _DatePickerClass,\n Checkbox as _CheckboxClass,\n Radio as _RadioClass,\n RadioGroup as _RadioGroupClass,\n Select as _SelectClass,\n Menu as _MenuClass,\n MenuItem as _MenuItemClass,\n Button as _ButtonClass,\n Separator as _SeparatorClass,\n Slider as _SliderClass,\n Progress as _ProgressClass,\n Tabs as _TabsClass,\n Tab as _TabClass,\n ListView as _ListViewClass,\n TreeView as _TreeViewClass,\n Widget,\n Event,\n ListenerToken,\n native_file_open,\n native_file_save,\n native_directory_open,\n native_directory_save,\n native_colour_picker,\n meta as _gui_meta;\n\nfrom std/gui/objects try import\n native_alert,\n native_confirm,\n native_prompt;\n\nfrom std/data/xml import XML;\nfrom std/data/xml/escape import escape_xml;\nfrom std/internals import getupperprop;\nfrom std/path/zz import ZZPath;\nfrom std/string import join, split, substr, trim;\n\n\nconst Number EM := _gui_meta{font_size_pixels};\n\nfunction _assert_known_props ( String ctor, PairList props, Array allowed ) {\n let geometry := [\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n ];\n for ( let key in props.keys() ) {\n if ( key \u2209 allowed and key \u2209 geometry ) {\n die `GUI_PROP_UNKNOWN: ${ctor} does not accept property \'${key}\'`;\n }\n }\n}\n\nfunction _assert_prop_value ( String ctor, String prop, value, Array allowed ) {\n if ( value \u2209 allowed ) {\n die `GUI_PROP_TYPE: ${ctor} property \'${prop}\' has invalid value`;\n }\n}\n\nfunction _assert_prop_array ( String ctor, String prop, value ) {\n if ( not( value instanceof Array ) ) {\n die `GUI_PROP_TYPE: ${ctor} property \'${prop}\' expects Array`;\n }\n}\n\nfunction _with_geometry_props ( Array allowed ) {\n for ( let key in [\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n ] ) {\n allowed.push(key) unless key \u2208 allowed;\n }\n return allowed;\n}\n\nfunction _apply_geometry ( Widget widget, PairList p ) {\n for ( let key in [\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n ] ) {\n if ( p.has(key) ) {\n widget.(key)( p.get(key) );\n }\n }\n return widget;\n}\n\nfunction Window ( ... PairList p, Array c ) {\n _assert_known_props(\n "Window",\n p,\n _with_geometry_props( [\n "id",\n "title",\n "width",\n "height",\n "resizable",\n "modal",\n "visible",\n "enabled",\n "disabled",\n ] ),\n );\n\n return new _WindowClass(\n id: p.get( "id", null ),\n title: p.get( "title", "" ),\n width: p.get( "width", 800 ),\n height: p.get( "height", 600 ),\n minwidth: p.get( "minwidth", null ),\n minheight: p.get( "minheight", null ),\n maxwidth: p.get( "maxwidth", null ),\n maxheight: p.get( "maxheight", null ),\n resizable: p.get( "resizable", true ),\n modal: p.get( "modal", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n );\n}\n\nfunction VBox ( ... PairList p, Array c ) {\n _assert_known_props(\n "VBox",\n p,\n [\n "id",\n "align",\n "gap",\n "padding",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n _assert_prop_value(\n "VBox",\n "align",\n p.get( "align", "top" ),\n [ "top", "centre", "bottom", "stretch" ],\n );\n\n return _apply_geometry( new _VBoxClass(\n id: p.get( "id", null ),\n align: p.get( "align", "top" ),\n gap: p.get( "gap", 0 ),\n padding: p.get( "padding", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction HBox ( ... PairList p, Array c ) {\n _assert_known_props(\n "HBox",\n p,\n _with_geometry_props( [\n "id",\n "align",\n "gap",\n "padding",\n "visible",\n "enabled",\n "disabled",\n ] ),\n );\n\n _assert_prop_value(\n "HBox",\n "align",\n p.get( "align", "left" ),\n [ "left", "centre", "right", "stretch" ],\n );\n\n return new _HBoxClass(\n id: p.get( "id", null ),\n align: p.get( "align", "left" ),\n gap: p.get( "gap", 0 ),\n padding: p.get( "padding", 0 ),\n width: p.get( "width", null ),\n height: p.get( "height", null ),\n minwidth: p.get( "minwidth", null ),\n minheight: p.get( "minheight", null ),\n maxwidth: p.get( "maxwidth", null ),\n maxheight: p.get( "maxheight", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n );\n}\n\nfunction Frame ( ... PairList p, Array c ) {\n _assert_known_props(\n "Frame",\n p,\n [\n "id",\n "label",\n "collapsible",\n "collapsed",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _FrameClass(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n collapsible: p.get( "collapsible", false ),\n collapsed: p.get( "collapsed", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Label ( ... PairList p, Array c ) {\n _assert_known_props(\n "Label",\n p,\n [ "id", "text", "for", "visible", "enabled", "disabled" ],\n );\n\n let label := new _LabelClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n );\n if ( p.has("for") ) {\n label.set_for_id( p.get( "for", null ) );\n }\n\n return _apply_geometry( label, p );\n}\n\nfunction Text ( ... PairList p, Array c ) {\n _assert_known_props(\n "Text",\n p,\n [\n "id",\n "value",\n "multiline",\n "readonly",\n "wrap",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _TextClass(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n multiline: p.get( "multiline", false ),\n readonly: p.get( "readonly", false ),\n wrap: p.get( "wrap", true ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction RichText ( ... PairList p, Array c ) {\n _assert_known_props(\n "RichText",\n p,\n [\n "id",\n "value",\n "multiline",\n "readonly",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _RichTextClass(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n multiline: p.get( "multiline", true ),\n readonly: p.get( "readonly", true ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Image ( ... PairList p, Array c ) {\n _assert_known_props(\n "Image",\n p,\n [ "id", "src", "alt", "fit", "visible", "enabled", "disabled" ],\n );\n\n _assert_prop_value(\n "Image",\n "fit",\n p.get( "fit", "none" ),\n [ "none", "contain", "cover", "stretch" ],\n );\n\n return _apply_geometry( new _ImageClass(\n id: p.get( "id", null ),\n src: p.get( "src", "" ),\n alt: p.get( "alt", "" ),\n fit: p.get( "fit", "none" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Input ( ... PairList p, Array c ) {\n _assert_known_props(\n "Input",\n p,\n [\n "id",\n "value",\n "placeholder",\n "multiline",\n "readonly",\n "password",\n "required",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _InputClass(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n placeholder: p.get( "placeholder", "" ),\n multiline: p.get( "multiline", false ),\n readonly: p.get( "readonly", false ),\n password: p.get( "password", false ),\n required: p.get( "required", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction DatePicker ( ... PairList p, Array c ) {\n _assert_known_props(\n "DatePicker",\n p,\n [\n "id",\n "value",\n "min",\n "max",\n "first_day_of_week",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _DatePickerClass(\n id: p.get( "id", null ),\n value: p.get( "value", null ),\n min: p.get( "min", null ),\n max: p.get( "max", null ),\n first_day_of_week: p.get( "first_day_of_week", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Checkbox ( ... PairList p, Array c ) {\n _assert_known_props(\n "Checkbox",\n p,\n [\n "id",\n "label",\n "checked",\n "indeterminate",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _CheckboxClass(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n checked: p.get( "checked", false ),\n indeterminate: p.get( "indeterminate", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Radio ( ... PairList p, Array c ) {\n _assert_known_props(\n "Radio",\n p,\n [\n "id",\n "label",\n "value",\n "group",\n "checked",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _RadioClass(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n value: p.get( "value", "" ),\n group: p.get( "group", null ),\n checked: p.get( "checked", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction RadioGroup ( ... PairList p, Array c ) {\n _assert_known_props(\n "RadioGroup",\n p,\n [\n "id",\n "name",\n "value",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _RadioGroupClass(\n id: p.get( "id", null ),\n name: p.get( "name", "" ),\n value: p.get( "value", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Select ( ... PairList p, Array c ) {\n _assert_known_props(\n "Select",\n p,\n [\n "id",\n "value",\n "options",\n "multiple",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n if ( p.has("options") ) {\n _assert_prop_array( "Select", "options", p.get("options") );\n }\n\n return _apply_geometry( new _SelectClass(\n id: p.get( "id", null ),\n value: p.get( "value", null ),\n options: p.get( "options", [] ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Menu ( ... PairList p, Array c ) {\n _assert_known_props(\n "Menu",\n p,\n [ "id", "text", "visible", "enabled", "disabled" ],\n );\n\n return _apply_geometry( new _MenuClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction MenuItem ( ... PairList p, Array c ) {\n _assert_known_props(\n "MenuItem",\n p,\n [ "id", "text", "disabled", "visible", "enabled" ],\n );\n\n return _apply_geometry( new _MenuItemClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Button ( ... PairList p, Array c ) {\n _assert_known_props(\n "Button",\n p,\n _with_geometry_props( [\n "id",\n "text",\n "variant",\n "visible",\n "enabled",\n "disabled",\n ] ),\n );\n\n _assert_prop_value(\n "Button",\n "variant",\n p.get( "variant", "default" ),\n [ "default", "primary", "danger" ],\n );\n\n return new _ButtonClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n variant: p.get( "variant", "default" ),\n width: p.get( "width", null ),\n height: p.get( "height", null ),\n minwidth: p.get( "minwidth", null ),\n minheight: p.get( "minheight", null ),\n maxwidth: p.get( "maxwidth", null ),\n maxheight: p.get( "maxheight", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n );\n}\n\nfunction Separator ( ... PairList p, Array c ) {\n _assert_known_props(\n "Separator",\n p,\n [ "id", "orientation", "visible", "enabled", "disabled" ],\n );\n\n _assert_prop_value(\n "Separator",\n "orientation",\n p.get( "orientation", "horizontal" ),\n [ "horizontal", "vertical" ],\n );\n\n return _apply_geometry( new _SeparatorClass(\n id: p.get( "id", null ),\n orientation: p.get( "orientation", "horizontal" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Slider ( ... PairList p, Array c ) {\n _assert_known_props(\n "Slider",\n p,\n [\n "id",\n "value",\n "min",\n "max",\n "step",\n "orientation",\n "readonly",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n _assert_prop_value(\n "Slider",\n "orientation",\n p.get( "orientation", "horizontal" ),\n [ "horizontal", "vertical" ],\n );\n\n return _apply_geometry( new _SliderClass(\n id: p.get( "id", null ),\n value: p.get( "value", 0 ),\n min: p.get( "min", 0 ),\n max: p.get( "max", 100 ),\n step: p.get( "step", 1 ),\n orientation: p.get( "orientation", "horizontal" ),\n readonly: p.get( "readonly", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Progress ( ... PairList p, Array c ) {\n _assert_known_props(\n "Progress",\n p,\n [\n "id",\n "value",\n "min",\n "max",\n "indeterminate",\n "show_text",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _ProgressClass(\n id: p.get( "id", null ),\n value: p.get( "value", 0 ),\n min: p.get( "min", 0 ),\n max: p.get( "max", 100 ),\n indeterminate: p.get( "indeterminate", false ),\n show_text: p.get( "show_text", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Tabs ( ... PairList p, Array c ) {\n _assert_known_props(\n "Tabs",\n p,\n [\n "id",\n "selected",\n "placement",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n _assert_prop_value(\n "Tabs",\n "placement",\n p.get( "placement", "top" ),\n [ "top", "bottom", "left", "right" ],\n );\n\n return _apply_geometry( new _TabsClass(\n id: p.get( "id", null ),\n selected: p.get( "selected", null ),\n placement: p.get( "placement", "top" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction Tab ( ... PairList p, Array c ) {\n _assert_known_props(\n "Tab",\n p,\n [\n "id",\n "title",\n "value",\n "selected",\n "closable",\n "icon",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n return _apply_geometry( new _TabClass(\n id: p.get( "id", null ),\n title: p.get( "title", "" ),\n value: p.get( "value", "" ),\n selected: p.get( "selected", false ),\n closable: p.get( "closable", false ),\n icon: p.get( "icon", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction ListView ( ... PairList p, Array c ) {\n _assert_known_props(\n "ListView",\n p,\n [\n "id",\n "items",\n "selected_index",\n "multiple",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n if ( p.has("items") ) {\n _assert_prop_array( "ListView", "items", p.get("items") );\n }\n\n return _apply_geometry( new _ListViewClass(\n id: p.get( "id", null ),\n items: p.get( "items", [] ),\n selected_index: p.get( "selected_index", null ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction TreeView ( ... PairList p, Array c ) {\n _assert_known_props(\n "TreeView",\n p,\n [\n "id",\n "items",\n "selected_path",\n "multiple",\n "visible",\n "enabled",\n "disabled",\n ],\n );\n\n if ( p.has("items") ) {\n _assert_prop_array( "TreeView", "items", p.get("items") );\n }\n if ( p.has("selected_path") ) {\n _assert_prop_array( "TreeView", "selected_path", p.get("selected_path") );\n }\n\n return _apply_geometry( new _TreeViewClass(\n id: p.get( "id", null ),\n items: p.get( "items", [] ),\n selected_path: p.get( "selected_path", [] ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: c,\n ), p );\n}\n\nfunction _binding_path_class ( path_class ) {\n if ( path_class \u2261 null ) {\n return ZZPath;\n }\n if ( not( path_class instanceof Class ) ) {\n die "GUI_BIND_PATH: paths special property must be Class or null";\n }\n return path_class;\n}\n\nfunction _binding_compile_path ( String path_text, path_class ) {\n if ( path_class \u2261 null ) {\n try {\n return new ZZPath( path: path_text );\n }\n catch ( Exception e ) {\n die `GUI_BIND_PATH: ${e{message}}`;\n }\n }\n\n let path_type := _binding_path_class(path_class);\n try {\n return new path_type( path: path_text );\n }\n catch ( Exception e ) {\n die `GUI_BIND_PATH: ${e{message}}`;\n }\n}\n\nfunction _binding_path ( pathish, path_class ) {\n if ( pathish instanceof String ) {\n return _binding_compile_path( pathish, path_class );\n }\n if (\n pathish can first\n and ( pathish can assign_first or pathish can ref_first )\n ) {\n return pathish;\n }\n\n die "GUI_BIND_PATH: binding path must be String or path-like Object";\n}\n\nfunction _binding_get ( model, path ) {\n return path.first( model, null );\n}\n\nfunction _binding_set ( model, path, value ) {\n if ( path can assign_first ) {\n return path.assign_first( model, value );\n }\n\n let ref := path.ref_first(model);\n return ref(value);\n}\n\nclass BindingToken {\n let Widget widget but weak;\n let String widget_prop;\n let model;\n let model_path;\n let String event := "change";\n let listener := null;\n let Boolean _active := true;\n\n method is_active () {\n return _active;\n }\n\n method set_listener ( token ) {\n listener := token;\n return self;\n }\n\n method sync_model_to_widget () {\n widget.(widget_prop)( _binding_get( model, model_path ) );\n return self;\n }\n\n method sync_widget_to_model () {\n _binding_set( model, model_path, widget.(widget_prop)() );\n return self;\n }\n\n method unbind () {\n if ( _active and listener \u2262 null ) {\n widget.off(listener);\n }\n _active := false;\n return self;\n }\n}\n\nfunction bind (\n Widget widget,\n String widget_prop,\n model,\n model_path,\n ... PairList p\n) {\n let path := _binding_path( model_path, getupperprop( 1, "paths" ) );\n\n let token := new BindingToken(\n widget: widget,\n widget_prop: widget_prop,\n model: model,\n model_path: path,\n event: p.get( "event", "change" ),\n );\n let event_name := p.get( "event", "change" );\n\n switch ( p.get( "initial", "model" ): eq ) {\n case "model":\n token.sync_model_to_widget();\n case "widget":\n token.sync_widget_to_model();\n case "none":\n // No initial sync.\n default:\n die "GUI_BIND_INITIAL: initial must be model, widget, or none";\n }\n\n token.set_listener(\n widget.on( event_name, function () {\n if ( token.is_active() ) {\n token.sync_widget_to_model();\n }\n } ),\n );\n return token;\n}\n\nfunction unbind ( BindingToken token ) {\n return token.unbind();\n}\n\nlet GUI_XML_NS := "https://zuzulang.org/ns/std/gui";\n\nfunction _xml_error ( String code, String message ) {\n die `${code}: ${message}`;\n}\n\nfunction _xml_tag_name ( node ) {\n return node.localName() or? node.nodeName();\n}\n\nfunction _xml_assert_namespace ( node ) {\n let ns := node.namespaceURI();\n if ( ns \u2262 null and ns \u2262 "" and ns \u2262 GUI_XML_NS ) {\n _xml_error(\n "GUI_XML_STRUCTURE",\n `unsupported GUI XML namespace \'${ns}\'`,\n );\n }\n}\n\nfunction _xml_common_allowed ( Array extra ) {\n let out := [\n "id",\n "visible",\n "enabled",\n "disabled",\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n ];\n for ( let attr in extra ) {\n out.push(attr);\n }\n return out;\n}\n\nfunction _xml_allowed_attrs ( String tag ) {\n switch ( tag: eq ) {\n case "Window":\n return _xml_common_allowed(\n [ "title", "width", "height", "resizable", "modal" ],\n );\n case "VBox", "HBox":\n return _xml_common_allowed( [ "align", "gap", "padding" ] );\n case "Frame":\n return _xml_common_allowed( [ "label", "collapsible", "collapsed" ] );\n case "Label":\n return _xml_common_allowed( [ "text", "for" ] );\n case "Text":\n return _xml_common_allowed(\n [ "value", "multiline", "readonly", "wrap" ],\n );\n case "RichText":\n return _xml_common_allowed(\n [ "value", "multiline", "readonly" ],\n );\n case "Image":\n return _xml_common_allowed( [ "src", "alt", "fit" ] );\n case "Input":\n return _xml_common_allowed( [\n "value",\n "placeholder",\n "multiline",\n "readonly",\n "password",\n "required",\n ] );\n case "DatePicker":\n return _xml_common_allowed(\n [ "value", "min", "max", "first_day_of_week" ],\n );\n case "Checkbox":\n return _xml_common_allowed( [ "label", "checked", "indeterminate" ] );\n case "Radio":\n return _xml_common_allowed( [ "label", "value", "group", "checked" ] );\n case "RadioGroup":\n return _xml_common_allowed( [ "name", "value" ] );\n case "Select":\n return _xml_common_allowed( [ "value", "multiple" ] );\n case "Menu":\n return _xml_common_allowed( [ "text" ] );\n case "MenuItem":\n return _xml_common_allowed( [ "text" ] );\n case "Button":\n return _xml_common_allowed( [ "text", "variant" ] );\n case "Separator":\n return _xml_common_allowed( [ "orientation" ] );\n case "Slider":\n return _xml_common_allowed( [\n "value",\n "min",\n "max",\n "step",\n "orientation",\n "readonly",\n ] );\n case "Progress":\n return _xml_common_allowed( [\n "value",\n "min",\n "max",\n "indeterminate",\n "show_text",\n ] );\n case "Tabs":\n return _xml_common_allowed( [ "selected", "placement" ] );\n case "Tab":\n return _xml_common_allowed(\n [ "title", "value", "selected", "closable", "icon" ],\n );\n case "ListView":\n return _xml_common_allowed( [ "selected_index", "multiple" ] );\n case "TreeView":\n return _xml_common_allowed( [ "selected_path", "multiple" ] );\n }\n\n _xml_error( "GUI_XML_STRUCTURE", `unsupported GUI XML element \'${tag}\'` );\n}\n\nfunction _xml_bool_attrs ( String tag ) {\n return [\n "visible",\n "enabled",\n "disabled",\n "resizable",\n "modal",\n "collapsible",\n "collapsed",\n "multiline",\n "readonly",\n "wrap",\n "password",\n "required",\n "checked",\n "indeterminate",\n "multiple",\n "show_text",\n "selected",\n "closable",\n ];\n}\n\nfunction _xml_number_attrs ( String tag ) {\n return [\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n "gap",\n "padding",\n "first_day_of_week",\n "selected_index",\n "value",\n "min",\n "max",\n "step",\n ];\n}\n\nfunction _xml_bool ( String tag, String attr, String raw ) {\n switch ( lc(raw): eq ) {\n case "true", "1", "yes", "on": return true;\n case "false", "0", "no", "off": return false;\n }\n _xml_error(\n "GUI_XML_ATTR_TYPE",\n `${tag}.${attr} expects a boolean XML attribute`,\n );\n}\n\nfunction _xml_number ( String tag, String attr, String raw ) {\n if ( not ( raw ~ /^-?[0-9]+(\\.[0-9]+)?$/ ) ) {\n _xml_error(\n "GUI_XML_ATTR_TYPE",\n `${tag}.${attr} expects a numeric XML attribute`,\n );\n }\n return 0 + raw;\n}\n\nfunction _xml_int_list ( String tag, String attr, String raw ) {\n let out := [];\n return out if raw eq "";\n for ( let part in split( raw, "," ) ) {\n let item := trim(part);\n if ( not ( item ~ /^-?[0-9]+$/ ) ) {\n _xml_error(\n "GUI_XML_ATTR_TYPE",\n `${tag}.${attr} expects comma-separated integers`,\n );\n }\n out.push( 0 + item );\n }\n return out;\n}\n\nfunction _xml_coerce_attr ( String tag, String attr, String raw ) {\n if ( attr eq "selected" and tag eq "Tabs" ) {\n return raw;\n }\n if ( attr eq "value" and tag \u2209 [ "Slider", "Progress" ] ) {\n return raw;\n }\n if ( attr eq "min" and tag eq "DatePicker" ) {\n return raw;\n }\n if ( attr eq "max" and tag eq "DatePicker" ) {\n return raw;\n }\n if ( attr eq "selected_path" ) {\n return _xml_int_list( tag, attr, raw );\n }\n if ( attr \u2208 _xml_bool_attrs(tag) ) {\n return _xml_bool( tag, attr, raw );\n }\n if ( attr \u2208 _xml_number_attrs(tag) ) {\n return _xml_number( tag, attr, raw );\n }\n return raw;\n}\n\nfunction _xml_attrs ( node ) {\n let tag := _xml_tag_name(node);\n let props := {};\n let meta := {};\n let meta_keys := [];\n let style := {};\n let style_keys := [];\n let allowed := _xml_allowed_attrs(tag);\n\n for ( let attr in node.attributeNames() ) {\n next if attr eq "xmlns";\n next if substr( attr, 0, 6 ) eq "xmlns:";\n let value := node.getAttribute(attr);\n if ( substr( attr, 0, 5 ) eq "meta." ) {\n let key := substr( attr, 5 );\n _xml_error( "GUI_XML_ATTR_UNKNOWN", "empty meta attribute name" )\n if key eq "";\n meta{(key)} := value;\n meta_keys.push(key);\n next;\n }\n if ( substr( attr, 0, 6 ) eq "style." ) {\n let key := substr( attr, 6 );\n _xml_error( "GUI_XML_ATTR_UNKNOWN", "empty style attribute name" )\n if key eq "";\n style{(key)} := value;\n style_keys.push(key);\n next;\n }\n if ( attr \u2209 allowed ) {\n _xml_error(\n "GUI_XML_ATTR_UNKNOWN",\n `${tag} does not accept XML attribute \'${attr}\'`,\n );\n }\n props{(attr)} := _xml_coerce_attr( tag, attr, value );\n }\n\n return {\n props: props,\n meta: meta,\n meta_keys: meta_keys,\n style: style,\n style_keys: style_keys,\n };\n}\n\nfunction _xml_apply_meta ( Widget widget, Dict meta, Array keys ) {\n for ( let key in keys ) {\n widget.meta( key, meta{(key)} );\n }\n if ( keys.length() > 0 ) {\n widget.meta( "__xml_meta_keys", keys );\n }\n return widget;\n}\n\nfunction _xml_apply_style ( Widget widget, Dict style, Array keys ) {\n for ( let key in keys ) {\n widget.style( key, style{(key)} );\n }\n if ( keys.length() > 0 ) {\n widget.meta( "__xml_style_keys", keys );\n }\n return widget;\n}\n\nfunction _xml_widget_from_node ( node ) {\n _xml_assert_namespace(node);\n\n let tag := _xml_tag_name(node);\n let child_widgets := [];\n for ( let child in node.children() ) {\n child_widgets.push( _xml_widget_from_node(child) );\n }\n let parsed := _xml_attrs(node);\n let p := parsed{props};\n let widget;\n\n switch ( tag: eq ) {\n case "Window":\n widget := new _WindowClass(\n id: p.get( "id", null ),\n title: p.get( "title", "" ),\n width: p.get( "width", 800 ),\n height: p.get( "height", 600 ),\n resizable: p.get( "resizable", true ),\n modal: p.get( "modal", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "VBox":\n widget := new _VBoxClass(\n id: p.get( "id", null ),\n align: p.get( "align", "top" ),\n gap: p.get( "gap", 0 ),\n padding: p.get( "padding", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "HBox":\n widget := new _HBoxClass(\n id: p.get( "id", null ),\n align: p.get( "align", "left" ),\n gap: p.get( "gap", 0 ),\n padding: p.get( "padding", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "Frame":\n widget := new _FrameClass(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n collapsible: p.get( "collapsible", false ),\n collapsed: p.get( "collapsed", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "Label":\n widget := Label(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n ("for"): p.get( "for", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Text":\n widget := Text(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n multiline: p.get( "multiline", false ),\n readonly: p.get( "readonly", false ),\n wrap: p.get( "wrap", true ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "RichText":\n widget := RichText(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n multiline: p.get( "multiline", true ),\n readonly: p.get( "readonly", true ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Image":\n widget := Image(\n id: p.get( "id", null ),\n src: p.get( "src", "" ),\n alt: p.get( "alt", "" ),\n fit: p.get( "fit", "none" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Input":\n widget := Input(\n id: p.get( "id", null ),\n value: p.get( "value", "" ),\n placeholder: p.get( "placeholder", "" ),\n multiline: p.get( "multiline", false ),\n readonly: p.get( "readonly", false ),\n password: p.get( "password", false ),\n required: p.get( "required", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "DatePicker":\n widget := DatePicker(\n id: p.get( "id", null ),\n value: p.get( "value", null ),\n min: p.get( "min", null ),\n max: p.get( "max", null ),\n first_day_of_week: p.get( "first_day_of_week", 0 ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Checkbox":\n widget := Checkbox(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n checked: p.get( "checked", false ),\n indeterminate: p.get( "indeterminate", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Radio":\n widget := Radio(\n id: p.get( "id", null ),\n label: p.get( "label", "" ),\n value: p.get( "value", "" ),\n group: p.get( "group", null ),\n checked: p.get( "checked", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "RadioGroup":\n widget := new _RadioGroupClass(\n id: p.get( "id", null ),\n name: p.get( "name", "" ),\n value: p.get( "value", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "Select":\n widget := Select(\n id: p.get( "id", null ),\n value: p.get( "value", null ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Menu":\n widget := new _MenuClass(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "MenuItem":\n widget := MenuItem(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Button":\n widget := Button(\n id: p.get( "id", null ),\n text: p.get( "text", "" ),\n variant: p.get( "variant", "default" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Separator":\n widget := Separator(\n id: p.get( "id", null ),\n orientation: p.get( "orientation", "horizontal" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Slider":\n widget := Slider(\n id: p.get( "id", null ),\n value: p.get( "value", 0 ),\n min: p.get( "min", 0 ),\n max: p.get( "max", 100 ),\n step: p.get( "step", 1 ),\n orientation: p.get( "orientation", "horizontal" ),\n readonly: p.get( "readonly", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Progress":\n widget := Progress(\n id: p.get( "id", null ),\n value: p.get( "value", 0 ),\n min: p.get( "min", 0 ),\n max: p.get( "max", 100 ),\n indeterminate: p.get( "indeterminate", false ),\n show_text: p.get( "show_text", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "Tabs":\n widget := new _TabsClass(\n id: p.get( "id", null ),\n selected: p.get( "selected", null ),\n placement: p.get( "placement", "top" ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "Tab":\n widget := new _TabClass(\n id: p.get( "id", null ),\n title: p.get( "title", "" ),\n value: p.get( "value", "" ),\n selected: p.get( "selected", false ),\n closable: p.get( "closable", false ),\n icon: p.get( "icon", null ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n children: child_widgets,\n );\n case "ListView":\n widget := ListView(\n id: p.get( "id", null ),\n selected_index: p.get( "selected_index", null ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n case "TreeView":\n widget := TreeView(\n id: p.get( "id", null ),\n selected_path: p.get( "selected_path", [] ),\n multiple: p.get( "multiple", false ),\n visible: p.get( "visible", true ),\n enabled: p.get( "enabled", true ),\n disabled: p.get( "disabled", false ),\n );\n default:\n _xml_error(\n "GUI_XML_STRUCTURE",\n `unsupported GUI XML element \'${tag}\'`,\n );\n }\n\n _xml_apply_meta( widget, parsed{meta}, parsed{meta_keys} );\n return _xml_apply_style( widget, parsed{style}, parsed{style_keys} );\n}\n\nfunction gui_from_xml ( String xml ) {\n return _xml_widget_from_node( XML.parse(xml).documentElement() );\n}\n\nfunction gui_from_xml_file ( path ) {\n die "XML.load is denied by runtime policy" if __system__{deny_fs};\n from std/io import Path;\n let file := path instanceof Path ? path : new Path(path);\n return _xml_widget_from_node( XML.load(file).documentElement() );\n}\n\nfunction _xml_tag_for_widget ( Widget widget ) {\n if ( widget instanceof _WindowClass ) { return "Window"; }\n if ( widget instanceof _VBoxClass ) { return "VBox"; }\n if ( widget instanceof _HBoxClass ) { return "HBox"; }\n if ( widget instanceof _FrameClass ) { return "Frame"; }\n if ( widget instanceof _LabelClass ) { return "Label"; }\n if ( widget instanceof _TextClass ) { return "Text"; }\n if ( widget instanceof _RichTextClass ) { return "RichText"; }\n if ( widget instanceof _ImageClass ) { return "Image"; }\n if ( widget instanceof _InputClass ) { return "Input"; }\n if ( widget instanceof _DatePickerClass ) { return "DatePicker"; }\n if ( widget instanceof _CheckboxClass ) { return "Checkbox"; }\n if ( widget instanceof _RadioClass ) { return "Radio"; }\n if ( widget instanceof _RadioGroupClass ) { return "RadioGroup"; }\n if ( widget instanceof _SelectClass ) { return "Select"; }\n if ( widget instanceof _MenuClass ) { return "Menu"; }\n if ( widget instanceof _MenuItemClass ) { return "MenuItem"; }\n if ( widget instanceof _ButtonClass ) { return "Button"; }\n if ( widget instanceof _SeparatorClass ) { return "Separator"; }\n if ( widget instanceof _SliderClass ) { return "Slider"; }\n if ( widget instanceof _ProgressClass ) { return "Progress"; }\n if ( widget instanceof _TabsClass ) { return "Tabs"; }\n if ( widget instanceof _TabClass ) { return "Tab"; }\n if ( widget instanceof _ListViewClass ) { return "ListView"; }\n if ( widget instanceof _TreeViewClass ) { return "TreeView"; }\n _xml_error( "GUI_XML_STRUCTURE", "unsupported widget for gui_to_xml" );\n}\n\nfunction _xml_attr ( String name, value ) {\n return "" if value \u2261 null;\n return ` ${name}="${escape_xml(value)}"`;\n}\n\nfunction _xml_attr_if ( String name, value, fallback ) {\n return "" if value \u2261 fallback;\n return _xml_attr( name, value );\n}\n\nfunction _xml_bool_attr_if ( String name, value, fallback ) {\n let normalized := value ? true : false;\n let normalized_fallback := fallback ? true : false;\n return "" if normalized \u2261 normalized_fallback;\n return _xml_attr( name, normalized ? "true" : "false" );\n}\n\nfunction _xml_attr_nonempty ( String name, value ) {\n return "" if value \u2261 null or value eq "";\n return _xml_attr( name, value );\n}\n\nfunction _xml_join_ints ( Array values ) {\n let out := [];\n for ( let value in values ) {\n out.push( "" _ value );\n }\n return join( ",", out );\n}\n\nfunction _xml_meta_attrs ( Widget widget ) {\n // These metadata slots are null or arrays; empty arrays are values.\n let keys := widget.meta( "__xml_meta_keys" ) ?: [];\n let out := "";\n for ( let key in keys ) {\n out _= _xml_attr( "meta." _ key, widget.meta(key) );\n }\n return out;\n}\n\nfunction _xml_style_attrs ( Widget widget ) {\n // These metadata slots are null or arrays; empty arrays are values.\n let keys := widget.meta( "__xml_style_keys" ) ?: [];\n let out := "";\n for ( let key in keys ) {\n out _= _xml_attr( "style." _ key, widget.style(key) );\n }\n return out;\n}\n\nfunction _xml_common_attrs ( Widget widget ) {\n let out := "";\n out _= _xml_attr( "id", widget.id() ) if widget.id() \u2262 null;\n out _= _xml_bool_attr_if( "visible", widget.visible(), true );\n out _= _xml_bool_attr_if( "disabled", not widget.enabled(), false );\n out _= _xml_meta_attrs(widget);\n out _= _xml_style_attrs(widget);\n return out;\n}\n\nfunction _xml_widget_attrs ( Widget widget ) {\n let out := _xml_common_attrs(widget);\n if ( widget instanceof _WindowClass ) {\n out _= _xml_attr_if( "title", widget.title(), "" );\n }\n else if ( widget instanceof _VBoxClass or widget instanceof _HBoxClass ) {\n let default_align := widget instanceof _VBoxClass ? "top" : "left";\n out _= _xml_attr_if( "align", widget.align(), default_align );\n out _= _xml_attr_if( "gap", widget.gap(), 0 );\n out _= _xml_attr_if( "padding", widget.padding(), 0 )\n unless widget.padding() instanceof Array;\n }\n else if ( widget instanceof _FrameClass ) {\n out _= _xml_attr_if( "label", widget.label(), "" );\n out _= _xml_bool_attr_if( "collapsible", widget.collapsible(), false );\n out _= _xml_bool_attr_if( "collapsed", widget.collapsed(), false );\n }\n else if ( widget instanceof _LabelClass ) {\n out _= _xml_attr_if( "text", widget.text(), "" );\n out _= _xml_attr_nonempty( "for", widget.for_id() );\n }\n else if ( widget instanceof _TextClass ) {\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_bool_attr_if( "multiline", widget.multiline(), false );\n out _= _xml_bool_attr_if( "readonly", widget.readonly(), false );\n out _= _xml_bool_attr_if( "wrap", widget.wrap(), true );\n }\n else if ( widget instanceof _RichTextClass ) {\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_bool_attr_if( "multiline", widget.multiline(), true );\n out _= _xml_bool_attr_if( "readonly", widget.readonly(), true );\n }\n else if ( widget instanceof _ImageClass ) {\n out _= _xml_attr_if( "src", widget.src(), "" );\n out _= _xml_attr_if( "alt", widget.alt(), "" );\n out _= _xml_attr_if( "fit", widget.fit(), "none" );\n }\n else if ( widget instanceof _InputClass ) {\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_attr_if( "placeholder", widget.placeholder(), "" );\n out _= _xml_bool_attr_if( "multiline", widget.multiline(), false );\n out _= _xml_bool_attr_if( "readonly", widget.readonly(), false );\n out _= _xml_bool_attr_if( "password", widget.password(), false );\n out _= _xml_bool_attr_if( "required", widget.required(), false );\n }\n else if ( widget instanceof _DatePickerClass ) {\n out _= _xml_attr( "value", widget.value() );\n out _= _xml_attr( "min", widget.min() ) if widget.min() \u2262 null;\n out _= _xml_attr( "max", widget.max() ) if widget.max() \u2262 null;\n out _= _xml_attr_if(\n "first_day_of_week",\n widget.first_day_of_week(),\n 0,\n );\n }\n else if ( widget instanceof _CheckboxClass ) {\n out _= _xml_attr_if( "label", widget.label(), "" );\n out _= _xml_bool_attr_if( "checked", widget.checked(), false );\n out _= _xml_bool_attr_if( "indeterminate", widget.indeterminate(), false );\n }\n else if ( widget instanceof _RadioClass ) {\n out _= _xml_attr_if( "label", widget.label(), "" );\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_attr_nonempty( "group", widget.group() );\n out _= _xml_bool_attr_if( "checked", widget.checked(), false );\n }\n else if ( widget instanceof _RadioGroupClass ) {\n out _= _xml_attr_if( "name", widget.name(), "" );\n out _= _xml_attr( "value", widget.value() ) if widget.value() \u2262 null;\n }\n else if ( widget instanceof _SelectClass ) {\n out _= _xml_attr( "value", widget.value() ) if widget.value() \u2262 null;\n out _= _xml_bool_attr_if( "multiple", widget.multiple(), false );\n }\n else if ( widget instanceof _MenuClass ) {\n out _= _xml_attr_if( "text", widget.text(), "" );\n }\n else if ( widget instanceof _MenuItemClass ) {\n out _= _xml_attr_if( "text", widget.text(), "" );\n }\n else if ( widget instanceof _ButtonClass ) {\n out _= _xml_attr_if( "text", widget.text(), "" );\n out _= _xml_attr_if( "variant", widget.variant(), "default" );\n }\n else if ( widget instanceof _SeparatorClass ) {\n out _= _xml_attr_if( "orientation", widget.orientation(), "horizontal" );\n }\n else if ( widget instanceof _SliderClass ) {\n out _= _xml_attr_if( "value", widget.value(), 0 );\n out _= _xml_attr_if( "min", widget.min(), 0 );\n out _= _xml_attr_if( "max", widget.max(), 100 );\n out _= _xml_attr_if( "step", widget.step(), 1 );\n out _= _xml_attr_if( "orientation", widget.orientation(), "horizontal" );\n out _= _xml_bool_attr_if( "readonly", widget.readonly(), false );\n }\n else if ( widget instanceof _ProgressClass ) {\n out _= _xml_attr_if( "value", widget.value(), 0 );\n out _= _xml_attr_if( "min", widget.min(), 0 );\n out _= _xml_attr_if( "max", widget.max(), 100 );\n out _= _xml_bool_attr_if( "indeterminate", widget.indeterminate(), false );\n out _= _xml_bool_attr_if( "show_text", widget.show_text(), false );\n }\n else if ( widget instanceof _TabsClass ) {\n out _= _xml_attr( "selected", widget.selected() )\n if widget.selected() \u2262 null;\n out _= _xml_attr_if( "placement", widget.placement(), "top" );\n }\n else if ( widget instanceof _TabClass ) {\n out _= _xml_attr_if( "title", widget.title(), "" );\n out _= _xml_attr_if( "value", widget.value(), "" );\n out _= _xml_attr( "icon", widget.icon() ) if widget.icon() \u2262 null;\n out _= _xml_bool_attr_if( "selected", widget.selected(), false );\n out _= _xml_bool_attr_if( "closable", widget.closable(), false );\n }\n else if ( widget instanceof _ListViewClass ) {\n out _= _xml_attr( "selected_index", widget.selected_index() )\n if widget.selected_index() \u2262 null;\n out _= _xml_bool_attr_if( "multiple", widget.multiple(), false );\n }\n else if ( widget instanceof _TreeViewClass ) {\n let selected_path := widget.selected_path();\n out _= _xml_attr( "selected_path", _xml_join_ints(selected_path) )\n if selected_path.length() > 0;\n out _= _xml_bool_attr_if( "multiple", widget.multiple(), false );\n }\n return out;\n}\n\nfunction _xml_serialize_widget ( Widget widget, Number depth ) {\n let indent := "";\n let i := 0;\n while ( i < depth ) {\n indent _= "\\t";\n i++;\n }\n\n let tag := _xml_tag_for_widget(widget);\n let attrs := _xml_widget_attrs(widget);\n attrs _= _xml_attr( "xmlns", GUI_XML_NS ) if depth = 0;\n let children := widget.children();\n if ( children.length() = 0 ) {\n return indent _ "<" _ tag _ attrs _ " />";\n }\n\n let parts := [];\n for ( let child in children ) {\n parts.push( _xml_serialize_widget( child, depth + 1 ) );\n }\n\n return indent _ "<" _ tag _ attrs _ ">\\n"\n _ join( "\\n", parts )\n _ "\\n" _ indent _ "</" _ tag _ ">";\n}\n\nfunction gui_to_xml ( Widget root ) {\n return _xml_serialize_widget( root, 0 ) _ "\\n";\n}\n';
41650
+ virtualFiles["/modules/std/gui/dialogue.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/gui/dialogue - Dialogue helpers.\n\n=head1 SYNOPSIS\n\n from std/gui/dialogue import *;\n\n alert("Saved");\n let ok := confirm("Continue?");\n let name := prompt("Name:", value: "Ada");\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by zuzu.pl, zuzu-rust, and zuzu-js on Electron\nand Browser. It is not supported by zuzu-js on Node.\n\n=head1 DESCRIPTION\n\nThis module provides convenience dialogue helpers layered on top of the\nregular C<std/gui> widgets.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item C<< alert(String message, ... PairList p) >>, C<< alert_window(String message, ... PairList p) >>\n\nParameters: C<message> is display text and C<p> contains options.\nReturns: C<null> for C<alert> or C<Window> for C<alert_window>. Shows or\nbuilds an alert dialogue.\n\n=item C<< confirm(String message, ... PairList p) >>, C<< confirm_window(String message, ... PairList p) >>\n\nParameters: C<message> is display text and C<p> contains options.\nReturns: C<Boolean> for C<confirm> or C<Window> for\nC<confirm_window>. Shows or builds a confirmation dialogue.\n\n=item C<< prompt(String message, ... PairList p) >>, C<< prompt_window(String message, ... PairList p) >>\n\nParameters: C<message> is prompt text and C<p> contains options such as\nC<value>. Returns: C<String> or C<null> for C<prompt>, or C<Window> for\nC<prompt_window>. Shows or builds a text prompt.\n\n=item C<< file_open(... PairList p) >>, C<< file_save(... PairList p) >>\n\nParameters: C<p> contains dialogue options. Returns: path value or\nC<null>. Opens a file selection dialogue.\n\n=item C<< directory_open(... PairList p) >>, C<< directory_save(... PairList p) >>\n\nParameters: C<p> contains dialogue options. Returns: path value or\nC<null>. Opens a directory selection dialogue.\n\n=item C<< colour_picker(... PairList p) >>\n\nParameters: C<p> contains dialogue options. Returns: C<String> or\nC<null>. Opens a colour picker and returns a normalized colour string.\n\n=item C<< file_open_window(... PairList p) >>, C<< file_save_window(... PairList p) >>, C<< directory_open_window(... PairList p) >>, C<< directory_save_window(... PairList p) >>, C<< colour_picker_window(... PairList p) >>\n\nParameters: C<p> contains dialogue options. Returns: C<Window>. Builds\nthe corresponding GUI dialogue window.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/gui/dialogue >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/gui try import\n EM,\n Window,\n VBox,\n HBox,\n Label,\n Text,\n Input,\n Button,\n Widget,\n native_file_open,\n native_file_save,\n native_directory_open,\n native_directory_save,\n native_colour_picker;\nfrom std/gui/objects try import\n meta as _objects_meta,\n native_alert,\n native_confirm,\n native_prompt;\nfrom std/colour import parse_colour;\nfrom std/string import split;\nfrom std/tui import\n colour_text,\n directory_completions,\n filename_completions,\n readline;\n\nfunction _tui_string ( value ) {\n return value \u2261 null ? "" : "" _ value;\n}\n\nfunction _gui_backend () {\n if ( _objects_meta \u2262 null ) {\n return _objects_meta{backend};\n }\n return "";\n}\n\nfunction _path_dialogue_unsupported () {\n if ( __system__{deny_fs} ) {\n die "GUI_DIALOGUE_FS_DENIED: file and directory dialogues require filesystem capability";\n }\n if ( _gui_backend() eq "browser-dom" ) {\n die "GUI_DIALOGUE_UNSUPPORTED: file and directory dialogues are unsupported in JS/Browser";\n }\n}\n\nfunction _tui_prompt ( String message, PairList p, String default_value ) {\n return readline(\n colour_text( message _ " ", "cyan" ),\n default_value,\n null,\n );\n}\n\nfunction _tui_path_dialog (\n String label,\n String default_value,\n completion,\n PairList p\n) {\n let answer := readline(\n colour_text( p.get( "label", label ) _ " ", "cyan" ),\n _tui_string( p.get( "value", default_value ) ),\n completion,\n );\n if ( p.get( "multiple", false ) ) {\n return split( answer, p.get( "separator", "\\n" ) );\n }\n return answer;\n}\n\nfunction _parse_colour_or_null ( value ) {\n return try {\n parse_colour( _tui_string(value) );\n }\n catch {\n null;\n };\n}\n\nfunction _colour_initial_value ( PairList p ) {\n let raw := _tui_string( p.get( "value", "#000000" ) );\n let parsed := _parse_colour_or_null(raw);\n return parsed \u2262 null ? parsed : raw;\n}\n\nfunction _colour_default_value ( PairList p ) {\n // The parser returns null for invalid input; valid colours are values.\n return _parse_colour_or_null( p.get( "value", "#000000" ) ) ?: "#000000";\n}\n\nfunction _tui_colour_dialog ( PairList p ) {\n let default_value := _colour_default_value(p);\n while ( true ) {\n let answer := readline(\n colour_text( p.get( "label", "Colour:" ) _ " ", "cyan" ),\n default_value,\n null,\n );\n let parsed := _parse_colour_or_null(answer);\n if ( parsed \u2262 null ) {\n return parsed;\n }\n say( colour_text( "Invalid colour; try #336699 or red.", "yellow" ) );\n }\n}\n\nfunction _dialogue_window (\n String kind,\n String title,\n Widget body,\n Array buttons,\n PairList p\n) {\n let button_row := HBox(\n id: "buttons",\n align: "right",\n gap: 6,\n height: p.get( "button_row_height", 2.125 \xD7 EM ),\n maxheight: p.get( "button_row_height", 2.125 \xD7 EM ),\n );\n for ( let button in buttons ) {\n button_row.add_child(button);\n }\n let w := Window(\n title: title,\n width: p.get( "width", 360 ),\n height: p.get( "height", 180 ),\n resizable: p.get( "resizable", false ),\n modal: true,\n VBox(\n id: p.get( "id", null ),\n gap: p.get( "gap", 8 ),\n padding: p.get( "padding", 12 ),\n body,\n button_row,\n ),\n );\n w.meta( "dialogue.kind", kind );\n return w;\n}\n\nfunction _message_body ( String message, PairList p ) {\n return Text(\n id: p.get( "message_id", "message" ),\n value: message,\n readonly: true,\n wrap: true,\n );\n}\n\nfunction _primary_button ( PairList p, String fallback ) {\n return Button(\n id: p.get( "ok_id", "ok" ),\n text: p.get( "ok_text", fallback ),\n variant: "primary",\n width: p.get( "button_width", 5.5 \xD7 EM ),\n height: p.get( "button_height", 1.75 \xD7 EM ),\n maxheight: p.get( "button_height", 1.75 \xD7 EM ),\n );\n}\n\nfunction _cancel_button ( PairList p ) {\n return Button(\n id: p.get( "cancel_id", "cancel" ),\n text: p.get( "cancel_text", "Cancel" ),\n width: p.get( "button_width", 5.5 \xD7 EM ),\n height: p.get( "button_height", 1.75 \xD7 EM ),\n maxheight: p.get( "button_height", 1.75 \xD7 EM ),\n );\n}\n\nfunction _alert_window ( String message, PairList p ) {\n let ok := _primary_button( p, "OK" );\n let w := _dialogue_window(\n "alert",\n p.get( "title", "Alert" ),\n _message_body( message, p ),\n [ ok ],\n p,\n );\n ok.click( function () {\n w.close(null);\n } );\n return w;\n}\n\nfunction alert_window ( String message, ... PairList p ) {\n return _alert_window( message, p );\n}\n\nfunction alert ( String message, ... PairList p ) {\n if ( p.has("auto_result") ) {\n return null;\n }\n if ( __system__{deny_gui} ) {\n say( colour_text( message, "yellow" ) );\n return null;\n }\n if ( native_alert \u2262 null ) {\n let native_result := native_alert( message, p );\n if ( native_result ) {\n return null;\n }\n }\n return _alert_window( message, p ).call();\n}\n\nfunction _confirm_window ( String message, PairList p ) {\n let ok := _primary_button( p, p.get( "ok_text", "OK" ) );\n let cancel := _cancel_button(p);\n let w := _dialogue_window(\n "confirm",\n p.get( "title", "Confirm" ),\n _message_body( message, p ),\n [ cancel, ok ],\n p,\n );\n ok.click( function () {\n w.close(true);\n } );\n cancel.click( function () {\n w.close(false);\n } );\n return w;\n}\n\nfunction confirm_window ( String message, ... PairList p ) {\n return _confirm_window( message, p );\n}\n\nfunction confirm ( String message, ... PairList p ) {\n if ( p.has("auto_result") ) {\n return p.get("auto_result") ? true : false;\n }\n if ( __system__{deny_gui} ) {\n let yes_default := p.get( "value", p.get( "default", false ) );\n let suffix := yes_default ? " [Y/n] " : " [y/N] ";\n let answer := readline(\n colour_text( message _ suffix, "cyan" ),\n yes_default ? "y" : "n",\n null,\n );\n return ( answer ~ /^(?:y|yes|1|true)$/i ) ? true : false;\n }\n if ( native_confirm \u2262 null ) {\n let native_result := native_confirm( message, p );\n if ( native_result \u2262 null ) {\n return native_result ? true : false;\n }\n }\n return _confirm_window( message, p ).call() ? true : false;\n}\n\nfunction _prompt_window ( String message, PairList p ) {\n let input := Input(\n id: p.get( "input_id", "value" ),\n value: p.get( "value", "" ),\n placeholder: p.get( "placeholder", "" ),\n );\n let ok := _primary_button( p, p.get( "ok_text", "OK" ) );\n let cancel := _cancel_button(p);\n let w := _dialogue_window(\n "prompt",\n p.get( "title", "Prompt" ),\n VBox(\n gap: 6,\n _message_body( message, p ),\n input,\n ),\n [ cancel, ok ],\n p,\n );\n ok.click( function () {\n w.close( input.value() );\n } );\n cancel.click( function () {\n w.close(null);\n } );\n return w;\n}\n\nfunction prompt_window ( String message, ... PairList p ) {\n return _prompt_window( message, p );\n}\n\nfunction prompt ( String message, ... PairList p ) {\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_prompt(\n message,\n p,\n _tui_string( p.get( "value", "" ) ),\n );\n }\n if ( native_prompt \u2262 null ) {\n let native_result := native_prompt( message, p );\n if ( _gui_backend() eq "browser-dom" ) {\n return native_result;\n }\n if ( native_result \u2262 null ) {\n return native_result;\n }\n }\n return _prompt_window( message, p ).call();\n}\n\nfunction _path_dialog_window (\n String kind,\n String title,\n String label,\n String default_value,\n PairList p\n) {\n let input := Input(\n id: p.get( "input_id", "path" ),\n value: p.get( "value", default_value ),\n placeholder: p.get( "placeholder", "" ),\n multiline: p.get( "multiple", false ),\n );\n let ok := _primary_button( p, p.get( "ok_text", "OK" ) );\n let cancel := _cancel_button(p);\n let w := _dialogue_window(\n kind,\n p.get( "title", title ),\n VBox(\n gap: 6,\n Label( text: p.get( "label", label ), ("for"): input.id() ),\n input,\n ),\n [ cancel, ok ],\n p,\n );\n ok.click( function () {\n if ( p.get( "multiple", false ) ) {\n w.close( split( input.value(), p.get( "separator", "\\n" ) ) );\n }\n else {\n w.close( input.value() );\n }\n } );\n cancel.click( function () {\n w.close(null);\n } );\n return w;\n}\n\nfunction file_open_window ( ... PairList p ) {\n return _path_dialog_window( "file_open", "Open File", "File:", "", p );\n}\n\nfunction file_save_window ( ... PairList p ) {\n return _path_dialog_window( "file_save", "Save File", "File:", "", p );\n}\n\nfunction directory_open_window ( ... PairList p ) {\n return _path_dialog_window(\n "directory_open",\n "Open Directory",\n "Directory:",\n "",\n p,\n );\n}\n\nfunction directory_save_window ( ... PairList p ) {\n return _path_dialog_window(\n "directory_save",\n "Save Directory",\n "Directory:",\n "",\n p,\n );\n}\n\nfunction _colour_picker_window ( PairList p ) {\n let input := Input(\n id: p.get( "input_id", "path" ),\n value: _colour_initial_value(p),\n placeholder: p.get( "placeholder", "#336699" ),\n );\n let ok := _primary_button( p, p.get( "ok_text", "OK" ) );\n let cancel := _cancel_button(p);\n let default_value := _colour_default_value(p);\n let sync_ok := function () {\n ok.set_enabled( _parse_colour_or_null( input.value() ) \u2262 null );\n };\n let w := _dialogue_window(\n "colour_picker",\n p.get( "title", "Choose Colour" ),\n VBox(\n gap: 6,\n Label(\n text: p.get( "label", "Colour:" ),\n ("for"): input.id(),\n ),\n input,\n ),\n [ cancel, ok ],\n p,\n );\n sync_ok();\n input.on( "change", sync_ok );\n ok.click( function () {\n let parsed := _parse_colour_or_null( input.value() );\n if ( parsed \u2262 null ) {\n w.close(parsed);\n }\n } );\n cancel.click( function () {\n w.close(default_value);\n } );\n return w;\n}\n\nfunction colour_picker_window ( ... PairList p ) {\n return _colour_picker_window(p);\n}\n\nfunction file_open ( ... PairList p ) {\n _path_dialogue_unsupported();\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_path_dialog( "File:", "", filename_completions, p );\n }\n return native_file_open(p);\n}\n\nfunction file_save ( ... PairList p ) {\n _path_dialogue_unsupported();\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_path_dialog( "File:", "", filename_completions, p );\n }\n return native_file_save(p);\n}\n\nfunction directory_open ( ... PairList p ) {\n _path_dialogue_unsupported();\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_path_dialog(\n "Directory:",\n "",\n directory_completions,\n p,\n );\n }\n return native_directory_open(p);\n}\n\nfunction directory_save ( ... PairList p ) {\n _path_dialogue_unsupported();\n if ( p.has("auto_result") ) {\n return p.get("auto_result");\n }\n if ( __system__{deny_gui} ) {\n return _tui_path_dialog(\n "Directory:",\n "",\n directory_completions,\n p,\n );\n }\n if ( native_directory_save \u2262 null ) {\n let native_result := native_directory_save(p);\n if ( native_result \u2262 null ) {\n return native_result;\n }\n if ( _gui_backend() eq "electron-dom" ) {\n return null;\n }\n }\n return _path_dialog_window(\n "directory_save",\n "Save Directory",\n "Directory:",\n "",\n p,\n ).call();\n}\n\nfunction colour_picker ( ... PairList p ) {\n if ( p.has("auto_result") ) {\n return parse_colour( _tui_string( p.get("auto_result") ) );\n }\n if ( __system__{deny_gui} ) {\n return _tui_colour_dialog(p);\n }\n if ( native_colour_picker \u2262 null ) {\n let native_result := native_colour_picker(p);\n if ( native_result \u2262 null ) {\n return parse_colour(native_result);\n }\n if ( _gui_backend() eq "browser-dom" ) {\n return null;\n }\n }\n if ( _gui_backend() eq "electron-dom" ) {\n return _colour_picker_window(p).call();\n }\n return parse_colour( _colour_picker_window(p).call() );\n}\n';
41651
+ virtualFiles["/modules/std/uuid.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/uuid - Pure ZuzuScript UUID v1 generator.\n\n=head1 SYNOPSIS\n\n from std/uuid import create_uuid, create_uuid_binary;\n\n let text := create_uuid();\n let raw := create_uuid_binary();\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module implements UUID version 1 generation using only\nZuzuScript code.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item * C<create_uuid_binary()>\n\nParameters: none. Returns: C<BinaryString>. Returns a single UUID as 16\nraw bytes.\n\n=item * C<create_uuid()>\n\nParameters: none. Returns: C<String>. Returns a single UUID as\nlowercase hexadecimal text with hyphens in the usual C<8-4-4-4-12>\nlayout.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/uuid >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/math import Math;\nfrom std/string import substr;\nfrom std/string/base64 import decode;\nfrom std/time import Time;\n\nlet _B64_ALPHABET := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";\nlet _HEX_ALPHABET := "0123456789abcdef";\n\nfunction _div_floor ( Number n, Number d ) {\n return floor( n / d );\n}\n\nfunction _mod ( Number n, Number d ) {\n return n - _div_floor( n, d ) * d;\n}\n\nfunction _rand_int ( Number max ) {\n return floor( Math.rand(max) );\n}\n\nfunction _bytes_to_binary ( Array bytes ) {\n let out := "";\n let i := 0;\n let n := bytes.length();\n\n while ( i < n ) {\n let b0 := bytes[i];\n let b1 := null;\n let b2 := null;\n if ( i + 1 < n ) {\n b1 := bytes[i + 1];\n }\n if ( i + 2 < n ) {\n b2 := bytes[i + 2];\n }\n\n let c0 := _div_floor( b0, 4 );\n let c1 := _mod( b0, 4 ) * 16;\n let c2 := 64;\n let c3 := 64;\n\n if ( b1 \u2262 null ) {\n c1 += _div_floor( b1, 16 );\n c2 := _mod( b1, 16 ) * 4;\n if ( b2 \u2262 null ) {\n c2 += _div_floor( b2, 64 );\n c3 := _mod( b2, 64 );\n }\n }\n\n out _= substr( _B64_ALPHABET, c0, 1 );\n out _= substr( _B64_ALPHABET, c1, 1 );\n if ( c2 \u2261 64 ) {\n out _= "=";\n }\n else {\n out _= substr( _B64_ALPHABET, c2, 1 );\n }\n if ( c3 \u2261 64 ) {\n out _= "=";\n }\n else {\n out _= substr( _B64_ALPHABET, c3, 1 );\n }\n\n i += 3;\n }\n\n return decode(out);\n}\n\nfunction _timestamp_words () {\n // Seconds between 1582-10-15 and 1970-01-01.\n let epoch_offset := 12219292800;\n let seconds := floor( new Time().epoch() ) + epoch_offset;\n let ticks := _rand_int(10000000);\n\n let words := [\n _mod( seconds, 65536 ),\n _mod( _div_floor( seconds, 65536 ), 65536 ),\n _mod( _div_floor( seconds, 4294967296 ), 65536 ),\n 0,\n 0,\n ];\n\n let scale := 10000000;\n let i := 0;\n let carry := 0;\n while ( i < words.length() ) {\n let product := words[i] * scale + carry;\n words[i] := _mod( product, 65536 );\n carry := _div_floor( product, 65536 );\n i++;\n }\n\n let add_i := 0;\n let add_carry := ticks;\n while ( add_carry > 0 and add_i < words.length() ) {\n let sum := words[add_i] + _mod( add_carry, 65536 );\n words[add_i] := _mod( sum, 65536 );\n add_carry := _div_floor( add_carry, 65536 ) + _div_floor( sum, 65536 );\n add_i++;\n }\n\n return words;\n}\n\nfunction _create_uuid_bytes () {\n let words := _timestamp_words();\n let time_low := words[0] + words[1] * 65536;\n let time_mid := words[2];\n let time_hi_and_version := _mod( words[3], 4096 ) + 4096;\n\n let clock_seq := _rand_int(16384);\n let clock_seq_hi_and_reserved := _mod( _div_floor( clock_seq, 256 ), 64 ) + 128;\n let clock_seq_low := _mod( clock_seq, 256 );\n\n let node := [];\n let j := 0;\n while ( j < 6 ) {\n node.push( _rand_int(256) );\n j++;\n }\n // Multicast bit set means this is not an IEEE MAC address.\n node[0] := node[0] | 1;\n\n return [\n _mod( _div_floor( time_low, 16777216 ), 256 ),\n _mod( _div_floor( time_low, 65536 ), 256 ),\n _mod( _div_floor( time_low, 256 ), 256 ),\n _mod( time_low, 256 ),\n _mod( _div_floor( time_mid, 256 ), 256 ),\n _mod( time_mid, 256 ),\n _mod( _div_floor( time_hi_and_version, 256 ), 256 ),\n _mod( time_hi_and_version, 256 ),\n clock_seq_hi_and_reserved,\n clock_seq_low,\n node[0],\n node[1],\n node[2],\n node[3],\n node[4],\n node[5],\n ];\n}\n\nfunction _byte_to_hex ( Number b ) {\n let hi := _div_floor( b, 16 );\n let lo := _mod( b, 16 );\n return substr( _HEX_ALPHABET, hi, 1 ) _ substr( _HEX_ALPHABET, lo, 1 );\n}\n\nfunction _bytes_to_uuid_text ( Array bytes ) {\n let out := "";\n let i := 0;\n while ( i < 16 ) {\n out _= _byte_to_hex( bytes[i] );\n if ( i \u2261 3 or i \u2261 5 or i \u2261 7 or i \u2261 9 ) {\n out _= "-";\n }\n i++;\n }\n return out;\n}\n\nfunction create_uuid_binary () {\n return _bytes_to_binary( _create_uuid_bytes() );\n}\n\nfunction create_uuid () {\n return _bytes_to_uuid_text( _create_uuid_bytes() );\n}\n';
40641
41652
  virtualFiles["/modules/std/dump.zzm"] = `=encoding utf8
40642
41653
 
40643
41654
  =head1 NAME
@@ -40959,7 +41970,7 @@ class Dumper {
40959
41970
  virtualFiles["/modules/std/data/cbor.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/data/cbor - CBOR encoding and decoding for ZuzuScript.\n\n=head1 SYNOPSIS\n\n from std/data/cbor import CBOR, TaggedValue;\n\n let codec := new CBOR();\n let bytes := codec.encode({ answer: 42 });\n let value := codec.decode(bytes);\n\n // Decode CBOR maps to Zuzu PairLists instead of Dicts.\n // This preserves key order and allows duplicate keys.\n codec := new CBOR( pairlists: true );\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by zuzu.pl, zuzu-rust, and zuzu-js on Node and\nElectron. It is partially supported by zuzu-js in the browser: in-memory\nCBOR encode/decode coverage passes, but file-backed load/dump coverage is\nunsupported because browser filesystem capability is unavailable.\n\n=head1 DESCRIPTION\n\nPure-Zuzu implementation of CBOR (RFC 8949).\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<< TaggedValue({ tag: Number, value: value }) >>\n\nConstructs a tagged CBOR value. Returns: C<TaggedValue>. Stores the CBOR\ntag number and associated value.\n\n=item C<< CBOR({ pairlists?: Boolean }) >>\n\nConstructs a CBOR codec. Returns: C<CBOR>. The C<pairlists> option makes\ndecoded maps return as C<PairList> values instead of C<Dict> values.\n\n=over\n\n=item C<< codec.encode(value) >>\n\nParameters: C<value> is any CBOR-encodable ZuzuScript value. Returns:\nC<BinaryString>. Encodes C<value> as CBOR bytes.\n\n=item C<< codec.encode_binarystring(value) >>\n\nAlias for C<codec.encode(value)>.\n\n=item C<< codec.decode(BinaryString raw) >>\n\nParameters: C<raw> is CBOR data. Returns: value. Decodes CBOR bytes into\nthe equivalent ZuzuScript value.\n\n=item C<< codec.decode_binarystring(BinaryString raw) >>\n\nAlias for C<codec.decode(raw)>.\n\n=item C<< codec.load(Path path) >>\n\nParameters: C<path> is a C<std/io> C<Path>. Returns: value. Reads CBOR\nbytes from C<path> and decodes them.\n\n=item C<< codec.dump(Path path, value) >>\n\nParameters: C<path> is a C<std/io> C<Path> and C<value> is any\nCBOR-encodable value. Returns: C<null>. Encodes C<value> and writes CBOR\nbytes to C<path>.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/data/cbor >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/string import substr, index;\nfrom std/string/base64 import encode, decode;\nfrom std/time import Time;\n\n\nlet _B64_ALPHABET := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";\n\n// Set to "PairList" to return CBOR objects as Zuzu PairLists.\nlet _DECODE_MAP_AS := "Dict";\n\nclass TaggedValue {\n let Number tag;\n let value;\n}\n\nfunction _div_floor ( Number n, Number d ) {\n return floor( n / d ); // fix syntax highlighting\n}\n\nfunction _mod ( Number n, Number d ) {\n return n - _div_floor( n, d ) * d;\n}\n\nfunction _bytes_to_binary ( Array bytes ) {\n let out := "";\n let i := 0;\n let n := bytes.length();\n\n while ( i < n ) {\n let b0 := bytes[i];\n let b1 := null;\n let b2 := null;\n if ( i + 1 < n ) {\n b1 := bytes[i + 1];\n }\n if ( i + 2 < n ) {\n b2 := bytes[i + 2];\n }\n\n let c0 := _div_floor( b0, 4 );\n let c1 := _mod( b0, 4 ) * 16;\n let c2 := 64;\n let c3 := 64;\n\n if ( b1 \u2262 null ) {\n c1 += _div_floor( b1, 16 );\n c2 := _mod( b1, 16 ) * 4;\n if ( b2 \u2262 null ) {\n c2 += _div_floor( b2, 64 );\n c3 := _mod( b2, 64 );\n }\n }\n\n out _= substr( _B64_ALPHABET, c0, 1 );\n out _= substr( _B64_ALPHABET, c1, 1 );\n if ( c2 \u2261 64 ) {\n out _= "=";\n }\n else {\n out _= substr( _B64_ALPHABET, c2, 1 );\n }\n if ( c3 \u2261 64 ) {\n out _= "=";\n }\n else {\n out _= substr( _B64_ALPHABET, c3, 1 );\n }\n i += 3;\n }\n\n return decode(out);\n}\n\nfunction _binary_to_bytes ( BinaryString raw ) {\n let b64 := encode(raw);\n let out := [];\n let i := 0;\n let n := length b64;\n\n while ( i < n ) {\n let c0 := index( _B64_ALPHABET, substr( b64, i, 1 ) );\n let c1 := index( _B64_ALPHABET, substr( b64, i + 1, 1 ) );\n let ch2 := substr( b64, i + 2, 1 );\n let ch3 := substr( b64, i + 3, 1 );\n let c2 := -1;\n let c3 := -1;\n if ( ch2 \u2262 "=" ) {\n c2 := index( _B64_ALPHABET, ch2 );\n }\n if ( ch3 \u2262 "=" ) {\n c3 := index( _B64_ALPHABET, ch3 );\n }\n\n out.push( c0 * 4 + _div_floor( c1, 16 ) );\n\n if ( c2 >= 0 ) {\n out.push( _mod( c1, 16 ) * 16 + _div_floor( c2, 4 ) );\n }\n if ( c3 >= 0 ) {\n out.push( _mod( c2, 4 ) * 64 + c3 );\n }\n\n i += 4;\n }\n\n return out;\n}\n\nfunction _is_int ( value ) {\n if ( not( value instanceof Number ) ) {\n return false;\n }\n\n return _mod( value, 1 ) \u2261 0;\n}\n\nfunction _emit_uint ( Array out, Number major, Number n ) {\n if ( n < 24 ) {\n out.push( major * 32 + n );\n return;\n }\n if ( n < 256 ) {\n out.push( major * 32 + 24 );\n out.push(n);\n return;\n }\n if ( n < 65536 ) {\n out.push( major * 32 + 25 );\n out.push( _mod( _div_floor( n, 256 ), 256 ) );\n out.push( _mod( n, 256 ) );\n return;\n }\n if ( n < 4294967296 ) {\n out.push( major * 32 + 26 );\n out.push( _mod( _div_floor( n, 16777216 ), 256 ) );\n out.push( _mod( _div_floor( n, 65536 ), 256 ) );\n out.push( _mod( _div_floor( n, 256 ), 256 ) );\n out.push( _mod( n, 256 ) );\n return;\n }\n\n let hi := _div_floor( n, 4294967296 );\n let lo := _mod( n, 4294967296 );\n out.push( major * 32 + 27 );\n out.push( _mod( _div_floor( hi, 16777216 ), 256 ) );\n out.push( _mod( _div_floor( hi, 65536 ), 256 ) );\n out.push( _mod( _div_floor( hi, 256 ), 256 ) );\n out.push( _mod( hi, 256 ) );\n out.push( _mod( _div_floor( lo, 16777216 ), 256 ) );\n out.push( _mod( _div_floor( lo, 65536 ), 256 ) );\n out.push( _mod( _div_floor( lo, 256 ), 256 ) );\n out.push( _mod( lo, 256 ) );\n}\n\nfunction _encode_value ( Array out, value ) {\n if ( value \u2261 null ) {\n out.push(246);\n return;\n }\n if ( value \u2261 true ) {\n out.push(245);\n return;\n }\n if ( value \u2261 false ) {\n out.push(244);\n return;\n }\n\n if ( value instanceof Number ) {\n die "CBOR.encode currently supports only integer Number values" if not _is_int(value);\n if ( value >= 0 ) {\n _emit_uint( out, 0, value );\n }\n else {\n _emit_uint( out, 1, -1 - value );\n }\n return;\n }\n\n if ( value instanceof BinaryString ) {\n let b := _binary_to_bytes(value);\n _emit_uint( out, 2, b.length() );\n let i := 0;\n while ( i < b.length() ) {\n out.push( b[i] );\n i++;\n }\n return;\n }\n\n if ( value instanceof String ) {\n let b := _binary_to_bytes( to_binary(value) );\n _emit_uint( out, 3, b.length() );\n let i := 0;\n while ( i < b.length() ) {\n out.push( b[i] );\n i++;\n }\n return;\n }\n\n if ( value instanceof Array ) {\n _emit_uint( out, 4, value.length() );\n let i := 0;\n while ( i < value.length() ) {\n _encode_value( out, value[i] );\n i++;\n }\n return;\n }\n\n if ( value instanceof Set ) {\n _emit_uint( out, 6, 258 );\n _encode_value( out, value.sortstr() );\n return;\n }\n\n if ( value instanceof Bag ) {\n _encode_value( out, value.sortstr() );\n return;\n }\n\n if ( value instanceof PairList ) {\n let pairs := value.to_Array();\n _emit_uint( out, 5, pairs.length() );\n let i := 0;\n while ( i < pairs.length() ) {\n let pair := pairs[i]{pair};\n _encode_value( out, pair[0] );\n _encode_value( out, pair[1] );\n i++;\n }\n return;\n }\n\n if ( value instanceof Dict ) {\n let keys := value.sorted_keys();\n _emit_uint( out, 5, keys.length() );\n let i := 0;\n while ( i < keys.length() ) {\n let k := keys[i];\n _encode_value( out, k );\n _encode_value( out, value.get(k) );\n i++;\n }\n return;\n }\n\n if ( value instanceof TaggedValue ) {\n _emit_uint( out, 6, value { tag } );\n _encode_value( out, value { value } );\n return;\n }\n if ( value instanceof Time ) {\n _emit_uint( out, 6, 0 );\n _encode_value( out, value.epoch() );\n return;\n }\n\n die `CBOR cannot encode value of type ${typeof value}`;\n}\n\nfunction _read_uint ( Array bytes, Number ai, Number pos ) {\n if ( ai < 24 ) {\n return [ ai, pos ];\n }\n if ( ai \u2261 24 ) {\n return [ bytes[pos], pos + 1 ];\n }\n if ( ai \u2261 25 ) {\n return [ bytes[pos] * 256 + bytes[pos + 1], pos + 2 ];\n }\n if ( ai \u2261 26 ) {\n return [ bytes[pos] * 16777216 + bytes[pos + 1] * 65536 + bytes[pos + 2] * 256 + bytes[pos + 3], pos + 4 ];\n }\n if ( ai \u2261 27 ) {\n let hi := bytes[pos] * 16777216 + bytes[pos + 1] * 65536 + bytes[pos + 2] * 256 + bytes[pos + 3];\n let lo := bytes[pos + 4] * 16777216 + bytes[pos + 5] * 65536 + bytes[pos + 6] * 256 + bytes[pos + 7];\n return [ hi * 4294967296 + lo, pos + 8 ];\n }\n\n die `Unsupported CBOR additional info: ${ai}`;\n}\n\nfunction _decode_at ( Array bytes, Number pos ) {\n let head := bytes[pos];\n let major := _div_floor( head, 32 );\n let ai := _mod( head, 32 );\n let parsed := _read_uint( bytes, ai, pos + 1 );\n let arg := parsed[0];\n let p := parsed[1];\n\n if ( major \u2261 0 ) {\n return [ arg, p ];\n }\n if ( major \u2261 1 ) {\n return [ -1 - arg, p ];\n }\n if ( major \u2261 2 ) {\n let chunk := [];\n let i := 0;\n while ( i < arg ) {\n chunk.push( bytes[p + i] );\n i++;\n }\n return [ _bytes_to_binary(chunk), p + arg ];\n }\n if ( major \u2261 3 ) {\n let chunk := [];\n let i := 0;\n while ( i < arg ) {\n chunk.push( bytes[p + i] );\n i++;\n }\n return [ to_string( _bytes_to_binary(chunk) ), p + arg ];\n }\n if ( major \u2261 4 ) {\n let arr := [];\n let i := 0;\n let q := p;\n while ( i < arg ) {\n let item := _decode_at( bytes, q );\n arr.push( item[0] );\n q := item[1];\n i++;\n }\n return [ arr, q ];\n }\n if ( major \u2261 5 ) {\n let d := _DECODE_MAP_AS eq "PairList" ? new PairList() : new Dict();\n let i := 0;\n let q := p;\n while ( i < arg ) {\n let k := _decode_at( bytes, q );\n let v := _decode_at( bytes, k[1] );\n d.add( k[0], v[0] ) unless _DECODE_MAP_AS eq "Dict" and d.exists( k[0] );\n q := v[1];\n i++;\n }\n return [ d, q ];\n }\n if ( major \u2261 6 ) {\n let tagged := _decode_at( bytes, p );\n if ( arg \u2261 0 and tagged[0] instanceof Number ) {\n return [ new Time( tagged[0] ), tagged[1] ];\n }\n if ( arg \u2261 258 and tagged[0] instanceof Array ) {\n return [ tagged[0].to_Set(), tagged[1] ];\n }\n return [ new TaggedValue( tag: arg, value: tagged[0] ), tagged[1] ];\n }\n if ( major \u2261 7 ) {\n if ( ai \u2261 20 ) {\n return [ false, pos + 1 ];\n }\n if ( ai \u2261 21 ) {\n return [ true, pos + 1 ];\n }\n if ( ai \u2261 22 ) {\n return [ null, pos + 1 ];\n }\n die `Unsupported CBOR simple/float value (ai=${ai})`;\n }\n\n die `Unsupported CBOR major type: ${major}`;\n}\n\nclass CBOR {\n let pairlists := false;\n\n method encode ( value ) {\n let out := [];\n _encode_value( out, value );\n return _bytes_to_binary(out);\n }\n\n method encode_binarystring ( value ) {\n return self.encode(value);\n }\n\n method decode ( BinaryString raw ) {\n _DECODE_MAP_AS := pairlists ? "PairList" : "Dict";\n let bytes := _binary_to_bytes(raw);\n let parsed := _decode_at( bytes, 0 );\n die "Trailing bytes after CBOR value" if parsed[1] < bytes.length();\n return parsed[0];\n }\n\n method decode_binarystring ( BinaryString raw ) {\n return self.decode(raw);\n }\n\n method load ( path ) {\n from std/io import Path;\n die "CBOR.load is denied by runtime policy" if __system__{deny_fs};\n die "CBOR.load expects a std/io Path object" if not( path instanceof Path );\n return self.decode( path.slurp() );\n }\n\n method dump ( path, value ) {\n from std/io import Path;\n die "CBOR.dump is denied by runtime policy" if __system__{deny_fs};\n die "CBOR.dump expects a std/io Path object" if not( path instanceof Path );\n path.spew( self.encode(value) );\n return path;\n }\n}\n';
40960
41971
  virtualFiles["/modules/std/data/kdl.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/data/kdl - KDL parsing and serialization for ZuzuScript.\n\n=head1 SYNOPSIS\n\n from std/data/kdl import KDL;\n\n let kdl := new KDL();\n let doc := kdl.decode("""\n package name="zuzu" { version "0.1.0" }\n """);\n let text := kdl.encode(doc);\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by zuzu.pl, zuzu-rust, and zuzu-js on Node and\nElectron. It is partially supported by zuzu-js in the browser: in-memory\nKDL parsing and serialization coverage passes, but fixture and load/dump\ncoverage is unsupported because browser filesystem capability is\nunavailable.\n\n=head1 DESCRIPTION\n\nThis module provides a pure-Zuzu implementation of the KDL document\nmodel and parser, with a user-facing API modelled on C<std/data/json>.\n\n=head1 EXPORTS\n\nThe parser returns explicit KDL model objects rather than generic Dict\nand Array structures.\n\n=head2 C<KDLDocument>\n\nC<KDLDocument> represents a complete parsed KDL document.\n\nConstructor fields:\n\n=over\n\n=item * C<nodes>\n\nAn Array of top-level C<KDLNode> objects. Defaults to an empty Array.\n\n=back\n\nMethods:\n\n=over\n\n=item C<nodes()>\n\nParameters: none. Returns: C<Array>. Returns the Array of top-level\nC<KDLNode> objects.\n\n=item C<to_Iterator()>\n\nParameters: none. Returns: C<Function>. Returns an iterator over the\ndocument\'s top-level nodes.\n\n=back\n\n=head2 C<KDLNode>\n\nC<KDLNode> represents one KDL node.\n\nConstructor fields:\n\n=over\n\n=item * C<name>\n\nThe node name as a String.\n\n=item * C<type_annotation>\n\nThe node type annotation as a String, or C<null> when unannotated.\n\n=item * C<args>\n\nAn Array of unnamed argument C<KDLValue> objects. Defaults to an empty\nArray.\n\n=item * C<props>\n\nA C<PairList> mapping property names to C<KDLValue> objects. Defaults\nto an empty C<PairList>, and preserves duplicate property names.\n\n=item * C<children>\n\nAn Array of child C<KDLNode> objects. Defaults to an empty Array.\n\n=back\n\nMethods:\n\n=over\n\n=item C<name()>\n\nParameters: none. Returns: C<String>. Returns the node name.\n\n=item C<type_annotation()>\n\nParameters: none. Returns: C<String> or C<null>. Returns the node type\nannotation.\n\n=item C<args()>\n\nParameters: none. Returns: C<Array>. Returns the unnamed argument\nC<KDLValue> objects.\n\n=item C<props()>\n\nParameters: none. Returns: C<PairList>. Returns the named property\nC<KDLValue> objects.\n\n=item C<children()>\n\nParameters: none. Returns: C<Array>. Returns the child C<KDLNode>\nobjects.\n\n=item C<to_Iterator()>\n\nParameters: none. Returns: C<Function>. Returns an iterator over the\nnode\'s arguments followed by its child nodes.\n\n=back\n\n=head2 C<KDLValue>\n\nC<KDLValue> represents an unnamed argument or named property value.\n\nConstructor fields:\n\n=over\n\n=item * C<type>\n\nThe KDL value type: C<null>, C<boolean>, C<number>, or C<string>.\nSome conversion helpers may create opaque C<KDLValue> objects with\nother type names; those are not KDL primitives.\n\n=item * C<kind>\n\nFor numbers, one of C<integer>, C<float>, or C<string>. C<string> is\nused for KDL number keywords such as C<#inf> that do not have a native\nJSON-like numeric value.\n\n=item * C<value>\n\nThe raw native value.\n\n=item * C<type_annotation>\n\nThe value type annotation as a String, or C<null> when unannotated.\n\n=item * C<canonical_number>\n\nThe canonical numeric spelling used by canonical serialization, or\nC<null>.\n\n=back\n\nMethods:\n\n=over\n\n=item C<type()>\n\nParameters: none. Returns: C<String>. Returns the value type.\n\n=item C<kind()>\n\nParameters: none. Returns: C<String> or C<null>. Returns the numeric\nkind for number values.\n\n=item C<value()>\n\nParameters: none. Returns: value. Returns the raw native value.\n\n=item C<type_annotation()>\n\nParameters: none. Returns: C<String> or C<null>. Returns the value type\nannotation.\n\n=item C<canonical_number()>\n\nParameters: none. Returns: C<String> or C<null>. Returns the canonical\nnumeric spelling.\n\n=item C<is_null()>, C<is_boolean()>, C<is_number()>, C<is_string()>\n\nParameters: none. Returns: C<Boolean>. Predicate methods for the four\nKDL primitive value types.\n\n=item C<is_true()>, C<is_false()>\n\nParameters: none. Returns: C<Boolean>. Predicate methods for boolean\nC<#true> and C<#false> values.\n\n=item C<to_Number()>\n\nParameters: none. Returns: C<Number>. Returns the native number, or\nthrows when the value is not a finite native number.\n\n=item C<to_String()>\n\nParameters: none. Returns: C<String>. Coerces the value to a string.\n\n=item C<to_Boolean()>\n\nParameters: none. Returns: C<Boolean>. Coerces the value to a boolean.\n\n=item C<native_value()>\n\nParameters: none. Returns: value. Returns the raw native value, with KDL\nC<#null> represented as C<null>.\n\n=back\n\n=head2 C<KDL>\n\nC<KDL> is the codec class for KDL text.\n\n=over\n\n=item C<< codec.decode(String text) >>\n\nParameters: C<text> is KDL text. Returns: C<KDLDocument>. Parses KDL\ntext.\n\n=item C<< codec.decode_binarystring(BinaryString bytes) >>\n\nParameters: C<bytes> is UTF-8 KDL bytes. Returns: C<KDLDocument>.\nParses KDL bytes.\n\n=item C<< codec.encode(value) >>\n\nParameters: C<value> is a C<KDLDocument>, C<KDLNode>, or compatible\nvalue. Returns: C<String>. Serializes KDL data to text.\n\n=item C<< codec.encode_binarystring(value) >>\n\nParameters: C<value> is a C<KDLDocument>, C<KDLNode>, or compatible\nvalue. Returns: C<BinaryString>. Serializes KDL data to UTF-8 bytes.\n\n=item C<< codec.load(path) >>\n\nParameters: C<path> is a C<std/io> C<Path>. Returns: C<KDLDocument>.\nReads and parses a KDL file.\n\n=item C<< codec.dump(path, value) >>\n\nParameters: C<path> is a C<std/io> C<Path> and C<value> is KDL data.\nReturns: C<null>. Serializes and writes a KDL file.\n\n=back\n\n=head1 ZPATH\n\nC<KDLDocument>, C<KDLNode>, and C<KDLValue> objects can be queried with\nC<std/path/z>. A KDL document\'s top-level nodes are exposed as children.\nA KDL node exposes its arguments first, then its child nodes, as ZPath\nchildren. KDL properties are exposed as ZPath attributes.\n\n from std/data/kdl import KDL;\n from std/path/z import ZPath;\n\n let doc := ( new KDL() ).decode( """\n (pkg)package "zuzu" (email)"dev@example.test" version="0.1.0" {\n foo "first"\n bar "middle"\n foo "second"\n foo "third"\n }\n """ );\n\n // The package node\'s first child is its first argument.\n say( ( new ZPath( path: "/package/#0" ) ).first(doc) ); // zuzu\n\n // Properties are attributes.\n say( ( new ZPath( path: "/package/@version" ) ).first(doc) ); // 0.1.0\n\n // Child nodes can be selected by name and zero-based occurrence.\n say( ( new ZPath( path: "/package/foo#2/#0" ) ).first(doc) ); // third\n\n // tag() exposes type annotations on nodes and values.\n say( ( new ZPath( path: "tag(/package)" ) ).first(doc) ); // pkg\n say( ( new ZPath( path: "tag(/package/#1)" ) ).first(doc) ); // email\n\n // local-name() exposes the node name, like it does for XML elements.\n say( ( new ZPath( path: "local-name(/package)" ) ).first(doc) );\n say( ( new ZPath( path: "/package/foo#2/local-name()" ) ).first(doc) );\n\n=head1 COMPATIBILITY\n\nThe parser is recursive descent and covers the usual KDL 2.0 document\nmodel: nodes, arguments, properties, annotations, comments, slashdash,\nstrings, booleans, nulls, numbers, and children.\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/data/kdl >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/string import substr, index, chr, ord, sprint;\n\n\nfunction _kdl_starts_with ( String text, Number pos, String prefix ) {\n return substr( text, pos, length prefix ) \u2261 prefix;\n}\n\nfunction _kdl_is_newline ( String ch ) {\n return ch \u2261 "\\n" or ch \u2261 "\\r" or ch \u2261 chr(11)\n or ch \u2261 chr(133) or ch \u2261 chr(8232) or ch \u2261 chr(8233);\n}\n\nfunction _kdl_is_ws ( String ch ) {\n return false if ch \u2261 "";\n let code := ord(ch);\n return ch \u2261 " " or ch \u2261 "\\t" or ch \u2261 chr(12)\n or code \u2261 160 or code \u2261 5760 or code \u2261 8239\n or code \u2261 8287 or code \u2261 12288\n or ( code >= 8192 and code <= 8202 );\n}\n\nfunction _kdl_is_line_space ( String ch ) {\n return _kdl_is_ws(ch) or _kdl_is_newline(ch);\n}\n\nfunction _kdl_is_digit ( String ch ) {\n return ch ~ /^[0-9]$/;\n}\n\nfunction _kdl_is_hex_digit ( String ch ) {\n return ch ~ /^[0-9a-fA-F]$/;\n}\n\nfunction _kdl_hex_value ( String ch ) {\n if ( _kdl_is_digit(ch) ) {\n return ch + 0;\n }\n if ( index( "abcdef", ch ) >= 0 ) {\n return 10 + index( "abcdef", ch );\n }\n if ( index( "ABCDEF", ch ) >= 0 ) {\n return 10 + index( "ABCDEF", ch );\n }\n die `Invalid hexadecimal digit \'${ch}\'`;\n}\n\nfunction _kdl_from_hex ( String raw ) {\n let n := 0;\n let i := 0;\n while ( i < length raw ) {\n n := n * 16 + _kdl_hex_value( substr( raw, i, 1 ) );\n i++;\n }\n return n;\n}\n\nfunction _kdl_clean_number ( String token ) {\n let out := "";\n let i := 0;\n while ( i < length token ) {\n let ch := substr( token, i, 1 );\n out _= ch if ch \u2262 "_";\n i++;\n }\n return out;\n}\n\nfunction _kdl_parse_radix ( String token, Number radix ) {\n let sign := 1;\n let i := 0;\n if ( substr( token, 0, 1 ) \u2261 "-" ) {\n sign := -1;\n i := 1;\n }\n else if ( substr( token, 0, 1 ) \u2261 "+" ) {\n i := 1;\n }\n i += 2;\n\n let n := 0;\n while ( i < length token ) {\n let ch := substr( token, i, 1 );\n if ( ch \u2262 "_" ) {\n let v := radix \u2261 16 ? _kdl_hex_value(ch) : ch + 0;\n die `Invalid digit \'${ch}\' for radix ${radix}` if v >= radix;\n n := n * radix + v;\n }\n i++;\n }\n return sign * n;\n}\n\nfunction _kdl_strip_decimal_zeroes ( String raw ) {\n let i := 0;\n while ( i < length raw - 1 and substr( raw, i, 1 ) \u2261 "0" ) {\n i++;\n }\n return substr( raw, i, length raw - i );\n}\n\nfunction _kdl_decimal_mul_add ( String raw, Number factor, Number add ) {\n let out := "";\n let carry := add;\n let i := length raw - 1;\n while ( i >= 0 ) {\n let n := ( substr( raw, i, 1 ) + 0 ) * factor + carry;\n out := "" _ ( n mod 10 ) _ out;\n carry := floor( n / 10 );\n i--;\n }\n while ( carry > 0 ) {\n out := "" _ ( carry mod 10 ) _ out;\n carry := floor( carry / 10 );\n }\n return _kdl_strip_decimal_zeroes(out);\n}\n\nfunction _kdl_decimal_from_radix ( String token, Number radix ) {\n let sign := "";\n let i := 0;\n if ( substr( token, 0, 1 ) \u2261 "-" ) {\n sign := "-";\n i := 1;\n }\n else if ( substr( token, 0, 1 ) \u2261 "+" ) {\n i := 1;\n }\n i += 2;\n\n let out := "0";\n while ( i < length token ) {\n out := _kdl_decimal_mul_add(\n out,\n radix,\n _kdl_hex_value( substr( token, i, 1 ) ),\n );\n i++;\n }\n return sign _ out if sign \u2262 "" and out \u2262 "0";\n return out;\n}\n\nfunction _kdl_canonical_decimal_int ( String token ) {\n let sign := "";\n let i := 0;\n if ( substr( token, 0, 1 ) \u2261 "-" ) {\n sign := "-";\n i := 1;\n }\n else if ( substr( token, 0, 1 ) \u2261 "+" ) {\n i := 1;\n }\n let body := _kdl_strip_decimal_zeroes(\n substr( token, i, length token - i )\n );\n return sign _ body if sign \u2262 "" and body \u2262 "0";\n return body;\n}\n\nfunction _kdl_canonical_float ( String token ) {\n let epos := index( token, "e" );\n epos := index( token, "E" ) if epos < 0;\n return token if epos < 0;\n\n let mantissa := substr( token, 0, epos );\n let exponent := substr( token, epos + 1, length token - epos - 1 );\n if ( substr( exponent, 0, 1 ) \u2262 "+"\n and substr( exponent, 0, 1 ) \u2262 "-"\n ) {\n exponent := "+" _ exponent;\n }\n return mantissa _ "E" _ exponent;\n}\n\nfunction _kdl_find_multiline_close (\n String text,\n String close,\n Number start,\n Boolean raw,\n) {\n let found := index( text, close, start );\n while ( found >= 0 ) {\n if ( raw or found = 0 or substr( text, found - 1, 1 ) \u2262 "\\\\" ) {\n return found;\n }\n found := index( text, close, found + 1 );\n }\n return -1;\n}\n\nfunction _kdl_reserved_ident ( String text ) {\n return text \u2261 "true" or text \u2261 "false" or text \u2261 "null"\n or text \u2261 "inf" or text \u2261 "-inf" or text \u2261 "nan";\n}\n\nfunction _kdl_ident_break ( String ch ) {\n return ch \u2261 "" or _kdl_is_line_space(ch)\n or ch \u2261 "\\\\" or ch \u2261 "/" or ch \u2261 "(" or ch \u2261 ")"\n or ch \u2261 "{" or ch \u2261 "}" or ch \u2261 ";" or ch \u2261 "["\n or ch \u2261 "]" or ch \u2261 "\\"" or ch \u2261 "#" or ch \u2261 "=";\n}\n\nfunction _kdl_forbidden_codepoint ( Number code ) {\n return code < 32 or code \u2261 127 or code \u2261 65279\n or code \u2261 8206 or code \u2261 8207\n or ( code >= 8234 and code <= 8238 )\n or ( code >= 8294 and code <= 8297 );\n}\n\nfunction _kdl_is_plain_ident ( String text ) {\n if ( text \u2261 "" or _kdl_reserved_ident(text) ) {\n return false;\n }\n let first := substr( text, 0, 1 );\n let second := substr( text, 1, 1 );\n if ( _kdl_is_digit(first) ) {\n return false;\n }\n if ( ( first \u2261 "+" or first \u2261 "-" ) and _kdl_is_digit(second) ) {\n return false;\n }\n if ( first \u2261 "." and _kdl_is_digit(second) ) {\n return false;\n }\n let i := 0;\n while ( i < length text ) {\n return false if _kdl_ident_break( substr( text, i, 1 ) );\n i++;\n }\n return true;\n}\n\nfunction _kdl_escape_string ( String text ) {\n let out := "";\n let i := 0;\n while ( i < length text ) {\n let ch := substr( text, i, 1 );\n if ( ch \u2261 "\\\\" ) { out _= "\\\\\\\\"; }\n else if ( ch \u2261 "\\"" ) { out _= "\\\\\\""; }\n else if ( ch \u2261 chr(8) ) { out _= "\\\\b"; }\n else if ( ch \u2261 chr(12) ) { out _= "\\\\f"; }\n else if ( ch \u2261 "\\n" ) { out _= "\\\\n"; }\n else if ( ch \u2261 "\\r" ) { out _= "\\\\r"; }\n else if ( ch \u2261 "\\t" ) { out _= "\\\\t"; }\n else if ( ord(ch) < 32 ) {\n out _= "\\\\u{" _ sprint( "%x", ord(ch) ) _ "}";\n }\n else { out _= ch; }\n i++;\n }\n return out;\n}\n\nfunction _kdl_name ( String text ) {\n return _kdl_is_plain_ident(text) ? text : `"${_kdl_escape_string(text)}"`;\n}\n\nfunction _kdl_indent ( Number level, Boolean canonical := false ) {\n let out := "";\n let i := 0;\n let unit := canonical ? " " : "\\t";\n while ( i < level ) {\n out _= unit;\n i++;\n }\n return out;\n}\n\nfunction _kdl_canonical_props ( PairList props ) {\n let latest := {};\n for ( let pair in props.to_Array() ) {\n let kv := pair{pair};\n latest{(kv[0])} := kv[1];\n }\n\n let out := [];\n for ( let key in latest.sorted_keys() ) {\n out.push( [ key, latest{(key)} ] );\n }\n return out;\n}\n\nclass KDLValue {\n let String type := "null";\n let kind := null;\n let value := null;\n let type_annotation := null;\n let canonical_number := null;\n\n method type () { return type; }\n method kind () { return kind; }\n method value () { return value; }\n method type_annotation () { return type_annotation; }\n method canonical_number () { return canonical_number; }\n\n method is_null () { return type \u2261 "null"; }\n method is_boolean () { return type \u2261 "boolean"; }\n method is_number () { return type \u2261 "number"; }\n method is_string () { return type \u2261 "string"; }\n method is_true () { return type \u2261 "boolean" and value; }\n method is_false () { return type \u2261 "boolean" and not value; }\n\n method to_Number () {\n die "KDLValue is not a number" if type \u2262 "number";\n die "KDL keyword number has no native numeric value" if kind \u2261 "string";\n return value;\n }\n\n method to_String () {\n return "" if type \u2261 "null";\n return "" _ value;\n }\n\n method to_Boolean () {\n if ( type \u2261 "boolean" ) { return value; }\n if ( type \u2261 "null" ) { return false; }\n if ( type \u2261 "number" ) {\n return kind \u2262 "string" and value \u2262 0;\n }\n return value \u2262 "";\n }\n\n method native_value () {\n return null if type \u2261 "null";\n return value;\n }\n}\n\nclass KDLNode {\n let name := "";\n let type_annotation := null;\n let args := [];\n let props := null;\n let children := [];\n\n method __build__ () {\n props := new PairList() if props \u2261 null;\n args := [] if args \u2261 null;\n children := [] if children \u2261 null;\n }\n\n method name () { return name; }\n method type_annotation () { return type_annotation; }\n method args () { return args; }\n method props () { return props; }\n method children () { return children; }\n\n method to_Iterator () {\n let values := [];\n for ( let arg in args ) {\n values.push(arg);\n }\n for ( let child in children ) {\n values.push(child);\n }\n return values.to_Iterator();\n }\n}\n\nclass KDLDocument {\n let nodes := [];\n\n method __build__ () {\n nodes := [] if nodes \u2261 null;\n }\n\n method nodes () { return nodes; }\n\n method to_Iterator () {\n return nodes.to_Iterator();\n }\n}\n\nclass _KDLParser {\n let String text := "";\n let Number pos := 0;\n\n method _eof () {\n return pos >= length text;\n }\n\n method _peek () {\n return "" if self._eof();\n return substr( text, pos, 1 );\n }\n\n method _take () {\n let ch := self._peek();\n pos++;\n return ch;\n }\n\n method _error ( String message ) {\n die `KDL parse error at offset ${pos}: ${message}`;\n }\n\n method _skip_newline () {\n if ( _kdl_starts_with( text, pos, "\\r\\n" ) ) {\n pos += 2;\n return true;\n }\n if ( _kdl_is_newline( self._peek() ) ) {\n pos++;\n return true;\n }\n return false;\n }\n\n method _skip_line_comment () {\n return false if not _kdl_starts_with( text, pos, "//" );\n pos += 2;\n while ( not self._eof() and not _kdl_is_newline( self._peek() ) ) {\n pos++;\n }\n self._skip_newline();\n return true;\n }\n\n method _skip_block_comment () {\n return false if not _kdl_starts_with( text, pos, "/*" );\n pos += 2;\n let depth := 1;\n while ( depth > 0 ) {\n self._error("Unterminated block comment") if self._eof();\n if ( _kdl_starts_with( text, pos, "/*" ) ) {\n depth++;\n pos += 2;\n }\n else if ( _kdl_starts_with( text, pos, "*/" ) ) {\n depth--;\n pos += 2;\n }\n else {\n pos++;\n }\n }\n return true;\n }\n\n method _skip_line_space () {\n let moved := false;\n let keep := true;\n while ( keep and not self._eof() ) {\n keep := false;\n if ( _kdl_is_ws( self._peek() ) ) {\n pos++;\n moved := true;\n keep := true;\n }\n else if ( self._skip_newline() ) {\n moved := true;\n keep := true;\n }\n else if ( self._skip_line_comment() ) {\n moved := true;\n keep := true;\n }\n else if ( self._skip_block_comment() ) {\n moved := true;\n keep := true;\n }\n else if ( self._skip_escline() ) {\n moved := true;\n keep := true;\n }\n }\n return moved;\n }\n\n method _skip_node_space () {\n let moved := false;\n let keep := true;\n while ( keep and not self._eof() ) {\n keep := false;\n if ( _kdl_is_ws( self._peek() ) ) {\n pos++;\n moved := true;\n keep := true;\n }\n else if ( self._skip_block_comment() ) {\n moved := true;\n keep := true;\n }\n else if ( self._skip_escline() ) {\n moved := true;\n keep := true;\n }\n }\n return moved;\n }\n\n method _skip_escline () {\n return false if self._peek() \u2262 "\\\\";\n let save := pos;\n pos++;\n let keep := true;\n while ( keep and not self._eof() ) {\n keep := false;\n while ( _kdl_is_ws( self._peek() ) ) {\n pos++;\n keep := true;\n }\n if ( self._skip_block_comment() ) {\n keep := true;\n }\n }\n if ( self._skip_line_comment() or self._skip_newline() or self._eof() ) {\n return true;\n }\n pos := save;\n return false;\n }\n\n method _parse_document () {\n if ( _kdl_starts_with( text, pos, chr(65279) ) ) {\n pos++;\n }\n let nodes := self._parse_nodes(false);\n self._skip_line_space();\n self._error("Unexpected trailing input") if not self._eof();\n return new KDLDocument( nodes: nodes );\n }\n\n method _parse_nodes ( Boolean in_children ) {\n let nodes := [];\n while (true) {\n self._skip_line_space();\n if ( self._eof() ) {\n return nodes;\n }\n if ( in_children and self._peek() \u2261 "}" ) {\n return nodes;\n }\n if ( _kdl_starts_with( text, pos, "/-" ) ) {\n self._consume_slashdash();\n self._parse_node(true);\n }\n else {\n let node := self._parse_node(false);\n nodes.push(node) if node \u2262 null;\n }\n }\n return nodes;\n }\n\n method _consume_slashdash () {\n self._error("Expected slashdash") if not _kdl_starts_with( text, pos, "/-" );\n pos += 2;\n self._skip_line_space();\n }\n\n method _parse_type_annotation () {\n return null if self._peek() \u2262 "(";\n pos++;\n self._skip_node_space();\n let name := self._parse_string();\n self._skip_node_space();\n self._error("Expected \')\' after type annotation") if self._peek() \u2262 ")";\n pos++;\n self._skip_node_space();\n return name;\n }\n\n method _parse_node ( Boolean discard ) {\n let annotation := self._parse_type_annotation();\n self._skip_node_space();\n let name := self._parse_string();\n let args := [];\n let props := new PairList();\n let children := [];\n let slashdash_child_before_entry := false;\n\n while (true) {\n let had_space := self._skip_node_space();\n if ( self._eof() ) {\n return discard ? null : new KDLNode(\n name: name,\n type_annotation: annotation,\n args: args,\n props: props,\n children: children,\n );\n }\n\n let ch := self._peek();\n if ( ch \u2261 ";" ) {\n pos++;\n return discard ? null : new KDLNode(\n name: name,\n type_annotation: annotation,\n args: args,\n props: props,\n children: children,\n );\n }\n if ( ch \u2261 "}" ) {\n return discard ? null : new KDLNode(\n name: name,\n type_annotation: annotation,\n args: args,\n props: props,\n children: children,\n );\n }\n if ( _kdl_is_newline(ch) ) {\n self._skip_newline();\n return discard ? null : new KDLNode(\n name: name,\n type_annotation: annotation,\n args: args,\n props: props,\n children: children,\n );\n }\n if ( _kdl_starts_with( text, pos, "//" ) ) {\n self._skip_line_comment();\n return discard ? null : new KDLNode(\n name: name,\n type_annotation: annotation,\n args: args,\n props: props,\n children: children,\n );\n }\n if ( _kdl_starts_with( text, pos, "/-" ) ) {\n self._consume_slashdash();\n if ( self._peek() \u2261 "{" ) {\n self._parse_children();\n slashdash_child_before_entry := true;\n }\n else {\n self._parse_entry(true, args, props);\n }\n next;\n }\n if ( ch \u2261 "{" ) {\n self._error("Child block must be the final node field")\n if children.length() > 0;\n children := self._parse_children();\n next;\n }\n self._error("Expected whitespace before node field")\n if not had_space;\n self._error("Child block must be the final node field")\n if children.length() > 0;\n self._error("Discarded child block must not precede node fields")\n if slashdash_child_before_entry;\n self._parse_entry(discard, args, props);\n }\n }\n\n method _parse_children () {\n self._error("Expected \'{\'") if self._peek() \u2262 "{";\n pos++;\n let kids := self._parse_nodes(true);\n self._skip_line_space();\n self._error("Expected \'}\' after child block") if self._peek() \u2262 "}";\n pos++;\n return kids;\n }\n\n method _parse_entry ( Boolean discard, Array args, PairList props ) {\n if ( self._peek() \u2261 "(" ) {\n let value := self._parse_value();\n args.push(value) if not discard;\n return;\n }\n\n if ( self._peek() \u2261 "#" or self._starts_number() ) {\n let value := self._parse_value();\n args.push(value) if not discard;\n return;\n }\n\n let key := self._parse_string();\n let after_key := pos;\n self._skip_node_space();\n if ( self._peek() \u2261 "=" ) {\n pos++;\n self._skip_node_space();\n let value := self._parse_value();\n props.add( key, value ) if not discard;\n return;\n }\n pos := after_key;\n\n let value := new KDLValue( type: "string", value: key );\n args.push(value) if not discard;\n return;\n }\n\n method _parse_value () {\n let annotation := self._parse_type_annotation();\n self._skip_node_space();\n\n if ( self._peek() \u2261 "#" ) {\n return self._parse_hash_value(annotation);\n }\n if ( self._starts_number() ) {\n return self._parse_number(annotation);\n }\n return new KDLValue(\n type: "string",\n value: self._parse_string(),\n type_annotation: annotation,\n );\n }\n\n method _starts_number () {\n let ch := self._peek();\n let nxt := substr( text, pos + 1, 1 );\n return _kdl_is_digit(ch)\n or ( ( ch \u2261 "+" or ch \u2261 "-" ) and _kdl_is_digit(nxt) );\n }\n\n method _read_token () {\n let start := pos;\n while ( not self._eof() ) {\n let ch := self._peek();\n last if _kdl_ident_break(ch);\n pos++;\n }\n return substr( text, start, pos - start );\n }\n\n method _parse_hash_value ( annotation ) {\n if ( _kdl_starts_with( text, pos, "#true" ) ) {\n pos += 5;\n return new KDLValue(\n type: "boolean",\n value: true,\n type_annotation: annotation,\n );\n }\n if ( _kdl_starts_with( text, pos, "#false" ) ) {\n pos += 6;\n return new KDLValue(\n type: "boolean",\n value: false,\n type_annotation: annotation,\n );\n }\n if ( _kdl_starts_with( text, pos, "#null" ) ) {\n pos += 5;\n return new KDLValue(\n type: "null",\n value: null,\n type_annotation: annotation,\n );\n }\n if ( _kdl_starts_with( text, pos, "#inf" ) ) {\n pos += 4;\n return new KDLValue(\n type: "number",\n kind: "string",\n value: "#inf",\n type_annotation: annotation,\n );\n }\n if ( _kdl_starts_with( text, pos, "#-inf" ) ) {\n pos += 5;\n return new KDLValue(\n type: "number",\n kind: "string",\n value: "#-inf",\n type_annotation: annotation,\n );\n }\n if ( _kdl_starts_with( text, pos, "#nan" ) ) {\n pos += 4;\n return new KDLValue(\n type: "number",\n kind: "string",\n value: "#nan",\n type_annotation: annotation,\n );\n }\n return new KDLValue(\n type: "string",\n value: self._parse_raw_string(),\n type_annotation: annotation,\n );\n }\n\n method _parse_number ( annotation ) {\n let token := self._read_token();\n self._error( `Invalid number \'${token}\'` )\n if token ~ /^[+-]?0[xob]_/i or token ~ /\\._/;\n let clean := _kdl_clean_number(token);\n if ( clean ~ /^[+-]?0x[0-9a-fA-F]+$/ ) {\n return new KDLValue(\n type: "number",\n kind: "integer",\n value: _kdl_parse_radix( clean, 16 ),\n canonical_number: _kdl_decimal_from_radix( clean, 16 ),\n type_annotation: annotation,\n );\n }\n if ( clean ~ /^[+-]?0o[0-7]+$/ ) {\n return new KDLValue(\n type: "number",\n kind: "integer",\n value: _kdl_parse_radix( clean, 8 ),\n canonical_number: _kdl_decimal_from_radix( clean, 8 ),\n type_annotation: annotation,\n );\n }\n if ( clean ~ /^[+-]?0b[01]+$/ ) {\n return new KDLValue(\n type: "number",\n kind: "integer",\n value: _kdl_parse_radix( clean, 2 ),\n canonical_number: _kdl_decimal_from_radix( clean, 2 ),\n type_annotation: annotation,\n );\n }\n if ( clean ~ /^[+-]?[0-9]+$/ ) {\n return new KDLValue(\n type: "number",\n kind: "integer",\n value: clean + 0,\n canonical_number: _kdl_canonical_decimal_int(clean),\n type_annotation: annotation,\n );\n }\n if ( clean ~ /^[+-]?[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$/ ) {\n return new KDLValue(\n type: "number",\n kind: "float",\n value: clean + 0,\n canonical_number: _kdl_canonical_float(clean),\n type_annotation: annotation,\n );\n }\n self._error( `Invalid number \'${token}\'` );\n }\n\n method _parse_string () {\n if ( self._peek() \u2261 "#" ) {\n return self._parse_raw_string();\n }\n if ( self._peek() \u2261 "\\"" ) {\n return self._parse_quoted_string();\n }\n return self._parse_identifier_string();\n }\n\n method _parse_identifier_string () {\n let ident := self._read_token();\n self._error("Expected string") if ident \u2261 "";\n self._error( `Reserved KDL identifier \'${ident}\'` )\n if _kdl_reserved_ident(ident);\n self._error( `Invalid KDL identifier \'${ident}\'` )\n if substr( ident, 0, 1 ) \u2261 "."\n and _kdl_is_digit( substr( ident, 1, 1 ) );\n let i := 0;\n while ( i < length ident ) {\n let code := ord( substr( ident, i, 1 ) );\n self._error( `Forbidden codepoint in KDL identifier \'${ident}\'` )\n if _kdl_forbidden_codepoint(code);\n i++;\n }\n return ident;\n }\n\n method _parse_quoted_string () {\n if ( _kdl_starts_with( text, pos, "\\"\\"\\"" ) ) {\n return self._parse_multiline_string( "", false );\n }\n pos++;\n let out := "";\n while ( not self._eof() ) {\n let ch := self._take();\n if ( ch \u2261 "\\"" ) {\n return out;\n }\n if ( ch \u2261 "\\\\" ) {\n out _= self._parse_escape();\n }\n else {\n self._error("Newline in quoted string") if _kdl_is_newline(ch);\n out _= ch;\n }\n }\n self._error("Unterminated quoted string");\n }\n\n method _parse_escape () {\n self._error("Unterminated string escape") if self._eof();\n let ch := self._take();\n if ( ch \u2261 "\\"" ) { return "\\""; }\n if ( ch \u2261 "\\\\" ) { return "\\\\"; }\n if ( ch \u2261 "b" ) { return chr(8); }\n if ( ch \u2261 "f" ) { return chr(12); }\n if ( ch \u2261 "n" ) { return "\\n"; }\n if ( ch \u2261 "r" ) { return "\\r"; }\n if ( ch \u2261 "s" ) { return " "; }\n if ( ch \u2261 "t" ) { return "\\t"; }\n if ( _kdl_is_line_space(ch) ) {\n while ( _kdl_is_line_space(ch) ) {\n if ( _kdl_is_newline(ch) ) {\n self._skip_newline();\n }\n ch := self._peek();\n pos++ if _kdl_is_ws(ch);\n }\n return "";\n }\n if ( ch \u2261 "u" ) {\n self._error("Expected \'{\' in Unicode escape") if self._peek() \u2262 "{";\n pos++;\n let start := pos;\n while ( not self._eof() and self._peek() \u2262 "}" ) {\n self._error("Invalid Unicode escape") if not _kdl_is_hex_digit( self._peek() );\n pos++;\n }\n self._error("Unterminated Unicode escape") if self._peek() \u2262 "}";\n let raw := substr( text, start, pos - start );\n self._error("Unicode escape must contain 1 to 6 hex digits")\n if length raw < 1 or length raw > 6;\n pos++;\n let code := _kdl_from_hex(raw);\n return chr(code);\n }\n self._error( `Invalid string escape \'\\\\${ch}\'` );\n }\n\n method _parse_raw_string () {\n let hashes := "";\n while ( self._peek() \u2261 "#" ) {\n hashes _= "#";\n pos++;\n }\n self._error("Expected raw string quotes") if self._peek() \u2262 "\\"";\n if ( _kdl_starts_with( text, pos, "\\"\\"\\"" ) ) {\n return self._parse_multiline_string( hashes, true );\n }\n pos++;\n let close := "\\"" _ hashes;\n let end := index( text, close, pos );\n self._error("Unterminated raw string") if end < 0;\n let out := substr( text, pos, end - pos );\n self._error("Newline in raw string") if out ~ /[\\r\\n\\v]/;\n pos := end + length close;\n return out;\n }\n\n method _parse_multiline_string ( String hashes, Boolean raw ) {\n pos += 3;\n self._error("Expected newline after multiline string opener")\n if not self._skip_newline();\n let close := "\\"\\"\\"" _ hashes;\n let end := _kdl_find_multiline_close( text, close, pos, raw );\n self._error("Unterminated multiline string") if end < 0;\n let body := substr( text, pos, end - pos );\n pos := end + length close;\n\n let last_nl := -1;\n let i := length body - 1;\n while ( i >= 0 and last_nl < 0 ) {\n if ( substr( body, i, 1 ) \u2261 "\\n" ) {\n last_nl := i;\n }\n i--;\n }\n return "" if last_nl < 0;\n let content := substr( body, 0, last_nl );\n let indent := substr( body, last_nl + 1, length body - last_nl - 1 );\n if ( not raw ) {\n let bs := 0;\n while ( bs < length indent\n and _kdl_is_ws( substr( indent, bs, 1 ) )\n ) {\n bs++;\n }\n if ( bs < length indent and substr( indent, bs, 1 ) \u2261 "\\\\" ) {\n let k := bs + 1;\n let only_ws := true;\n while ( k < length indent ) {\n only_ws := false\n if not _kdl_is_ws( substr( indent, k, 1 ) );\n k++;\n }\n indent := substr( indent, 0, bs ) if only_ws;\n }\n }\n if ( indent \u2261 "" ) {\n let min_indent := null;\n let scan := 0;\n let scan_start := 0;\n while ( scan <= length content ) {\n if ( scan \u2261 length content\n or substr( content, scan, 1 ) \u2261 "\\n"\n ) {\n let raw_line := substr(\n content,\n scan_start,\n scan - scan_start,\n );\n if ( not( raw_line ~ /^\\s*$/ ) ) {\n let width := 0;\n while ( width < length raw_line\n and _kdl_is_ws( substr( raw_line, width, 1 ) )\n ) {\n width++;\n }\n min_indent := width\n if min_indent \u2261 null or width < min_indent;\n }\n scan_start := scan + 1;\n }\n scan++;\n }\n if ( min_indent \u2262 null and min_indent > 0 ) {\n indent := "";\n let si := 0;\n while ( si < min_indent ) {\n indent _= " ";\n si++;\n }\n }\n }\n let out := "";\n i := 0;\n let line_start := 0;\n let emitted := false;\n let continued := false;\n let last_line_continued_with_text := false;\n while ( i <= length content ) {\n if ( i \u2261 length content or substr( content, i, 1 ) \u2261 "\\n" ) {\n let line := substr( content, line_start, i - line_start );\n if ( line ~ /^\\s*$/ ) {\n line := "";\n }\n else if ( continued ) {\n if ( length indent > 0\n and substr( line, 0, length indent ) \u2261 indent\n ) {\n line := substr(\n line,\n length indent,\n length line - length indent,\n );\n }\n }\n else if ( length indent > 0 and substr( line, 0, length indent ) \u2261 indent ) {\n line := substr( line, length indent, length line - length indent );\n }\n else if ( length indent > 0 ) {\n let k := 0;\n while ( k < length line\n and _kdl_is_ws( substr( line, k, 1 ) )\n ) {\n k++;\n }\n let ok_escape_prefix := false;\n if ( not raw and substr( line, k, 1 ) \u2261 "\\\\" ) {\n k++;\n ok_escape_prefix := true;\n while ( k < length line ) {\n ok_escape_prefix := false\n if not _kdl_is_ws( substr( line, k, 1 ) );\n k++;\n }\n }\n self._error("Multiline string indentation mismatch")\n if not ok_escape_prefix;\n }\n\n let line_continues := false;\n if ( not raw ) {\n let j := length line - 1;\n while ( j >= 0 and _kdl_is_ws( substr( line, j, 1 ) ) ) {\n j--;\n }\n if ( j >= 0 and substr( line, j, 1 ) \u2261 "\\\\" ) {\n let slashes := 0;\n while ( j - slashes >= 0\n and substr( line, j - slashes, 1 ) \u2261 "\\\\"\n ) {\n slashes++;\n }\n if ( slashes mod 2 = 1 ) {\n line_continues := true;\n line := substr( line, 0, j );\n }\n }\n }\n self._error("Invalid final multiline whitespace escape")\n if line_continues and line \u2261 "" and indent \u2261 "";\n\n out _= "\\n"\n if emitted and not continued\n and not ( line_continues and line \u2261 "" );\n out _= raw ? line : self._unescape_multiline_line(line);\n emitted := true;\n continued := line_continues;\n last_line_continued_with_text := line_continues and line \u2262 "";\n line_start := i + 1;\n }\n i++;\n }\n self._error("Invalid final multiline whitespace escape")\n if last_line_continued_with_text;\n return out;\n }\n\n method _unescape_multiline_line ( String line ) {\n let out := "";\n let i := 0;\n while ( i < length line ) {\n let ch := substr( line, i, 1 );\n if ( ch \u2262 "\\\\" ) {\n out _= ch;\n i++;\n next;\n }\n\n i++;\n self._error("Unterminated string escape") if i >= length line;\n ch := substr( line, i, 1 );\n if ( ch \u2261 "\\"" ) { out _= "\\""; }\n else if ( ch \u2261 "\\\\" ) { out _= "\\\\"; }\n else if ( ch \u2261 "b" ) { out _= chr(8); }\n else if ( ch \u2261 "f" ) { out _= chr(12); }\n else if ( ch \u2261 "n" ) { out _= "\\n"; }\n else if ( ch \u2261 "r" ) { out _= "\\r"; }\n else if ( ch \u2261 "s" ) { out _= " "; }\n else if ( ch \u2261 "t" ) { out _= "\\t"; }\n else if ( _kdl_is_ws(ch) ) {\n while ( i < length line and _kdl_is_ws( substr( line, i, 1 ) ) ) {\n i++;\n }\n i--;\n }\n else if ( ch \u2261 "u" ) {\n self._error("Expected \'{\' in Unicode escape")\n if substr( line, i + 1, 1 ) \u2262 "{";\n i += 2;\n let start := i;\n while ( i < length line and substr( line, i, 1 ) \u2262 "}" ) {\n self._error("Invalid Unicode escape")\n if not _kdl_is_hex_digit( substr( line, i, 1 ) );\n i++;\n }\n self._error("Unterminated Unicode escape")\n if i >= length line;\n let raw := substr( line, start, i - start );\n self._error("Unicode escape must contain 1 to 6 hex digits")\n if length raw < 1 or length raw > 6;\n let code := _kdl_from_hex(raw);\n out _= chr(code);\n }\n else {\n self._error( `Invalid string escape \'\\\\${ch}\'` );\n }\n i++;\n }\n return out;\n }\n}\n\nfunction _kdl_value ( value ) {\n if ( value instanceof KDLValue ) {\n return value;\n }\n if ( value \u2261 null ) {\n return new KDLValue( type: "null", value: null );\n }\n if ( value instanceof Boolean ) {\n return new KDLValue( type: "boolean", value: value );\n }\n if ( value instanceof Number ) {\n return new KDLValue( type: "number", kind: "float", value: value );\n }\n return new KDLValue( type: "string", value: "" _ value );\n}\n\nfunction _kdl_encode_value ( value, Boolean canonical := false ) {\n let v := _kdl_value(value);\n let prefix := "";\n if ( v.type_annotation() \u2262 null ) {\n prefix := "(" _ _kdl_name( v.type_annotation() ) _ ")";\n }\n if ( v.is_null() ) { return prefix _ "#null"; }\n if ( v.is_boolean() ) {\n return prefix _ ( v.native_value() ? "#true" : "#false" );\n }\n if ( v.is_number() ) {\n if ( canonical and v.canonical_number() \u2262 null ) {\n return prefix _ v.canonical_number();\n }\n return prefix _ ( v.kind() \u2261 "string" ? v.value() : "" _ v.value() );\n }\n if ( v.is_string() ) {\n if ( canonical and _kdl_is_plain_ident( v.native_value() ) ) {\n return prefix _ v.native_value();\n }\n return prefix _ "\\"" _ _kdl_escape_string( v.native_value() ) _ "\\"";\n }\n\n let native := v.native_value();\n try {\n if ( native can "to_String" ) {\n let text := native.to_String();\n if ( canonical and _kdl_is_plain_ident(text) ) {\n return prefix _ text;\n }\n return prefix _ "\\"" _ _kdl_escape_string(text) _ "\\"";\n }\n }\n catch {\n }\n\n die `Cannot serialize KDLValue of type \'${v.type()}\'`;\n}\n\nfunction _kdl_encode_node (\n KDLNode node,\n Number level,\n Boolean canonical := false,\n) {\n let out := _kdl_indent( level, canonical );\n if ( node.type_annotation() \u2262 null ) {\n out _= "(" _ _kdl_name( node.type_annotation() ) _ ")";\n }\n out _= _kdl_name( node.name() );\n for ( let arg in node.args() ) {\n out _= " " _ _kdl_encode_value( arg, canonical );\n }\n if ( canonical ) {\n for ( let kv in _kdl_canonical_props( node.props() ) ) {\n out _= " " _ _kdl_name(kv[0]) _ "="\n _ _kdl_encode_value( kv[1], canonical );\n }\n }\n else {\n for ( let pair in node.props().to_Array() ) {\n let kv := pair{pair};\n out _= " " _ _kdl_name(kv[0]) _ "="\n _ _kdl_encode_value( kv[1], canonical );\n }\n }\n if ( node.children().length() > 0 ) {\n out _= " {\\n";\n let i := 0;\n while ( i < node.children().length() ) {\n out _= _kdl_encode_node(\n node.children()[i],\n level + 1,\n canonical,\n );\n out _= "\\n";\n i++;\n }\n out _= _kdl_indent( level, canonical ) _ "}";\n }\n return out;\n}\n\nfunction _kdl_encode_document ( KDLDocument doc, Boolean canonical := false ) {\n let out := "";\n let i := 0;\n while ( i < doc.nodes().length() ) {\n out _= "\\n" if i > 0;\n out _= _kdl_encode_node( doc.nodes()[i], 0, canonical );\n i++;\n }\n out _= "\\n" if canonical;\n return out;\n}\n\nclass KDL {\n let Boolean canonical := false;\n\n method decode ( String text ) {\n return new _KDLParser( text: text, pos: 0 )._parse_document();\n }\n\n method decode_binarystring ( BinaryString raw ) {\n return self.decode( to_string(raw) );\n }\n\n method encode ( value ) {\n if ( value instanceof KDLDocument ) {\n return _kdl_encode_document( value, canonical );\n }\n if ( value instanceof KDLNode ) {\n return _kdl_encode_node( value, 0, canonical );\n }\n if ( value instanceof Array ) {\n return _kdl_encode_document(\n new KDLDocument( nodes: value ),\n canonical,\n );\n }\n die "KDL.encode expects a KDLDocument, KDLNode, or Array of KDLNode";\n }\n\n method encode_binarystring ( value ) {\n return to_binary( self.encode(value) );\n }\n\n method load ( path ) {\n from std/io import Path;\n die "KDL.load is denied by runtime policy" if __system__{deny_fs};\n die "KDL.load expects a std/io Path object" if not( path instanceof Path );\n return self.decode_binarystring( path.slurp() );\n }\n\n method dump ( path, value ) {\n from std/io import Path;\n die "KDL.dump is denied by runtime policy" if __system__{deny_fs};\n die "KDL.dump expects a std/io Path object" if not( path instanceof Path );\n path.spew( self.encode_binarystring(value) );\n return path;\n }\n}\n';
40961
41972
  virtualFiles["/modules/std/data/kdl/json.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/data/kdl/json - JSON-in-KDL structure conversion.\n\n=head1 SYNOPSIS\n\n from std/data/kdl import KDL;\n from std/data/kdl/json import kdl_to_json, json_to_kdl;\n\n let kdl_doc := ( new KDL() ).decode( """- foo=1 bar=#true""" );\n let data := kdl_to_json(kdl_doc);\n let roundtrip := json_to_kdl(data);\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module implements the JSON-in-KDL (JiK) mapping for parsed Zuzu\nobjects. C<kdl_to_json> accepts a C<KDLDocument> or C<KDLNode> and\nreturns native Zuzu data structures. C<json_to_kdl> accepts native\nJSON-like data structures and returns a C<KDLDocument>.\n\nC<kdl_to_json(value, pairlists: true)> maps object-like JiK nodes to\nC<PairList> values instead of C<Dict> values, preserving key order and\nduplicate keys.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item C<< kdl_to_json(value, ... PairList opts) >>\n\nParameters: C<value> is a C<KDLDocument>, C<KDLNode>, or compatible KDL\nvalue and C<opts> may include C<pairlists>. Returns: value. Converts\nJSON-in-KDL structures into native JSON-like ZuzuScript data.\n\n=item C<< json_to_kdl(value) >>\n\nParameters: C<value> is a JSON-like ZuzuScript value. Returns:\nC<KDLDocument>. Converts native JSON-like data into JSON-in-KDL nodes.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/data/kdl/json >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/data/kdl import KDLDocument, KDLNode, KDLValue;\nfrom std/data/kdl/xml import xml_to_kdl;\nfrom std/time import Time;\n\n\nfunction _jik_has_props ( KDLNode node ) {\n return node.props().to_Array().length() > 0;\n}\n\nfunction _jik_literal_value ( value ) {\n die "JSON-in-KDL literal must be a KDLValue"\n if not( value instanceof KDLValue );\n if ( value.is_number() and value.kind() \u2261 "string" ) {\n die "JSON-in-KDL does not support non-finite KDL number keywords";\n }\n return value.native_value();\n}\n\nfunction _jik_is_time_native ( value ) {\n return value instanceof Time;\n}\n\nfunction _jik_pad2 ( value ) {\n let text := "" _ value;\n return length text < 2 ? "0" _ text : text;\n}\n\nfunction _jik_pad4 ( value ) {\n let text := "" _ value;\n while ( length text < 4 ) {\n text := "0" _ text;\n }\n return text;\n}\n\nfunction _jik_time_text ( value ) {\n return _jik_pad4( value.year() )\n _ "-" _ _jik_pad2( value.mon() )\n _ "-" _ _jik_pad2( value.day_of_month() )\n _ "T" _ _jik_pad2( value.hour() )\n _ ":" _ _jik_pad2( value.min() )\n _ ":" _ _jik_pad2( value.sec() );\n}\n\nfunction _jik_value ( value ) {\n if ( value instanceof KDLValue ) {\n return value;\n }\n if ( value \u2261 null ) {\n return new KDLValue( type: "null", value: null );\n }\n\n if ( value instanceof Boolean ) {\n return new KDLValue( type: "boolean", value: value );\n }\n if ( value instanceof Number ) {\n return new KDLValue( type: "number", kind: "float", value: value );\n }\n if ( value instanceof String or value instanceof BinaryString ) {\n return new KDLValue( type: "string", value: "" _ value );\n }\n if ( _jik_is_time_native(value) ) {\n return new KDLValue(\n type: "string",\n value: _jik_time_text(value),\n type_annotation: "date-time",\n );\n }\n\n return new KDLValue( type: typeof value, value: value );\n}\n\nfunction _jik_is_array_native ( value ) {\n return value instanceof Array or value instanceof Set or value instanceof Bag;\n}\n\nfunction _jik_is_object_native ( value ) {\n return value instanceof Dict or value instanceof PairList;\n}\n\nfunction _jik_is_kdl_native ( value ) {\n return value instanceof KDLDocument or value instanceof KDLNode;\n}\n\nfunction _jik_is_xml_native ( value ) {\n try {\n if ( value can nodeType ) {\n return true if value.nodeType() \u2262 null;\n }\n }\n catch {\n }\n\n try {\n return false if not( value can documentElement );\n value.documentElement();\n return true;\n }\n catch {\n }\n\n return false;\n}\n\nfunction _jik_is_opaque_literal_native ( value ) {\n return not _jik_is_array_native(value)\n and not _jik_is_object_native(value)\n and not _jik_is_kdl_native(value)\n and not _jik_is_xml_native(value);\n}\n\nfunction _jik_is_literal_native ( value ) {\n return value \u2261 null\n or value instanceof Boolean\n or value instanceof Number\n or value instanceof String\n or value instanceof BinaryString\n or _jik_is_time_native(value)\n or value instanceof KDLValue\n or _jik_is_opaque_literal_native(value);\n}\n\nfunction _jik_sorted_array ( value ) {\n if ( value instanceof Set or value instanceof Bag ) {\n return value.sortstr();\n }\n return value;\n}\n\nfunction _jik_pairs ( obj ) {\n let out := [];\n if ( obj instanceof PairList ) {\n for ( let p in obj.to_Array() ) {\n out.push( p{pair} );\n }\n return out;\n }\n\n for ( let key in obj.sorted_keys() ) {\n out.push( [ key, obj.get(key) ] );\n }\n return out;\n}\n\nfunction _jik_native_to_node;\nfunction _jik_native_to_nodes;\n\nfunction _jik_xml_nodes ( value ) {\n return xml_to_kdl(value).nodes();\n}\n\nfunction _jik_kdl_nodes ( value ) {\n if ( value instanceof KDLDocument ) {\n return value.nodes();\n }\n return [ value ];\n}\n\nfunction _jik_structural_nodes ( value, String name := "-" ) {\n let nodes := _jik_is_kdl_native(value)\n ? _jik_kdl_nodes(value)\n : _jik_xml_nodes(value);\n\n if ( name \u2261 "-" ) {\n return nodes.length() = 0 ? [ new KDLNode( name: name ) ] : nodes;\n }\n return [ new KDLNode( name: name, children: nodes ) ];\n}\n\nfunction _jik_make_array_node ( value, String name := "-" ) {\n let items := _jik_sorted_array(value);\n let all_literals := true;\n for ( let item in items ) {\n all_literals := false unless _jik_is_literal_native(item);\n }\n\n let annotate := items.length() < 2;\n if ( all_literals ) {\n return new KDLNode(\n name: name,\n type_annotation: annotate ? "array" : null,\n args: items.map( fn item -> _jik_value(item) ),\n );\n }\n\n let children := [];\n for ( let item in items ) {\n for ( let child in _jik_native_to_nodes( item, "-" ) ) {\n children.push(child);\n }\n }\n\n return new KDLNode(\n name: name,\n type_annotation: annotate ? "array" : null,\n children: children,\n );\n}\n\nfunction _jik_object_needs_annotation ( Array pairs, Boolean pairlist ) {\n if ( pairs.length() = 0 ) {\n return true;\n }\n if ( pairlist ) {\n return pairs.all( fn pair -> pair[0] \u2261 "-" );\n }\n return pairs.length() = 1 and pairs[0][0] \u2261 "-"\n and not _jik_is_literal_native( pairs[0][1] );\n}\n\nfunction _jik_make_object_node ( value, String name := "-" ) {\n let pairs := _jik_pairs(value);\n let pairlist := value instanceof PairList;\n let props := new PairList();\n let children := [];\n\n if ( pairlist ) {\n for ( let pair in pairs ) {\n for ( let child in _jik_native_to_nodes( pair[1], pair[0] ) ) {\n children.push(child);\n }\n }\n }\n else {\n for ( let pair in pairs ) {\n if ( _jik_is_literal_native( pair[1] ) ) {\n props.add( pair[0], _jik_value( pair[1] ) );\n }\n else {\n for ( let child in _jik_native_to_nodes( pair[1], pair[0] ) ) {\n children.push(child);\n }\n }\n }\n }\n\n return new KDLNode(\n name: name,\n type_annotation: _jik_object_needs_annotation( pairs, pairlist )\n ? "object"\n : null,\n props: props,\n children: children,\n );\n}\n\nfunction _jik_native_to_node ( value, String name := "-" ) {\n if ( _jik_is_literal_native(value) ) {\n return new KDLNode( name: name, args: [ _jik_value(value) ] );\n }\n if ( _jik_is_array_native(value) ) {\n return _jik_make_array_node( value, name );\n }\n if ( _jik_is_object_native(value) ) {\n return _jik_make_object_node( value, name );\n }\n if ( _jik_is_kdl_native(value) or _jik_is_xml_native(value) ) {\n let nodes := _jik_structural_nodes( value, name );\n return nodes.length() = 1 ? nodes[0] : new KDLNode(\n name: name,\n children: nodes,\n );\n }\n die `Cannot convert ${typeof value} to JSON-in-KDL`;\n}\n\nfunction _jik_native_to_nodes ( value, String name := "-" ) {\n if ( _jik_is_kdl_native(value) or _jik_is_xml_native(value) ) {\n return _jik_structural_nodes( value, name );\n }\n return [ _jik_native_to_node( value, name ) ];\n}\n\nfunction _jik_node_has_only_dash_children ( KDLNode node ) {\n return node.children().all( fn child -> child.name() \u2261 "-" );\n}\n\nfunction _jik_node_kind ( KDLNode node ) {\n let annotation := node.type_annotation();\n if ( annotation \u2261 "array" or annotation \u2261 "object" ) {\n return annotation;\n }\n if ( _jik_has_props(node) ) {\n return "object";\n }\n if ( node.children().length() > 0 ) {\n if ( _jik_node_has_only_dash_children(node) ) {\n return "array";\n }\n return "object";\n }\n if ( node.args().length() = 1 ) {\n return "literal";\n }\n if ( node.args().length() > 1 ) {\n return "array";\n }\n die "Empty JSON-in-KDL node must be annotated as array or object";\n}\n\nfunction _jik_node_to_native;\n\nfunction _jik_array_from_node ( KDLNode node, PairList opts ) {\n die "JSON-in-KDL array node cannot contain properties"\n if _jik_has_props(node);\n die "Empty JSON-in-KDL array node must be annotated as array"\n if node.args().length() = 0\n and node.children().length() = 0\n and node.type_annotation() \u2262 "array";\n die "JSON-in-KDL array child nodes must be named \'-\'"\n unless _jik_node_has_only_dash_children(node);\n\n let out := [];\n for ( let arg in node.args() ) {\n out.push( _jik_literal_value(arg) );\n }\n for ( let child in node.children() ) {\n out.push( _jik_node_to_native( child, opts ) );\n }\n return out;\n}\n\nfunction _jik_store_object_item ( obj, String key, value, Boolean pairlists ) {\n if ( pairlists ) {\n obj.add( key, value );\n return;\n }\n die `Duplicate JSON-in-KDL object key \'${key}\'` if obj.exists(key);\n obj.set( key, value );\n}\n\nfunction _jik_object_from_node ( KDLNode node, PairList opts ) {\n die "JSON-in-KDL object node cannot contain unnamed arguments"\n if node.args().length() > 0;\n die "Empty JSON-in-KDL object node must be annotated as object"\n if not _jik_has_props(node)\n and node.children().length() = 0\n and node.type_annotation() \u2262 "object";\n\n let pairlists := opts.get( "pairlists", false );\n let out := pairlists ? new PairList() : {};\n\n for ( let pair in node.props().to_Array() ) {\n let kv := pair{pair};\n _jik_store_object_item(\n out,\n kv[0],\n _jik_literal_value( kv[1] ),\n pairlists,\n );\n }\n for ( let child in node.children() ) {\n _jik_store_object_item(\n out,\n child.name(),\n _jik_node_to_native( child, opts ),\n pairlists,\n );\n }\n return out;\n}\n\nfunction _jik_node_to_native ( KDLNode node, PairList opts ) {\n let kind := _jik_node_kind(node);\n if ( kind \u2261 "literal" ) {\n die "JSON-in-KDL literal node must contain one argument only"\n if node.args().length() \u2262 1\n or _jik_has_props(node)\n or node.children().length() > 0;\n return _jik_literal_value( node.args()[0] );\n }\n if ( kind \u2261 "array" ) {\n return _jik_array_from_node( node, opts );\n }\n if ( kind \u2261 "object" ) {\n return _jik_object_from_node( node, opts );\n }\n die `Unknown JSON-in-KDL node kind \'${kind}\'`;\n}\n\nfunction kdl_to_json ( value, ... PairList opts ) {\n if ( value instanceof KDLNode ) {\n return _jik_node_to_native( value, opts );\n }\n if ( value instanceof KDLDocument ) {\n die "JSON-in-KDL document must contain exactly one top-level node"\n if value.nodes().length() \u2262 1;\n return _jik_node_to_native( value.nodes()[0], opts );\n }\n die "kdl_to_json expects a KDLDocument or KDLNode";\n}\n\nfunction json_to_kdl ( value ) {\n if ( value instanceof KDLDocument ) {\n return value;\n }\n if ( value instanceof KDLNode ) {\n return value;\n }\n return new KDLDocument( nodes: [ _jik_native_to_node(value) ] );\n}\n';
40962
- virtualFiles["/modules/std/data/kdl/xml.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/data/kdl/xml - XML-in-KDL structure conversion.\n\n=head1 SYNOPSIS\n\n from std/data/kdl import KDL;\n from std/data/kdl/xml import kdl_to_xml, xml_to_kdl;\n from std/data/xml import XML;\n\n let kdl_doc := ( new KDL() ).decode(\n "a href=\\"http://example.com\\" \\"here\'s a link\\""\n );\n let xml_doc := kdl_to_xml(kdl_doc);\n let roundtrip := xml_to_kdl(xml_doc);\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module implements the XML-in-KDL (XiK) mapping for parsed Zuzu\nobjects. C<kdl_to_xml> accepts a C<KDLDocument> or C<KDLNode> and\nreturns an C<XMLDocument>. C<xml_to_kdl> accepts an C<XMLDocument> or\nC<XMLNode> and returns a C<KDLDocument>.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item C<< kdl_to_xml(value) >>\n\nParameters: C<value> is a C<KDLDocument>, C<KDLNode>, or compatible KDL\nvalue. Returns: C<XMLDocument>. Converts XML-in-KDL structures into an\nXML document.\n\n=item C<< xml_to_kdl(value) >>\n\nParameters: C<value> is an C<XMLDocument>, C<XMLNode>, or compatible XML\nvalue. Returns: C<KDLDocument>. Converts XML data into XML-in-KDL nodes.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/data/kdl/xml >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/data/kdl import KDLDocument, KDLNode, KDLValue;\nfrom std/data/xml import XML;\nfrom std/data/xml/escape import escape_xml;\nfrom std/string import index, substr;\n\nfunction _xik_starts_with ( String text, String prefix ) {\n return substr( text, 0, length prefix ) \u2261 prefix;\n}\n\nfunction _xik_has_props ( KDLNode node ) {\n return node.props().to_Array().length() > 0;\n}\n\nfunction _xik_string_value ( value, String context ) {\n die `${context} must be a KDLValue` if not( value instanceof KDLValue );\n die `${context} must be a string value` if not value.is_string();\n return value.native_value();\n}\n\nfunction _xik_single_string_arg ( KDLNode node, String context ) {\n die `${context} must contain one string argument`\n if node.args().length() \u2262 1;\n return _xik_string_value( node.args()[0], context );\n}\n\nfunction _xik_no_props_or_children ( KDLNode node, String context ) {\n die `${context} cannot contain properties` if _xik_has_props(node);\n die `${context} cannot contain children`\n if node.children().length() > 0;\n}\n\nfunction _xik_attr_text ( PairList props ) {\n let out := "";\n for ( let pair in props.to_Array() ) {\n let kv := pair{pair};\n let key := kv[0];\n let value := _xik_string_value(\n kv[1],\n `XML-in-KDL attribute \'${key}\'`,\n );\n out _= ` ${key}="${escape_xml(value)}"`;\n }\n return out;\n}\n\nfunction _xik_comment_text ( KDLNode node ) {\n _xik_no_props_or_children( node, "XML-in-KDL comment node" );\n let text := _xik_single_string_arg( node, "XML-in-KDL comment node" );\n die "XML-in-KDL comment cannot contain \'--\'" if index( text, "--" ) >= 0;\n die "XML-in-KDL comment cannot end with \'-\'"\n if length text > 0 and substr( text, length text - 1, 1 ) \u2261 "-";\n return "<!--" _ text _ "-->";\n}\n\nfunction _xik_doctype_text ( KDLNode node ) {\n _xik_no_props_or_children( node, "XML-in-KDL doctype node" );\n let text := _xik_single_string_arg( node, "XML-in-KDL doctype node" );\n die "XML-in-KDL doctype cannot contain \'>\'" if index( text, ">" ) >= 0;\n return "<!DOCTYPE " _ text _ ">";\n}\n\nfunction _xik_pi_text ( KDLNode node ) {\n die "XML-in-KDL processing instruction cannot contain children"\n if node.children().length() > 0;\n\n let target := substr( node.name(), 1, length node.name() - 1 );\n die "XML-in-KDL processing instruction has an empty target"\n if target \u2261 "";\n\n let content := "";\n if ( node.args().length() > 0 ) {\n _xik_no_props_or_children(\n node,\n "XML-in-KDL unstructured processing instruction",\n );\n content := _xik_single_string_arg(\n node,\n "XML-in-KDL unstructured processing instruction",\n );\n }\n else {\n content := _xik_attr_text( node.props() );\n content := substr( content, 1, length content - 1 )\n if length content > 0;\n }\n\n die "XML-in-KDL processing instruction cannot contain \'?>\'"\n if index( content, "?>" ) >= 0;\n\n return content \u2261 "" ? `<?${target}?>` : `<?${target} ${content}?>`;\n}\n\nfunction _xik_text_node_text ( KDLNode node ) {\n _xik_no_props_or_children( node, "XML-in-KDL text node" );\n return escape_xml( _xik_single_string_arg( node, "XML-in-KDL text node" ) );\n}\n\nfunction _xik_node_text;\n\nfunction _xik_element_text ( KDLNode node ) {\n die `XML-in-KDL element \'${node.name()}\' cannot have type annotations`\n if node.type_annotation() \u2262 null;\n\n let args := node.args();\n let children := node.children();\n die `XML-in-KDL element \'${node.name()}\' cannot contain multiple arguments`\n if args.length() > 1;\n die `XML-in-KDL element \'${node.name()}\' cannot mix text argument and children`\n if args.length() > 0 and children.length() > 0;\n\n let attrs := _xik_attr_text( node.props() );\n if ( args.length() = 0 and children.length() = 0 ) {\n return `<${node.name()}${attrs}/>`;\n }\n\n let body := "";\n if ( args.length() = 1 ) {\n body := escape_xml(\n _xik_string_value(\n args[0],\n `XML-in-KDL element \'${node.name()}\' text argument`,\n ),\n );\n }\n else {\n for ( let child in children ) {\n body _= _xik_node_text(child);\n }\n }\n\n return `<${node.name()}${attrs}>${body}</${node.name()}>`;\n}\n\nfunction _xik_node_text ( KDLNode node ) {\n let name := node.name();\n if ( name \u2261 "-" ) {\n return _xik_text_node_text(node);\n }\n if ( name \u2261 "!" ) {\n return _xik_comment_text(node);\n }\n if ( name \u2261 "!doctype" ) {\n return _xik_doctype_text(node);\n }\n if ( _xik_starts_with( name, "?" ) ) {\n return _xik_pi_text(node);\n }\n return _xik_element_text(node);\n}\n\nfunction _xik_is_element_node ( KDLNode node ) {\n let name := node.name();\n return name \u2262 "-" and name \u2262 "!" and name \u2262 "!doctype"\n and not _xik_starts_with( name, "?" );\n}\n\nfunction kdl_to_xml ( value ) {\n let nodes;\n if ( value instanceof KDLDocument ) {\n nodes := value.nodes();\n }\n else if ( value instanceof KDLNode ) {\n nodes := [ value ];\n }\n else {\n die "kdl_to_xml expects a KDLDocument or KDLNode";\n }\n\n let element_count := 0;\n let out := "";\n for ( let node in nodes ) {\n element_count++ if _xik_is_element_node(node);\n out _= _xik_node_text(node);\n }\n\n die "XML-in-KDL document must contain exactly one top-level element"\n if element_count \u2262 1;\n\n return XML.parse(out);\n}\n\nfunction _xik_kdl_value ( String text ) {\n return new KDLValue( type: "string", value: text );\n}\n\nfunction _xik_xml_attrs_to_kdl ( xml_node ) {\n let props := new PairList();\n for ( let attr in xml_node.attributes() ) {\n props.add( attr.nodeName(), _xik_kdl_value( attr.nodeValue() ) );\n }\n return props;\n}\n\nfunction _xik_xml_text_to_kdl ( xml_node ) {\n return new KDLNode(\n name: "-",\n args: [ _xik_kdl_value( xml_node.nodeValue() ?: "" ) ],\n );\n}\n\nfunction _xik_xml_comment_to_kdl ( xml_node ) {\n return new KDLNode(\n name: "!",\n args: [ _xik_kdl_value( xml_node.nodeValue() ?: "" ) ],\n );\n}\n\nfunction _xik_xml_node_to_kdl;\n\nfunction _xik_xml_pi_to_kdl ( xml_node ) {\n return new KDLNode(\n name: "?" _ xml_node.nodeName(),\n args: [ _xik_kdl_value( xml_node.nodeValue() ?: "" ) ],\n );\n}\n\nfunction _xik_xml_doctype_to_kdl ( xml_node ) {\n return new KDLNode(\n name: "!doctype",\n args: [ _xik_kdl_value( xml_node.nodeValue() ?: xml_node.nodeName() ) ],\n );\n}\n\nfunction _xik_xml_element_to_kdl ( xml_node ) {\n let child_nodes := xml_node.childNodes();\n let text_only := child_nodes.length() > 0;\n let text := "";\n\n for ( let child in child_nodes ) {\n let kind := 0 + child.nodeType();\n if ( kind = 3 or kind = 4 ) {\n text _= child.nodeValue() ?: "";\n }\n else {\n text_only := false;\n }\n }\n\n if ( text_only ) {\n return new KDLNode(\n name: xml_node.nodeName(),\n props: _xik_xml_attrs_to_kdl(xml_node),\n args: [ _xik_kdl_value(text) ],\n );\n }\n\n let children := [];\n for ( let child in child_nodes ) {\n let kdl_child := _xik_xml_node_to_kdl(child);\n children.push(kdl_child) if kdl_child \u2262 null;\n }\n\n return new KDLNode(\n name: xml_node.nodeName(),\n props: _xik_xml_attrs_to_kdl(xml_node),\n children: children,\n );\n}\n\nfunction _xik_xml_node_to_kdl ( xml_node ) {\n switch ( 0 + xml_node.nodeType() ) {\n case 1:\n return _xik_xml_element_to_kdl(xml_node);\n case 3, 4:\n return _xik_xml_text_to_kdl(xml_node);\n case 7:\n return _xik_xml_pi_to_kdl(xml_node);\n case 8:\n return _xik_xml_comment_to_kdl(xml_node);\n case 10:\n return _xik_xml_doctype_to_kdl(xml_node);\n }\n return null;\n}\n\nfunction _xik_xml_document_nodes ( xml_doc ) {\n let raw_nodes := null;\n try {\n raw_nodes := xml_doc.childNodes();\n }\n catch {\n try {\n raw_nodes := xml_doc.findnodes("/node()");\n }\n catch {\n let root := xml_doc.documentElement();\n raw_nodes := root \u2261 null ? [] : [ root ];\n }\n }\n\n if ( raw_nodes.length() = 0 ) {\n let root := xml_doc.documentElement();\n raw_nodes := root \u2261 null ? [] : [ root ];\n }\n\n let nodes := [];\n for ( let raw in raw_nodes ) {\n let node := _xik_xml_node_to_kdl(raw);\n nodes.push(node) if node \u2262 null;\n }\n return nodes;\n}\n\nfunction xml_to_kdl ( value ) {\n let node_type := null;\n try {\n node_type := 0 + value.nodeType();\n }\n catch {\n }\n\n if ( node_type \u2262 null ) {\n if ( node_type = 9 ) {\n return new KDLDocument( nodes: _xik_xml_document_nodes(value) );\n }\n\n let node := _xik_xml_node_to_kdl(value);\n die "xml_to_kdl cannot convert this XML node type" if node \u2261 null;\n return new KDLDocument( nodes: [ node ] );\n }\n\n try {\n value.documentElement();\n return new KDLDocument( nodes: _xik_xml_document_nodes(value) );\n }\n catch {\n die "xml_to_kdl expects an XMLDocument or XMLNode";\n }\n}\n';
41973
+ virtualFiles["/modules/std/data/kdl/xml.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/data/kdl/xml - XML-in-KDL structure conversion.\n\n=head1 SYNOPSIS\n\n from std/data/kdl import KDL;\n from std/data/kdl/xml import kdl_to_xml, xml_to_kdl;\n from std/data/xml import XML;\n\n let kdl_doc := ( new KDL() ).decode(\n "a href=\\"http://example.com\\" \\"here\'s a link\\""\n );\n let xml_doc := kdl_to_xml(kdl_doc);\n let roundtrip := xml_to_kdl(xml_doc);\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module implements the XML-in-KDL (XiK) mapping for parsed Zuzu\nobjects. C<kdl_to_xml> accepts a C<KDLDocument> or C<KDLNode> and\nreturns an C<XMLDocument>. C<xml_to_kdl> accepts an C<XMLDocument> or\nC<XMLNode> and returns a C<KDLDocument>.\n\n=head1 EXPORTS\n\n=head2 Functions\n\n=over\n\n=item C<< kdl_to_xml(value) >>\n\nParameters: C<value> is a C<KDLDocument>, C<KDLNode>, or compatible KDL\nvalue. Returns: C<XMLDocument>. Converts XML-in-KDL structures into an\nXML document.\n\n=item C<< xml_to_kdl(value) >>\n\nParameters: C<value> is an C<XMLDocument>, C<XMLNode>, or compatible XML\nvalue. Returns: C<KDLDocument>. Converts XML data into XML-in-KDL nodes.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/data/kdl/xml >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/data/kdl import KDLDocument, KDLNode, KDLValue;\nfrom std/data/xml import XML;\nfrom std/data/xml/escape import escape_xml;\nfrom std/string import index, substr;\n\nfunction _xik_starts_with ( String text, String prefix ) {\n return substr( text, 0, length prefix ) \u2261 prefix;\n}\n\nfunction _xik_has_props ( KDLNode node ) {\n return node.props().to_Array().length() > 0;\n}\n\nfunction _xik_string_value ( value, String context ) {\n die `${context} must be a KDLValue` if not( value instanceof KDLValue );\n die `${context} must be a string value` if not value.is_string();\n return value.native_value();\n}\n\nfunction _xik_single_string_arg ( KDLNode node, String context ) {\n die `${context} must contain one string argument`\n if node.args().length() \u2262 1;\n return _xik_string_value( node.args()[0], context );\n}\n\nfunction _xik_no_props_or_children ( KDLNode node, String context ) {\n die `${context} cannot contain properties` if _xik_has_props(node);\n die `${context} cannot contain children`\n if node.children().length() > 0;\n}\n\nfunction _xik_attr_text ( PairList props ) {\n let out := "";\n for ( let pair in props.to_Array() ) {\n let kv := pair{pair};\n let key := kv[0];\n let value := _xik_string_value(\n kv[1],\n `XML-in-KDL attribute \'${key}\'`,\n );\n out _= ` ${key}="${escape_xml(value)}"`;\n }\n return out;\n}\n\nfunction _xik_comment_text ( KDLNode node ) {\n _xik_no_props_or_children( node, "XML-in-KDL comment node" );\n let text := _xik_single_string_arg( node, "XML-in-KDL comment node" );\n die "XML-in-KDL comment cannot contain \'--\'" if index( text, "--" ) >= 0;\n die "XML-in-KDL comment cannot end with \'-\'"\n if length text > 0 and substr( text, length text - 1, 1 ) \u2261 "-";\n return "<!--" _ text _ "-->";\n}\n\nfunction _xik_doctype_text ( KDLNode node ) {\n _xik_no_props_or_children( node, "XML-in-KDL doctype node" );\n let text := _xik_single_string_arg( node, "XML-in-KDL doctype node" );\n die "XML-in-KDL doctype cannot contain \'>\'" if index( text, ">" ) >= 0;\n return "<!DOCTYPE " _ text _ ">";\n}\n\nfunction _xik_pi_text ( KDLNode node ) {\n die "XML-in-KDL processing instruction cannot contain children"\n if node.children().length() > 0;\n\n let target := substr( node.name(), 1, length node.name() - 1 );\n die "XML-in-KDL processing instruction has an empty target"\n if target \u2261 "";\n\n let content := "";\n if ( node.args().length() > 0 ) {\n _xik_no_props_or_children(\n node,\n "XML-in-KDL unstructured processing instruction",\n );\n content := _xik_single_string_arg(\n node,\n "XML-in-KDL unstructured processing instruction",\n );\n }\n else {\n content := _xik_attr_text( node.props() );\n content := substr( content, 1, length content - 1 )\n if length content > 0;\n }\n\n die "XML-in-KDL processing instruction cannot contain \'?>\'"\n if index( content, "?>" ) >= 0;\n\n return content \u2261 "" ? `<?${target}?>` : `<?${target} ${content}?>`;\n}\n\nfunction _xik_text_node_text ( KDLNode node ) {\n _xik_no_props_or_children( node, "XML-in-KDL text node" );\n return escape_xml( _xik_single_string_arg( node, "XML-in-KDL text node" ) );\n}\n\nfunction _xik_node_text;\n\nfunction _xik_element_text ( KDLNode node ) {\n die `XML-in-KDL element \'${node.name()}\' cannot have type annotations`\n if node.type_annotation() \u2262 null;\n\n let args := node.args();\n let children := node.children();\n die `XML-in-KDL element \'${node.name()}\' cannot contain multiple arguments`\n if args.length() > 1;\n die `XML-in-KDL element \'${node.name()}\' cannot mix text argument and children`\n if args.length() > 0 and children.length() > 0;\n\n let attrs := _xik_attr_text( node.props() );\n if ( args.length() = 0 and children.length() = 0 ) {\n return `<${node.name()}${attrs}/>`;\n }\n\n let body := "";\n if ( args.length() = 1 ) {\n body := escape_xml(\n _xik_string_value(\n args[0],\n `XML-in-KDL element \'${node.name()}\' text argument`,\n ),\n );\n }\n else {\n for ( let child in children ) {\n body _= _xik_node_text(child);\n }\n }\n\n return `<${node.name()}${attrs}>${body}</${node.name()}>`;\n}\n\nfunction _xik_node_text ( KDLNode node ) {\n let name := node.name();\n if ( name \u2261 "-" ) {\n return _xik_text_node_text(node);\n }\n if ( name \u2261 "!" ) {\n return _xik_comment_text(node);\n }\n if ( name \u2261 "!doctype" ) {\n return _xik_doctype_text(node);\n }\n if ( _xik_starts_with( name, "?" ) ) {\n return _xik_pi_text(node);\n }\n return _xik_element_text(node);\n}\n\nfunction _xik_is_element_node ( KDLNode node ) {\n let name := node.name();\n return name \u2262 "-" and name \u2262 "!" and name \u2262 "!doctype"\n and not _xik_starts_with( name, "?" );\n}\n\nfunction kdl_to_xml ( value ) {\n let nodes;\n if ( value instanceof KDLDocument ) {\n nodes := value.nodes();\n }\n else if ( value instanceof KDLNode ) {\n nodes := [ value ];\n }\n else {\n die "kdl_to_xml expects a KDLDocument or KDLNode";\n }\n\n let element_count := 0;\n let out := "";\n for ( let node in nodes ) {\n element_count++ if _xik_is_element_node(node);\n out _= _xik_node_text(node);\n }\n\n die "XML-in-KDL document must contain exactly one top-level element"\n if element_count \u2262 1;\n\n return XML.parse(out);\n}\n\nfunction _xik_kdl_value ( String text ) {\n return new KDLValue( type: "string", value: text );\n}\n\nfunction _xik_xml_attrs_to_kdl ( xml_node ) {\n let props := new PairList();\n for ( let attr in xml_node.attributes() ) {\n props.add( attr.nodeName(), _xik_kdl_value( attr.nodeValue() ) );\n }\n return props;\n}\n\nfunction _xik_xml_text_to_kdl ( xml_node ) {\n return new KDLNode(\n name: "-",\n args: [ _xik_kdl_value( xml_node.nodeValue() ?: "" ) ],\n );\n}\n\nfunction _xik_xml_comment_to_kdl ( xml_node ) {\n return new KDLNode(\n name: "!",\n args: [ _xik_kdl_value( xml_node.nodeValue() ?: "" ) ],\n );\n}\n\nfunction _xik_xml_node_to_kdl;\n\nfunction _xik_xml_pi_to_kdl ( xml_node ) {\n return new KDLNode(\n name: "?" _ xml_node.nodeName(),\n args: [ _xik_kdl_value( xml_node.nodeValue() ?: "" ) ],\n );\n}\n\nfunction _xik_xml_doctype_to_kdl ( xml_node ) {\n return new KDLNode(\n name: "!doctype",\n args: [ _xik_kdl_value( xml_node.nodeValue() or? xml_node.nodeName() ) ],\n );\n}\n\nfunction _xik_xml_element_to_kdl ( xml_node ) {\n let child_nodes := xml_node.childNodes();\n let text_only := child_nodes.length() > 0;\n let text := "";\n\n for ( let child in child_nodes ) {\n let kind := 0 + child.nodeType();\n if ( kind = 3 or kind = 4 ) {\n text _= child.nodeValue() ?: "";\n }\n else {\n text_only := false;\n }\n }\n\n if ( text_only ) {\n return new KDLNode(\n name: xml_node.nodeName(),\n props: _xik_xml_attrs_to_kdl(xml_node),\n args: [ _xik_kdl_value(text) ],\n );\n }\n\n let children := [];\n for ( let child in child_nodes ) {\n let kdl_child := _xik_xml_node_to_kdl(child);\n children.push(kdl_child) if kdl_child \u2262 null;\n }\n\n return new KDLNode(\n name: xml_node.nodeName(),\n props: _xik_xml_attrs_to_kdl(xml_node),\n children: children,\n );\n}\n\nfunction _xik_xml_node_to_kdl ( xml_node ) {\n switch ( 0 + xml_node.nodeType() ) {\n case 1:\n return _xik_xml_element_to_kdl(xml_node);\n case 3, 4:\n return _xik_xml_text_to_kdl(xml_node);\n case 7:\n return _xik_xml_pi_to_kdl(xml_node);\n case 8:\n return _xik_xml_comment_to_kdl(xml_node);\n case 10:\n return _xik_xml_doctype_to_kdl(xml_node);\n }\n return null;\n}\n\nfunction _xik_xml_document_nodes ( xml_doc ) {\n let raw_nodes := null;\n try {\n raw_nodes := xml_doc.childNodes();\n }\n catch {\n try {\n raw_nodes := xml_doc.findnodes("/node()");\n }\n catch {\n let root := xml_doc.documentElement();\n raw_nodes := root \u2261 null ? [] : [ root ];\n }\n }\n\n if ( raw_nodes.length() = 0 ) {\n let root := xml_doc.documentElement();\n raw_nodes := root \u2261 null ? [] : [ root ];\n }\n\n let nodes := [];\n for ( let raw in raw_nodes ) {\n let node := _xik_xml_node_to_kdl(raw);\n nodes.push(node) if node \u2262 null;\n }\n return nodes;\n}\n\nfunction xml_to_kdl ( value ) {\n let node_type := null;\n try {\n node_type := 0 + value.nodeType();\n }\n catch {\n }\n\n if ( node_type \u2262 null ) {\n if ( node_type = 9 ) {\n return new KDLDocument( nodes: _xik_xml_document_nodes(value) );\n }\n\n let node := _xik_xml_node_to_kdl(value);\n die "xml_to_kdl cannot convert this XML node type" if node \u2261 null;\n return new KDLDocument( nodes: [ node ] );\n }\n\n try {\n value.documentElement();\n return new KDLDocument( nodes: _xik_xml_document_nodes(value) );\n }\n catch {\n die "xml_to_kdl expects an XMLDocument or XMLNode";\n }\n}\n';
40963
41974
  virtualFiles["/modules/std/path/jsonpointer.zzm"] = `=encoding utf8
40964
41975
 
40965
41976
  =head1 NAME
@@ -45515,8 +46526,8 @@ function unescape_xml ( value ) {
45515
46526
  `;
45516
46527
  virtualFiles["/modules/std/path/simple.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/simple - Tiny JSONPath/XPath-like traversal helper.\n\n=head1 SYNOPSIS\n\n from std/path/simple import SimplePath;\n\n let data := {\n store: {\n books: [\n { author: "Nigel Rees" },\n { author: "J. R. R. Tolkien" },\n ],\n },\n };\n\n let p := new SimplePath( path: "store.books[*].author" );\n for ( let name in p.query(data) ) {\n say name;\n }\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nC<SimplePath> implements only a tiny subset of path traversal:\n\n=over\n\n=item * C<.something>\n\nDict/PairList lookup by key.\n\n=item * C<.*>\n\nDict/PairList wildcard that yields all values.\n\n=item * C<[1]>\n\nArray lookup by numeric index.\n\n=item * C<[-1]>\n\nNegative indexes count from the end of arrays.\n\n=item * C<[*]>\n\nArray/Bag/Set wildcard that yields all items.\n\n=back\n\nNo other syntax is supported.\n\nThe public API intentionally mirrors key C<std/path/z> methods:\nC<get>, C<select>, C<query>, C<first>, C<exists>,\nC<expression>, C<assign_first>, C<assign_all>, C<assign_maybe>,\nC<ref_first>, C<ref_all>, and C<ref_maybe>.\n\nThe path operators C<@>, C<@@>, and C<@?> can be set to use this module\nin a lexical scope:\n\n from std/path/simple import SimplePath;\n SimplePath.use();\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<< SimplePath({ path: String }) >>\n\nConstructs a simple path selector. Returns: C<SimplePath>.\n\n=over\n\n=item C<< SimplePath.use() >>\n\nParameters: none. Returns: C<null>. Makes this path class the lexical\nimplementation for C<@>, C<@@>, and C<@?>.\n\n=item C<< path.expression() >>\n\nParameters: none. Returns: C<String>. Returns the original path\nexpression.\n\n=item C<< path.get(value) >>, C<< path.select(value) >>, C<< path.query(value) >>\n\nParameters: C<value> is the query root. Returns: C<Array>. Evaluates the\npath and returns selected values.\n\n=item C<< path.first(value, fallback) >>\n\nParameters: C<value> is the query root and C<fallback> is returned when\nthere is no match. Returns: value. Returns the first selected value.\n\n=item C<< path.exists(value) >>\n\nParameters: C<value> is the query root. Returns: C<Boolean>. Returns\ntrue when the path selects at least one value.\n\n=item C<< path.assign_first(target, value, op := ":=", weak := false) >>\n\nParameters: C<target> is the query root, C<value> is the assignment\nvalue, C<op> is an assignment operator, and C<weak> is accepted for path\nAPI compatibility. Returns: value. Updates the first selected location.\n\n=item C<< path.assign_all(target, value, op := ":=", weak := false) >>\n\nParameters: same as C<assign_first>. Returns: value. Updates every\nselected location.\n\n=item C<< path.assign_maybe(target, value, op := ":=", weak := false) >>\n\nParameters: same as C<assign_first>. Returns: C<Boolean>. Updates the\nfirst selected location when one exists.\n\n=item C<< path.ref_first(target) >>\n\nParameters: C<target> is the query root. Returns: C<Function>. Returns a\nreference-like getter/setter for the first selected location.\n\n=item C<< path.ref_all(target) >>\n\nParameters: C<target> is the query root. Returns: C<Array>. Returns\nreference-like getter/setters for all selected locations.\n\n=item C<< path.ref_maybe(target) >>\n\nParameters: C<target> is the query root. Returns: C<Function> or\nC<null>. Returns a reference-like getter/setter for the first selected\nlocation when one exists.\n\n=back\n\n=back\n\n=cut\n\nfrom std/string import substr;\n\n\nclass SimplePath {\n let String path;\n let Array _steps := [];\n\n static method use () {\n from std/internals import setupperprop;\n setupperprop( 1, "paths", self );\n }\n\n method __build__ () {\n _steps := self._parse(path);\n }\n\n method expression () {\n return path;\n }\n\n method _parse ( String raw ) {\n let text := raw;\n let steps := [];\n let i := 0;\n\n while ( i < length text ) {\n let ch := substr( text, i, 1 );\n if ( ch \u2261 "." ) {\n i++;\n die "SimplePath parse error: expected name or * after \'.\'"\n if i >= length text;\n let after_dot := substr( text, i, 1 );\n if ( after_dot \u2261 "*" ) {\n steps.push( { kind: "dict_wildcard" } );\n i++;\n next;\n }\n let start := i;\n while ( i < length text ) {\n let c := substr( text, i, 1 );\n last if c \u2261 "." or c \u2261 "[" or c \u2261 "]";\n i++;\n }\n let key := substr( text, start, i - start );\n die "SimplePath parse error: empty key"\n if key \u2261 "";\n steps.push( { kind: "key", value: key } );\n next;\n }\n\n if ( ch \u2261 "[" ) {\n let close := i + 1;\n while ( close < length text and substr( text, close, 1 ) \u2262 "]" ) {\n close++;\n }\n die "SimplePath parse error: missing closing \']\'"\n if close >= length text;\n let inner := substr( text, i + 1, close - i - 1 );\n if ( inner \u2261 "*" ) {\n steps.push( { kind: "list_wildcard" } );\n }\n else if ( inner ~ /^-?[0-9]+$/ ) {\n steps.push( { kind: "index", value: int(inner) } );\n }\n else {\n die `SimplePath parse error: unsupported bracket token \'[${inner}]\'`;\n }\n i := close + 1;\n next;\n }\n\n let start := i;\n while ( i < length text ) {\n let c := substr( text, i, 1 );\n last if c \u2261 "." or c \u2261 "[" or c \u2261 "]";\n i++;\n }\n let key := substr( text, start, i - start );\n die `SimplePath parse error: unexpected token \'${ch}\'`\n if key \u2261 "";\n steps.push( { kind: "key", value: key } );\n }\n\n return steps;\n }\n\n method _node ( value, parent, key ) {\n return {\n value: value,\n parent: parent,\n key: key,\n };\n }\n\n method _evaluate_nodes ( value ) {\n let current := [ self._node( value, null, null ) ];\n\n for ( let step in _steps ) {\n let next_nodes := [];\n\n for ( let node in current ) {\n let v := node{value};\n\n if ( step{kind} \u2261 "key" ) {\n if ( v instanceof Dict and v.exists( step{value} ) ) {\n next_nodes.push( self._node( v.get( step{value} ), node, step{value} ) );\n }\n else if ( v instanceof PairList ) {\n for ( let item in v.get_all( step{value} ) ) {\n next_nodes.push( self._node( item, node, step{value} ) );\n }\n }\n next;\n }\n\n if ( step{kind} \u2261 "dict_wildcard" ) {\n if ( v instanceof Dict ) {\n for ( let k in v.keys() ) {\n next_nodes.push( self._node( v.get(k), node, k ) );\n }\n }\n else if ( v instanceof PairList ) {\n for ( let pair in v.to_Array() ) {\n next_nodes.push( self._node( pair.value, node, pair.key ) );\n }\n }\n next;\n }\n\n if ( step{kind} \u2261 "index" ) {\n if ( v instanceof Array ) {\n let idx := step{value};\n if ( idx < 0 ) {\n idx := v.length() + idx;\n }\n if ( idx >= 0 and idx < v.length() ) {\n next_nodes.push( self._node( v[idx], node, idx ) );\n }\n }\n next;\n }\n\n if ( step{kind} \u2261 "list_wildcard" ) {\n if ( v instanceof Array ) {\n let i := 0;\n while ( i < v.length() ) {\n next_nodes.push( self._node( v[i], node, i ) );\n i++;\n }\n }\n else if ( v instanceof Bag or v instanceof Set ) {\n for ( let item in v.to_Array() ) {\n next_nodes.push( self._node( item, node, "*" ) );\n }\n }\n next;\n }\n }\n\n current := next_nodes;\n }\n\n return current;\n }\n\n method _evaluate ( value ) {\n return self._evaluate_nodes(value).map( fn n -> n{value} );\n }\n\n method get ( value ) {\n return self._evaluate(value);\n }\n\n method select ( value ) {\n return self._evaluate(value);\n }\n\n method query ( value ) {\n return self._evaluate(value);\n }\n\n method first ( value, fallback ) {\n let out := self._evaluate(value);\n return out.length() = 0 ? fallback : out[0];\n }\n\n method exists ( value ) {\n return self._evaluate(value).length() > 0;\n }\n\n method _assign_node ( Dict node, value ) {\n let parent := node{parent};\n die "SimplePath assignment target has no parent node"\n if parent \u2261 null;\n\n let container := parent{value};\n let key := node{key};\n\n if ( container instanceof Array ) {\n die "SimplePath assignment expects numeric array index"\n if not( key instanceof Number );\n container[key] := value;\n return value;\n }\n\n if ( container instanceof Dict ) {\n die "SimplePath assignment expects string dict key"\n if not( key instanceof String );\n container{(key)} := value;\n return value;\n }\n\n if ( container instanceof PairList ) {\n die "SimplePath assignment expects string pairlist key"\n if not( key instanceof String );\n container.set( key, value );\n return value;\n }\n\n die `SimplePath assignment target container \'${typeof container}\' is not assignable`;\n }\n\n method _ref_for_node ( Dict node ) {\n let parent := node{parent};\n die "SimplePath assignment target has no parent node"\n if parent \u2261 null;\n\n let container := parent{value};\n let key := node{key};\n\n if ( container instanceof Array ) {\n die "SimplePath assignment expects numeric array index"\n if not( key instanceof Number );\n return \\ container[key];\n }\n\n if ( container instanceof Dict ) {\n die "SimplePath assignment expects string dict key"\n if not( key instanceof String );\n return \\ container{(key)};\n }\n\n if ( container instanceof PairList ) {\n die "SimplePath assignment expects string pairlist key"\n if not( key instanceof String );\n return \\ container{(key)};\n }\n\n die `SimplePath assignment target container \'${typeof container}\' is not assignable`;\n }\n\n method _apply_assignment_ref ( ref, value, op := ":=", weak := false ) {\n die "SimplePath weak assignment is not supported"\n if weak;\n\n if ( op \u2261 ":=" ) {\n return ref(value);\n }\n\n let current := ref();\n\n if ( op \u2261 "+=" ) {\n current += value;\n }\n else if ( op \u2261 "-=" ) {\n current -= value;\n }\n else if ( op \u2261 "*=" or op \u2261 "\xD7=" ) {\n current *= value;\n }\n else if ( op \u2261 "/=" or op \u2261 "\xF7=" ) {\n current /= value;\n }\n else if ( op \u2261 "**=" ) {\n current **= value;\n }\n else if ( op \u2261 "_=" ) {\n current _= value;\n }\n else if ( op \u2261 "?:=" ) {\n current ?:= value;\n }\n else if ( op \u2261 "~=" ) {\n current ~= value[0] -> value[1](m);\n }\n else {\n die `Unsupported path assignment operator \'${op}\'`;\n }\n\n ref(current);\n return current;\n }\n\n method _assign_all_result ( value, op, last_result ) {\n return op \u2261 "~=" ? last_result : value;\n }\n\n method assign_first ( target, value, op := ":=", weak := false ) {\n let nodes := self._evaluate_nodes(target);\n die "SimplePath assignment found no matches"\n if nodes.length() \u2261 0;\n return self._apply_assignment_ref(\n self._ref_for_node( nodes[0] ),\n value,\n op,\n weak,\n );\n }\n\n method assign_all ( target, value, op := ":=", weak := false ) {\n let nodes := self._evaluate_nodes(target);\n if ( nodes.length() \u2261 0 ) {\n return self._assign_all_result( value, op, value );\n }\n\n let i := 0;\n let last_result := value;\n while ( i < nodes.length() ) {\n last_result := self._apply_assignment_ref(\n self._ref_for_node( nodes[i] ),\n value,\n op,\n weak,\n );\n i++;\n }\n\n return self._assign_all_result( value, op, last_result );\n }\n\n method assign_maybe ( target, value, op := ":=", weak := false ) {\n let nodes := self._evaluate_nodes(target);\n if ( nodes.length() \u2261 0 ) {\n return false;\n }\n\n self._apply_assignment_ref(\n self._ref_for_node( nodes[0] ),\n value,\n op,\n weak,\n );\n return true;\n }\n\n method ref_first ( target ) {\n let nodes := self._evaluate_nodes(target);\n die "SimplePath assignment found no matches"\n if nodes.length() \u2261 0;\n return self._ref_for_node( nodes[0] );\n }\n\n method ref_all ( target ) {\n let nodes := self._evaluate_nodes(target);\n let out := [];\n for ( let node in nodes ) {\n out.push( self._ref_for_node(node) );\n }\n return out;\n }\n\n method ref_maybe ( target ) {\n let nodes := self._evaluate_nodes(target);\n return nodes.length() \u2261 0 ? null : self._ref_for_node( nodes[0] );\n }\n}\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/simple >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n';
45517
46528
  virtualFiles["/modules/std/path/z.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z - Pure Zuzu implementation of ZPath selectors.\n\n=head1 SYNOPSIS\n\n from std/path/z import ZPath;\n from std/time import Time;\n\n let data := {\n users: [\n { name: "Ada", age: 32, updated: new Time() },\n { name: "Bob", age: 27 },\n ],\n };\n\n let names := query( data, "/users/*/name" );\n let zp := new ZPath( path: "/users/#0/name" );\n say( zp.first( data, "n/a" ) );\n say( exists( data, "/users/#9/name" ) );\n say( first( data, "/users/#0/updated/@year" ) );\n say( zp.assign_first( data, "Adele" ) );\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nNative (pure-Zuzu) path traversal for structured values.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<< ZPath({ path: String, ast? }) >>\n\nConstructs a compiled ZPath selector. Returns: C<ZPath>.\n\n=over\n\n=item C<< ZPath.use() >>\n\nParameters: none. Returns: C<null>. Makes this path class the lexical\nimplementation for C<@>, C<@@>, and C<@?>.\n\n=item C<< path.get_evaluator() >>\n\nParameters: none. Returns: C<Evaluator>. Returns the evaluator used for\nthis path.\n\n=item C<< path.evaluate(raw, meta := {}) >>\n\nParameters: C<raw> is the query root and C<meta> is optional evaluation\nmetadata. Returns: C<Array>. Evaluates the path and returns selected\nnodes.\n\n=item C<< path.get(raw) >>, C<< path.select(raw) >>, C<< path.query(raw) >>\n\nParameters: C<raw> is the query root. Returns: C<Array>. Evaluates the\npath and returns selected primitive values.\n\n=item C<< path.first(raw, fallback?) >>\n\nParameters: C<raw> is the query root and C<fallback> is optional.\nReturns: value. Returns the first selected value or C<fallback>/C<null>.\n\n=item C<< path.exists(raw) >>\n\nParameters: C<raw> is the query root. Returns: C<Boolean>. Returns true\nwhen the path selects at least one value.\n\n=item C<< path.assign_first(raw, value, op := ":=", weak := false) >>\n\nParameters: C<raw> is the query root, C<value> is the assignment value,\nC<op> is an assignment operator, and C<weak> requests weak assignment.\nReturns: value. Updates the first selected node or throws if none match.\n\n=item C<< path.assign_all(raw, value, op := ":=", weak := false) >>\n\nParameters: same as C<assign_first>. Returns: value. Updates every\nselected node.\n\n=item C<< path.assign_maybe(raw, value, op := ":=", weak := false) >>\n\nParameters: same as C<assign_first>. Returns: C<Boolean>. Updates the\nfirst selected node when one exists.\n\n=item C<< path.ref_first(raw) >>\n\nParameters: C<raw> is the query root. Returns: C<Function>. Returns a\nreference-like getter/setter for the first selected node.\n\n=item C<< path.ref_all(raw) >>\n\nParameters: C<raw> is the query root. Returns: C<Array>. Returns\nreference-like getter/setters for all selected nodes.\n\n=item C<< path.ref_maybe(raw) >>\n\nParameters: C<raw> is the query root. Returns: C<Function> or C<null>.\nReturns a reference-like getter/setter for the first selected node when\none exists.\n\n=back\n\n=back\n\n=head1 USE WITH PATH OPERATORS\n\nThe path operators C<@>, C<@@>, and C<@?> can be set to use this module\nin a lexical scope.\n\n from std/path/z import ZPath;\n\n function find_usernames (data) {\n ZPath.use();\n return data @@ "/users/*/name";\n }\n\nHowever, for repeatedly used paths it may be more efficient to compile the\npath once and use many times:\n\n let _usernames_zpath;\n function find_usernames (data) {\n from std/path/z import ZPath;\n _usernames_zpath ?:= new ZPath( path: "/users/*/name" );\n return data @@ _usernames_zpath;\n }\n\n=head1 SUPPORTED TYPES\n\n=over\n\n=item B<Null>, B<Boolean>, B<Number>, B<String>, B<BinaryString>, B<Regexp>\n\nTreated as terminal nodes. These objects cannot have child objects.\n\n=item B<Array>\n\nArray items can be indexed by number.\n\n=item B<Bag>, B<Set>\n\nItems cannot be indexed by number, but can be returned by "*".\n\n=item B<Dict>\n\nValues are named by their key.\n\n=item B<PairList>\n\nPairs can be indexed by number, named by their key, or use a combination of\nboth.\n\n {{ foo: 11, bar: -1, foo: 22, foo: 33 }}\n\nC<< /#2 >> (0-based index) will retrieve C<< foo: 22 >>.\nC<< /foo >> will retrieve C<< foo: 11 >>, C<< foo: 22 >>, and C<< foo: 33 >>.\nC<< /foo#2 >> (0-based index on just values with key "foo") will retrieve C<< foo: 33 >>.\n\nNote that rather than just retrieving the value, a Pair object is retrieved.\nThe selected Pair exposes C<< @key >> and C<< @value >> attributes. Path\nassignment to a selected Pair, or to its C<< @value >> attribute, replaces\nthat pair entry\'s value while preserving pair order and duplicate keys.\n\n=item B<< Pair >>\n\nPair objects do not have child objects but do have C<< @key >> and\nC<< @value >> attributes.\n\n let pairlist := {{ foo: 11, bar: -1, foo: 22, foo: 33 }};\n say( first( pairlist, "/#2/@key" ) ); // "foo"\n say( first( pairlist, "/#2/@value" ) ); // 22\n\n=item B<< Time >>\n\nTime is treated as a terminal node with attributes C<< @year >>,\nC<< @month >>, C<< @day >>, C<< @hour >>, C<< @min >>, and C<< @sec >>.\n\nSee C<< std/time >>.\n\n=item B<< Path >>\n\nPaths representing files are treated as terminal nodes with attributes\ncorresponding to the values from the C<stat> system call: C<< @dev >>,\nC<< @ino >>, C<< @mode >>, C<< @nlink >>, C<< @uid >>, C<< @gid >>,\nC<< @rdev >>, C<< @size >>, C<< @atime >>, C<< @mtime >>, C<< @ctime >>,\nC<< @blksize >>, and C<< @blocks >>.\n\nSee C<< std/io >>.\n\n=item B<< XMLDocument >>, B<< XMLNode >>, etc.\n\nAre treated roughly how the ZPath specification suggests.\n\n /html/body/table/tbody/tr // all rows in the tbody\n /html/body/table/tbody/tr#0 // the first row in the tbody\n /html/body/table/tbody/#0 // the child element in the tbody\n /html/body/table[@id] // all tables that have an id attribute\n\nSee C<< std/data/xml >>.\n\n=back\n\n=head1 SEE ALSO\n\nSpecification: L<https://zpath.me>.\n\nZPath specification: L<https://zpath.me>.\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z/parser import Parser;\nfrom std/path/z/evaluate import Evaluator;\nfrom std/path/z/context import Ctx;\n\nlet _cache;\ndo {\n from std/cache/lru try import Cache;\n if ( Cache ) {\n _cache := new Cache( capacity: 16 );\n }\n};\n\nclass ZPath {\n let String path;\n let ast;\n let ev;\n\n static method use () {\n from std/internals import setupperprop;\n setupperprop( 1, "paths", self );\n }\n\n method __build__ () {\n ev := self.get_evaluator;\n const p := new Parser( allowed_operators: ev.operator_definitions() );\n ast ?:= _cache\n ? _cache.get( path, fn x \u2192 p.parse_top_level_terms(x) )\n : p.parse_top_level_terms(path);\n }\n\n method get_evaluator () {\n return new Evaluator();\n }\n\n method evaluate ( raw, meta := {} ) {\n\n meta.set( "level", 0 ) unless meta.defined( "level" );\n const ctx := new Ctx(\n root: raw,\n nodeset: meta.get( "nodeset", null ),\n parentset: meta.get( "parentset", null ),\n meta: meta,\n );\n\n const short_circuit := ( meta.get( "want", "all" ) in [ "first", "exists" ] );\n\n let results := [];\n for ( let term in ast ) {\n for ( let node in ev.eval_expr( term, ctx ) ) {\n let next_node := ev.maybe_apply_action( node, ctx );\n results.push(next_node);\n return results if short_circuit;\n }\n }\n\n return results;\n }\n\n method get ( raw ) {\n return self.evaluate(raw).map( fn r \u2192 r.primitive_value );\n }\n\n method select ( raw ) {\n return self.evaluate(raw).map( fn r \u2192 r.primitive_value );\n }\n\n method query ( raw ) {\n return self.evaluate(raw).map( fn r \u2192 r.primitive_value );\n }\n\n method first ( raw, fallback? ) {\n let got := self.evaluate( raw, { want: "first" } );\n return got.empty ? fallback : got[0].primitive_value;\n }\n\n method exists ( raw ) {\n let got := self.evaluate( raw, { want: "exists" } );\n return not got.empty;\n }\n\n method _apply_assignment_ref ( ref, value, op := ":=", weak := false ) {\n if ( op \u2261 ":=" ) {\n return weak ? ref( value, true ) : ref(value);\n }\n\n let current := ref();\n\n if ( op \u2261 "+=" ) {\n current += value;\n }\n else if ( op \u2261 "-=" ) {\n current -= value;\n }\n else if ( op \u2261 "*=" or op \u2261 "\xD7=" ) {\n current *= value;\n }\n else if ( op \u2261 "/=" or op \u2261 "\xF7=" ) {\n current /= value;\n }\n else if ( op \u2261 "**=" ) {\n current **= value;\n }\n else if ( op \u2261 "_=" ) {\n current _= value;\n }\n else if ( op \u2261 "?:=" ) {\n current ?:= value;\n }\n else if ( op \u2261 "~=" ) {\n current ~= value[0] -> value[1](m);\n }\n else {\n die `Unsupported path assignment operator \'${op}\'`;\n }\n\n ref(current);\n return current;\n }\n\n method _assign_all_result ( value, op, last_result ) {\n return op \u2261 "~=" ? last_result : value;\n }\n\n method assign_first ( raw, value, op := ":=", weak := false ) {\n let got := self.evaluate( raw, { want: "first" } );\n die "Path assignment (@) found no matches" if got.empty;\n return self._apply_assignment_ref(\n got[0].ref(),\n value,\n op,\n weak,\n );\n }\n\n method assign_all ( raw, value, op := ":=", weak := false ) {\n let got := self.evaluate(raw);\n if ( got.empty ) {\n return self._assign_all_result( value, op, value );\n }\n\n let last_result := value;\n for ( let node in got ) {\n last_result := self._apply_assignment_ref(\n node.ref(),\n value,\n op,\n weak,\n );\n }\n\n return self._assign_all_result( value, op, last_result );\n }\n\n method assign_maybe ( raw, value, op := ":=", weak := false ) {\n let got := self.evaluate( raw, { want: "first" } );\n if ( got.empty ) {\n return false;\n }\n\n self._apply_assignment_ref( got[0].ref(), value, op, weak );\n return true;\n }\n\n method ref_first ( raw ) {\n let got := self.evaluate( raw, { want: "first" } );\n die "Path assignment (@) found no matches" if got.empty;\n return got[0].ref();\n }\n\n method ref_all ( raw ) {\n return self.evaluate(raw).map( fn n \u2192 n.ref );\n }\n\n method ref_maybe ( raw ) {\n let got := self.evaluate( raw, { want: "first" } );\n return got.empty ? null : got[0].ref();\n }\n}\n';
45518
- virtualFiles["/modules/std/path/z/context.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/context - Evaluation context used by std/path/z.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module provides the pure-Zuzu evaluation context used by ZPath.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<< Ctx({ root, nodeset?, parentset?, meta? }) >>\n\nConstructs a ZPath evaluation context. Returns: C<Ctx>. Wraps C<root>\nas a C<std/path/z/node> C<Node> and stores the active node sets.\n\n=over\n\n=item C<< ctx.with_nodeset(ns, ps) >>\n\nParameters: C<ns> is the next node set and C<ps> is the parent set.\nReturns: C<Ctx>. Returns a copy of the context with different node\nsets.\n\n=item C<< ctx.nested(extras?) >>\n\nParameters: C<extras> is optional metadata. Returns: C<Ctx>. Returns a\nnested context with incremented metadata level.\n\n=item C<< ctx.root() >>\n\nParameters: none. Returns: C<Node>. Returns the root node.\n\n=item C<< ctx.nodeset() >>\n\nParameters: none. Returns: C<Array>. Returns the current node set.\n\n=item C<< ctx.parentset() >>\n\nParameters: none. Returns: C<Array> or C<null>. Returns the parent node\nset.\n\n=item C<< ctx.meta() >>\n\nParameters: none. Returns: C<Dict>. Returns context metadata.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/context >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z/node import Node;\n\nclass Ctx {\n let root := null;\n let nodeset := null;\n let parentset := null;\n let meta := null;\n\n method __build__ () {\n if ( not ( root instanceof Node ) ) {\n let root_obj := root;\n let node_type := null;\n\n try {\n node_type := int( "" _ root_obj.nodeType() );\n }\n catch {\n }\n\n let xml_document := node_type = 9;\n if ( not xml_document and root_obj can documentElement ) {\n try {\n root_obj.documentElement();\n xml_document := true;\n }\n catch {\n }\n }\n\n if ( xml_document ) {\n try {\n let de := root_obj.documentElement();\n root_obj := de if de \u2262 null;\n }\n catch {\n }\n }\n\n root := Node.wrap( root_obj );\n }\n\n nodeset ?:= [ root ];\n meta ?:= { level: 0 };\n }\n\n method with_nodeset ( ns, ps ) {\n return new Ctx(\n root: root,\n nodeset: ns,\n parentset: ps,\n meta: meta,\n );\n }\n\n method nested ( extras? ) {\n let extra_meta := extras ?: {};\n\n let next_meta := {\n level: meta.get( "level", 0 ) + 1,\n };\n\n for ( let pair in extra_meta.enumerate ) {\n next_meta.add( pair );\n }\n\n return new Ctx(\n root: root,\n nodeset: nodeset,\n parentset: parentset,\n meta: next_meta,\n );\n }\n\n method root () { return root; }\n method nodeset () { return nodeset; }\n method parentset () { return parentset; }\n method meta () { return meta; }\n}\n';
45519
- virtualFiles["/modules/std/path/z/evaluate.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/evaluate - Pure Zuzu evaluator for ZPath expressions.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module provides the pure-Zuzu evaluator used by ZPath.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<Evaluator>\n\nPure-Zuzu evaluator for parsed ZPath AST values.\n\n=over\n\n=item C<< evaluator.operator_definitions() >>\n\nParameters: none. Returns: C<Array>. Returns the active operator\ndefinition table.\n\n=item C<< evaluator.function_definitions() >>\n\nParameters: none. Returns: C<Array>. Returns the active function\ndefinition table.\n\n=item C<< evaluator.eval_expr_wrap(ast, ctx) >>\n\nParameters: C<ast> is an AST node and C<ctx> is a C<Ctx>. Returns:\nC<Array>. Evaluates with optional debug dumping.\n\n=item C<< evaluator.eval_expr(ast, ctx) >>\n\nParameters: C<ast> is an AST node and C<ctx> is a C<Ctx>. Returns:\nC<Array>. Evaluates an expression to a node set.\n\n=item C<< evaluator.nested_ctx(ctx, ... PairList extras) >>\n\nParameters: C<ctx> is a C<Ctx> or C<null> and C<extras> are metadata.\nReturns: C<Ctx> or C<null>. Creates a nested evaluation context.\n\n=item C<< evaluator.eval_binop(ast, ctx) >>, C<< evaluator.eval_unop(ast, ctx) >>\n\nParameters: C<ast> is an operator AST node and C<ctx> is a C<Ctx>.\nReturns: C<Array>. Evaluates a binary or unary operator.\n\n=item C<< evaluator.eval_path(path_ast, ctx) >>\n\nParameters: C<path_ast> is a path AST node and C<ctx> is a C<Ctx>.\nReturns: C<Array>. Evaluates a path expression to selected nodes.\n\n=item C<< evaluator.maybe_apply_action(node, ctx) >>\n\nParameters: C<node> is a C<Node> and C<ctx> is a C<Ctx>. Returns:\nC<Node>. Applies any pending path action.\n\n=item C<< evaluator.eval_fn(fn_ast, ctx) >>\n\nParameters: C<fn_ast> is a function-call AST node and C<ctx> is a\nC<Ctx>. Returns: C<Array>. Evaluates a ZPath function call.\n\n=item C<< evaluator.string_replace(string, pattern, replacement) >>\n\nParameters: all arguments are strings or regex-like values. Returns:\nC<String>. Applies ZPath string replacement.\n\n=item C<< evaluator.dedup_nodes(nodes) >>\n\nParameters: C<nodes> is an array of nodes. Returns: C<Array>. Removes\nduplicate nodes while preserving order.\n\n=item C<< evaluator.truthy(n) >>\n\nParameters: C<n> is a node or value. Returns: C<Boolean>. Applies ZPath\ntruthiness.\n\n=item C<< evaluator.to_number(n) >>, C<< evaluator.to_string(n) >>\n\nParameters: C<n> is a node or value. Returns: C<Number> or C<String>.\nCoerces a ZPath value.\n\n=item C<< evaluator.equals(a, b) >>\n\nParameters: C<a> and C<b> are nodes or values. Returns: C<Boolean>.\nCompares two ZPath values.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/evaluate >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/string import replace, index, substr;\nfrom std/path/z/node import Node;\nfrom std/path/z/functions import STANDARD_FUNCTIONS;\nfrom std/path/z/operators import STANDARD_OPERATORS;\n\nlet _do_dump := false;\n\nclass Evaluator {\n let _operator_definitions;\n let _function_definitions;\n\n let \u03B5 := 0.000000001;\n\n method operator_definitions () {\n _operator_definitions ?:= STANDARD_OPERATORS;\n return _operator_definitions;\n }\n\n method function_definitions () {\n _function_definitions ?:= STANDARD_FUNCTIONS;\n return _function_definitions;\n }\n\n method eval_expr_wrap ( ast, ctx ) {\n from std/dump import Dumper;\n let got := self._real_eval_expr ( ast, ctx );\n say `AST ${Dumper.dump(ast)} CTX ${Dumper.dump(ctx)} \u21D2 GOT ${Dumper.dump(got)}` if _do_dump;\n return got;\n }\n\n method eval_expr ( ast, ctx ) {\n\n switch ( ast{t} : eq ) {\n case "num":\n return [ Node.wrap( ast{v} ) ];\n case "str":\n return [ Node.wrap( ast{v} ) ];\n case "path":\n return self.eval_path( ast, ctx );\n case "fn":\n return self.eval_fn( ast, ctx );\n case "un":\n return self.eval_unop( ast, ctx );\n case "bin":\n return self.eval_binop( ast, ctx );\n case "ternary":\n let c := self.eval_expr( ast{c}, ctx );\n let ab := self.truthy( c.get(0) ) ? "a" : "b";\n return self.eval_expr( ast{(ab)}, ctx );\n case "elvis":\n let left := self.eval_expr( ast{c}, ctx );\n return left if self.truthy( left.get(0) );\n return self.eval_expr( ast{b}, ctx );\n default:\n die "Panic! Unknown AST node type!";\n }\n }\n\n method nested_ctx ( ctx, ... PairList extras ) {\n return null if ctx \u2261 null;\n return ctx.nested( extras );\n }\n\n method _hold_parent ( node, parent ) {\n if ( node \u2262 null and parent \u2262 null ) {\n node.hold_parent(parent);\n }\n return node;\n }\n\n method _children ( node ) {\n let out := [];\n for ( let child in node.children() ) {\n out.push( self._hold_parent( child, node ) );\n }\n return out;\n }\n\n method _attributes ( node ) {\n let out := [];\n for ( let attr in node.attributes() ) {\n out.push( self._hold_parent( attr, node ) );\n }\n return out;\n }\n\n method _indexed_child ( node, i ) {\n return self._hold_parent( node.indexed_child(i), node );\n }\n\n method _named_child ( node, name ) {\n return self._hold_parent( node.named_child(name), node );\n }\n\n method _named_indexed_child ( node, name, i ) {\n return self._hold_parent( node.named_indexed_child( name, i ), node );\n }\n\n method eval_binop ( ast, ctx ) {\n const op := ast{op};\n const op_def := self.operator_definitions().first(\n fn x \u2192 x.get_spelling eq op and x.is_binary()\n );\n if ( op_def and op_def{f} ) {\n const implementation := op_def{f};\n return implementation( op_def, self, ast, ctx, ast{l}, ast{r} );\n }\n return [];\n }\n\n method eval_unop ( ast, ctx ) {\n const op := ast{op};\n const op_def := self.operator_definitions().first(\n fn x \u2192 x.get_spelling eq op and x.is_unary()\n );\n if ( op_def and op_def{f} ) {\n const implementation := op_def{f};\n return implementation( op_def, self, ast, ctx, ast{e} );\n }\n return [];\n }\n\n method eval_path ( path_ast, ctx ) {\n let current := [];\n for ( let n in ctx.nodeset() ) {\n current.push(n);\n }\n\n let parentset := ctx.parentset();\n\n for ( let seg in path_ast{s} ) {\n let next_nodes := [];\n\n if ( seg{k} \u2261 "root" ) {\n next_nodes := [ ctx.root() ];\n }\n else if ( seg{k} \u2261 "dot" ) {\n next_nodes := current;\n }\n else if ( seg{k} \u2261 "parent" ) {\n for ( let n in current ) {\n let p := n.parent();\n if ( p \u2262 null ) {\n next_nodes.push(p);\n }\n }\n next_nodes := self.dedup_nodes(next_nodes);\n }\n else if ( seg{k} \u2261 "ancestors" ) {\n let anc := [];\n for ( let n in current ) {\n let p := n.parent();\n while ( p \u2262 null ) {\n anc.push(p);\n p := p.parent();\n }\n }\n next_nodes := self.dedup_nodes(anc);\n }\n else if ( seg{k} \u2261 "star" ) {\n let kids := [];\n for ( let n in current ) {\n for ( let child in self._children(n) ) {\n if ( child.type() \u2262 "attr" ) {\n kids.push(child);\n }\n }\n }\n next_nodes := self.dedup_nodes(kids);\n }\n else if ( seg{k} \u2261 "desc" ) {\n let acc := [];\n let stack := [];\n for ( let n in current ) {\n stack.push(n);\n }\n\n while ( stack.length() > 0 ) {\n let n := stack.shift();\n acc.push(n);\n for ( let child in self._children(n) ) {\n if ( child.type() \u2262 "attr" ) {\n stack.push(child);\n }\n }\n }\n next_nodes := self.dedup_nodes(acc);\n }\n else if ( seg{k} \u2261 "index" ) {\n let idx := seg{i};\n let kids := [];\n for ( let n in current ) {\n let c := self._indexed_child( n, idx );\n kids.push(c) if c \u2262 null;\n }\n next_nodes := self.dedup_nodes(kids);\n }\n else if ( seg{k} \u2261 "fnseg" ) {\n let out := [];\n for ( let n in current ) {\n let seg_ctx := ctx.with_nodeset( [ n ], current );\n let fn_ast := { t: "fn", n: seg{n}, a: seg{a} };\n let res := self.eval_fn( fn_ast, seg_ctx );\n for ( let x in res ) {\n out.push(x);\n }\n }\n next_nodes := out;\n }\n else if ( seg{k} \u2261 "name" ) {\n let name := seg{n};\n\n if ( name ~ /^\\@/ ) {\n if ( name \u2261 "@*" ) {\n let attrs := [];\n for ( let n in current ) {\n for ( let a in self._attributes(n) ) {\n attrs.push(a);\n }\n }\n next_nodes := self.dedup_nodes(attrs);\n }\n else {\n let attrs := [];\n for ( let n in current ) {\n for ( let a in self._attributes(n) ) {\n if ( a.name() \u2261 name ) {\n attrs.push(a);\n }\n }\n }\n next_nodes := self.dedup_nodes(attrs);\n }\n }\n else {\n let kids := [];\n for ( let n in current ) {\n if ( n.can_have_named_indexed_children() ) {\n let idx := 0;\n while ( true ) {\n let c := self._named_indexed_child( n, name, idx );\n last if c \u2261 null;\n kids.push(c);\n idx++;\n }\n }\n else {\n let c := self._named_child( n, name );\n kids.push(c) if c \u2262 null;\n }\n }\n next_nodes := self.dedup_nodes(kids);\n }\n\n if ( seg.exists("i") and seg{i} \u2262 null ) {\n let idx := seg{i};\n let picked := [];\n for ( let n in current ) {\n let c := self._named_indexed_child( n, name, idx );\n picked.push(c) if c \u2262 null;\n }\n next_nodes := self.dedup_nodes(picked);\n }\n }\n else {\n die "Unknown path segment kind: " _ seg{k};\n }\n\n if ( seg.exists("q") and seg{q}.length() > 0 ) {\n for ( let q in seg{q} ) {\n if ( q.exists("t") and q{t} \u2261 "num" and q{v} ~ /^\\d+$/ ) {\n let idx := 0 + q{v};\n\n if ( next_nodes.length() > 0 and self._node_is_xml(next_nodes[0]) ) {\n next_nodes := ( idx \u2265 0 and idx < next_nodes.length() ) ? [ next_nodes[idx] ] : [];\n }\n else {\n let picked := [];\n for ( let node in next_nodes ) {\n let ch := self._children(node)\n .grep( fn c \u2192 c.type() \u2262 "attr" );\n if ( idx \u2265 0 and idx < ch.length() ) {\n picked.push( ch[idx] );\n }\n }\n next_nodes := picked;\n }\n\n next;\n }\n\n let filtered := [];\n let i := 0;\n while ( i < next_nodes.length() ) {\n let node := next_nodes[i];\n let ns_ctx := ctx.with_nodeset( next_nodes, current );\n let filter_ctx := ns_ctx.with_nodeset( [ node ], next_nodes );\n let r := self.eval_expr( q, self.nested_ctx( filter_ctx, want: "exists" ) );\n\n let ok := false;\n if ( q.exists("t") and q{t} \u2261 "path" ) {\n ok := r.length() > 0;\n }\n else {\n ok := self.truthy( r.length() > 0 ? r[0] : null );\n }\n\n if ( ok ) {\n filtered.push(node);\n }\n i++;\n }\n next_nodes := filtered;\n }\n }\n\n parentset := current;\n current := next_nodes;\n }\n\n return current;\n }\n\n method maybe_apply_action ( node, ctx ) {\n const meta := ctx.meta();\n return node if not meta.exists( "action" );\n const action := meta{action};\n return node if action \u2261 null;\n return node if not action.exists( "op" );\n node.do_action(action);\n return node;\n }\n\n method eval_fn ( fn_ast, ctx ) {\n const name := fn_ast{n};\n const fn_def := self.function_definitions().first( fn f \u2192 f.has_name(name) );\n return fn_def{f}( fn_def, self, fn_ast, ctx, fn_ast{a} ?: [] ) if fn_def;\n return [];\n }\n\n method string_replace ( string, pattern, replacement ) {\n let text := string \u2261 null ? "" : "" _ string;\n let rep := replacement \u2261 null ? "" : "" _ replacement;\n\n try {\n return replace( text, pattern, rep, "g" );\n }\n catch {\n return replace( text, "" _ pattern, rep, "g" );\n }\n }\n\n method dedup_nodes ( nodes ) {\n let seen := {};\n let out := [];\n for ( let n in nodes ) {\n let key := n.id ?: ( "anon:" _ out.length() _ ":" _ ( "" _ n.raw() ) );\n if ( not seen.exists(key) ) {\n seen.set( key, true );\n out.push(n);\n }\n }\n return out;\n }\n\n method truthy ( n ) {\n return false if n \u2261 null;\n let pv := n.primitive_value();\n return pv ? true : false;\n }\n\n method to_number ( n ) {\n return null if n \u2261 null;\n return n.number_value();\n }\n\n method to_string ( n ) {\n return null if n \u2261 null;\n return n.string_value();\n }\n\n method equals ( a, b ) {\n return false if a \u2261 null or b \u2261 null;\n\n let a_type := a.type();\n let b_type := b.type();\n\n if ( b_type eq "null" ) {\n return a_type eq "null";\n }\n if ( a_type eq "null" ) {\n return b_type eq "null";\n }\n\n if ( a_type eq "boolean" and b_type eq "boolean" ) {\n let av := a.primitive_value() ? true : false;\n let bv := b.primitive_value() ? true : false;\n return av \u2261 bv;\n }\n\n if ( a_type \u2261 "number" and b_type \u2261 "number" ) {\n let av := a.number_value();\n let bv := b.number_value();\n return false if av \u2261 null or bv \u2261 null;\n\n if ( ( "" _ av ) ~ /\\./ or ( "" _ bv ) ~ /\\./ ) {\n return abs( av - bv ) < \u03B5;\n }\n\n return av = bv;\n }\n\n let string_like := [ "string", "text", "attr", "comment", "element" ];\n if ( a_type in string_like and b_type in string_like ) {\n let av := a.string_value();\n let bv := b.string_value();\n return av eq bv;\n }\n\n let aid := a.id();\n let bid := b.id();\n return false if aid \u2261 null or bid \u2261 null;\n return aid eq bid;\n }\n\n method _node_is_xml ( n ) {\n if ( n \u2261 null ) {\n return false;\n }\n let raw := n.raw();\n try {\n let node_type := raw.nodeType();\n return node_type \u2262 null;\n }\n catch {\n return false;\n }\n }\n}\n';
46529
+ virtualFiles["/modules/std/path/z/context.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/context - Evaluation context used by std/path/z.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module provides the pure-Zuzu evaluation context used by ZPath.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<< Ctx({ root, nodeset?, parentset?, meta? }) >>\n\nConstructs a ZPath evaluation context. Returns: C<Ctx>. Wraps C<root>\nas a C<std/path/z/node> C<Node> and stores the active node sets.\n\n=over\n\n=item C<< ctx.with_nodeset(ns, ps) >>\n\nParameters: C<ns> is the next node set and C<ps> is the parent set.\nReturns: C<Ctx>. Returns a copy of the context with different node\nsets.\n\n=item C<< ctx.nested(extras?) >>\n\nParameters: C<extras> is optional metadata. Returns: C<Ctx>. Returns a\nnested context with incremented metadata level.\n\n=item C<< ctx.root() >>\n\nParameters: none. Returns: C<Node>. Returns the root node.\n\n=item C<< ctx.nodeset() >>\n\nParameters: none. Returns: C<Array>. Returns the current node set.\n\n=item C<< ctx.parentset() >>\n\nParameters: none. Returns: C<Array> or C<null>. Returns the parent node\nset.\n\n=item C<< ctx.meta() >>\n\nParameters: none. Returns: C<Dict>. Returns context metadata.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/context >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z/node import Node;\n\nclass Ctx {\n let root := null;\n let nodeset := null;\n let parentset := null;\n let meta := null;\n\n method __build__ () {\n if ( not ( root instanceof Node ) ) {\n let root_obj := root;\n let node_type := null;\n\n try {\n node_type := int( "" _ root_obj.nodeType() );\n }\n catch {\n }\n\n let xml_document := node_type = 9;\n if ( not xml_document and root_obj can documentElement ) {\n try {\n root_obj.documentElement();\n xml_document := true;\n }\n catch {\n }\n }\n\n if ( xml_document ) {\n try {\n let de := root_obj.documentElement();\n root_obj := de if de \u2262 null;\n }\n catch {\n }\n }\n\n root := Node.wrap( root_obj );\n }\n\n nodeset ?:= [ root ];\n meta ?:= { level: 0 };\n }\n\n method with_nodeset ( ns, ps ) {\n return new Ctx(\n root: root,\n nodeset: ns,\n parentset: ps,\n meta: meta,\n );\n }\n\n method nested ( extras? ) {\n // Extras is optional metadata; an empty map is still a value.\n let extra_meta := extras ?: {};\n\n let next_meta := {\n level: meta.get( "level", 0 ) + 1,\n };\n\n for ( let pair in extra_meta.enumerate ) {\n next_meta.add( pair );\n }\n\n return new Ctx(\n root: root,\n nodeset: nodeset,\n parentset: parentset,\n meta: next_meta,\n );\n }\n\n method root () { return root; }\n method nodeset () { return nodeset; }\n method parentset () { return parentset; }\n method meta () { return meta; }\n}\n';
46530
+ virtualFiles["/modules/std/path/z/evaluate.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/evaluate - Pure Zuzu evaluator for ZPath expressions.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module provides the pure-Zuzu evaluator used by ZPath.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<Evaluator>\n\nPure-Zuzu evaluator for parsed ZPath AST values.\n\n=over\n\n=item C<< evaluator.operator_definitions() >>\n\nParameters: none. Returns: C<Array>. Returns the active operator\ndefinition table.\n\n=item C<< evaluator.function_definitions() >>\n\nParameters: none. Returns: C<Array>. Returns the active function\ndefinition table.\n\n=item C<< evaluator.eval_expr_wrap(ast, ctx) >>\n\nParameters: C<ast> is an AST node and C<ctx> is a C<Ctx>. Returns:\nC<Array>. Evaluates with optional debug dumping.\n\n=item C<< evaluator.eval_expr(ast, ctx) >>\n\nParameters: C<ast> is an AST node and C<ctx> is a C<Ctx>. Returns:\nC<Array>. Evaluates an expression to a node set.\n\n=item C<< evaluator.nested_ctx(ctx, ... PairList extras) >>\n\nParameters: C<ctx> is a C<Ctx> or C<null> and C<extras> are metadata.\nReturns: C<Ctx> or C<null>. Creates a nested evaluation context.\n\n=item C<< evaluator.eval_binop(ast, ctx) >>, C<< evaluator.eval_unop(ast, ctx) >>\n\nParameters: C<ast> is an operator AST node and C<ctx> is a C<Ctx>.\nReturns: C<Array>. Evaluates a binary or unary operator.\n\n=item C<< evaluator.eval_path(path_ast, ctx) >>\n\nParameters: C<path_ast> is a path AST node and C<ctx> is a C<Ctx>.\nReturns: C<Array>. Evaluates a path expression to selected nodes.\n\n=item C<< evaluator.maybe_apply_action(node, ctx) >>\n\nParameters: C<node> is a C<Node> and C<ctx> is a C<Ctx>. Returns:\nC<Node>. Applies any pending path action.\n\n=item C<< evaluator.eval_fn(fn_ast, ctx) >>\n\nParameters: C<fn_ast> is a function-call AST node and C<ctx> is a\nC<Ctx>. Returns: C<Array>. Evaluates a ZPath function call.\n\n=item C<< evaluator.string_replace(string, pattern, replacement) >>\n\nParameters: all arguments are strings or regex-like values. Returns:\nC<String>. Applies ZPath string replacement.\n\n=item C<< evaluator.dedup_nodes(nodes) >>\n\nParameters: C<nodes> is an array of nodes. Returns: C<Array>. Removes\nduplicate nodes while preserving order.\n\n=item C<< evaluator.truthy(n) >>\n\nParameters: C<n> is a node or value. Returns: C<Boolean>. Applies ZPath\ntruthiness.\n\n=item C<< evaluator.to_number(n) >>, C<< evaluator.to_string(n) >>\n\nParameters: C<n> is a node or value. Returns: C<Number> or C<String>.\nCoerces a ZPath value.\n\n=item C<< evaluator.equals(a, b) >>\n\nParameters: C<a> and C<b> are nodes or values. Returns: C<Boolean>.\nCompares two ZPath values.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/evaluate >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/string import replace, index, substr;\nfrom std/path/z/node import Node;\nfrom std/path/z/functions import STANDARD_FUNCTIONS;\nfrom std/path/z/operators import STANDARD_OPERATORS;\n\nlet _do_dump := false;\n\nclass Evaluator {\n let _operator_definitions;\n let _function_definitions;\n\n let \u03B5 := 0.000000001;\n\n method operator_definitions () {\n _operator_definitions ?:= STANDARD_OPERATORS;\n return _operator_definitions;\n }\n\n method function_definitions () {\n _function_definitions ?:= STANDARD_FUNCTIONS;\n return _function_definitions;\n }\n\n method eval_expr_wrap ( ast, ctx ) {\n from std/dump import Dumper;\n let got := self._real_eval_expr ( ast, ctx );\n say `AST ${Dumper.dump(ast)} CTX ${Dumper.dump(ctx)} \u21D2 GOT ${Dumper.dump(got)}` if _do_dump;\n return got;\n }\n\n method eval_expr ( ast, ctx ) {\n\n switch ( ast{t} : eq ) {\n case "num":\n return [ Node.wrap( ast{v} ) ];\n case "str":\n return [ Node.wrap( ast{v} ) ];\n case "path":\n return self.eval_path( ast, ctx );\n case "fn":\n return self.eval_fn( ast, ctx );\n case "un":\n return self.eval_unop( ast, ctx );\n case "bin":\n return self.eval_binop( ast, ctx );\n case "ternary":\n let c := self.eval_expr( ast{c}, ctx );\n let ab := self.truthy( c.get(0) ) ? "a" : "b";\n return self.eval_expr( ast{(ab)}, ctx );\n case "elvis":\n let left := self.eval_expr( ast{c}, ctx );\n let first := left.get( 0, null );\n return left if first \u2262 null and first.primitive_value() \u2262 null;\n return self.eval_expr( ast{b}, ctx );\n default:\n die "Panic! Unknown AST node type!";\n }\n }\n\n method nested_ctx ( ctx, ... PairList extras ) {\n return null if ctx \u2261 null;\n return ctx.nested( extras );\n }\n\n method _hold_parent ( node, parent ) {\n if ( node \u2262 null and parent \u2262 null ) {\n node.hold_parent(parent);\n }\n return node;\n }\n\n method _children ( node ) {\n let out := [];\n for ( let child in node.children() ) {\n out.push( self._hold_parent( child, node ) );\n }\n return out;\n }\n\n method _attributes ( node ) {\n let out := [];\n for ( let attr in node.attributes() ) {\n out.push( self._hold_parent( attr, node ) );\n }\n return out;\n }\n\n method _indexed_child ( node, i ) {\n return self._hold_parent( node.indexed_child(i), node );\n }\n\n method _named_child ( node, name ) {\n return self._hold_parent( node.named_child(name), node );\n }\n\n method _named_indexed_child ( node, name, i ) {\n return self._hold_parent( node.named_indexed_child( name, i ), node );\n }\n\n method eval_binop ( ast, ctx ) {\n const op := ast{op};\n const op_def := self.operator_definitions().first(\n fn x \u2192 x.get_spelling eq op and x.is_binary()\n );\n if ( op_def and op_def{f} ) {\n const implementation := op_def{f};\n return implementation( op_def, self, ast, ctx, ast{l}, ast{r} );\n }\n return [];\n }\n\n method eval_unop ( ast, ctx ) {\n const op := ast{op};\n const op_def := self.operator_definitions().first(\n fn x \u2192 x.get_spelling eq op and x.is_unary()\n );\n if ( op_def and op_def{f} ) {\n const implementation := op_def{f};\n return implementation( op_def, self, ast, ctx, ast{e} );\n }\n return [];\n }\n\n method eval_path ( path_ast, ctx ) {\n let current := [];\n for ( let n in ctx.nodeset() ) {\n current.push(n);\n }\n\n let parentset := ctx.parentset();\n\n for ( let seg in path_ast{s} ) {\n let next_nodes := [];\n\n if ( seg{k} \u2261 "root" ) {\n next_nodes := [ ctx.root() ];\n }\n else if ( seg{k} \u2261 "dot" ) {\n next_nodes := current;\n }\n else if ( seg{k} \u2261 "parent" ) {\n for ( let n in current ) {\n let p := n.parent();\n if ( p \u2262 null ) {\n next_nodes.push(p);\n }\n }\n next_nodes := self.dedup_nodes(next_nodes);\n }\n else if ( seg{k} \u2261 "ancestors" ) {\n let anc := [];\n for ( let n in current ) {\n let p := n.parent();\n while ( p \u2262 null ) {\n anc.push(p);\n p := p.parent();\n }\n }\n next_nodes := self.dedup_nodes(anc);\n }\n else if ( seg{k} \u2261 "star" ) {\n let kids := [];\n for ( let n in current ) {\n for ( let child in self._children(n) ) {\n if ( child.type() \u2262 "attr" ) {\n kids.push(child);\n }\n }\n }\n next_nodes := self.dedup_nodes(kids);\n }\n else if ( seg{k} \u2261 "desc" ) {\n let acc := [];\n let stack := [];\n for ( let n in current ) {\n stack.push(n);\n }\n\n while ( stack.length() > 0 ) {\n let n := stack.shift();\n acc.push(n);\n for ( let child in self._children(n) ) {\n if ( child.type() \u2262 "attr" ) {\n stack.push(child);\n }\n }\n }\n next_nodes := self.dedup_nodes(acc);\n }\n else if ( seg{k} \u2261 "index" ) {\n let idx := seg{i};\n let kids := [];\n for ( let n in current ) {\n let c := self._indexed_child( n, idx );\n kids.push(c) if c \u2262 null;\n }\n next_nodes := self.dedup_nodes(kids);\n }\n else if ( seg{k} \u2261 "fnseg" ) {\n let out := [];\n for ( let n in current ) {\n let seg_ctx := ctx.with_nodeset( [ n ], current );\n let fn_ast := { t: "fn", n: seg{n}, a: seg{a} };\n let res := self.eval_fn( fn_ast, seg_ctx );\n for ( let x in res ) {\n out.push(x);\n }\n }\n next_nodes := out;\n }\n else if ( seg{k} \u2261 "name" ) {\n let name := seg{n};\n\n if ( name ~ /^\\@/ ) {\n if ( name \u2261 "@*" ) {\n let attrs := [];\n for ( let n in current ) {\n for ( let a in self._attributes(n) ) {\n attrs.push(a);\n }\n }\n next_nodes := self.dedup_nodes(attrs);\n }\n else {\n let attrs := [];\n for ( let n in current ) {\n for ( let a in self._attributes(n) ) {\n if ( a.name() \u2261 name ) {\n attrs.push(a);\n }\n }\n }\n next_nodes := self.dedup_nodes(attrs);\n }\n }\n else {\n let kids := [];\n for ( let n in current ) {\n if ( n.can_have_named_indexed_children() ) {\n let idx := 0;\n while ( true ) {\n let c := self._named_indexed_child( n, name, idx );\n last if c \u2261 null;\n kids.push(c);\n idx++;\n }\n }\n else {\n let c := self._named_child( n, name );\n kids.push(c) if c \u2262 null;\n }\n }\n next_nodes := self.dedup_nodes(kids);\n }\n\n if ( seg.exists("i") and seg{i} \u2262 null ) {\n let idx := seg{i};\n let picked := [];\n for ( let n in current ) {\n let c := self._named_indexed_child( n, name, idx );\n picked.push(c) if c \u2262 null;\n }\n next_nodes := self.dedup_nodes(picked);\n }\n }\n else {\n die "Unknown path segment kind: " _ seg{k};\n }\n\n if ( seg.exists("q") and seg{q}.length() > 0 ) {\n for ( let q in seg{q} ) {\n if ( q.exists("t") and q{t} \u2261 "num" and q{v} ~ /^\\d+$/ ) {\n let idx := 0 + q{v};\n\n if ( next_nodes.length() > 0 and self._node_is_xml(next_nodes[0]) ) {\n next_nodes := ( idx \u2265 0 and idx < next_nodes.length() ) ? [ next_nodes[idx] ] : [];\n }\n else {\n let picked := [];\n for ( let node in next_nodes ) {\n let ch := self._children(node)\n .grep( fn c \u2192 c.type() \u2262 "attr" );\n if ( idx \u2265 0 and idx < ch.length() ) {\n picked.push( ch[idx] );\n }\n }\n next_nodes := picked;\n }\n\n next;\n }\n\n let filtered := [];\n let i := 0;\n while ( i < next_nodes.length() ) {\n let node := next_nodes[i];\n let ns_ctx := ctx.with_nodeset( next_nodes, current );\n let filter_ctx := ns_ctx.with_nodeset( [ node ], next_nodes );\n let r := self.eval_expr( q, self.nested_ctx( filter_ctx, want: "exists" ) );\n\n let ok := false;\n if ( q.exists("t") and q{t} \u2261 "path" ) {\n ok := r.length() > 0;\n }\n else {\n ok := self.truthy( r.length() > 0 ? r[0] : null );\n }\n\n if ( ok ) {\n filtered.push(node);\n }\n i++;\n }\n next_nodes := filtered;\n }\n }\n\n parentset := current;\n current := next_nodes;\n }\n\n return current;\n }\n\n method maybe_apply_action ( node, ctx ) {\n const meta := ctx.meta();\n return node if not meta.exists( "action" );\n const action := meta{action};\n return node if action \u2261 null;\n return node if not action.exists( "op" );\n node.do_action(action);\n return node;\n }\n\n method eval_fn ( fn_ast, ctx ) {\n const name := fn_ast{n};\n const fn_def := self.function_definitions().first( fn f \u2192 f.has_name(name) );\n // Function-argument ASTs are null or arrays; empty arrays are values.\n return fn_def{f}( fn_def, self, fn_ast, ctx, fn_ast{a} ?: [] ) if fn_def;\n return [];\n }\n\n method string_replace ( string, pattern, replacement ) {\n let text := string \u2261 null ? "" : "" _ string;\n let rep := replacement \u2261 null ? "" : "" _ replacement;\n\n try {\n return replace( text, pattern, rep, "g" );\n }\n catch {\n return replace( text, "" _ pattern, rep, "g" );\n }\n }\n\n method dedup_nodes ( nodes ) {\n let seen := {};\n let out := [];\n for ( let n in nodes ) {\n // Node ids are null or stable identifiers; only null needs anon keys.\n let key := n.id ?: ( "anon:" _ out.length() _ ":" _ ( "" _ n.raw() ) );\n if ( not seen.exists(key) ) {\n seen.set( key, true );\n out.push(n);\n }\n }\n return out;\n }\n\n method truthy ( n ) {\n return false if n \u2261 null;\n let pv := n.primitive_value();\n return pv ? true : false;\n }\n\n method to_number ( n ) {\n return null if n \u2261 null;\n return n.number_value();\n }\n\n method to_string ( n ) {\n return null if n \u2261 null;\n return n.string_value();\n }\n\n method equals ( a, b ) {\n return false if a \u2261 null or b \u2261 null;\n\n let a_type := a.type();\n let b_type := b.type();\n\n if ( b_type eq "null" ) {\n return a_type eq "null";\n }\n if ( a_type eq "null" ) {\n return b_type eq "null";\n }\n\n if ( a_type eq "boolean" and b_type eq "boolean" ) {\n let av := a.primitive_value() ? true : false;\n let bv := b.primitive_value() ? true : false;\n return av \u2261 bv;\n }\n\n if ( a_type \u2261 "number" and b_type \u2261 "number" ) {\n let av := a.number_value();\n let bv := b.number_value();\n return false if av \u2261 null or bv \u2261 null;\n\n if ( ( "" _ av ) ~ /\\./ or ( "" _ bv ) ~ /\\./ ) {\n return abs( av - bv ) < \u03B5;\n }\n\n return av = bv;\n }\n\n let string_like := [ "string", "text", "attr", "comment", "element" ];\n if ( a_type in string_like and b_type in string_like ) {\n let av := a.string_value();\n let bv := b.string_value();\n return av eq bv;\n }\n\n let aid := a.id();\n let bid := b.id();\n return false if aid \u2261 null or bid \u2261 null;\n return aid eq bid;\n }\n\n method _node_is_xml ( n ) {\n if ( n \u2261 null ) {\n return false;\n }\n let raw := n.raw();\n try {\n let node_type := raw.nodeType();\n return node_type \u2262 null;\n }\n catch {\n return false;\n }\n }\n}\n';
45520
46531
  virtualFiles["/modules/std/path/z/functions.zzm"] = `=encoding utf8
45521
46532
 
45522
46533
  =head1 NAME
@@ -45774,6 +46785,7 @@ const STANDARD_FUNCTIONS := [
45774
46785
  n += got.length;
45775
46786
  }
45776
46787
  else {
46788
+ // Parentset can be an empty array; only null falls back.
45777
46789
  const cur := ctx.parentset ?: ctx.nodeset;
45778
46790
  n := cur.length;
45779
46791
  }
@@ -46393,12 +47405,12 @@ const STANDARD_FUNCTIONS := [
46393
47405
  ];
46394
47406
  `;
46395
47407
  virtualFiles["/modules/std/path/z/lexer.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/lexer - Pure Zuzu lexer for ZPath expressions.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module provides the pure-Zuzu lexer used by ZPath.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<< Lexer({ src: String, allowed_operators: Array }) >>\n\nConstructs a ZPath lexer. Returns: C<Lexer>. Tokenizes C<src> using the\nallowed operator table.\n\n=over\n\n=item C<< lexer.peek() >>\n\nParameters: none. Returns: C<Dict>. Returns the current token without\nadvancing.\n\n=item C<< lexer.peek_n(n) >>\n\nParameters: C<n> is a zero-based lookahead offset. Returns: C<Dict>.\nReturns a token ahead of the current position.\n\n=item C<< lexer.peek_kind() >>\n\nParameters: none. Returns: C<String>. Returns the current token kind.\n\n=item C<< lexer.peek_kind_n(n) >>\n\nParameters: C<n> is a zero-based lookahead offset. Returns: C<String>.\nReturns a token kind ahead of the current position.\n\n=item C<< lexer.next_tok() >>\n\nParameters: none. Returns: C<Dict>. Consumes and returns the current\ntoken.\n\n=item C<< lexer.expect(k) >>\n\nParameters: C<k> is the expected token kind. Returns: C<Dict>. Consumes\nand returns the current token, throwing when it has a different kind.\n\n=item C<< lexer.known_operators() >>\n\nParameters: none. Returns: C<Array>. Returns the allowed operator\ndefinitions.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/lexer >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/string import substr;\n\nclass Lexer {\n let src := "";\n let scan_i := 0;\n let toks := [];\n let pos := 0;\n let allowed_operators;\n\n method __build__ () {\n die "Expected some operators" unless allowed_operators;\n toks := self._tokenize(src);\n }\n\n method peek () {\n return toks[pos];\n }\n\n method peek_n ( n ) {\n return toks[pos + n];\n }\n\n method peek_kind () {\n return toks[pos]{k};\n }\n\n method peek_kind_n ( n ) {\n return toks[pos + n]{k};\n }\n\n method next_tok () {\n let tok := toks[pos];\n pos++;\n return tok;\n }\n\n method expect ( k ) {\n let t := self.next_tok();\n die `Expected ${k}, got ${t{k}}` if t{k} \u2262 k;\n return t;\n }\n\n method _is_ws ( c ) {\n return c \u2262 null and c ~ /\\s/;\n }\n\n method _prev_sig ( chars, idx ) {\n let j := idx - 1;\n while ( j >= 0 ) {\n if ( chars[j] ~ /\\s/ ) {\n j--;\n next;\n }\n return chars[j];\n }\n return null;\n }\n\n method _next_sig ( chars, idx, n ) {\n let j := idx + 1;\n while ( j < n ) {\n if ( chars[j] ~ /\\s/ ) {\n j++;\n next;\n }\n return chars[j];\n }\n return null;\n }\n\n method _is_path_ctx_prev ( c ) {\n return c \u2261 "[" or c \u2261 "(" or c \u2261 "," or c \u2261 ":" or c \u2261 "?" or c \u2261 "/";\n }\n\n method _is_path_ctx_next ( c ) {\n return c \u2261 "]" or c \u2261 ")" or c \u2261 "," or c \u2261 ":" or c \u2261 "?" or c \u2261 "/";\n }\n\n method _ws_on_both ( left, right ) {\n return self._is_ws(left) and self._is_ws(right);\n }\n\n method known_operators () {\n return allowed_operators;\n }\n\n method _sorted_operators () {\n return self.known_operators()\n .grep( fn o \u2192 not o.lexer_should_ignore )\n .sort( fn ( x, y ) \u2192 y.char_length <=> x.char_length );\n }\n\n method _operator_at ( chars, i, n, ops ) {\n let oi := 0;\n while ( oi < ops.length ) {\n let op := ops[oi];\n let spell := op{spelling};\n let m := length spell;\n if ( i + m <= n ) {\n let got := "";\n let j := 0;\n while ( j < m ) {\n got _= chars[i + j];\n j++;\n }\n if ( got \u2261 spell ) {\n return op;\n }\n }\n oi++;\n }\n return null;\n }\n\n method _is_name_char ( c ) {\n return c ~ /[A-Za-z0-9_\\-]/;\n }\n\n method _allows_operator_kind ( kind ) {\n return allowed_operators.first( fn op \u2192 op.get_kind() \u2261 kind )\n ? true\n : false;\n }\n\n method _tokenize ( String source ) {\n let t := [];\n let chars := [];\n let n := length source;\n let ops := self._sorted_operators();\n let z := 0;\n while ( z < n ) {\n chars.push( substr( source, z, 1 ) );\n z++;\n }\n\n let i := 0;\n while ( i < n ) {\n let ch := chars[i];\n\n if ( ch ~ /\\s/ ) {\n i++;\n next;\n }\n\n let prev := i > 0 ? chars[i - 1] : null;\n let nxt := i + 1 < n ? chars[i + 1] : null;\n\n let prev_nonws := self._prev_sig( chars, i );\n let next_nonws := self._next_sig( chars, i, n );\n\n function push_token ( tok ) {\n tok{ws_before} := self._is_ws(prev) ? true : false;\n tok{ws_after} := self._is_ws(nxt) ? true : false;\n t.push( tok );\n }\n\n function push_sized_token ( tok, after ) {\n tok{ws_before} := self._is_ws(prev) ? true : false;\n tok{ws_after} := self._is_ws(after) ? true : false;\n t.push( tok );\n }\n\n if ( ch \u2261 "/" ) {\n if (\n self._ws_on_both( prev, nxt )\n and prev_nonws \u2262 null and next_nonws \u2262 null\n and not self._is_path_ctx_prev(prev_nonws)\n and not self._is_path_ctx_next(next_nonws)\n ) {\n push_token( { k: "SLASH", v: "/" } );\n i++;\n next;\n }\n if (\n ( self._is_ws(prev) xor self._is_ws(nxt) )\n and prev_nonws \u2262 null and next_nonws \u2262 null\n and not self._is_path_ctx_prev(prev_nonws)\n and not self._is_path_ctx_next(next_nonws)\n ) {\n die `Binary operator \'/\' requires whitespace around it`;\n }\n t.push( { k: "SLASH_PATH", v: "/" } );\n i++;\n next;\n }\n\n let op := self._operator_at( chars, i, n, ops );\n if ( op \u2262 null ) {\n let spell := op.get_spelling();\n let need_ws := op.requires_whitespace();\n let m := length spell;\n let op_prev := i > 0 ? chars[i - 1] : null;\n let op_next := i + m < n ? chars[i + m] : null;\n let path_ambiguous_star :=\n ( spell \u2261 "*" or spell \u2261 "**" )\n and not self._ws_on_both( op_prev, op_next );\n\n let left_name := false;\n let right_name := false;\n if ( spell ~ /^[A-Za-z_]/ ) {\n left_name := op_prev \u2262 null and self._is_name_char(op_prev);\n right_name := op_next \u2262 null and self._is_name_char(op_next);\n }\n\n if (\n not path_ambiguous_star\n and not left_name\n and not right_name\n ) {\n if ( need_ws ) {\n if ( self._ws_on_both( op_prev, op_next ) ) {\n push_sized_token( { k: op.get_kind(), v: spell }, op_next );\n i := i + m;\n next;\n }\n die `Binary operator \'${spell}\' requires whitespace around it`;\n }\n push_sized_token( { k: op.get_kind(), v: spell }, op_next );\n i := i + m;\n next;\n }\n }\n\n if ( ch \u2261 "(" ) { t.push( { k: "LPAREN", v: "(" } ); i++; next; }\n if ( ch \u2261 ")" ) { t.push( { k: "RPAREN", v: ")" } ); i++; next; }\n if ( ch \u2261 "[" ) { t.push( { k: "LBRACK", v: "[" } ); i++; next; }\n if ( ch \u2261 "]" ) { t.push( { k: "RBRACK", v: "]" } ); i++; next; }\n if ( ch \u2261 "," ) { t.push( { k: "COMMA", v: "," } ); i++; next; }\n\n if ( ch \u2261 "." ) {\n if ( i + 2 < n and chars[i + 1] \u2261 "." and chars[i + 2] \u2261 "*" ) {\n push_token( { k: "DOTDOTSTAR", v: "..*" } );\n i := i + 3;\n next;\n }\n if ( i + 1 < n and chars[i + 1] \u2261 "." ) {\n push_token( { k: "DOTDOT", v: ".." } );\n i := i + 2;\n next;\n }\n push_token( { k: "DOT", v: "." } );\n i++;\n next;\n }\n\n if ( ch \u2261 "*" and self._ws_on_both( prev, nxt ) ) {\n push_token( { k: "STAR", v: "*" } );\n i++;\n next;\n }\n\n if ( ch \u2261 "*" ) {\n if ( i + 1 < n and chars[i + 1] \u2261 "*" ) {\n push_token( { k: "STARSTAR", v: "**" } );\n i := i + 2;\n next;\n }\n push_token( { k: "STAR_PATH", v: "*" } );\n i++;\n next;\n }\n\n if ( self._allows_operator_kind( "ELVIS" ) and ch \u2261 "?" and nxt \u2261 ":" ) {\n let op_next := i + 2 < n ? chars[i + 2] : null;\n if ( self._ws_on_both( prev, op_next ) ) {\n t.push( {\n k: "ELVIS",\n v: "?:",\n ws_before: self._is_ws(prev) ? true : false,\n ws_after: self._is_ws(op_next) ? true : false,\n } );\n i := i + 2;\n next;\n }\n die "Elvis operator \'?:\' requires whitespace around it";\n }\n\n if ( ch \u2261 "?" or ch \u2261 ":" ) {\n if ( self._ws_on_both( prev, nxt ) ) {\n push_token( { k: ch \u2261 "?" ? "QMARK" : "COLON", v: ch } );\n i++;\n next;\n }\n die `Ternary operator \'${ch}\' requires whitespace around it`;\n }\n\n if ( ch \u2261 "\\"" or ch \u2261 "\'" ) {\n let quote := ch;\n i++;\n let s := "";\n let esc := false;\n while ( i < n ) {\n let cc := chars[i];\n i++;\n if ( esc ) {\n if ( cc \u2261 "\\\\" or cc \u2261 quote or cc \u2261 "\\"" or cc \u2261 "\'" ) {\n s _= cc;\n }\n else {\n s _= self._unescape_char(cc);\n }\n esc := false;\n next;\n }\n if ( cc \u2261 "\\\\" ) { esc := true; next; }\n last if cc \u2261 quote;\n s _= cc;\n }\n push_token( { k: "STRING", v: s, q: quote } );\n next;\n }\n\n if ( ch \u2261 "#" ) {\n let j := i + 1;\n let neg := false;\n if ( j < n and chars[j] \u2261 "-" ) {\n neg := true;\n j++;\n }\n die "Invalid index \'#\'" if j >= n or not( chars[j] ~ /\\d/ );\n let num := "";\n while ( j < n and chars[j] ~ /\\d/ ) {\n num _= chars[j];\n j++;\n }\n let parsed := 0 + num;\n parsed := 0 - parsed if neg;\n push_token( { k: "INDEX", v: parsed } );\n i := j;\n next;\n }\n\n if ( ch ~ /[0-9]/ ) {\n let j := i;\n let num := "";\n while ( j < n and chars[j] ~ /[0-9.]/ ) {\n num _= chars[j];\n j++;\n }\n push_token( { k: "NUMBER", v: 0 + num } );\n i := j;\n next;\n }\n\n let name := self._read_name( chars, i );\n if ( name{v} \u2262 "" ) {\n push_token( { k: "NAME", v: name{v} } );\n i := name{i};\n next;\n }\n\n die `Unexpected character \'${ch}\' at position ${i}`;\n }\n\n t.push( { k: "EOF", v: "" } );\n return t;\n }\n\n method _unescape_char ( c ) {\n return "\\n" if c \u2261 "n";\n return "\\r" if c \u2261 "r";\n return "\\t" if c \u2261 "t";\n return c;\n }\n\n method _read_name ( chars, start_i ) {\n let n := chars.length();\n let delim := {\n "\\n": true,\n "\\r": true,\n "\\t": true,\n "(": true,\n ")": true,\n "[": true,\n "]": true,\n "/": true,\n ",": true,\n "=": true,\n "&": true,\n "|": true,\n "!": true,\n "<": true,\n ">": true,\n "#": true,\n " ": true,\n };\n let buf := "";\n let esc := false;\n let i := start_i;\n\n while ( i < n ) {\n let c := chars[i];\n if ( esc ) {\n buf _= c;\n esc := false;\n i++;\n next;\n }\n\n if ( c \u2261 "\\\\" ) {\n esc := true;\n i++;\n next;\n }\n\n last if delim.exists(c);\n last if c ~ /\\s/;\n buf _= c;\n i++;\n }\n\n return { v: "", i: start_i } if buf \u2261 "";\n return { v: buf, i: i };\n }\n}\n';
46396
- virtualFiles["/modules/std/path/z/node.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/node - Node wrapper used by std/path/z.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nObjects of this class wrap underlying values and provide traversal and\ncoercion helpers used while evaluating ZPath expressions.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<Node>\n\nBase wrapper for values traversed by ZPath.\n\n=over\n\n=item C<< Node.from_root(obj) >>\n\nParameters: C<obj> is any query root. Returns: C<Node>. Wraps a root\nvalue for traversal.\n\n=item C<< Node.wrap(obj, parent?, key?, ix?) >>\n\nParameters: C<obj> is any value, with optional parent, key, and index\nmetadata. Returns: C<Node>. Wraps a value in the appropriate node\nsubclass.\n\n=item C<< node.hold_parent(owner) >>\n\nParameters: C<owner> is a parent node. Returns: C<Node>. Retains parent\nmetadata on the node.\n\n=item C<< node.raw() >>, C<< node.value() >>, C<< node.primitive_value() >>\n\nParameters: none. Returns: value. Returns the wrapped raw, node, or\nprimitive value.\n\n=item C<< node.parent() >>\n\nParameters: none. Returns: C<Node> or C<null>. Returns the parent node.\n\n=item C<< node.key() >>, C<< node.ix() >>, C<< node.index() >>, C<< node.id() >>\n\nParameters: none. Returns: value. Returns traversal key, index, or\nstable node id metadata.\n\n=item C<< node.type() >>, C<< node.name() >>\n\nParameters: none. Returns: C<String> or C<null>. Returns the node type\nor name.\n\n=item C<< node.tagged() >>\n\nParameters: none. Returns: C<String> or C<null>. Returns a KDL value\ntag when present.\n\n=item C<< node.has_tagged() >>\n\nParameters: none. Returns: C<Boolean>. Returns true when tag metadata is\navailable.\n\n=item C<< node.string_value() >>, C<< node.number_value() >>\n\nParameters: none. Returns: C<String>, C<Number>, or C<null>. Coerces the\nnode for string or numeric ZPath operations.\n\n=item C<< node.can_have_named_children() >>, C<< node.can_have_indexed_children() >>, C<< node.can_have_named_indexed_children() >>\n\nParameters: none. Returns: C<Boolean>. Reports supported child lookup\nmodes.\n\n=item C<< node.named_child(name) >>, C<< node.indexed_child(i) >>, C<< node.named_indexed_child(name, i) >>\n\nParameters: C<name> is a child name and C<i> is an index. Returns:\nC<Node> or C<null>. Looks up child nodes.\n\n=item C<< node.next_child(child) >>, C<< node.prev_child(child) >>, C<< node.next_sibling() >>, C<< node.prev_sibling() >>\n\nParameters: C<child> is a child node where required. Returns: C<Node> or\nC<null>. Traverses adjacent nodes.\n\n=item C<< node.named_attribute(name) >>\n\nParameters: C<name> is an attribute name. Returns: C<Node> or C<null>.\nLooks up one attribute node.\n\n=item C<< node.children() >>, C<< node.attributes() >>\n\nParameters: none. Returns: C<Array>. Returns child or attribute nodes.\n\n=item C<< node.dump() >>\n\nParameters: none. Returns: C<String>. Returns a diagnostic rendering of\nthe node.\n\n=item C<< node.find(zpath) >>\n\nParameters: C<zpath> is a C<ZPath> object or subclass, such as C<ZZPath>.\nReturns: C<Array>. Evaluates a nested path from this node and returns\nselected node objects.\n\n=item C<< node.do_action(action) >>, C<< node.do_action_on_child(child, action) >>\n\nParameters: C<action> is a path action and C<child> is a child node.\nReturns: value. Applies path mutation actions.\n\n=item C<< node.ref() >>, C<< node.ref_on_child(child) >>\n\nParameters: C<child> is a child node where required. Returns:\nC<Function>. Returns a reference-like getter/setter.\n\n=back\n\n=item C<SimpleNode>, C<StringNode>, C<NumberNode>, C<BooleanNode>, C<NullNode>\n\nScalar node wrappers. They inherit the C<Node> API and override\nC<type>, C<string_value>, or C<number_value> where appropriate.\n\n=item C<ArrayNode>, C<SetNode>, C<BagNode>, C<DictNode>, C<PairListNode>, C<PairNode>\n\nCollection node wrappers. They inherit the C<Node> API and override\nchild, attribute, assignment, and reference methods appropriate to their\ncollection type. PairList children are C<PairNode> objects in pair order;\nC<indexed_child> uses global pair index, while C<named_indexed_child> uses\nthe occurrence index among pairs with the same key.\n\n=item C<KDLDocumentNode>, C<KDLNodeNode>, C<KDLValueNode>\n\nKDL node wrappers. They inherit the C<Node> API and expose KDL children,\nattributes, names, value tags, and scalar coercions.\n\n=item C<XmlNodeNode>\n\nXML node wrapper. It inherits the C<Node> API and exposes XML names,\nchildren, attributes, sibling traversal, and scalar text values.\n\n=item C<TimeNode>\n\nC<std/time> wrapper. It inherits the C<Node> API and exposes time\nattributes plus string and numeric values.\n\n=item C<PathNode>\n\nC<std/io> path wrapper. It inherits the C<Node> API and exposes path\nattributes plus string values.\n\n=item C<WidgetNode>\n\nC<std/gui/objects> widget wrapper. It inherits the C<Node> API and\nexposes widget children by widget class name and index, plus public\nwidget properties as assignable attributes.\n\n=back\n\n=head2 Variables\n\n=over\n\n=item C<determine_class>\n\nType: C<Function>. Chooses the concrete node wrapper class for a raw\nvalue.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/node >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/data/cbor import TaggedValue;\nfrom std/data/kdl import KDLDocument, KDLNode, KDLValue;\nfrom std/internals import class_name, load_module, ref_id;\nfrom std/io try import Path;\nfrom std/math import Math;\nfrom std/string import substr;\nfrom std/time import Time;\n\n\nfunction determine_class;\nlet _widget_class_loaded := false;\nlet _widget_class := null;\n\nconst _WIDGET_ATTRS := [\n { name: "id", getter: "id", setter: "set_id" },\n { name: "enabled", getter: "enabled", setter: "set_enabled" },\n { name: "visible", getter: "visible", setter: "set_visible" },\n { name: "width", getter: "width", accessor: true },\n { name: "height", getter: "height", accessor: true },\n { name: "minwidth", getter: "minwidth", accessor: true },\n { name: "minheight", getter: "minheight", accessor: true },\n { name: "maxwidth", getter: "maxwidth", accessor: true },\n { name: "maxheight", getter: "maxheight", accessor: true },\n { name: "classes", getter: "classes" },\n { name: "title", getter: "title", setter: "set_title", accessor: true },\n { name: "text", getter: "text", setter: "set_text", accessor: true },\n { name: "value", getter: "value", setter: "set_value", accessor: true },\n { name: "placeholder", getter: "placeholder", setter: "set_placeholder", accessor: true },\n { name: "for", getter: "for_id", setter: "set_for_id", accessor: true },\n { name: "align", getter: "align", accessor: true },\n { name: "gap", getter: "gap", accessor: true },\n { name: "padding", getter: "padding", accessor: true },\n { name: "label", getter: "label", accessor: true },\n { name: "collapsible", getter: "collapsible", accessor: true },\n { name: "collapsed", getter: "collapsed", accessor: true },\n { name: "wrap", getter: "wrap", accessor: true },\n { name: "readonly", getter: "readonly", accessor: true },\n { name: "multiline", getter: "multiline", accessor: true },\n { name: "password", getter: "password", accessor: true },\n { name: "required", getter: "required", accessor: true },\n { name: "src", getter: "src", accessor: true },\n { name: "alt", getter: "alt", accessor: true },\n { name: "fit", getter: "fit", accessor: true },\n { name: "checked", getter: "checked", accessor: true },\n { name: "indeterminate", getter: "indeterminate", accessor: true },\n { name: "name", getter: "name", accessor: true },\n { name: "disabled", getter: "disabled", accessor: true },\n { name: "group", getter: "group", accessor: true },\n { name: "multiple", getter: "multiple", accessor: true },\n { name: "min", getter: "min", accessor: true },\n { name: "max", getter: "max", accessor: true },\n { name: "first_day_of_week", getter: "first_day_of_week", accessor: true },\n { name: "orientation", getter: "orientation", accessor: true },\n { name: "step", getter: "step", accessor: true },\n { name: "show_text", getter: "show_text", accessor: true },\n { name: "selected", getter: "selected", accessor: true },\n { name: "placement", getter: "placement", accessor: true },\n { name: "icon", getter: "icon", setter: "set_icon", accessor: true },\n { name: "closable", getter: "closable", accessor: true },\n { name: "selected_index", getter: "selected_index", accessor: true },\n { name: "selected_path", getter: "selected_path", accessor: true },\n { name: "variant", getter: "variant", accessor: true },\n];\n\nfunction _zpath_widget_class () {\n if ( not _widget_class_loaded ) {\n _widget_class_loaded := true;\n try {\n _widget_class := load_module( "std/gui/objects", "Widget" );\n }\n catch {\n _widget_class := null;\n }\n }\n return _widget_class;\n}\n\nfunction _zpath_is_widget ( obj ) {\n let Widget := _zpath_widget_class();\n return Widget \u2262 null and obj instanceof Widget;\n}\n\nfunction _zpath_xml_node_type ( obj ) {\n return null if not( obj can nodeType );\n try {\n return int( "" _ obj.nodeType() );\n }\n catch {\n }\n return null;\n}\n\nfunction _zpath_is_xml_document ( obj ) {\n return true if _zpath_xml_node_type(obj) = 9;\n return false if not( obj can documentElement );\n try {\n obj.documentElement();\n return true;\n }\n catch {\n }\n return false;\n}\n\nfunction _zpath_is_xml_node ( obj ) {\n return true if _zpath_xml_node_type(obj) \u2262 null;\n return _zpath_is_xml_document(obj);\n}\n\nfunction _zpath_widget_attr_name ( name ) {\n return ( name ~ /^@/ ) ? substr( name, 1 ) : name;\n}\n\nfunction _zpath_widget_attr_spec ( name ) {\n let attr := _zpath_widget_attr_name(name);\n return _WIDGET_ATTRS.first( fn spec \u2192 spec{name} \u2261 attr );\n}\n\nfunction _zpath_widget_attr_applies ( widget, spec ) {\n let attr := spec{name};\n if ( attr in [\n "id",\n "enabled",\n "visible",\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n "classes",\n ] ) {\n return true;\n }\n\n switch ( class_name(widget) : eq ) {\n case "Window":\n return attr in [ "title" ];\n case "VBox", "HBox":\n return attr in [ "align", "gap", "padding" ];\n case "Frame":\n return attr in [ "label", "collapsible", "collapsed" ];\n case "Label":\n return attr in [ "text", "for" ];\n case "Text":\n return attr in [ "value", "multiline", "readonly", "wrap" ];\n case "RichText":\n return attr in [ "value", "multiline", "readonly" ];\n case "Image":\n return attr in [ "src", "alt", "fit" ];\n case "Input":\n return attr in [\n "value",\n "placeholder",\n "multiline",\n "readonly",\n "password",\n "required",\n ];\n case "DatePicker":\n return attr in [ "value", "min", "max", "first_day_of_week" ];\n case "Checkbox":\n return attr in [ "label", "checked", "indeterminate" ];\n case "Radio":\n return attr in [ "label", "value", "group", "checked" ];\n case "RadioGroup":\n return attr in [ "name", "value" ];\n case "Select":\n return attr in [ "value", "multiple" ];\n case "Menu":\n return attr in [ "text" ];\n case "MenuItem":\n return attr in [ "text", "disabled" ];\n case "Button":\n return attr in [ "text", "variant" ];\n case "Separator":\n return attr in [ "orientation" ];\n case "Slider":\n return attr in [ "value", "min", "max", "step", "orientation", "readonly" ];\n case "Progress":\n return attr in [ "value", "min", "max", "indeterminate", "show_text" ];\n case "Tabs":\n return attr in [ "selected", "placement" ];\n case "Tab":\n return attr in [ "title", "value", "icon", "selected", "closable" ];\n case "ListView":\n return attr in [ "selected_index", "multiple" ];\n case "TreeView":\n return attr in [ "selected_path", "multiple" ];\n }\n\n return false;\n}\n\nfunction _zpath_widget_get_attr ( widget, spec ) {\n let getter := spec{getter};\n return widget.(getter)();\n}\n\nfunction _zpath_widget_set_attr ( widget, spec, value ) {\n let setter := spec.get( "setter", null );\n if ( setter \u2262 null ) {\n try {\n return widget.(setter)(value);\n }\n catch {\n }\n }\n\n let getter := spec{getter};\n if ( spec.get( "accessor", false ) ) {\n try {\n return widget.(getter)(value);\n }\n catch {\n }\n }\n\n die `Widget attribute @${spec{name}} is not assignable`;\n}\n\nclass Node {\n let raw with set := null;\n let parent but weak := null;\n let _parent_keepalive := null;\n let key := null;\n let id := null;\n let ix := null;\n let tagged with set, has := null;\n\n static method _xml_node_type_code ( value ) {\n try {\n return int( "" _ value.nodeType() );\n }\n catch {\n return null;\n }\n }\n\n static method from_root ( obj ) {\n return self.wrap(obj);\n }\n\n static method wrap ( _obj, parent?, key?, ix? ) {\n\n let obj := _obj;\n\n if ( obj instanceof Node ) {\n return obj;\n }\n\n let Klass := determine_class( obj );\n\n let n := new Klass(\n raw: obj,\n parent: parent,\n key: key,\n ix: ix,\n );\n\n if ( obj instanceof TaggedValue ) {\n n.set_tagged(obj);\n while ( obj instanceof TaggedValue ) {\n obj := obj{value}\n }\n n.set_raw(obj);\n }\n\n n._build_id_with_parent(parent);\n\n return n;\n }\n\n method hold_parent ( owner ) {\n _parent_keepalive := owner;\n return self;\n }\n\n method _use_ref_as_id () {\n return false;\n }\n\n method _build_id () {\n id := self._generate_id;\n }\n\n method _build_id_with_parent ( owner ) {\n id := self._generate_id_with_parent(owner);\n }\n\n method _generate_id () {\n return self._generate_id_with_parent(parent);\n }\n\n method _generate_id_with_parent ( owner ) {\n\n if ( self._use_ref_as_id ) {\n return "ref:" _ ref_id(raw);\n }\n\n if ( owner \u2261 null ) {\n return "root";\n }\n\n if ( ix \u2262 null ) {\n if ( key \u2262 null ) {\n return owner.id _ "/" _ key _ "#" _ ix;\n }\n return owner.id _ "/#" _ ix;\n }\n\n if ( key \u2262 null ) {\n return owner.id _ "/" _ key;\n }\n\n return "rand:" _ floor( 1000 * 1000 * 1000 * Math.rand() );\n }\n\n method raw () { return raw; }\n method parent () { return parent; }\n method key () { return key; }\n method id () { return id; }\n method ix () { return ix; }\n method index () { return ix; }\n method tagged () { return tagged; }\n\n method type () {\n return typeof raw;\n }\n\n method value () {\n return raw;\n }\n\n method primitive_value () {\n return raw;\n }\n\n method string_value () {\n let p := self.primitive_value;\n return p \u2261 null ? null : "" _ p;\n }\n\n method number_value () {\n let v := self.primitive_value();\n return 0 + v if v ~ /^-?(?:[0-9]+(?:\\.[0-9]+)?|\\.[0-9]+)$/;\n return null;\n }\n\n method can_have_named_children () {\n return false;\n }\n\n method can_have_indexed_children () {\n return false;\n }\n\n method can_have_named_indexed_children () {\n return false;\n }\n\n method named_child ( name ) {\n self.children.first( fn kid \u2192 kid.key \u2261 name );\n }\n\n method indexed_child ( i ) {\n self.children.first( fn kid \u2192 kid.ix \u2261 i );\n }\n\n method named_indexed_child ( name, i ) {\n self.children.first( fn kid \u2192 kid.ix \u2261 i and kid.key \u2261 name );\n }\n\n method next_child ( child ) {\n let i := child.ix;\n return null if i \u2261 null;\n return self.indexed_child( i + 1 );\n }\n\n method prev_child ( child ) {\n let i := child.ix;\n return null if i \u2261 null;\n return null if i = 0;\n return self.indexed_child( i - 1 );\n }\n\n method next_sibling () {\n return self.parent.next_child( self );\n }\n\n method prev_sibling () {\n return self.parent.prev_child( self );\n }\n\n method named_attribute ( name ) {\n let attrname := ( name ~ /^@/ ) ? name : `@${name}`;\n self.attributes.first( fn kid \u2192 kid.name \u2261 attrname );\n }\n\n method children () {\n return [];\n }\n\n method attributes () {\n return [];\n }\n\n method name () {\n return key;\n }\n\n method dump () {\n return {\n "@type": self.type(),\n "@id": self.id(),\n "@key": self.key(),\n "@index": self.index(),\n "@value": self.primitive_value(),\n children: self.children().map( fn c -> c.dump() ),\n attributes: self.attributes().map( fn a -> a.dump() ),\n };\n }\n\n method find ( zpath ) {\n from std/path/z import ZPath;\n\n die "Node.find expects a ZPath object"\n unless zpath instanceof ZPath;\n\n return zpath.evaluate(self);\n }\n\n method do_action ( action ) {\n const container_node := self.parent();\n die "Path assignment target has no parent node"\n if container_node \u2261 null;\n return container_node.do_action_on_child( self, action );\n }\n\n method ref () {\n const container_node := self.parent();\n die "Path assignment target has no parent node"\n if container_node \u2261 null;\n return container_node.ref_on_child(self);\n }\n\n method do_action_on_child ( child, action ) {\n die `Path assignment target container \'${self.type()}\' is not assignable`;\n }\n\n method ref_on_child ( child ) {\n die `Path assignment target container \'${self.type()}\' is not assignable`;\n }\n}\n\nclass SimpleNode extends Node {\n}\n\nclass StringNode extends SimpleNode {\n\n method string_value () {\n let raw := self.raw;\n if ( raw instanceof BinaryString ) {\n return to_string( raw );\n }\n return raw;\n }\n\n method type () {\n return "string";\n }\n}\n\nclass NumberNode extends SimpleNode {\n method number_value () {\n let raw := self.raw;\n return raw;\n }\n\n method type () {\n return "number";\n }\n}\n\nclass BooleanNode extends SimpleNode {\n method type () {\n return "boolean";\n }\n}\n\nclass NullNode extends SimpleNode {\n method type () {\n return "null";\n }\n}\n\nclass ArrayNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "list";\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n while ( i < raw.length ) {\n let child := Node.wrap( raw[i], self, null, i );\n out.push(child);\n i++;\n }\n return out;\n }\n\n method can_have_indexed_children () {\n return false;\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n\n const ix := child.ix();\n die "Path assignment expects numeric array index" if ix \u2261 null;\n let container := self.raw();\n container[ix] := action{value};\n return action{value};\n }\n\n method ref_on_child ( child ) {\n const ix := child.ix();\n die "Path assignment expects numeric array index" if ix \u2261 null;\n let container := self.raw();\n return \\ container[ix];\n }\n}\n\nclass SetNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n while ( i < raw.length ) {\n let child := Node.wrap( raw[i], self, null, null );\n out.push(child);\n i++;\n }\n return out;\n }\n}\n\nclass BagNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n while ( i < raw.length ) {\n let child := Node.wrap( raw[i], self, null, null );\n out.push(child);\n i++;\n }\n return out;\n }\n}\n\nclass DictNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "map";\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n for ( let k in raw.keys() ) {\n let child := Node.wrap( raw.get(k), self, k );\n out.push(child);\n }\n return out;\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n\n const key := child.key();\n die "Path assignment expects string dict key" if key \u2261 null;\n let container := self.raw();\n container{(key)} := action{value};\n return action{value};\n }\n\n method ref_on_child ( child ) {\n const key := child.key();\n die "Path assignment expects string dict key" if key \u2261 null;\n let container := self.raw();\n return \\ container{(key)};\n }\n}\n\nclass PairListNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method children () {\n let raw := self.raw;\n let pairs := raw.to_Array();\n let out := [];\n let i := 0;\n while ( i < pairs.length() ) {\n let pair := pairs[i];\n out.push( Node.wrap( pair, self, pair.key, i ) );\n i++;\n }\n return out;\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method named_child ( name ) {\n return self.named_indexed_child( name, 0 );\n }\n\n method indexed_child ( i ) {\n let pairs := self.raw().to_Array();\n return null if i < 0 or i >= pairs.length();\n let pair := pairs[i];\n return Node.wrap( pair, self, pair.key, i );\n }\n\n method named_indexed_child ( name, i ) {\n let pairs := self.raw().to_Array();\n let seen := 0;\n let pos := 0;\n while ( pos < pairs.length() ) {\n let pair := pairs[pos];\n if ( pair.key \u2261 name ) {\n if ( seen = i ) {\n return Node.wrap( pair, self, pair.key, pos );\n }\n seen++;\n }\n pos++;\n }\n return null;\n }\n\n method _replace_value_at ( pair_ix, value ) {\n let container := self.raw();\n let pairs := container.to_Array();\n die "Path assignment expects numeric pairlist index"\n if pair_ix \u2261 null or pair_ix < 0 or pair_ix >= pairs.length();\n\n container.clear();\n let i := 0;\n while ( i < pairs.length() ) {\n let pair := pairs[i];\n container.add( pair.key, i = pair_ix ? value : pair.value );\n i++;\n }\n return value;\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n\n return self._replace_value_at( child.ix(), action{value} );\n }\n\n method ref_on_child ( child ) {\n let pair_ix := child.ix();\n return function ( ... args ) {\n let current := self.indexed_child(pair_ix);\n die "Path assignment target pairlist entry no longer exists"\n if current \u2261 null;\n return current.raw().value if args.length() = 0;\n return self._replace_value_at( pair_ix, args[0] );\n };\n }\n}\n\nclass PairNode extends Node {\n\n method attributes () {\n let raw := self.raw;\n return [ "key", "value" ]\n .map( fn a \u2192 Node.wrap( raw.(a)(), self, `@${a}` ) );\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n return super( child, action ) if child.key() ne "@value";\n\n let container := self.parent();\n return super( child, action )\n if container \u2261 null or not ( container instanceof PairListNode );\n return container.do_action_on_child( self, action );\n }\n\n method ref_on_child ( child ) {\n return super(child) if child.key() ne "@value";\n\n let container := self.parent();\n return super(child)\n if container \u2261 null or not ( container instanceof PairListNode );\n return container.ref_on_child(self);\n }\n}\n\nclass KDLDocumentNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "document";\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n while ( i < raw.nodes().length() ) {\n let child := raw.nodes()[i];\n out.push( Node.wrap( child, self, child.name(), i ) );\n i++;\n }\n return out;\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method indexed_child ( i ) {\n return self.children().get( i, null );\n }\n\n method named_indexed_child ( name, i ) {\n let n := 0;\n for ( let child in self.children() ) {\n if ( child.key() \u2261 name ) {\n return child if n \u2261 i;\n n++;\n }\n }\n return null;\n }\n}\n\nclass KDLNodeNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "element";\n }\n\n method name () {\n return self.raw().name();\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n for ( let arg in raw.args() ) {\n out.push( Node.wrap( arg, self, null, i ) );\n i++;\n }\n for ( let child in raw.children() ) {\n out.push( Node.wrap( child, self, child.name(), i ) );\n i++;\n }\n return out;\n }\n\n method attributes () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n for ( let pair in raw.props().to_Array() ) {\n out.push( Node.wrap( pair.value, self, "@" _ pair.key, i ) );\n i++;\n }\n return out;\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method indexed_child ( i ) {\n return self.children().get( i, null );\n }\n\n method named_indexed_child ( name, i ) {\n let n := 0;\n for ( let child in self.children() ) {\n if ( child.key() \u2261 name ) {\n return child if n \u2261 i;\n n++;\n }\n }\n return null;\n }\n}\n\nclass KDLValueNode extends Node {\n\n method type () {\n return self.raw().type();\n }\n\n method value () {\n return self.raw().native_value();\n }\n\n method primitive_value () {\n return self.raw().native_value();\n }\n\n method string_value () {\n return self.raw().to_String();\n }\n\n method number_value () {\n try {\n return self.raw().to_Number();\n }\n catch {\n return null;\n }\n }\n\n method has_tagged () {\n return true;\n }\n\n method tagged () {\n let ann := self.raw().type_annotation();\n return {\n tag: ann \u2261 null ? null : "" _ ann,\n value: self.raw().native_value(),\n };\n }\n}\n\nclass XmlNodeNode extends Node {\n\n method _generate_id () {\n return\n try {\n let i := self.raw.unique_id;\n i \u2261 null ? super() : `xml:${i}`;\n }\n catch {\n super();\n };\n }\n\n method type () {\n let raw := self.raw;\n if ( _zpath_is_xml_document(raw) ) {\n return "document";\n }\n switch ( Node._xml_node_type_code(raw) ) {\n case 1:\n return "element";\n case 2, 18:\n return "attr";\n case 3, 4:\n return "text";\n case 8:\n return "comment";\n case 9:\n return "document";\n }\n\n return super();\n }\n\n method name () {\n let raw := self.raw;\n switch ( Node._xml_node_type_code(raw) ) {\n case 1:\n return raw.nodeName();\n case 2, 18:\n return "@" _ raw.nodeName();\n case 3, 4:\n return "#text";\n }\n\n return super();\n }\n\n method next_child ( child ) {\n return child.next_sibling;\n }\n\n method prev_child ( child ) {\n return child.prev_sibling;\n }\n\n method next_sibling () {\n let x := self.raw.nextSibling;\n return null if x \u2261 null;\n return Node.wrap( x, self.parent, x.nodeName );\n }\n\n method prev_sibling () {\n let x := self.raw.previousSibling;\n return null if x \u2261 null;\n return Node.wrap( x, self.parent, x.nodeName );\n }\n\n method primitive_value () {\n let raw := self.raw;\n if ( _zpath_is_xml_document(raw) ) {\n let de := raw.documentElement();\n return de \u2261 null ? null : de.textContent();\n }\n switch ( Node._xml_node_type_code(raw) ) {\n case 1:\n return raw.textContent;\n case 2, 18:\n return raw.nodeValue();\n case 3, 4, 8:\n return raw.data;\n case 9:\n let de := raw.documentElement();\n return de \u2261 null ? null : de.textContent();\n }\n\n return super();\n }\n\n method string_value () {\n let raw := self.raw;\n if ( _zpath_is_xml_document(raw) ) {\n let de := raw.documentElement();\n return de \u2261 null ? null : de.textContent();\n }\n switch ( Node._xml_node_type_code(raw) ) {\n case 1:\n return raw.textContent;\n case 2, 18:\n return raw.nodeValue();\n case 3, 4, 8:\n return raw.data;\n case 9:\n let de := raw.documentElement();\n return de \u2261 null ? null : de.textContent();\n }\n\n return super();\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method children () {\n let raw := self.raw;\n if ( _zpath_is_xml_document(raw) ) {\n let de := raw.documentElement();\n return de \u2261 null ? []\n : [ Node.wrap( de, self, de.nodeName(), 0 ) ];\n }\n let node_type := Node._xml_node_type_code(raw);\n if ( node_type = 9 ) {\n let de := raw.documentElement();\n return de \u2261 null ? []\n : [ Node.wrap( de, self, de.nodeName(), 0 ) ];\n }\n\n if ( node_type = 1 ) {\n let kids := [];\n let count := {};\n for ( let child in raw.childNodes() ) {\n let nm := child.nodeName();\n let n := count.exists(nm) ? count.get( nm ) : 0;\n count.set( nm, n + 1 );\n kids.push( Node.wrap( child, self, nm, n ) );\n }\n return kids;\n }\n\n return [];\n }\n\n method attributes () {\n let raw := self.raw;\n let out := super();\n if ( Node._xml_node_type_code(raw) \u2261 1 ) {\n for ( let attr in raw.attributes() ) {\n let n := Node.wrap( attr, self, "@" _ attr.nodeName() );\n out.push(n);\n }\n }\n return out;\n }\n}\n\nclass TimeNode extends Node {\n\n method string_value () {\n let raw := self.raw;\n return raw.datetime;\n }\n\n method number_value () {\n let raw := self.raw;\n return raw.epoch;\n }\n\n method attributes () {\n let raw := self.raw;\n return [ "year", "month", "day", "hour", "min", "sec" ]\n .map( fn a \u2192 Node.wrap( raw.(a)(), self, `@${a}` ) );\n }\n}\n\nclass PathNode extends Node {\n\n method string_value () {\n let raw := self.raw;\n return raw.to_String;\n }\n\n method attributes () {\n let raw := self.raw;\n if ( raw.is_file ) {\n const stat := raw.stat;\n return stat.keys.map( fn a \u2192 Node.wrap( stat{a}, self, `@${a}` ) );\n }\n\n return super();\n }\n}\n\nclass WidgetNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "widget";\n }\n\n method name () {\n return class_name( self.raw() ) ?: "Widget";\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method children () {\n let out := [];\n let i := 0;\n for ( let child in self.raw().children() ) {\n let nm := class_name(child) ?: "Widget";\n out.push( Node.wrap( child, self, nm, i ) );\n i++;\n }\n return out;\n }\n\n method indexed_child ( i ) {\n return self.children().get( i, null );\n }\n\n method named_indexed_child ( name, i ) {\n let n := 0;\n for ( let child in self.children() ) {\n if ( child.key() \u2261 name ) {\n return child if n \u2261 i;\n n++;\n }\n }\n return null;\n }\n\n method attributes () {\n let raw := self.raw();\n let out := [];\n let i := 0;\n for ( let spec in _WIDGET_ATTRS ) {\n next unless _zpath_widget_attr_applies( raw, spec );\n try {\n let value := _zpath_widget_get_attr( raw, spec );\n out.push( Node.wrap( value, self, "@" _ spec{name}, i ) );\n i++;\n }\n catch {\n }\n }\n return out;\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n\n let spec := _zpath_widget_attr_spec( child.key() );\n return super( child, action ) if spec \u2261 null;\n\n _zpath_widget_set_attr( self.raw(), spec, action{value} );\n return action{value};\n }\n\n method ref_on_child ( child ) {\n let spec := _zpath_widget_attr_spec( child.key() );\n return super(child) if spec \u2261 null;\n\n let widget := self.raw();\n return function ( ... args ) {\n if ( args.length() = 0 ) {\n return _zpath_widget_get_attr( widget, spec );\n }\n _zpath_widget_set_attr( widget, spec, args[0] );\n return args[0];\n };\n }\n}\n\n// Need to define the body of this function late so it can refer back to\n// classes that have been declared.\n\nfunction determine_class ( obj ) {\n let Klass;\n\n let real_obj := obj;\n while ( real_obj instanceof TaggedValue ) {\n real_obj := real_obj{value};\n }\n\n if ( real_obj instanceof Null ) {\n Klass := NullNode;\n }\n else if ( real_obj instanceof Boolean ) {\n Klass := BooleanNode;\n }\n else if ( real_obj instanceof Number ) {\n Klass := NumberNode;\n }\n else if ( real_obj instanceof String or real_obj instanceof BinaryString ) {\n Klass := StringNode;\n }\n else if ( real_obj instanceof Array ) {\n Klass := ArrayNode;\n }\n else if ( real_obj instanceof Bag ) {\n Klass := BagNode;\n }\n else if ( real_obj instanceof Set ) {\n Klass := SetNode;\n }\n else if ( real_obj instanceof Dict ) {\n Klass := DictNode;\n }\n else if ( real_obj instanceof PairList ) {\n Klass := PairListNode;\n }\n else if ( real_obj instanceof Pair ) {\n Klass := PairNode;\n }\n else if ( real_obj instanceof KDLDocument ) {\n Klass := KDLDocumentNode;\n }\n else if ( real_obj instanceof KDLNode ) {\n Klass := KDLNodeNode;\n }\n else if ( real_obj instanceof KDLValue ) {\n Klass := KDLValueNode;\n }\n else if ( _zpath_is_xml_node(real_obj) ) {\n Klass := XmlNodeNode;\n }\n else if ( real_obj instanceof Time ) {\n Klass := TimeNode;\n }\n else if ( Path and ( real_obj instanceof Path ) ) {\n Klass := PathNode;\n }\n else if ( real_obj instanceof Object ) {\n if ( real_obj can __zpath_node_class__ ) {\n Klass = real_obj.__zpath_node_class__;\n }\n }\n\n if ( Klass \u2261 null and _zpath_is_widget(real_obj) ) {\n Klass := WidgetNode;\n }\n\n if ( Klass \u2261 null ) {\n Klass := XmlNodeNode if _zpath_is_xml_node(real_obj);\n }\n\n return Klass ?: Node;\n}\n';
47408
+ virtualFiles["/modules/std/path/z/node.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/node - Node wrapper used by std/path/z.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nObjects of this class wrap underlying values and provide traversal and\ncoercion helpers used while evaluating ZPath expressions.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<Node>\n\nBase wrapper for values traversed by ZPath.\n\n=over\n\n=item C<< Node.from_root(obj) >>\n\nParameters: C<obj> is any query root. Returns: C<Node>. Wraps a root\nvalue for traversal.\n\n=item C<< Node.wrap(obj, parent?, key?, ix?) >>\n\nParameters: C<obj> is any value, with optional parent, key, and index\nmetadata. Returns: C<Node>. Wraps a value in the appropriate node\nsubclass.\n\n=item C<< node.hold_parent(owner) >>\n\nParameters: C<owner> is a parent node. Returns: C<Node>. Retains parent\nmetadata on the node.\n\n=item C<< node.raw() >>, C<< node.value() >>, C<< node.primitive_value() >>\n\nParameters: none. Returns: value. Returns the wrapped raw, node, or\nprimitive value.\n\n=item C<< node.parent() >>\n\nParameters: none. Returns: C<Node> or C<null>. Returns the parent node.\n\n=item C<< node.key() >>, C<< node.ix() >>, C<< node.index() >>, C<< node.id() >>\n\nParameters: none. Returns: value. Returns traversal key, index, or\nstable node id metadata.\n\n=item C<< node.type() >>, C<< node.name() >>\n\nParameters: none. Returns: C<String> or C<null>. Returns the node type\nor name.\n\n=item C<< node.tagged() >>\n\nParameters: none. Returns: C<String> or C<null>. Returns a KDL value\ntag when present.\n\n=item C<< node.has_tagged() >>\n\nParameters: none. Returns: C<Boolean>. Returns true when tag metadata is\navailable.\n\n=item C<< node.string_value() >>, C<< node.number_value() >>\n\nParameters: none. Returns: C<String>, C<Number>, or C<null>. Coerces the\nnode for string or numeric ZPath operations.\n\n=item C<< node.can_have_named_children() >>, C<< node.can_have_indexed_children() >>, C<< node.can_have_named_indexed_children() >>\n\nParameters: none. Returns: C<Boolean>. Reports supported child lookup\nmodes.\n\n=item C<< node.named_child(name) >>, C<< node.indexed_child(i) >>, C<< node.named_indexed_child(name, i) >>\n\nParameters: C<name> is a child name and C<i> is an index. Returns:\nC<Node> or C<null>. Looks up child nodes.\n\n=item C<< node.next_child(child) >>, C<< node.prev_child(child) >>, C<< node.next_sibling() >>, C<< node.prev_sibling() >>\n\nParameters: C<child> is a child node where required. Returns: C<Node> or\nC<null>. Traverses adjacent nodes.\n\n=item C<< node.named_attribute(name) >>\n\nParameters: C<name> is an attribute name. Returns: C<Node> or C<null>.\nLooks up one attribute node.\n\n=item C<< node.children() >>, C<< node.attributes() >>\n\nParameters: none. Returns: C<Array>. Returns child or attribute nodes.\n\n=item C<< node.dump() >>\n\nParameters: none. Returns: C<String>. Returns a diagnostic rendering of\nthe node.\n\n=item C<< node.find(zpath) >>\n\nParameters: C<zpath> is a C<ZPath> object or subclass, such as C<ZZPath>.\nReturns: C<Array>. Evaluates a nested path from this node and returns\nselected node objects.\n\n=item C<< node.do_action(action) >>, C<< node.do_action_on_child(child, action) >>\n\nParameters: C<action> is a path action and C<child> is a child node.\nReturns: value. Applies path mutation actions.\n\n=item C<< node.ref() >>, C<< node.ref_on_child(child) >>\n\nParameters: C<child> is a child node where required. Returns:\nC<Function>. Returns a reference-like getter/setter.\n\n=back\n\n=item C<SimpleNode>, C<StringNode>, C<NumberNode>, C<BooleanNode>, C<NullNode>\n\nScalar node wrappers. They inherit the C<Node> API and override\nC<type>, C<string_value>, or C<number_value> where appropriate.\n\n=item C<ArrayNode>, C<SetNode>, C<BagNode>, C<DictNode>, C<PairListNode>, C<PairNode>\n\nCollection node wrappers. They inherit the C<Node> API and override\nchild, attribute, assignment, and reference methods appropriate to their\ncollection type. PairList children are C<PairNode> objects in pair order;\nC<indexed_child> uses global pair index, while C<named_indexed_child> uses\nthe occurrence index among pairs with the same key.\n\n=item C<KDLDocumentNode>, C<KDLNodeNode>, C<KDLValueNode>\n\nKDL node wrappers. They inherit the C<Node> API and expose KDL children,\nattributes, names, value tags, and scalar coercions.\n\n=item C<XmlNodeNode>\n\nXML node wrapper. It inherits the C<Node> API and exposes XML names,\nchildren, attributes, sibling traversal, and scalar text values.\n\n=item C<TimeNode>\n\nC<std/time> wrapper. It inherits the C<Node> API and exposes time\nattributes plus string and numeric values.\n\n=item C<PathNode>\n\nC<std/io> path wrapper. It inherits the C<Node> API and exposes path\nattributes plus string values.\n\n=item C<WidgetNode>\n\nC<std/gui/objects> widget wrapper. It inherits the C<Node> API and\nexposes widget children by widget class name and index, plus public\nwidget properties as assignable attributes.\n\n=back\n\n=head2 Variables\n\n=over\n\n=item C<determine_class>\n\nType: C<Function>. Chooses the concrete node wrapper class for a raw\nvalue.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/node >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/data/cbor import TaggedValue;\nfrom std/data/kdl import KDLDocument, KDLNode, KDLValue;\nfrom std/internals import class_name, load_module, ref_id;\nfrom std/io try import Path;\nfrom std/math import Math;\nfrom std/string import substr;\nfrom std/time import Time;\n\n\nfunction determine_class;\nlet _widget_class_loaded := false;\nlet _widget_class := null;\n\nconst _WIDGET_ATTRS := [\n { name: "id", getter: "id", setter: "set_id" },\n { name: "enabled", getter: "enabled", setter: "set_enabled" },\n { name: "visible", getter: "visible", setter: "set_visible" },\n { name: "width", getter: "width", accessor: true },\n { name: "height", getter: "height", accessor: true },\n { name: "minwidth", getter: "minwidth", accessor: true },\n { name: "minheight", getter: "minheight", accessor: true },\n { name: "maxwidth", getter: "maxwidth", accessor: true },\n { name: "maxheight", getter: "maxheight", accessor: true },\n { name: "classes", getter: "classes" },\n { name: "title", getter: "title", setter: "set_title", accessor: true },\n { name: "text", getter: "text", setter: "set_text", accessor: true },\n { name: "value", getter: "value", setter: "set_value", accessor: true },\n { name: "placeholder", getter: "placeholder", setter: "set_placeholder", accessor: true },\n { name: "for", getter: "for_id", setter: "set_for_id", accessor: true },\n { name: "align", getter: "align", accessor: true },\n { name: "gap", getter: "gap", accessor: true },\n { name: "padding", getter: "padding", accessor: true },\n { name: "label", getter: "label", accessor: true },\n { name: "collapsible", getter: "collapsible", accessor: true },\n { name: "collapsed", getter: "collapsed", accessor: true },\n { name: "wrap", getter: "wrap", accessor: true },\n { name: "readonly", getter: "readonly", accessor: true },\n { name: "multiline", getter: "multiline", accessor: true },\n { name: "password", getter: "password", accessor: true },\n { name: "required", getter: "required", accessor: true },\n { name: "src", getter: "src", accessor: true },\n { name: "alt", getter: "alt", accessor: true },\n { name: "fit", getter: "fit", accessor: true },\n { name: "checked", getter: "checked", accessor: true },\n { name: "indeterminate", getter: "indeterminate", accessor: true },\n { name: "name", getter: "name", accessor: true },\n { name: "disabled", getter: "disabled", accessor: true },\n { name: "group", getter: "group", accessor: true },\n { name: "multiple", getter: "multiple", accessor: true },\n { name: "min", getter: "min", accessor: true },\n { name: "max", getter: "max", accessor: true },\n { name: "first_day_of_week", getter: "first_day_of_week", accessor: true },\n { name: "orientation", getter: "orientation", accessor: true },\n { name: "step", getter: "step", accessor: true },\n { name: "show_text", getter: "show_text", accessor: true },\n { name: "selected", getter: "selected", accessor: true },\n { name: "placement", getter: "placement", accessor: true },\n { name: "icon", getter: "icon", setter: "set_icon", accessor: true },\n { name: "closable", getter: "closable", accessor: true },\n { name: "selected_index", getter: "selected_index", accessor: true },\n { name: "selected_path", getter: "selected_path", accessor: true },\n { name: "variant", getter: "variant", accessor: true },\n];\n\nfunction _zpath_widget_class () {\n if ( not _widget_class_loaded ) {\n _widget_class_loaded := true;\n try {\n _widget_class := load_module( "std/gui/objects", "Widget" );\n }\n catch {\n _widget_class := null;\n }\n }\n return _widget_class;\n}\n\nfunction _zpath_is_widget ( obj ) {\n let Widget := _zpath_widget_class();\n return Widget \u2262 null and obj instanceof Widget;\n}\n\nfunction _zpath_xml_node_type ( obj ) {\n return null if not( obj can nodeType );\n try {\n return int( "" _ obj.nodeType() );\n }\n catch {\n }\n return null;\n}\n\nfunction _zpath_is_xml_document ( obj ) {\n return true if _zpath_xml_node_type(obj) = 9;\n return false if not( obj can documentElement );\n try {\n obj.documentElement();\n return true;\n }\n catch {\n }\n return false;\n}\n\nfunction _zpath_is_xml_node ( obj ) {\n return true if _zpath_xml_node_type(obj) \u2262 null;\n return _zpath_is_xml_document(obj);\n}\n\nfunction _zpath_widget_attr_name ( name ) {\n return ( name ~ /^@/ ) ? substr( name, 1 ) : name;\n}\n\nfunction _zpath_widget_attr_spec ( name ) {\n let attr := _zpath_widget_attr_name(name);\n return _WIDGET_ATTRS.first( fn spec \u2192 spec{name} \u2261 attr );\n}\n\nfunction _zpath_widget_attr_applies ( widget, spec ) {\n let attr := spec{name};\n if ( attr in [\n "id",\n "enabled",\n "visible",\n "width",\n "height",\n "minwidth",\n "minheight",\n "maxwidth",\n "maxheight",\n "classes",\n ] ) {\n return true;\n }\n\n switch ( class_name(widget) : eq ) {\n case "Window":\n return attr in [ "title" ];\n case "VBox", "HBox":\n return attr in [ "align", "gap", "padding" ];\n case "Frame":\n return attr in [ "label", "collapsible", "collapsed" ];\n case "Label":\n return attr in [ "text", "for" ];\n case "Text":\n return attr in [ "value", "multiline", "readonly", "wrap" ];\n case "RichText":\n return attr in [ "value", "multiline", "readonly" ];\n case "Image":\n return attr in [ "src", "alt", "fit" ];\n case "Input":\n return attr in [\n "value",\n "placeholder",\n "multiline",\n "readonly",\n "password",\n "required",\n ];\n case "DatePicker":\n return attr in [ "value", "min", "max", "first_day_of_week" ];\n case "Checkbox":\n return attr in [ "label", "checked", "indeterminate" ];\n case "Radio":\n return attr in [ "label", "value", "group", "checked" ];\n case "RadioGroup":\n return attr in [ "name", "value" ];\n case "Select":\n return attr in [ "value", "multiple" ];\n case "Menu":\n return attr in [ "text" ];\n case "MenuItem":\n return attr in [ "text", "disabled" ];\n case "Button":\n return attr in [ "text", "variant" ];\n case "Separator":\n return attr in [ "orientation" ];\n case "Slider":\n return attr in [ "value", "min", "max", "step", "orientation", "readonly" ];\n case "Progress":\n return attr in [ "value", "min", "max", "indeterminate", "show_text" ];\n case "Tabs":\n return attr in [ "selected", "placement" ];\n case "Tab":\n return attr in [ "title", "value", "icon", "selected", "closable" ];\n case "ListView":\n return attr in [ "selected_index", "multiple" ];\n case "TreeView":\n return attr in [ "selected_path", "multiple" ];\n }\n\n return false;\n}\n\nfunction _zpath_widget_get_attr ( widget, spec ) {\n let getter := spec{getter};\n return widget.(getter)();\n}\n\nfunction _zpath_widget_set_attr ( widget, spec, value ) {\n let setter := spec.get( "setter", null );\n if ( setter \u2262 null ) {\n try {\n return widget.(setter)(value);\n }\n catch {\n }\n }\n\n let getter := spec{getter};\n if ( spec.get( "accessor", false ) ) {\n try {\n return widget.(getter)(value);\n }\n catch {\n }\n }\n\n die `Widget attribute @${spec{name}} is not assignable`;\n}\n\nclass Node {\n let raw with set := null;\n let parent but weak := null;\n let _parent_keepalive := null;\n let key := null;\n let id := null;\n let ix := null;\n let tagged with set, has := null;\n\n static method _xml_node_type_code ( value ) {\n try {\n return int( "" _ value.nodeType() );\n }\n catch {\n return null;\n }\n }\n\n static method from_root ( obj ) {\n return self.wrap(obj);\n }\n\n static method wrap ( _obj, parent?, key?, ix? ) {\n\n let obj := _obj;\n\n if ( obj instanceof Node ) {\n return obj;\n }\n\n let Klass := determine_class( obj );\n\n let n := new Klass(\n raw: obj,\n parent: parent,\n key: key,\n ix: ix,\n );\n\n if ( obj instanceof TaggedValue ) {\n n.set_tagged(obj);\n while ( obj instanceof TaggedValue ) {\n obj := obj{value}\n }\n n.set_raw(obj);\n }\n\n n._build_id_with_parent(parent);\n\n return n;\n }\n\n method hold_parent ( owner ) {\n _parent_keepalive := owner;\n return self;\n }\n\n method _use_ref_as_id () {\n return false;\n }\n\n method _build_id () {\n id := self._generate_id;\n }\n\n method _build_id_with_parent ( owner ) {\n id := self._generate_id_with_parent(owner);\n }\n\n method _generate_id () {\n return self._generate_id_with_parent(parent);\n }\n\n method _generate_id_with_parent ( owner ) {\n\n if ( self._use_ref_as_id ) {\n return "ref:" _ ref_id(raw);\n }\n\n if ( owner \u2261 null ) {\n return "root";\n }\n\n if ( ix \u2262 null ) {\n if ( key \u2262 null ) {\n return owner.id _ "/" _ key _ "#" _ ix;\n }\n return owner.id _ "/#" _ ix;\n }\n\n if ( key \u2262 null ) {\n return owner.id _ "/" _ key;\n }\n\n return "rand:" _ floor( 1000 * 1000 * 1000 * Math.rand() );\n }\n\n method raw () { return raw; }\n method parent () { return parent; }\n method key () { return key; }\n method id () { return id; }\n method ix () { return ix; }\n method index () { return ix; }\n method tagged () { return tagged; }\n\n method type () {\n return typeof raw;\n }\n\n method value () {\n return raw;\n }\n\n method primitive_value () {\n return raw;\n }\n\n method string_value () {\n let p := self.primitive_value;\n return p \u2261 null ? null : "" _ p;\n }\n\n method number_value () {\n let v := self.primitive_value();\n return 0 + v if v ~ /^-?(?:[0-9]+(?:\\.[0-9]+)?|\\.[0-9]+)$/;\n return null;\n }\n\n method can_have_named_children () {\n return false;\n }\n\n method can_have_indexed_children () {\n return false;\n }\n\n method can_have_named_indexed_children () {\n return false;\n }\n\n method named_child ( name ) {\n self.children.first( fn kid \u2192 kid.key \u2261 name );\n }\n\n method indexed_child ( i ) {\n self.children.first( fn kid \u2192 kid.ix \u2261 i );\n }\n\n method named_indexed_child ( name, i ) {\n self.children.first( fn kid \u2192 kid.ix \u2261 i and kid.key \u2261 name );\n }\n\n method next_child ( child ) {\n let i := child.ix;\n return null if i \u2261 null;\n return self.indexed_child( i + 1 );\n }\n\n method prev_child ( child ) {\n let i := child.ix;\n return null if i \u2261 null;\n return null if i = 0;\n return self.indexed_child( i - 1 );\n }\n\n method next_sibling () {\n return self.parent.next_child( self );\n }\n\n method prev_sibling () {\n return self.parent.prev_child( self );\n }\n\n method named_attribute ( name ) {\n let attrname := ( name ~ /^@/ ) ? name : `@${name}`;\n self.attributes.first( fn kid \u2192 kid.name \u2261 attrname );\n }\n\n method children () {\n return [];\n }\n\n method attributes () {\n return [];\n }\n\n method name () {\n return key;\n }\n\n method dump () {\n return {\n "@type": self.type(),\n "@id": self.id(),\n "@key": self.key(),\n "@index": self.index(),\n "@value": self.primitive_value(),\n children: self.children().map( fn c -> c.dump() ),\n attributes: self.attributes().map( fn a -> a.dump() ),\n };\n }\n\n method find ( zpath ) {\n from std/path/z import ZPath;\n\n die "Node.find expects a ZPath object"\n unless zpath instanceof ZPath;\n\n return zpath.evaluate(self);\n }\n\n method do_action ( action ) {\n const container_node := self.parent();\n die "Path assignment target has no parent node"\n if container_node \u2261 null;\n return container_node.do_action_on_child( self, action );\n }\n\n method ref () {\n const container_node := self.parent();\n die "Path assignment target has no parent node"\n if container_node \u2261 null;\n return container_node.ref_on_child(self);\n }\n\n method do_action_on_child ( child, action ) {\n die `Path assignment target container \'${self.type()}\' is not assignable`;\n }\n\n method ref_on_child ( child ) {\n die `Path assignment target container \'${self.type()}\' is not assignable`;\n }\n}\n\nclass SimpleNode extends Node {\n}\n\nclass StringNode extends SimpleNode {\n\n method string_value () {\n let raw := self.raw;\n if ( raw instanceof BinaryString ) {\n return to_string( raw );\n }\n return raw;\n }\n\n method type () {\n return "string";\n }\n}\n\nclass NumberNode extends SimpleNode {\n method number_value () {\n let raw := self.raw;\n return raw;\n }\n\n method type () {\n return "number";\n }\n}\n\nclass BooleanNode extends SimpleNode {\n method type () {\n return "boolean";\n }\n}\n\nclass NullNode extends SimpleNode {\n method type () {\n return "null";\n }\n}\n\nclass ArrayNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "list";\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n while ( i < raw.length ) {\n let child := Node.wrap( raw[i], self, null, i );\n out.push(child);\n i++;\n }\n return out;\n }\n\n method can_have_indexed_children () {\n return false;\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n\n const ix := child.ix();\n die "Path assignment expects numeric array index" if ix \u2261 null;\n let container := self.raw();\n container[ix] := action{value};\n return action{value};\n }\n\n method ref_on_child ( child ) {\n const ix := child.ix();\n die "Path assignment expects numeric array index" if ix \u2261 null;\n let container := self.raw();\n return \\ container[ix];\n }\n}\n\nclass SetNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n while ( i < raw.length ) {\n let child := Node.wrap( raw[i], self, null, null );\n out.push(child);\n i++;\n }\n return out;\n }\n}\n\nclass BagNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n while ( i < raw.length ) {\n let child := Node.wrap( raw[i], self, null, null );\n out.push(child);\n i++;\n }\n return out;\n }\n}\n\nclass DictNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "map";\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n for ( let k in raw.keys() ) {\n let child := Node.wrap( raw.get(k), self, k );\n out.push(child);\n }\n return out;\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n\n const key := child.key();\n die "Path assignment expects string dict key" if key \u2261 null;\n let container := self.raw();\n container{(key)} := action{value};\n return action{value};\n }\n\n method ref_on_child ( child ) {\n const key := child.key();\n die "Path assignment expects string dict key" if key \u2261 null;\n let container := self.raw();\n return \\ container{(key)};\n }\n}\n\nclass PairListNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method children () {\n let raw := self.raw;\n let pairs := raw.to_Array();\n let out := [];\n let i := 0;\n while ( i < pairs.length() ) {\n let pair := pairs[i];\n out.push( Node.wrap( pair, self, pair.key, i ) );\n i++;\n }\n return out;\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method named_child ( name ) {\n return self.named_indexed_child( name, 0 );\n }\n\n method indexed_child ( i ) {\n let pairs := self.raw().to_Array();\n return null if i < 0 or i >= pairs.length();\n let pair := pairs[i];\n return Node.wrap( pair, self, pair.key, i );\n }\n\n method named_indexed_child ( name, i ) {\n let pairs := self.raw().to_Array();\n let seen := 0;\n let pos := 0;\n while ( pos < pairs.length() ) {\n let pair := pairs[pos];\n if ( pair.key \u2261 name ) {\n if ( seen = i ) {\n return Node.wrap( pair, self, pair.key, pos );\n }\n seen++;\n }\n pos++;\n }\n return null;\n }\n\n method _replace_value_at ( pair_ix, value ) {\n let container := self.raw();\n let pairs := container.to_Array();\n die "Path assignment expects numeric pairlist index"\n if pair_ix \u2261 null or pair_ix < 0 or pair_ix >= pairs.length();\n\n container.clear();\n let i := 0;\n while ( i < pairs.length() ) {\n let pair := pairs[i];\n container.add( pair.key, i = pair_ix ? value : pair.value );\n i++;\n }\n return value;\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n\n return self._replace_value_at( child.ix(), action{value} );\n }\n\n method ref_on_child ( child ) {\n let pair_ix := child.ix();\n return function ( ... args ) {\n let current := self.indexed_child(pair_ix);\n die "Path assignment target pairlist entry no longer exists"\n if current \u2261 null;\n return current.raw().value if args.length() = 0;\n return self._replace_value_at( pair_ix, args[0] );\n };\n }\n}\n\nclass PairNode extends Node {\n\n method attributes () {\n let raw := self.raw;\n return [ "key", "value" ]\n .map( fn a \u2192 Node.wrap( raw.(a)(), self, `@${a}` ) );\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n return super( child, action ) if child.key() ne "@value";\n\n let container := self.parent();\n return super( child, action )\n if container \u2261 null or not ( container instanceof PairListNode );\n return container.do_action_on_child( self, action );\n }\n\n method ref_on_child ( child ) {\n return super(child) if child.key() ne "@value";\n\n let container := self.parent();\n return super(child)\n if container \u2261 null or not ( container instanceof PairListNode );\n return container.ref_on_child(self);\n }\n}\n\nclass KDLDocumentNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "document";\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n while ( i < raw.nodes().length() ) {\n let child := raw.nodes()[i];\n out.push( Node.wrap( child, self, child.name(), i ) );\n i++;\n }\n return out;\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method indexed_child ( i ) {\n return self.children().get( i, null );\n }\n\n method named_indexed_child ( name, i ) {\n let n := 0;\n for ( let child in self.children() ) {\n if ( child.key() \u2261 name ) {\n return child if n \u2261 i;\n n++;\n }\n }\n return null;\n }\n}\n\nclass KDLNodeNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "element";\n }\n\n method name () {\n return self.raw().name();\n }\n\n method children () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n for ( let arg in raw.args() ) {\n out.push( Node.wrap( arg, self, null, i ) );\n i++;\n }\n for ( let child in raw.children() ) {\n out.push( Node.wrap( child, self, child.name(), i ) );\n i++;\n }\n return out;\n }\n\n method attributes () {\n let raw := self.raw;\n let out := [];\n let i := 0;\n for ( let pair in raw.props().to_Array() ) {\n out.push( Node.wrap( pair.value, self, "@" _ pair.key, i ) );\n i++;\n }\n return out;\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method indexed_child ( i ) {\n return self.children().get( i, null );\n }\n\n method named_indexed_child ( name, i ) {\n let n := 0;\n for ( let child in self.children() ) {\n if ( child.key() \u2261 name ) {\n return child if n \u2261 i;\n n++;\n }\n }\n return null;\n }\n}\n\nclass KDLValueNode extends Node {\n\n method type () {\n return self.raw().type();\n }\n\n method value () {\n return self.raw().native_value();\n }\n\n method primitive_value () {\n return self.raw().native_value();\n }\n\n method string_value () {\n return self.raw().to_String();\n }\n\n method number_value () {\n try {\n return self.raw().to_Number();\n }\n catch {\n return null;\n }\n }\n\n method has_tagged () {\n return true;\n }\n\n method tagged () {\n let ann := self.raw().type_annotation();\n return {\n tag: ann \u2261 null ? null : "" _ ann,\n value: self.raw().native_value(),\n };\n }\n}\n\nclass XmlNodeNode extends Node {\n\n method _generate_id () {\n return\n try {\n let i := self.raw.unique_id;\n i \u2261 null ? super() : `xml:${i}`;\n }\n catch {\n super();\n };\n }\n\n method type () {\n let raw := self.raw;\n if ( _zpath_is_xml_document(raw) ) {\n return "document";\n }\n switch ( Node._xml_node_type_code(raw) ) {\n case 1:\n return "element";\n case 2, 18:\n return "attr";\n case 3, 4:\n return "text";\n case 8:\n return "comment";\n case 9:\n return "document";\n }\n\n return super();\n }\n\n method name () {\n let raw := self.raw;\n switch ( Node._xml_node_type_code(raw) ) {\n case 1:\n return raw.nodeName();\n case 2, 18:\n return "@" _ raw.nodeName();\n case 3, 4:\n return "#text";\n }\n\n return super();\n }\n\n method next_child ( child ) {\n return child.next_sibling;\n }\n\n method prev_child ( child ) {\n return child.prev_sibling;\n }\n\n method next_sibling () {\n let x := self.raw.nextSibling;\n return null if x \u2261 null;\n return Node.wrap( x, self.parent, x.nodeName );\n }\n\n method prev_sibling () {\n let x := self.raw.previousSibling;\n return null if x \u2261 null;\n return Node.wrap( x, self.parent, x.nodeName );\n }\n\n method primitive_value () {\n let raw := self.raw;\n if ( _zpath_is_xml_document(raw) ) {\n let de := raw.documentElement();\n return de \u2261 null ? null : de.textContent();\n }\n switch ( Node._xml_node_type_code(raw) ) {\n case 1:\n return raw.textContent;\n case 2, 18:\n return raw.nodeValue();\n case 3, 4, 8:\n return raw.data;\n case 9:\n let de := raw.documentElement();\n return de \u2261 null ? null : de.textContent();\n }\n\n return super();\n }\n\n method string_value () {\n let raw := self.raw;\n if ( _zpath_is_xml_document(raw) ) {\n let de := raw.documentElement();\n return de \u2261 null ? null : de.textContent();\n }\n switch ( Node._xml_node_type_code(raw) ) {\n case 1:\n return raw.textContent;\n case 2, 18:\n return raw.nodeValue();\n case 3, 4, 8:\n return raw.data;\n case 9:\n let de := raw.documentElement();\n return de \u2261 null ? null : de.textContent();\n }\n\n return super();\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method children () {\n let raw := self.raw;\n if ( _zpath_is_xml_document(raw) ) {\n let de := raw.documentElement();\n return de \u2261 null ? []\n : [ Node.wrap( de, self, de.nodeName(), 0 ) ];\n }\n let node_type := Node._xml_node_type_code(raw);\n if ( node_type = 9 ) {\n let de := raw.documentElement();\n return de \u2261 null ? []\n : [ Node.wrap( de, self, de.nodeName(), 0 ) ];\n }\n\n if ( node_type = 1 ) {\n let kids := [];\n let count := {};\n for ( let child in raw.childNodes() ) {\n let nm := child.nodeName();\n let n := count.exists(nm) ? count.get( nm ) : 0;\n count.set( nm, n + 1 );\n kids.push( Node.wrap( child, self, nm, n ) );\n }\n return kids;\n }\n\n return [];\n }\n\n method attributes () {\n let raw := self.raw;\n let out := super();\n if ( Node._xml_node_type_code(raw) \u2261 1 ) {\n for ( let attr in raw.attributes() ) {\n let n := Node.wrap( attr, self, "@" _ attr.nodeName() );\n out.push(n);\n }\n }\n return out;\n }\n}\n\nclass TimeNode extends Node {\n\n method string_value () {\n let raw := self.raw;\n return raw.datetime;\n }\n\n method number_value () {\n let raw := self.raw;\n return raw.epoch;\n }\n\n method attributes () {\n let raw := self.raw;\n return [ "year", "month", "day", "hour", "min", "sec" ]\n .map( fn a \u2192 Node.wrap( raw.(a)(), self, `@${a}` ) );\n }\n}\n\nclass PathNode extends Node {\n\n method string_value () {\n let raw := self.raw;\n return raw.to_String;\n }\n\n method attributes () {\n let raw := self.raw;\n if ( raw.is_file ) {\n const stat := raw.stat;\n return stat.keys.map( fn a \u2192 Node.wrap( stat{a}, self, `@${a}` ) );\n }\n\n return super();\n }\n}\n\nclass WidgetNode extends Node {\n\n method _use_ref_as_id () {\n return true;\n }\n\n method type () {\n return "widget";\n }\n\n method name () {\n return class_name( self.raw() ) or? "Widget";\n }\n\n method can_have_named_children () {\n return true;\n }\n\n method can_have_indexed_children () {\n return true;\n }\n\n method can_have_named_indexed_children () {\n return true;\n }\n\n method children () {\n let out := [];\n let i := 0;\n for ( let child in self.raw().children() ) {\n let nm := class_name(child) or? "Widget";\n out.push( Node.wrap( child, self, nm, i ) );\n i++;\n }\n return out;\n }\n\n method indexed_child ( i ) {\n return self.children().get( i, null );\n }\n\n method named_indexed_child ( name, i ) {\n let n := 0;\n for ( let child in self.children() ) {\n if ( child.key() \u2261 name ) {\n return child if n \u2261 i;\n n++;\n }\n }\n return null;\n }\n\n method attributes () {\n let raw := self.raw();\n let out := [];\n let i := 0;\n for ( let spec in _WIDGET_ATTRS ) {\n next unless _zpath_widget_attr_applies( raw, spec );\n try {\n let value := _zpath_widget_get_attr( raw, spec );\n out.push( Node.wrap( value, self, "@" _ spec{name}, i ) );\n i++;\n }\n catch {\n }\n }\n return out;\n }\n\n method do_action_on_child ( child, action ) {\n return super( child, action ) if action{op} ne ":=";\n\n let spec := _zpath_widget_attr_spec( child.key() );\n return super( child, action ) if spec \u2261 null;\n\n _zpath_widget_set_attr( self.raw(), spec, action{value} );\n return action{value};\n }\n\n method ref_on_child ( child ) {\n let spec := _zpath_widget_attr_spec( child.key() );\n return super(child) if spec \u2261 null;\n\n let widget := self.raw();\n return function ( ... args ) {\n if ( args.length() = 0 ) {\n return _zpath_widget_get_attr( widget, spec );\n }\n _zpath_widget_set_attr( widget, spec, args[0] );\n return args[0];\n };\n }\n}\n\n// Need to define the body of this function late so it can refer back to\n// classes that have been declared.\n\nfunction determine_class ( obj ) {\n let Klass;\n\n let real_obj := obj;\n while ( real_obj instanceof TaggedValue ) {\n real_obj := real_obj{value};\n }\n\n if ( real_obj instanceof Null ) {\n Klass := NullNode;\n }\n else if ( real_obj instanceof Boolean ) {\n Klass := BooleanNode;\n }\n else if ( real_obj instanceof Number ) {\n Klass := NumberNode;\n }\n else if ( real_obj instanceof String or real_obj instanceof BinaryString ) {\n Klass := StringNode;\n }\n else if ( real_obj instanceof Array ) {\n Klass := ArrayNode;\n }\n else if ( real_obj instanceof Bag ) {\n Klass := BagNode;\n }\n else if ( real_obj instanceof Set ) {\n Klass := SetNode;\n }\n else if ( real_obj instanceof Dict ) {\n Klass := DictNode;\n }\n else if ( real_obj instanceof PairList ) {\n Klass := PairListNode;\n }\n else if ( real_obj instanceof Pair ) {\n Klass := PairNode;\n }\n else if ( real_obj instanceof KDLDocument ) {\n Klass := KDLDocumentNode;\n }\n else if ( real_obj instanceof KDLNode ) {\n Klass := KDLNodeNode;\n }\n else if ( real_obj instanceof KDLValue ) {\n Klass := KDLValueNode;\n }\n else if ( _zpath_is_xml_node(real_obj) ) {\n Klass := XmlNodeNode;\n }\n else if ( real_obj instanceof Time ) {\n Klass := TimeNode;\n }\n else if ( Path and ( real_obj instanceof Path ) ) {\n Klass := PathNode;\n }\n else if ( real_obj instanceof Object ) {\n if ( real_obj can __zpath_node_class__ ) {\n Klass = real_obj.__zpath_node_class__;\n }\n }\n\n if ( Klass \u2261 null and _zpath_is_widget(real_obj) ) {\n Klass := WidgetNode;\n }\n\n if ( Klass \u2261 null ) {\n Klass := XmlNodeNode if _zpath_is_xml_node(real_obj);\n }\n\n // The dispatch probe returns a class object or null.\n return Klass ?: Node;\n}\n';
46397
47409
  virtualFiles["/modules/std/path/z/operators.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/operators - Operator definitions for ZPath.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module defines the base ZPath operator model and the standard\noperator table used by C<std/path/z>.\n\n=head1 EXPORTS\n\n=head2 Traits\n\n=over\n\n=item C<EvalHelpers>\n\nShared helpers for operator and function definitions.\n\n=over\n\n=item C<< helper.wrap(value) >>\n\nParameters: C<value> is any value. Returns: C<Array>. Wraps C<value> as\na one-item ZPath node array.\n\n=item C<< helper.wrap_for_array(value) >>\n\nParameters: C<value> is any value. Returns: C<Node>. Wraps C<value> as a\nZPath node for array results.\n\n=back\n\n=back\n\n=head2 Classes\n\n=over\n\n=item C<< Operator({ spelling: String, kind: String, precedence: Number, ... }) >>\n\nConstructs an operator definition. Returns: C<Operator>.\n\n=over\n\n=item C<< operator.is_unary() >>, C<< operator.is_binary() >>\n\nParameters: none. Returns: C<Boolean>. Reports whether the operator is\nunary or binary.\n\n=item C<< operator.requires_whitespace() >>\n\nParameters: none. Returns: C<Boolean>. Reports whether the lexer\nrequires whitespace around the operator.\n\n=item C<< operator.lexer_should_ignore() >>\n\nParameters: none. Returns: C<Boolean>. Reports whether the lexer should\nignore this operator definition.\n\n=item C<< operator.is_right_associative() >>\n\nParameters: none. Returns: C<Boolean>. Reports whether the operator is\nright associative.\n\n=item C<< operator.char_length() >>\n\nParameters: none. Returns: C<Number>. Returns the operator spelling\nlength.\n\n=item C<< operator.precedence_is(lvl) >>\n\nParameters: C<lvl> is a precedence level. Returns: C<Boolean>. Returns\ntrue when the operator has that precedence.\n\n=back\n\n=back\n\n=head2 Constants\n\n=over\n\n=item C<STANDARD_OPERATORS>\n\nType: C<Array>. Standard ZPath operator definitions.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/operators >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z/node import Node;\n\nfunction _floaty_modulus ( ln, rn ) {\n let count := floor( ln / rn ); //\n return ln - ( count * rn );\n}\n\ntrait EvalHelpers {\n method _handle_numeric_operand ( ev, ctx, expr ) {\n const result := ev.eval_expr( expr, ev.nested_ctx( ctx ) );\n return 0 unless result.length;\n return ev.to_number( result[0] );\n }\n\n method _handle_stringy_operand ( ev, ctx, expr ) {\n const result := ev.eval_expr( expr, ev.nested_ctx( ctx ) );\n return 0 unless result.length;\n return ev.to_number( result[0] );\n }\n\n method wrap ( value ) {\n return [ Node.wrap( value ) ];\n }\n\n method wrap_for_array ( value ) {\n return Node.wrap( value );\n }\n}\n\nclass Operator with EvalHelpers {\n let String spelling with get;\n let String alias with get, has;\n let String kind with get;\n let Number precedence with get;\n let Boolean unary := false;\n let Boolean require_ws := false;\n let Boolean lex_ignore := false;\n let Boolean right_assoc := false;\n let Function f;\n\n method is_unary () {\n return unary;\n }\n\n method is_binary () {\n return not unary;\n }\n\n method requires_whitespace () {\n return require_ws;\n }\n\n method lexer_should_ignore () {\n return lex_ignore;\n }\n\n method is_right_associative () {\n return right_assoc;\n }\n\n method char_length () {\n return length spelling;\n }\n\n method precedence_is ( lvl ) {\n return precedence = lvl;\n }\n}\n\nconst STANDARD_OPERATORS := [\n new Operator(\n spelling: "||",\n kind: "OROR",\n precedence: 2,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( left_val.length and ev.truthy( left_val[0] ) ) {\n return op.wrap( true );\n }\n const right_val := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n if ( right_val.length and ev.truthy( right_val[0] ) ) {\n return op.wrap( true );\n }\n return op.wrap( false );\n },\n ),\n\n new Operator(\n spelling: "&&",\n kind: "ANDAND",\n precedence: 4,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( left_val.length and ev.truthy( left_val[0] ) ) {\n const right_val := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n if ( right_val.length and ev.truthy( right_val[0] ) ) {\n return op.wrap( true );\n }\n }\n return op.wrap( false );\n },\n ),\n\n new Operator(\n spelling: "==",\n kind: "EQEQ",\n precedence: 12,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n let is_eq := false;\n\n if ( left_vals and right_vals ) {\n for ( let ln in left_vals ) {\n last if is_eq;\n for ( let rn in right_vals ) {\n last if is_eq;\n if ( ev.equals( ln, rn ) ) {\n is_eq := true;\n }\n }\n }\n }\n\n return op.wrap( is_eq );\n },\n ),\n\n new Operator(\n spelling: "!=",\n kind: "NEQ",\n precedence: 12,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n let is_eq := false;\n\n if ( left_vals and right_vals ) {\n for ( let ln in left_vals ) {\n last if is_eq;\n for ( let rn in right_vals ) {\n last if is_eq;\n if ( ev.equals( ln, rn ) ) {\n is_eq := true;\n }\n }\n }\n }\n\n return op.wrap( not is_eq );\n },\n ),\n\n new Operator(\n spelling: ">=",\n kind: "GE",\n precedence: 14,\n f: function ( op, ev, ast, ctx, left, right ) {\n let left_val := op._handle_numeric_operand( ev, ctx, left );\n let right_val := op._handle_numeric_operand( ev, ctx, right );\n if ( ( left_val \u2261 null ) or ( right_val \u2261 null ) ) {\n left_val := op._handle_stringy_operand( ev, ctx, left );\n right_val := op._handle_stringy_operand( ev, ctx, right );\n return op.wrap( left_val ge right_val );\n }\n return op.wrap( left_val \u2265 right_val );\n },\n ),\n\n new Operator(\n spelling: "<=",\n kind: "LE",\n precedence: 14,\n f: function ( op, ev, ast, ctx, left, right ) {\n let left_val := op._handle_numeric_operand( ev, ctx, left );\n let right_val := op._handle_numeric_operand( ev, ctx, right );\n if ( ( left_val \u2261 null ) or ( right_val \u2261 null ) ) {\n left_val := op._handle_stringy_operand( ev, ctx, left );\n right_val := op._handle_stringy_operand( ev, ctx, right );\n return op.wrap( left_val le right_val );\n }\n return op.wrap( left_val \u2264 right_val );\n },\n ),\n\n new Operator(\n spelling: "+",\n kind: "PLUS",\n require_ws: true,\n precedence: 16,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val + right_val );\n },\n ),\n\n new Operator(\n spelling: "-",\n kind: "MINUS",\n require_ws: true,\n precedence: 16,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val - right_val );\n },\n ),\n\n new Operator(\n spelling: "%",\n kind: "PCT",\n require_ws: true,\n precedence: 18,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n if ( ( left_val ~ /\\./ ) or ( right_val ~ /\\./ ) ) {\n return op.wrap( _floaty_modulus( left_val, right_val ) );\n }\n return op.wrap( left_val mod right_val );\n },\n ),\n\n new Operator(\n spelling: "*",\n kind: "TIMES",\n require_ws: true,\n precedence: 18,\n lex_ignore: true,\n alias: "STAR",\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \xD7 right_val );\n },\n ),\n\n new Operator(\n spelling: "/",\n kind: "DIVIDE",\n require_ws: true,\n precedence: 18,\n lex_ignore: true,\n alias: "SLASH",\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \xF7 right_val );\n },\n ),\n\n new Operator(\n spelling: "^",\n kind: "BXOR",\n precedence: 8,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val ^ right_val );\n },\n ),\n\n new Operator(\n spelling: "&",\n kind: "BAND",\n precedence: 10,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val & right_val );\n },\n ),\n\n new Operator(\n spelling: "|",\n kind: "BOR",\n precedence: 6,\n f: function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val | right_val );\n },\n ),\n\n new Operator(\n spelling: ">",\n kind: "GT",\n precedence: 14,\n f: function ( op, ev, ast, ctx, left, right ) {\n let left_val := op._handle_numeric_operand( ev, ctx, left );\n let right_val := op._handle_numeric_operand( ev, ctx, right );\n if ( ( left_val \u2261 null ) or ( right_val \u2261 null ) ) {\n left_val := op._handle_stringy_operand( ev, ctx, left );\n right_val := op._handle_stringy_operand( ev, ctx, right );\n return op.wrap( left_val gt right_val );\n }\n return op.wrap( left_val > right_val );\n },\n ),\n\n new Operator(\n spelling: "<",\n kind: "LT",\n precedence: 14,\n f: function ( op, ev, ast, ctx, left, right ) {\n let left_val := op._handle_numeric_operand( ev, ctx, left );\n let right_val := op._handle_numeric_operand( ev, ctx, right );\n if ( ( left_val \u2261 null ) or ( right_val \u2261 null ) ) {\n left_val := op._handle_stringy_operand( ev, ctx, left );\n right_val := op._handle_stringy_operand( ev, ctx, right );\n return op.wrap( left_val le right_val );\n }\n return op.wrap( left_val < right_val );\n },\n ),\n\n new Operator(\n spelling: "!",\n kind: "NOT",\n unary: true,\n precedence: 20,\n f: function ( op, ev, ast, ctx, expr ) {\n const got := ev.eval_expr( expr, ctx );\n const value := got.length() > 0 ? got[0] : null;\n return op.wrap( not ev.truthy(value) );\n },\n ),\n\n new Operator(\n spelling: "~",\n kind: "BNOT",\n unary: true,\n precedence: 20,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( ~value );\n },\n ),\n];\n';
46398
47410
  virtualFiles["/modules/std/path/z/parser.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/z/parser - Pure Zuzu parser for ZPath expressions.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module provides the pure-Zuzu parser used by ZPath.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<< Parser({ lexer_class?, allowed_operators }) >>\n\nConstructs a ZPath parser. Returns: C<Parser>.\n\n=over\n\n=item C<< parser.parse_top_level_terms(src) >>\n\nParameters: C<src> is a ZPath expression string. Returns: C<Array>.\nParses comma-separated top-level expression terms.\n\n=item C<< parser.parse_expression(lx) >>\n\nParameters: C<lx> is a C<Lexer>. Returns: C<Dict>. Parses an expression.\n\n=item C<< parser.parse_ternary(lx) >>\n\nParameters: C<lx> is a C<Lexer>. Returns: C<Dict>. Parses ternary and\nElvis expressions.\n\n=item C<< parser.parse_subexpression(lx, min_prec) >>\n\nParameters: C<lx> is a C<Lexer> and C<min_prec> is a precedence floor.\nReturns: C<Dict>. Parses a precedence-climbing subexpression.\n\n=item C<< parser.parse_primary(lx) >>\n\nParameters: C<lx> is a C<Lexer>. Returns: C<Dict>. Parses a primary\nexpression.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/z/parser >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/string import trim;\nfrom std/path/z/lexer import Lexer;\n\nclass Parser {\n let lexer_class;\n let allowed_operators;\n let _binop_prec := {};\n let _unop_prec := {};\n let _need_ws := {};\n let _right_assoc := {};\n let _path_terminators := {};\n let _allow_elvis := false;\n\n method __build__ () {\n lexer_class ?:= Lexer;\n self._init_path_terminators();\n for ( let op in allowed_operators ) {\n let spell := op.get_spelling();\n if ( op.is_unary() ) {\n _unop_prec.set( spell, op.get_precedence() );\n }\n else {\n if ( op.get_kind() \u2261 "ELVIS" ) {\n _allow_elvis := true;\n _path_terminators.set( op.get_kind(), true );\n next;\n }\n _binop_prec.set( spell, op.get_precedence() );\n _path_terminators.set( op.get_kind(), true );\n if ( op.has_alias() ) {\n _path_terminators.set( op.get_alias(), true );\n }\n if ( op.requires_whitespace() ) {\n _need_ws.set( spell, true );\n }\n if ( op.is_right_associative() ) {\n _right_assoc.set( spell, true );\n }\n }\n }\n }\n\n method _init_path_terminators () {\n for ( let k in [\n "EOF",\n "COMMA",\n "RPAREN",\n "RBRACK",\n "QMARK",\n "COLON",\n ] ) {\n _path_terminators.set( k, true );\n }\n }\n\n method parse_top_level_terms ( src ) {\n let terms := [];\n let lexer := new lexer_class(\n src: src,\n allowed_operators: allowed_operators,\n );\n\n while ( true ) {\n let expr := self.parse_expression(lexer);\n terms.push(expr);\n if ( lexer.peek_kind() \u2261 "COMMA" ) {\n lexer.next_tok();\n next;\n }\n lexer.expect("EOF");\n last;\n }\n\n return terms;\n }\n\n method _trim ( s ) {\n return trim(s);\n }\n\n method parse_expression ( lx ) {\n return self.parse_ternary(lx);\n }\n\n method parse_ternary ( lx ) {\n let cond := self.parse_subexpression( lx, 1 );\n\n if ( lx.peek_kind() \u2261 "QMARK" ) {\n lx.next_tok();\n let then := self.parse_expression(lx); // ZZPath should use: self.parse_subexpression( lx, 1 )\n lx.expect("COLON");\n let els := self.parse_expression(lx);\n return { t: "ternary", c: cond, a: then, b: els };\n }\n if ( _allow_elvis and lx.peek_kind() \u2261 "ELVIS" ) {\n lx.next_tok();\n let fallback := self.parse_expression(lx);\n return { t: "elvis", c: cond, b: fallback };\n }\n\n return cond;\n }\n\n method parse_subexpression ( lx, min_prec ) {\n let left := self._parse_maybe_unary( lx, min_prec );\n\n while ( true ) {\n let spell := lx.peek{v};\n let op_prec := _binop_prec.get( spell, null );\n last if op_prec \u2261 null or op_prec < min_prec;\n\n let op := lx.next_tok;\n if ( _need_ws.exists( spell ) ) {\n if ( not ( op{ws_before} and op{ws_after} ) ) {\n die `Binary operator \'${spell}\' requires whitespace around it`;\n }\n }\n\n let next_min := _right_assoc.exists(spell) ? op_prec : op_prec + 1;\n let right := self.parse_subexpression( lx, next_min );\n left := { t: "bin", op: spell, l: left, r: right };\n }\n\n return left;\n }\n\n method _parse_maybe_unary ( lx, min_prec ) {\n let spell := lx.peek{v};\n let op_prec := _unop_prec.get( spell, null );\n if ( op_prec \u2262 null and op_prec >= min_prec ) {\n let op := lx.next_tok{v};\n let e := self._parse_maybe_unary( lx, op_prec );\n return { t: "un", op: op, e: e };\n }\n return self.parse_primary( lx );\n }\n\n method parse_primary ( lx ) {\n let k := lx.peek_kind;\n\n if ( k \u2261 "NUMBER" ) {\n return { t: "num", v: lx.next_tok(){v} };\n }\n if ( k \u2261 "STRING" ) {\n return { t: "str", v: lx.next_tok(){v} };\n }\n if ( k \u2261 "LPAREN" ) {\n lx.next_tok();\n let e := self.parse_expression(lx);\n lx.expect("RPAREN");\n return e;\n }\n\n if ( k \u2261 "NAME" and lx.peek_kind_n(1) \u2261 "LPAREN" ) {\n let name := lx.next_tok(){v};\n lx.expect("LPAREN");\n let args := [];\n if ( lx.peek_kind() \u2262 "RPAREN" ) {\n args.push( self.parse_expression(lx) );\n while ( lx.peek_kind() \u2261 "COMMA" ) {\n lx.next_tok();\n args.push( self.parse_expression(lx) );\n }\n }\n lx.expect("RPAREN");\n return { t: "fn", n: name, a: args };\n }\n\n return self._parse_path_expr(lx);\n }\n\n method _is_path_terminator ( k ) {\n return _path_terminators.exists(k);\n }\n\n method _parse_path_expr ( lx ) {\n let segs := [];\n\n if ( lx.peek_kind() \u2261 "SLASH_PATH" ) {\n lx.next_tok();\n let root := { k: "root", q: [] };\n segs.push(root);\n\n if ( lx.peek_kind() \u2261 "LBRACK" ) {\n root{q} := self._parse_qualifiers(lx);\n }\n\n if ( self._is_path_terminator( lx.peek_kind() ) ) {\n return { t: "path", s: segs };\n }\n }\n else if ( lx.peek_kind() \u2261 "LBRACK" ) {\n let seg := { k: "dot", q: self._parse_qualifiers(lx) };\n segs.push(seg);\n if ( self._is_path_terminator( lx.peek_kind() ) ) {\n return { t: "path", s: segs };\n }\n }\n\n if (\n lx.peek_kind() \u2262 "SLASH_PATH"\n and not self._is_path_terminator( lx.peek_kind() )\n ) {\n segs.push( self._parse_path_segment(lx) );\n }\n\n while ( lx.peek_kind() \u2261 "SLASH_PATH" ) {\n lx.next_tok();\n if ( lx.peek_kind() \u2261 "LBRACK" ) {\n let seg := { k: "star", q: [] };\n seg{q} := self._parse_qualifiers(lx);\n segs.push(seg);\n next;\n }\n segs.push( self._parse_path_segment(lx) );\n }\n\n return { t: "path", s: segs };\n }\n\n method _parse_path_segment ( lx ) {\n let k := lx.peek_kind();\n let seg := null;\n\n if ( k \u2261 "DOT" ) {\n lx.next_tok();\n seg := { k: "dot" };\n }\n else if ( k \u2261 "DOTDOT" ) {\n lx.next_tok();\n seg := { k: "parent" };\n }\n else if ( k \u2261 "DOTDOTSTAR" ) {\n lx.next_tok();\n seg := { k: "ancestors" };\n }\n else if ( k \u2261 "STAR_PATH" ) {\n lx.next_tok();\n seg := { k: "star" };\n }\n else if ( k \u2261 "STARSTAR" ) {\n lx.next_tok();\n seg := { k: "desc" };\n }\n else if ( k \u2261 "INDEX" ) {\n let i := lx.next_tok(){v};\n seg := { k: "index", i: i };\n }\n else if ( k \u2261 "NUMBER" ) {\n let i := lx.next_tok(){v};\n seg := { k: "index", i: i };\n }\n else if ( k \u2261 "NAME" and lx.peek_kind_n(1) \u2261 "LPAREN" ) {\n let name := lx.next_tok(){v};\n lx.expect("LPAREN");\n let args := [];\n if ( lx.peek_kind() \u2262 "RPAREN" ) {\n args.push( self.parse_expression(lx) );\n while ( lx.peek_kind() \u2261 "COMMA" ) {\n lx.next_tok();\n args.push( self.parse_expression(lx) );\n }\n }\n lx.expect("RPAREN");\n seg := { k: "fnseg", n: name, a: args };\n }\n else if ( k \u2261 "NAME" ) {\n let n := lx.next_tok(){v};\n seg := { k: "name", n: n };\n }\n else {\n die `Unexpected token in path segment: ${k}`;\n }\n\n if ( seg{k} \u2261 "name" and lx.peek_kind() \u2261 "INDEX" ) {\n seg{i} := lx.next_tok(){v};\n }\n\n seg{q} := self._parse_qualifiers(lx);\n return seg;\n }\n\n method _parse_qualifiers ( lx ) {\n let q := [];\n\n while ( lx.peek_kind() \u2261 "LBRACK" ) {\n lx.next_tok();\n let e := self.parse_expression(lx);\n lx.expect("RBRACK");\n q.push(e);\n }\n\n return q;\n }\n}\n';
46399
47411
  virtualFiles["/modules/std/path/zz.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/zz - ZuzuScript-flavoured path selectors.\n\n=head1 SYNOPSIS\n\n from std/path/zz import ZZPath;\n\n let data := { users: [ { name: "Ada" } ] };\n say( ( new ZZPath( path: "/users/#0/name" ) ).first(data) );\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module provides the public C<ZZPath> class. It currently reuses the\nZPath traversal, parser, lexer, assignment, and reference\nmachinery while routing expression operators through\nC<std/path/zz/operators> and expression functions through\nC<std/path/zz/functions>.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<ZZEvaluator>\n\nEvaluator class that supplies ZZPath operators and functions.\n\n=over\n\n=item C<< evaluator.operator_definitions() >>\n\nParameters: none. Returns: C<Array>. Returns ZZPath operator\ndefinitions.\n\n=item C<< evaluator.function_definitions() >>\n\nParameters: none. Returns: C<Array>. Returns ZZPath function\ndefinitions.\n\n=back\n\n=item C<< ZZPath({ path: String }) >>\n\nConstructs a ZZPath query object. Returns: C<ZZPath>. Inherits the\npublic query, assignment, and reference methods from C<ZPath>.\n\n=over\n\n=item C<< path.get_evaluator() >>\n\nParameters: none. Returns: C<ZZEvaluator>. Returns the evaluator used\nfor this path.\n\n=item C<< node.find(path) >>\n\nC<ZZPath> inherits from C<ZPath>, so a C<ZZPath> object can be passed to\nC<std/path/z/node> C<Node.find()>.\n\n=back\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/zz >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z import ZPath;\nfrom std/path/z/evaluate import Evaluator as ZEvaluator;\n\nclass ZZEvaluator extends ZEvaluator {\n method operator_definitions () {\n from std/path/zz/operators import STANDARD_OPERATORS;\n return STANDARD_OPERATORS;\n }\n\n method function_definitions () {\n from std/path/zz/functions import STANDARD_FUNCTIONS;\n return STANDARD_FUNCTIONS;\n }\n}\n\nclass ZZPath extends ZPath {\n method get_evaluator () {\n return new ZZEvaluator();\n }\n}\n';
46400
- virtualFiles["/modules/std/path/zz/functions.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/zz/functions - Function definitions for ZZPath expressions.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module extends the base ZPath function table with\nZuzuScript-flavoured path expression helpers.\n\n=head1 EXPORTS\n\n=head2 Constants\n\n=over\n\n=item C<STANDARD_FUNCTION_NAMES>\n\nType: C<Array>. Names of inherited ZPath functions available to ZZPath.\n\n=item C<STANDARD_FUNCTIONS>\n\nType: C<Array>. Complete ZZPath function definition table.\n\n=back\n\n=head2 Functions\n\n=over\n\n=item C<< first_arg_node(name, ev, ctx, args) >>\n\nParameters: function name, evaluator, context, and argument AST nodes.\nReturns: C<Node> or C<null>. Resolves the first argument or current\ncontext node.\n\n=item C<< first_number_arg(name, ev, ctx, args) >>, C<< nth_number_arg(name, ev, ctx, args, i) >>\n\nParameters: function name, evaluator, context, arguments, and optional\nindex. Returns: C<Number>. Resolves an argument as a number.\n\n=item C<< first_string_arg(name, ev, ctx, args) >>, C<< nth_string_arg(name, ev, ctx, args, i) >>\n\nParameters: function name, evaluator, context, arguments, and optional\nindex. Returns: C<String>. Resolves an argument as a string.\n\n=item C<< nth_value_arg(name, ev, ctx, args, i) >>\n\nParameters: function name, evaluator, context, arguments, and index.\nReturns: value. Resolves an argument as a primitive value.\n\n=item C<< nth_array_arg(name, ev, ctx, args, i) >>\n\nParameters: function name, evaluator, context, arguments, and index.\nReturns: C<Array>. Resolves an argument as an array value.\n\n=item C<< number_args(ev, ctx, args) >>\n\nParameters: evaluator, context, and argument AST nodes. Returns:\nC<Array>. Resolves all arguments as numbers.\n\n=item C<< first_arg_value(name, ev, ctx, args) >>\n\nParameters: function name, evaluator, context, and arguments. Returns:\nvalue. Resolves the first argument as a primitive value.\n\n=item C<< z_function(spelling) >>\n\nParameters: C<spelling> is a base ZPath function name. Returns:\nC<Function>. Returns the inherited function implementation.\n\n=item C<< string_index_of(funk, ev, ast, ctx, args) >>\n\nParameters: standard ZPath function callback arguments. Returns:\nC<Array>. Implements string C<index-of>.\n\n=item C<< string_rindex(funk, ev, ast, ctx, args) >>\n\nParameters: standard ZPath function callback arguments. Returns:\nC<Array>. Implements string reverse-index lookup.\n\n=item C<< defined_function(funk, ev, ast, ctx, args) >>\n\nParameters: standard ZPath function callback arguments. Returns:\nC<Array>. Implements defined-value testing.\n\n=item C<< empty_function(funk, ev, ast, ctx, args) >>\n\nParameters: standard ZPath function callback arguments. Returns:\nC<Array>. Implements empty-value testing.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/zz/functions >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z/functions import Func, STANDARD_FUNCTIONS as Z_STANDARD_FUNCTIONS;\n\n\nconst STANDARD_FUNCTION_NAMES := [\n "true",\n "false",\n "null",\n "tag",\n "url",\n "local-name",\n "key",\n "value",\n "index",\n "count",\n "is-first",\n "is-last",\n "next",\n "prev",\n];\n\nfunction first_arg_node ( name, ev, ctx, args ) {\n if ( args.length() = 0 ) {\n return ctx.nodeset.get( 0, null );\n }\n if ( args.length() = 1 ) {\n const got := ev.eval_expr( args[0], ev.nested_ctx( ctx ) );\n return got.get( 0, null );\n }\n die `Too many arguments for ${name}()`;\n}\n\nfunction first_number_arg ( name, ev, ctx, args ) {\n const node := first_arg_node( name, ev, ctx, args );\n return ev.to_number(node) ?: 0;\n}\n\nfunction nth_number_arg ( name, ev, ctx, args, i ) {\n die `Not enough arguments for ${name}()` if args.length() <= i;\n const got := ev.eval_expr( args[i], ev.nested_ctx( ctx ) );\n return ev.to_number( got.get( 0, null ) ) ?: 0;\n}\n\nfunction nth_string_arg ( name, ev, ctx, args, i ) {\n die `Not enough arguments for ${name}()` if args.length() <= i;\n const got := ev.eval_expr( args[i], ev.nested_ctx( ctx ) );\n const value := ev.to_string( got.get( 0, null ) );\n return value \u2261 null ? "" : "" _ value;\n}\n\nfunction nth_value_arg ( name, ev, ctx, args, i ) {\n die `Not enough arguments for ${name}()` if args.length() <= i;\n const got := ev.eval_expr( args[i], ev.nested_ctx( ctx ) );\n const node := got.get( 0, null );\n return node \u2261 null ? null : node.primitive_value();\n}\n\nfunction nth_array_arg ( name, ev, ctx, args, i ) {\n die `Not enough arguments for ${name}()` if args.length() <= i;\n const got := ev.eval_expr( args[i], ev.nested_ctx( ctx ) );\n if ( got.length() = 1 ) {\n const one := got[0].primitive_value();\n return one if one instanceof Array;\n }\n return got.map( fn node \u2192 node.primitive_value() );\n}\n\nfunction number_args ( ev, ctx, args ) {\n let nums := [];\n if ( args.length() = 0 ) {\n for ( let node in ctx.nodeset ) {\n const value := ev.to_number(node);\n nums.push(value) if value instanceof Number;\n }\n return nums;\n }\n for ( let arg in args ) {\n const got := ev.eval_expr( arg, ev.nested_ctx( ctx ) );\n for ( let node in got ) {\n const value := ev.to_number(node);\n nums.push(value) if value instanceof Number;\n }\n }\n return nums;\n}\n\nfunction first_string_arg ( name, ev, ctx, args ) {\n const node := first_arg_node( name, ev, ctx, args );\n const value := ev.to_string(node);\n return value \u2261 null ? "" : "" _ value;\n}\n\nfunction first_arg_value ( name, ev, ctx, args ) {\n const node := first_arg_node( name, ev, ctx, args );\n return node \u2261 null ? null : node.primitive_value();\n}\n\nfunction z_function ( spelling ) {\n const func := Z_STANDARD_FUNCTIONS.first( fn f \u2192 f.has_name(spelling) );\n die `std/path/z/functions is missing ${spelling}()` if func \u2261 null;\n return func;\n}\n\nfunction string_index_of ( funk, ev, ast, ctx, args ) {\n from std/string import index;\n die "Too many arguments for index-of()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( index(\n nth_string_arg( "index-of", ev, ctx, args, 0 ),\n nth_string_arg( "index-of", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( index(\n nth_string_arg( "index-of", ev, ctx, args, 0 ),\n nth_string_arg( "index-of", ev, ctx, args, 1 ),\n nth_number_arg( "index-of", ev, ctx, args, 2 ),\n ) );\n}\n\nfunction string_rindex ( funk, ev, ast, ctx, args ) {\n from std/string import rindex;\n const name := funk.get_spelling;\n die `Too many arguments for ${name}()` if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( rindex(\n nth_string_arg( name, ev, ctx, args, 0 ),\n nth_string_arg( name, ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( rindex(\n nth_string_arg( name, ev, ctx, args, 0 ),\n nth_string_arg( name, ev, ctx, args, 1 ),\n nth_number_arg( name, ev, ctx, args, 2 ),\n ) );\n}\n\nfunction defined_function ( funk, ev, ast, ctx, args ) {\n const node := first_arg_node( "defined", ev, ctx, args );\n return funk.wrap( node \u2262 null and node.primitive_value() \u2262 null );\n}\n\nfunction empty_function ( funk, ev, ast, ctx, args ) {\n const value := first_arg_value( "empty", ev, ctx, args );\n return funk.wrap( true ) if value \u2261 null;\n\n if (\n value instanceof Array or\n value instanceof Bag or\n value instanceof Set or\n value instanceof Dict or\n value instanceof PairList\n ) {\n return funk.wrap( value.length() = 0 );\n }\n\n die `empty() expects a Collection or null, got ${typeof value}`;\n}\n\n// Start with a base of functions inherited from ZPath:\nconst STANDARD_FUNCTIONS := STANDARD_FUNCTION_NAMES.map( fn n \u2192 z_function(n) );\n\n// Add equivalents for ZuzuScript word-like unary operators:\nSTANDARD_FUNCTIONS.push(\n new Func(\n spelling: "abs",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( abs first_number_arg( "abs", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "floor",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( floor first_number_arg( "floor", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "ceil",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( ceil first_number_arg( "ceil", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "round",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( round first_number_arg( "round", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "int",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( int first_number_arg( "int", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "sqrt",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( sqrt first_number_arg( "sqrt", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "uc",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( uc first_string_arg( "uc", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "lc",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( lc first_string_arg( "lc", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "length",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( length first_string_arg( "length", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "not",\n f: function ( funk, ev, ast, ctx, args ) {\n const node := first_arg_node( "not", ev, ctx, args );\n return funk.wrap( not ev.truthy(node) );\n },\n ),\n new Func(\n spelling: "typeof",\n f: function ( funk, ev, ast, ctx, args ) {\n const node := first_arg_node( "typeof", ev, ctx, args );\n const value := node \u2261 null ? null : node.primitive_value();\n return funk.wrap( typeof value );\n },\n ),\n new Func(\n spelling: "defined",\n f: defined_function,\n ),\n new Func(\n spelling: "empty",\n f: empty_function,\n ),\n);\n\n// Functions from std/internals:\nSTANDARD_FUNCTIONS.push(\n new Func(\n spelling: "string",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/internals import to_String;\n return funk.wrap( to_String( first_arg_value(\n "string",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "number",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/internals import to_Number;\n return funk.wrap( to_Number( first_arg_value(\n "number",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "boolean",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/internals import to_Boolean;\n return funk.wrap( to_Boolean( first_arg_value(\n "boolean",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "regexp",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/internals import to_Regexp;\n return funk.wrap( to_Regexp( first_arg_value(\n "regexp",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n);\n\n// Functions from std/math:\nSTANDARD_FUNCTIONS.push(\n new Func(\n spelling: "sum",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/math import Math;\n return funk.wrap( Math.sum( number_args( ev, ctx, args ) ) );\n },\n ),\n new Func(\n spelling: "min",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/math import Math;\n return funk.wrap( Math.min( number_args( ev, ctx, args ) ) );\n },\n ),\n new Func(\n spelling: "max",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/math import Math;\n return funk.wrap( Math.max( number_args( ev, ctx, args ) ) );\n },\n ),\n new Func(\n spelling: "clamp",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/math import Math;\n die "Too many arguments for clamp()" if args.length() > 3;\n return funk.wrap( Math.clamp(\n nth_number_arg( "clamp", ev, ctx, args, 0 ),\n nth_number_arg( "clamp", ev, ctx, args, 1 ),\n nth_number_arg( "clamp", ev, ctx, args, 2 ),\n ) );\n },\n ),\n);\n\n// Functions from std/string:\nSTANDARD_FUNCTIONS.push(\n new Func(\n spelling: "substr",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import substr;\n die "Too many arguments for substr()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( substr(\n nth_string_arg( "substr", ev, ctx, args, 0 ),\n nth_number_arg( "substr", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( substr(\n nth_string_arg( "substr", ev, ctx, args, 0 ),\n nth_number_arg( "substr", ev, ctx, args, 1 ),\n nth_number_arg( "substr", ev, ctx, args, 2 ),\n ) );\n },\n ),\n new Func(\n spelling: "index-of",\n f: string_index_of,\n ),\n new Func(\n spelling: "rindex",\n f: string_rindex,\n ),\n new Func(\n spelling: "last-index-of",\n f: string_rindex,\n ),\n new Func(\n spelling: "contains",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import contains;\n die "Too many arguments for contains()" if args.length() > 2;\n return funk.wrap( contains(\n nth_string_arg( "contains", ev, ctx, args, 0 ),\n nth_string_arg( "contains", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "chr",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import chr;\n die "Too many arguments for chr()" if args.length() > 1;\n return funk.wrap( chr( nth_number_arg(\n "chr",\n ev,\n ctx,\n args,\n 0,\n ) ) );\n },\n ),\n new Func(\n spelling: "ord",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import ord;\n die "Too many arguments for ord()" if args.length() > 2;\n if ( args.length() = 1 ) {\n return funk.wrap( ord( nth_string_arg(\n "ord",\n ev,\n ctx,\n args,\n 0,\n ) ) );\n }\n return funk.wrap( ord(\n nth_string_arg( "ord", ev, ctx, args, 0 ),\n nth_number_arg( "ord", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "replace",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import replace;\n die "Too many arguments for replace()" if args.length() > 4;\n if ( args.length() = 3 ) {\n return funk.wrap( replace(\n nth_string_arg( "replace", ev, ctx, args, 0 ),\n nth_value_arg( "replace", ev, ctx, args, 1 ),\n nth_string_arg( "replace", ev, ctx, args, 2 ),\n ) );\n }\n return funk.wrap( replace(\n nth_string_arg( "replace", ev, ctx, args, 0 ),\n nth_value_arg( "replace", ev, ctx, args, 1 ),\n nth_string_arg( "replace", ev, ctx, args, 2 ),\n nth_string_arg( "replace", ev, ctx, args, 3 ),\n ) );\n },\n ),\n new Func(\n spelling: "search",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import search;\n die "Too many arguments for search()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( search(\n nth_string_arg( "search", ev, ctx, args, 0 ),\n nth_value_arg( "search", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( search(\n nth_string_arg( "search", ev, ctx, args, 0 ),\n nth_value_arg( "search", ev, ctx, args, 1 ),\n nth_string_arg( "search", ev, ctx, args, 2 ),\n ) );\n },\n ),\n new Func(\n spelling: "starts_with",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import starts_with;\n die "Too many arguments for starts_with()" if args.length() > 2;\n return funk.wrap( starts_with(\n nth_string_arg( "starts_with", ev, ctx, args, 0 ),\n nth_string_arg( "starts_with", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "ends_with",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import ends_with;\n die "Too many arguments for ends_with()" if args.length() > 2;\n return funk.wrap( ends_with(\n nth_string_arg( "ends_with", ev, ctx, args, 0 ),\n nth_string_arg( "ends_with", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "matches",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import matches;\n die "Too many arguments for matches()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( matches(\n nth_string_arg( "matches", ev, ctx, args, 0 ),\n nth_value_arg( "matches", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( matches(\n nth_string_arg( "matches", ev, ctx, args, 0 ),\n nth_value_arg( "matches", ev, ctx, args, 1 ),\n nth_string_arg( "matches", ev, ctx, args, 2 ),\n ) );\n },\n ),\n new Func(\n spelling: "pattern_to_regexp",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import pattern_to_regexp;\n die "Too many arguments for pattern_to_regexp()"\n if args.length() > 2;\n if ( args.length() = 1 ) {\n return funk.wrap( pattern_to_regexp(\n nth_string_arg( "pattern_to_regexp", ev, ctx, args, 0 ),\n ) );\n }\n return funk.wrap( pattern_to_regexp(\n nth_string_arg( "pattern_to_regexp", ev, ctx, args, 0 ),\n nth_value_arg( "pattern_to_regexp", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "quotemeta",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import quotemeta;\n die "Too many arguments for quotemeta()" if args.length() > 1;\n return funk.wrap( quotemeta(\n nth_string_arg( "quotemeta", ev, ctx, args, 0 ),\n ) );\n },\n ),\n new Func(\n spelling: "sprint",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import sprint;\n die "Not enough arguments for sprint()" if args.length() = 0;\n die "Too many arguments for sprint()" if args.length() > 8;\n\n const fmt := nth_string_arg( "sprint", ev, ctx, args, 0 );\n if ( args.length() = 1 ) {\n return funk.wrap( sprint(fmt) );\n }\n if ( args.length() = 2 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n ) );\n }\n if ( args.length() = 3 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n ) );\n }\n if ( args.length() = 4 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n ) );\n }\n if ( args.length() = 5 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n nth_value_arg( "sprint", ev, ctx, args, 4 ),\n ) );\n }\n if ( args.length() = 6 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n nth_value_arg( "sprint", ev, ctx, args, 4 ),\n nth_value_arg( "sprint", ev, ctx, args, 5 ),\n ) );\n }\n if ( args.length() = 7 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n nth_value_arg( "sprint", ev, ctx, args, 4 ),\n nth_value_arg( "sprint", ev, ctx, args, 5 ),\n nth_value_arg( "sprint", ev, ctx, args, 6 ),\n ) );\n }\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n nth_value_arg( "sprint", ev, ctx, args, 4 ),\n nth_value_arg( "sprint", ev, ctx, args, 5 ),\n nth_value_arg( "sprint", ev, ctx, args, 6 ),\n nth_value_arg( "sprint", ev, ctx, args, 7 ),\n ) );\n },\n ),\n new Func(\n spelling: "split",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import split;\n die "Too many arguments for split()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( split(\n nth_string_arg( "split", ev, ctx, args, 0 ),\n nth_value_arg( "split", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( split(\n nth_string_arg( "split", ev, ctx, args, 0 ),\n nth_value_arg( "split", ev, ctx, args, 1 ),\n nth_number_arg( "split", ev, ctx, args, 2 ),\n ) );\n },\n ),\n new Func(\n spelling: "join",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import join;\n die "Too many arguments for join()" if args.length() > 2;\n return funk.wrap( join(\n nth_string_arg( "join", ev, ctx, args, 0 ),\n nth_array_arg( "join", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "trim",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import trim;\n die "Too many arguments for trim()" if args.length() > 1;\n return funk.wrap( trim( first_string_arg(\n "trim",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "pad",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import pad;\n die "Too many arguments for pad()" if args.length() > 4;\n if ( args.length() = 2 ) {\n return funk.wrap( pad(\n nth_string_arg( "pad", ev, ctx, args, 0 ),\n nth_number_arg( "pad", ev, ctx, args, 1 ),\n ) );\n }\n if ( args.length() = 3 ) {\n return funk.wrap( pad(\n nth_string_arg( "pad", ev, ctx, args, 0 ),\n nth_number_arg( "pad", ev, ctx, args, 1 ),\n nth_string_arg( "pad", ev, ctx, args, 2 ),\n ) );\n }\n return funk.wrap( pad(\n nth_string_arg( "pad", ev, ctx, args, 0 ),\n nth_number_arg( "pad", ev, ctx, args, 1 ),\n nth_string_arg( "pad", ev, ctx, args, 2 ),\n nth_string_arg( "pad", ev, ctx, args, 3 ),\n ) );\n },\n ),\n new Func(\n spelling: "chomp",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import chomp;\n die "Too many arguments for chomp()" if args.length() > 1;\n return funk.wrap( chomp( first_string_arg(\n "chomp",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "title",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import title;\n die "Too many arguments for title()" if args.length() > 1;\n return funk.wrap( title( first_string_arg(\n "title",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "snake",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import snake;\n die "Too many arguments for snake()" if args.length() > 1;\n return funk.wrap( snake( first_string_arg(\n "snake",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "kebab",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import kebab;\n die "Too many arguments for kebab()" if args.length() > 1;\n return funk.wrap( kebab( first_string_arg(\n "kebab",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "camel",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import camel;\n die "Too many arguments for camel()" if args.length() > 1;\n return funk.wrap( camel( first_string_arg(\n "camel",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n);\n';
46401
- virtualFiles["/modules/std/path/zz/operators.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/zz/operators - Operator definitions for ZZPath expressions.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module extends the base ZPath operator model with\nZuzuScript-flavoured expression operators.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<Operator>\n\nZZPath operator class extending C<std/path/z/operators> C<Operator>.\n\n=back\n\n=head2 Functions\n\n=over\n\n=item C<< string_operand(ev, ctx, expr) >>, C<< string_operands(ev, ctx, left, right) >>\n\nParameters: evaluator, context, and expression AST nodes. Returns:\nC<String> or C<Array>. Evaluates one or two operands as strings.\n\n=item C<< first_eval_value(values) >>, C<< primitive_eval_value(values) >>\n\nParameters: C<values> is an evaluated node array. Returns: value.\nExtracts the first or primitive value used by ZZPath operators.\n\n=item C<< collection_operand(ev, ctx, expr) >>\n\nParameters: evaluator, context, and expression AST node. Returns:\ncollection value. Evaluates an operand for collection operators.\n\n=item C<< builtin_can(value, method_name) >>\n\nParameters: C<value> is any value and C<method_name> is a method name.\nReturns: C<Boolean>. Tests whether built-in values support a method.\n\n=back\n\n=head2 Constants\n\n=over\n\n=item C<STANDARD_OPERATORS>\n\nType: C<Array>. Complete ZZPath operator definition table.\n\n=item C<logical_negation>, C<logical_and>, C<logical_nand>, C<logical_xor>, C<logical_or>\n\nType: C<Function>. Logical operator implementations.\n\n=item C<numeric_power>, C<numeric_multiplication>, C<numeric_division>, C<numeric_modulus>, C<numeric_addition>, C<numeric_subtraction>, C<numeric_equality>, C<numeric_inequality>, C<numeric_less_than>, C<numeric_greater_than>, C<numeric_less_than_or_equal>, C<numeric_greater_than_or_equal>, C<numeric_compare>\n\nType: C<Function>. Numeric operator implementations.\n\n=item C<string_concatenation>, C<string_equality>, C<string_inequality>, C<string_greater_than>, C<string_greater_than_or_equal>, C<string_less_than>, C<string_less_than_or_equal>, C<string_compare>, C<string_equality_insensitive>, C<string_inequality_insensitive>, C<string_greater_than_insensitive>, C<string_greater_than_or_equal_insensitive>, C<string_less_than_insensitive>, C<string_less_than_or_equal_insensitive>, C<string_compare_insensitive>\n\nType: C<Function>. String operator implementations.\n\n=item C<regexp_match>, C<bitwise_and>, C<bitwise_xor>, C<bitwise_or>, C<set_union>, C<set_intersection>, C<set_difference>, C<collection_membership>, C<collection_non_membership>, C<set_subsetof>, C<set_supersetof>, C<set_equivalentof>, C<type_aware_equality>, C<type_aware_inequality>, C<object_can>\n\nType: C<Function>. Regular expression, bitwise, collection, type-aware,\nand object capability operator implementations.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/zz/operators >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z/operators import Operator as ZOperator;\n\n\nclass Operator extends ZOperator;\n\nconst logical_negation := function ( op, ev, ast, ctx, expr ) {\n const got := ev.eval_expr( expr, ev.nested_ctx( ctx ) );\n const value := got.length() > 0 ? got[0] : null;\n return op.wrap( not ev.truthy(value) );\n};\n\nconst numeric_power := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val ** right_val );\n};\n\nconst numeric_multiplication := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \xD7 right_val );\n};\n\nconst numeric_division := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \xF7 right_val );\n};\n\nconst numeric_modulus := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val mod right_val );\n};\n\nconst numeric_addition := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val + right_val );\n};\n\nconst numeric_subtraction := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val - right_val );\n};\n\nconst numeric_equality := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val = right_val );\n};\n\nconst numeric_inequality := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \u2260 right_val );\n};\n\nconst numeric_less_than := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val < right_val );\n};\n\nconst numeric_greater_than := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val > right_val );\n};\n\nconst numeric_less_than_or_equal := function (\n op,\n ev,\n ast,\n ctx,\n left,\n right,\n) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \u2264 right_val );\n};\n\nconst numeric_greater_than_or_equal := function (\n op,\n ev,\n ast,\n ctx,\n left,\n right,\n) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \u2265 right_val );\n};\n\nconst numeric_compare := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val <=> right_val );\n};\n\nfunction string_operand ( ev, ctx, expr ) {\n const result := ev.eval_expr( expr, ev.nested_ctx( ctx ) );\n return "" unless result.length;\n const value := ev.to_string( result[0] );\n return value \u2261 null ? "" : "" _ value;\n}\n\nfunction string_operands ( ev, ctx, left, right ) {\n return [\n string_operand( ev, ctx, left ),\n string_operand( ev, ctx, right ),\n ];\n}\n\nconst string_concatenation := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] _ values[1] );\n};\n\nconst string_equality := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] eq values[1] );\n};\n\nconst string_inequality := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] ne values[1] );\n};\n\nconst string_greater_than := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] gt values[1] );\n};\n\nconst string_greater_than_or_equal := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] ge values[1] );\n};\n\nconst string_less_than := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] lt values[1] );\n};\n\nconst string_less_than_or_equal := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] le values[1] );\n};\n\nconst string_compare := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] cmp values[1] );\n};\n\nconst string_equality_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val eq right_val );\n};\n\nconst string_inequality_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val ne right_val );\n};\n\nconst string_greater_than_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val gt right_val );\n};\n\nconst string_greater_than_or_equal_insensitive := function (\n op,\n ev,\n ast,\n ctx,\n left,\n right,\n) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val ge right_val );\n};\n\nconst string_less_than_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val lt right_val );\n};\n\nconst string_less_than_or_equal_insensitive := function (\n op,\n ev,\n ast,\n ctx,\n left,\n right,\n) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val le right_val );\n};\n\nconst string_compare_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val cmp right_val );\n};\n\nconst regexp_match := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] ~ values[1] );\n};\n\nconst bitwise_and := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val & right_val );\n};\n\nconst bitwise_xor := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val ^ right_val );\n};\n\nconst bitwise_or := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val | right_val );\n};\n\nfunction first_eval_value ( values ) {\n return values.length() > 0 ? values[0] : null;\n}\n\nfunction primitive_eval_value ( values ) {\n const first := first_eval_value(values);\n return first \u2261 null ? null : first.primitive_value();\n}\n\nfunction collection_operand ( ev, ctx, expr ) {\n const values := ev.eval_expr( expr, ev.nested_ctx( ctx ) );\n if ( values.length() = 1 ) {\n return values[0].primitive_value();\n }\n return values.map( fn value \u2192 value.primitive_value() );\n}\n\nconst logical_and := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( ev.truthy( first_eval_value(left_vals) ) ) {\n return ev.eval_expr( right, ev.nested_ctx( ctx ) );\n }\n return op.wrap(false);\n};\n\nconst logical_nand := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( not ev.truthy( first_eval_value(left_vals) ) ) {\n return op.wrap(true);\n }\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.truthy( first_eval_value(right_vals) ) ? false : true );\n};\n\nconst logical_xor := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const left_truth := ev.truthy( first_eval_value(left_vals) );\n const right_truth := ev.truthy( first_eval_value(right_vals) );\n return op.wrap( ( left_truth xor right_truth ) ? true : false );\n};\n\nconst logical_or := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( ev.truthy( first_eval_value(left_vals) ) ) {\n return op.wrap(true);\n }\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.truthy( first_eval_value(right_vals) ) ? true : false );\n};\n\nconst set_union := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val union right_val );\n};\n\nconst set_intersection := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val intersection right_val );\n};\n\nconst set_difference := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val \u2216 right_val );\n};\n\nconst collection_membership := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const left_val := primitive_eval_value(left_vals);\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val in right_val );\n};\n\nconst collection_non_membership := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const left_val := primitive_eval_value(left_vals);\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val \u2209 right_val );\n};\n\nconst set_subsetof := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val subsetof right_val );\n};\n\nconst set_supersetof := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val supersetof right_val );\n};\n\nconst set_equivalentof := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val equivalentof right_val );\n};\n\nconst type_aware_equality := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.equals(\n first_eval_value(left_vals),\n first_eval_value(right_vals),\n ) );\n};\n\nconst type_aware_inequality := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( not ev.equals(\n first_eval_value(left_vals),\n first_eval_value(right_vals),\n ) );\n};\n\nfunction builtin_can ( value, method_name ) {\n return false if value \u2261 null\n or method_name \u2261 null\n or method_name eq "";\n\n if ( value instanceof Array or value instanceof Bag or value instanceof Set ) {\n return method_name in [\n "length",\n "empty",\n "contains",\n "push",\n "to_Array",\n "to_Bag",\n "to_Set",\n ];\n }\n if ( value instanceof Dict or value instanceof PairList ) {\n return method_name in [\n "length",\n "empty",\n "exists",\n "keys",\n "values",\n "to_Array",\n ];\n }\n if ( value instanceof String or value instanceof BinaryString ) {\n return method_name in [ "length" ];\n }\n return false;\n}\n\nconst object_can := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const left_val := primitive_eval_value(left_vals);\n const right_val := primitive_eval_value(right_vals);\n return op.wrap(\n ( left_val can (right_val) ) or builtin_can( left_val, right_val )\n );\n};\n\nconst STANDARD_OPERATORS := [\n new Operator(\n spelling: "?:",\n kind: "ELVIS",\n precedence: 1,\n lex_ignore: true,\n ),\n\n new Operator(\n spelling: "+",\n kind: "UPLUS",\n unary: true,\n precedence: 20,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( +value );\n },\n ),\n\n new Operator(\n spelling: "-",\n kind: "UMINUS",\n unary: true,\n precedence: 20,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( -value );\n },\n ),\n\n new Operator(\n spelling: "\u221A",\n kind: "SQRT",\n unary: true,\n precedence: 20,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( sqrt value );\n },\n ),\n\n new Operator(\n spelling: "!",\n kind: "NOT",\n unary: true,\n precedence: 20,\n f: logical_negation,\n ),\n\n new Operator(\n spelling: "\xAC",\n kind: "NOTSYM",\n unary: true,\n precedence: 20,\n f: logical_negation,\n ),\n\n new Operator(\n spelling: "**",\n kind: "POW",\n require_ws: true,\n right_assoc: true,\n precedence: 13,\n f: numeric_power,\n ),\n\n new Operator(\n spelling: "\xD7",\n kind: "TIMES_SIGN",\n precedence: 12,\n f: numeric_multiplication,\n ),\n\n new Operator(\n spelling: "*",\n kind: "TIMES",\n require_ws: true,\n precedence: 12,\n lex_ignore: true,\n alias: "STAR",\n f: numeric_multiplication,\n ),\n\n new Operator(\n spelling: "\xF7",\n kind: "DIVIDE_SIGN",\n precedence: 12,\n f: numeric_division,\n ),\n\n new Operator(\n spelling: "/",\n kind: "DIVIDE",\n require_ws: true,\n precedence: 12,\n lex_ignore: true,\n alias: "SLASH",\n f: numeric_division,\n ),\n\n new Operator(\n spelling: "mod",\n kind: "MOD",\n precedence: 12,\n f: numeric_modulus,\n ),\n\n new Operator(\n spelling: "+",\n kind: "PLUS",\n require_ws: true,\n precedence: 11,\n f: numeric_addition,\n ),\n\n new Operator(\n spelling: "-",\n kind: "MINUS",\n require_ws: true,\n precedence: 11,\n f: numeric_subtraction,\n ),\n\n new Operator(\n spelling: "=",\n kind: "EQ",\n precedence: 5,\n f: numeric_equality,\n ),\n\n new Operator(\n spelling: "\u2260",\n kind: "NE",\n precedence: 5,\n f: numeric_inequality,\n ),\n\n new Operator(\n spelling: "<",\n kind: "LT",\n precedence: 5,\n f: numeric_less_than,\n ),\n\n new Operator(\n spelling: ">",\n kind: "GT",\n precedence: 5,\n f: numeric_greater_than,\n ),\n\n new Operator(\n spelling: "\u2264",\n kind: "LE_SIGN",\n precedence: 5,\n f: numeric_less_than_or_equal,\n ),\n\n new Operator(\n spelling: "<=",\n kind: "LE",\n precedence: 5,\n f: numeric_less_than_or_equal,\n ),\n\n new Operator(\n spelling: "\u2265",\n kind: "GE_SIGN",\n precedence: 5,\n f: numeric_greater_than_or_equal,\n ),\n\n new Operator(\n spelling: ">=",\n kind: "GE",\n precedence: 5,\n f: numeric_greater_than_or_equal,\n ),\n\n new Operator(\n spelling: "\u2276",\n kind: "CMP_SIGN",\n precedence: 5,\n f: numeric_compare,\n ),\n\n new Operator(\n spelling: "<=>",\n kind: "CMP",\n precedence: 5,\n f: numeric_compare,\n ),\n\n new Operator(\n spelling: "\u2277",\n kind: "CMP_REV_SIGN",\n precedence: 5,\n f: numeric_compare,\n ),\n\n new Operator(\n spelling: "_",\n kind: "CONCAT",\n require_ws: true,\n precedence: 10,\n f: string_concatenation,\n ),\n\n new Operator(\n spelling: "eq",\n kind: "STR_EQ",\n precedence: 5,\n f: string_equality,\n ),\n\n new Operator(\n spelling: "ne",\n kind: "STR_NE",\n precedence: 5,\n f: string_inequality,\n ),\n\n new Operator(\n spelling: "gt",\n kind: "STR_GT",\n precedence: 5,\n f: string_greater_than,\n ),\n\n new Operator(\n spelling: "ge",\n kind: "STR_GE",\n precedence: 5,\n f: string_greater_than_or_equal,\n ),\n\n new Operator(\n spelling: "lt",\n kind: "STR_LT",\n precedence: 5,\n f: string_less_than,\n ),\n\n new Operator(\n spelling: "le",\n kind: "STR_LE",\n precedence: 5,\n f: string_less_than_or_equal,\n ),\n\n new Operator(\n spelling: "cmp",\n kind: "STR_CMP",\n precedence: 5,\n f: string_compare,\n ),\n\n new Operator(\n spelling: "eqi",\n kind: "STR_EQI",\n precedence: 5,\n f: string_equality_insensitive,\n ),\n\n new Operator(\n spelling: "nei",\n kind: "STR_NEI",\n precedence: 5,\n f: string_inequality_insensitive,\n ),\n\n new Operator(\n spelling: "gti",\n kind: "STR_GTI",\n precedence: 5,\n f: string_greater_than_insensitive,\n ),\n\n new Operator(\n spelling: "gei",\n kind: "STR_GEI",\n precedence: 5,\n f: string_greater_than_or_equal_insensitive,\n ),\n\n new Operator(\n spelling: "lti",\n kind: "STR_LTI",\n precedence: 5,\n f: string_less_than_insensitive,\n ),\n\n new Operator(\n spelling: "lei",\n kind: "STR_LEI",\n precedence: 5,\n f: string_less_than_or_equal_insensitive,\n ),\n\n new Operator(\n spelling: "cmpi",\n kind: "STR_CMPI",\n precedence: 5,\n f: string_compare_insensitive,\n ),\n\n new Operator(\n spelling: "~",\n kind: "REGEXP_MATCH",\n precedence: 5,\n f: regexp_match,\n ),\n\n new Operator(\n spelling: "&",\n kind: "BAND",\n precedence: 8,\n f: bitwise_and,\n ),\n\n new Operator(\n spelling: "^",\n kind: "BXOR",\n precedence: 7,\n f: bitwise_xor,\n ),\n\n new Operator(\n spelling: "|",\n kind: "BOR",\n precedence: 6,\n f: bitwise_or,\n ),\n\n new Operator(\n spelling: "\u22C0",\n kind: "LAND_SIGN",\n precedence: 3,\n f: logical_and,\n ),\n\n new Operator(\n spelling: "and",\n kind: "LAND",\n precedence: 3,\n f: logical_and,\n ),\n\n new Operator(\n spelling: "\u22BC",\n kind: "LNAND_SIGN",\n precedence: 3,\n f: logical_nand,\n ),\n\n new Operator(\n spelling: "nand",\n kind: "LNAND",\n precedence: 3,\n f: logical_nand,\n ),\n\n new Operator(\n spelling: "\u22BB",\n kind: "LXOR_SIGN",\n precedence: 2,\n f: logical_xor,\n ),\n\n new Operator(\n spelling: "xor",\n kind: "LXOR",\n precedence: 2,\n f: logical_xor,\n ),\n\n new Operator(\n spelling: "\u22C1",\n kind: "LOR_SIGN",\n precedence: 1,\n f: logical_or,\n ),\n\n new Operator(\n spelling: "or",\n kind: "LOR",\n precedence: 1,\n f: logical_or,\n ),\n\n new Operator(\n spelling: "\u22C3",\n kind: "SET_UNION_SIGN",\n precedence: 9,\n f: set_union,\n ),\n\n new Operator(\n spelling: "union",\n kind: "SET_UNION",\n precedence: 9,\n f: set_union,\n ),\n\n new Operator(\n spelling: "\u22C2",\n kind: "SET_INTERSECTION_SIGN",\n precedence: 9,\n f: set_intersection,\n ),\n\n new Operator(\n spelling: "intersection",\n kind: "SET_INTERSECTION",\n precedence: 9,\n f: set_intersection,\n ),\n\n new Operator(\n spelling: "\u2216",\n kind: "SET_DIFFERENCE_SIGN",\n precedence: 9,\n f: set_difference,\n ),\n\n new Operator(\n spelling: "\\\\",\n kind: "SET_DIFFERENCE",\n precedence: 9,\n require_ws: true,\n f: set_difference,\n ),\n\n new Operator(\n spelling: "\u2208",\n kind: "MEMBER_SIGN",\n precedence: 5,\n f: collection_membership,\n ),\n\n new Operator(\n spelling: "in",\n kind: "MEMBER",\n precedence: 5,\n f: collection_membership,\n ),\n\n new Operator(\n spelling: "\u2209",\n kind: "NOT_MEMBER_SIGN",\n precedence: 5,\n f: collection_non_membership,\n ),\n\n new Operator(\n spelling: "\u2282",\n kind: "SUBSETOF_SIGN",\n precedence: 5,\n f: set_subsetof,\n ),\n\n new Operator(\n spelling: "subsetof",\n kind: "SUBSETOF",\n precedence: 5,\n f: set_subsetof,\n ),\n\n new Operator(\n spelling: "\u2283",\n kind: "SUPERSETOF_SIGN",\n precedence: 5,\n f: set_supersetof,\n ),\n\n new Operator(\n spelling: "supersetof",\n kind: "SUPERSETOF",\n precedence: 5,\n f: set_supersetof,\n ),\n\n new Operator(\n spelling: "\u2282\u2283",\n kind: "EQUIVALENTOF_SIGN",\n precedence: 5,\n f: set_equivalentof,\n ),\n\n new Operator(\n spelling: "equivalentof",\n kind: "EQUIVALENTOF",\n precedence: 5,\n f: set_equivalentof,\n ),\n\n new Operator(\n spelling: "\u2261",\n kind: "TYPE_EQ_SIGN",\n precedence: 4,\n f: type_aware_equality,\n ),\n\n new Operator(\n spelling: "==",\n kind: "TYPE_EQ",\n precedence: 4,\n f: type_aware_equality,\n ),\n\n new Operator(\n spelling: "\u2262",\n kind: "TYPE_NE_SIGN",\n precedence: 4,\n f: type_aware_inequality,\n ),\n\n new Operator(\n spelling: "!=",\n kind: "TYPE_NE",\n precedence: 4,\n f: type_aware_inequality,\n ),\n\n new Operator(\n spelling: "can",\n kind: "CAN",\n precedence: 5,\n f: object_can,\n ),\n\n new Operator(\n spelling: "~",\n kind: "BNOT",\n unary: true,\n precedence: 20,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( ~value );\n },\n ),\n];\n';
47412
+ virtualFiles["/modules/std/path/zz/functions.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/zz/functions - Function definitions for ZZPath expressions.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module extends the base ZPath function table with\nZuzuScript-flavoured path expression helpers.\n\n=head1 EXPORTS\n\n=head2 Constants\n\n=over\n\n=item C<STANDARD_FUNCTION_NAMES>\n\nType: C<Array>. Names of inherited ZPath functions available to ZZPath.\n\n=item C<STANDARD_FUNCTIONS>\n\nType: C<Array>. Complete ZZPath function definition table.\n\n=back\n\n=head2 Functions\n\n=over\n\n=item C<< first_arg_node(name, ev, ctx, args) >>\n\nParameters: function name, evaluator, context, and argument AST nodes.\nReturns: C<Node> or C<null>. Resolves the first argument or current\ncontext node.\n\n=item C<< first_number_arg(name, ev, ctx, args) >>, C<< nth_number_arg(name, ev, ctx, args, i) >>\n\nParameters: function name, evaluator, context, arguments, and optional\nindex. Returns: C<Number>. Resolves an argument as a number.\n\n=item C<< first_string_arg(name, ev, ctx, args) >>, C<< nth_string_arg(name, ev, ctx, args, i) >>\n\nParameters: function name, evaluator, context, arguments, and optional\nindex. Returns: C<String>. Resolves an argument as a string.\n\n=item C<< nth_value_arg(name, ev, ctx, args, i) >>\n\nParameters: function name, evaluator, context, arguments, and index.\nReturns: value. Resolves an argument as a primitive value.\n\n=item C<< nth_array_arg(name, ev, ctx, args, i) >>\n\nParameters: function name, evaluator, context, arguments, and index.\nReturns: C<Array>. Resolves an argument as an array value.\n\n=item C<< number_args(ev, ctx, args) >>\n\nParameters: evaluator, context, and argument AST nodes. Returns:\nC<Array>. Resolves all arguments as numbers.\n\n=item C<< first_arg_value(name, ev, ctx, args) >>\n\nParameters: function name, evaluator, context, and arguments. Returns:\nvalue. Resolves the first argument as a primitive value.\n\n=item C<< z_function(spelling) >>\n\nParameters: C<spelling> is a base ZPath function name. Returns:\nC<Function>. Returns the inherited function implementation.\n\n=item C<< string_index_of(funk, ev, ast, ctx, args) >>\n\nParameters: standard ZPath function callback arguments. Returns:\nC<Array>. Implements string C<index-of>.\n\n=item C<< string_rindex(funk, ev, ast, ctx, args) >>\n\nParameters: standard ZPath function callback arguments. Returns:\nC<Array>. Implements string reverse-index lookup.\n\n=item C<< defined_function(funk, ev, ast, ctx, args) >>\n\nParameters: standard ZPath function callback arguments. Returns:\nC<Array>. Implements defined-value testing.\n\n=item C<< empty_function(funk, ev, ast, ctx, args) >>\n\nParameters: standard ZPath function callback arguments. Returns:\nC<Array>. Implements empty-value testing.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/zz/functions >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z/functions import Func, STANDARD_FUNCTIONS as Z_STANDARD_FUNCTIONS;\n\n\nconst STANDARD_FUNCTION_NAMES := [\n "true",\n "false",\n "null",\n "tag",\n "url",\n "local-name",\n "key",\n "value",\n "index",\n "count",\n "is-first",\n "is-last",\n "next",\n "prev",\n];\n\nfunction first_arg_node ( name, ev, ctx, args ) {\n if ( args.length() = 0 ) {\n return ctx.nodeset.get( 0, null );\n }\n if ( args.length() = 1 ) {\n const got := ev.eval_expr( args[0], ev.nested_ctx( ctx ) );\n return got.get( 0, null );\n }\n die `Too many arguments for ${name}()`;\n}\n\nfunction first_number_arg ( name, ev, ctx, args ) {\n const node := first_arg_node( name, ev, ctx, args );\n // Numeric coercion may produce 0; only null falls back.\n return ev.to_number(node) ?: 0;\n}\n\nfunction nth_number_arg ( name, ev, ctx, args, i ) {\n die `Not enough arguments for ${name}()` if args.length() <= i;\n const got := ev.eval_expr( args[i], ev.nested_ctx( ctx ) );\n // Numeric coercion may produce 0; only null falls back.\n return ev.to_number( got.get( 0, null ) ) ?: 0;\n}\n\nfunction nth_string_arg ( name, ev, ctx, args, i ) {\n die `Not enough arguments for ${name}()` if args.length() <= i;\n const got := ev.eval_expr( args[i], ev.nested_ctx( ctx ) );\n const value := ev.to_string( got.get( 0, null ) );\n return value \u2261 null ? "" : "" _ value;\n}\n\nfunction nth_value_arg ( name, ev, ctx, args, i ) {\n die `Not enough arguments for ${name}()` if args.length() <= i;\n const got := ev.eval_expr( args[i], ev.nested_ctx( ctx ) );\n const node := got.get( 0, null );\n return node \u2261 null ? null : node.primitive_value();\n}\n\nfunction nth_array_arg ( name, ev, ctx, args, i ) {\n die `Not enough arguments for ${name}()` if args.length() <= i;\n const got := ev.eval_expr( args[i], ev.nested_ctx( ctx ) );\n if ( got.length() = 1 ) {\n const one := got[0].primitive_value();\n return one if one instanceof Array;\n }\n return got.map( fn node \u2192 node.primitive_value() );\n}\n\nfunction number_args ( ev, ctx, args ) {\n let nums := [];\n if ( args.length() = 0 ) {\n for ( let node in ctx.nodeset ) {\n const value := ev.to_number(node);\n nums.push(value) if value instanceof Number;\n }\n return nums;\n }\n for ( let arg in args ) {\n const got := ev.eval_expr( arg, ev.nested_ctx( ctx ) );\n for ( let node in got ) {\n const value := ev.to_number(node);\n nums.push(value) if value instanceof Number;\n }\n }\n return nums;\n}\n\nfunction first_string_arg ( name, ev, ctx, args ) {\n const node := first_arg_node( name, ev, ctx, args );\n const value := ev.to_string(node);\n return value \u2261 null ? "" : "" _ value;\n}\n\nfunction first_arg_value ( name, ev, ctx, args ) {\n const node := first_arg_node( name, ev, ctx, args );\n return node \u2261 null ? null : node.primitive_value();\n}\n\nfunction z_function ( spelling ) {\n const func := Z_STANDARD_FUNCTIONS.first( fn f \u2192 f.has_name(spelling) );\n die `std/path/z/functions is missing ${spelling}()` if func \u2261 null;\n return func;\n}\n\nfunction string_index_of ( funk, ev, ast, ctx, args ) {\n from std/string import index;\n die "Too many arguments for index-of()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( index(\n nth_string_arg( "index-of", ev, ctx, args, 0 ),\n nth_string_arg( "index-of", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( index(\n nth_string_arg( "index-of", ev, ctx, args, 0 ),\n nth_string_arg( "index-of", ev, ctx, args, 1 ),\n nth_number_arg( "index-of", ev, ctx, args, 2 ),\n ) );\n}\n\nfunction string_rindex ( funk, ev, ast, ctx, args ) {\n from std/string import rindex;\n const name := funk.get_spelling;\n die `Too many arguments for ${name}()` if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( rindex(\n nth_string_arg( name, ev, ctx, args, 0 ),\n nth_string_arg( name, ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( rindex(\n nth_string_arg( name, ev, ctx, args, 0 ),\n nth_string_arg( name, ev, ctx, args, 1 ),\n nth_number_arg( name, ev, ctx, args, 2 ),\n ) );\n}\n\nfunction defined_function ( funk, ev, ast, ctx, args ) {\n const node := first_arg_node( "defined", ev, ctx, args );\n return funk.wrap( node \u2262 null and node.primitive_value() \u2262 null );\n}\n\nfunction empty_function ( funk, ev, ast, ctx, args ) {\n const value := first_arg_value( "empty", ev, ctx, args );\n return funk.wrap( true ) if value \u2261 null;\n\n if (\n value instanceof Array or\n value instanceof Bag or\n value instanceof Set or\n value instanceof Dict or\n value instanceof PairList\n ) {\n return funk.wrap( value.length() = 0 );\n }\n\n die `empty() expects a Collection or null, got ${typeof value}`;\n}\n\n// Start with a base of functions inherited from ZPath:\nconst STANDARD_FUNCTIONS := STANDARD_FUNCTION_NAMES.map( fn n \u2192 z_function(n) );\n\n// Add equivalents for ZuzuScript word-like unary operators:\nSTANDARD_FUNCTIONS.push(\n new Func(\n spelling: "abs",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( abs first_number_arg( "abs", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "floor",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( floor first_number_arg( "floor", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "ceil",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( ceil first_number_arg( "ceil", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "round",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( round first_number_arg( "round", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "int",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( int first_number_arg( "int", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "sqrt",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( sqrt first_number_arg( "sqrt", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "uc",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( uc first_string_arg( "uc", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "lc",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( lc first_string_arg( "lc", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "length",\n f: function ( funk, ev, ast, ctx, args ) {\n return funk.wrap( length first_string_arg( "length", ev, ctx, args ) );\n },\n ),\n new Func(\n spelling: "not",\n f: function ( funk, ev, ast, ctx, args ) {\n const node := first_arg_node( "not", ev, ctx, args );\n return funk.wrap( not ev.truthy(node) );\n },\n ),\n new Func(\n spelling: "typeof",\n f: function ( funk, ev, ast, ctx, args ) {\n const node := first_arg_node( "typeof", ev, ctx, args );\n const value := node \u2261 null ? null : node.primitive_value();\n return funk.wrap( typeof value );\n },\n ),\n new Func(\n spelling: "defined",\n f: defined_function,\n ),\n new Func(\n spelling: "empty",\n f: empty_function,\n ),\n);\n\n// Functions from std/internals:\nSTANDARD_FUNCTIONS.push(\n new Func(\n spelling: "string",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/internals import to_String;\n return funk.wrap( to_String( first_arg_value(\n "string",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "number",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/internals import to_Number;\n return funk.wrap( to_Number( first_arg_value(\n "number",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "boolean",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/internals import to_Boolean;\n return funk.wrap( to_Boolean( first_arg_value(\n "boolean",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "regexp",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/internals import to_Regexp;\n return funk.wrap( to_Regexp( first_arg_value(\n "regexp",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n);\n\n// Functions from std/math:\nSTANDARD_FUNCTIONS.push(\n new Func(\n spelling: "sum",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/math import Math;\n return funk.wrap( Math.sum( number_args( ev, ctx, args ) ) );\n },\n ),\n new Func(\n spelling: "min",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/math import Math;\n return funk.wrap( Math.min( number_args( ev, ctx, args ) ) );\n },\n ),\n new Func(\n spelling: "max",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/math import Math;\n return funk.wrap( Math.max( number_args( ev, ctx, args ) ) );\n },\n ),\n new Func(\n spelling: "clamp",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/math import Math;\n die "Too many arguments for clamp()" if args.length() > 3;\n return funk.wrap( Math.clamp(\n nth_number_arg( "clamp", ev, ctx, args, 0 ),\n nth_number_arg( "clamp", ev, ctx, args, 1 ),\n nth_number_arg( "clamp", ev, ctx, args, 2 ),\n ) );\n },\n ),\n);\n\n// Functions from std/string:\nSTANDARD_FUNCTIONS.push(\n new Func(\n spelling: "substr",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import substr;\n die "Too many arguments for substr()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( substr(\n nth_string_arg( "substr", ev, ctx, args, 0 ),\n nth_number_arg( "substr", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( substr(\n nth_string_arg( "substr", ev, ctx, args, 0 ),\n nth_number_arg( "substr", ev, ctx, args, 1 ),\n nth_number_arg( "substr", ev, ctx, args, 2 ),\n ) );\n },\n ),\n new Func(\n spelling: "index-of",\n f: string_index_of,\n ),\n new Func(\n spelling: "rindex",\n f: string_rindex,\n ),\n new Func(\n spelling: "last-index-of",\n f: string_rindex,\n ),\n new Func(\n spelling: "contains",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import contains;\n die "Too many arguments for contains()" if args.length() > 2;\n return funk.wrap( contains(\n nth_string_arg( "contains", ev, ctx, args, 0 ),\n nth_string_arg( "contains", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "chr",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import chr;\n die "Too many arguments for chr()" if args.length() > 1;\n return funk.wrap( chr( nth_number_arg(\n "chr",\n ev,\n ctx,\n args,\n 0,\n ) ) );\n },\n ),\n new Func(\n spelling: "ord",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import ord;\n die "Too many arguments for ord()" if args.length() > 2;\n if ( args.length() = 1 ) {\n return funk.wrap( ord( nth_string_arg(\n "ord",\n ev,\n ctx,\n args,\n 0,\n ) ) );\n }\n return funk.wrap( ord(\n nth_string_arg( "ord", ev, ctx, args, 0 ),\n nth_number_arg( "ord", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "replace",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import replace;\n die "Too many arguments for replace()" if args.length() > 4;\n if ( args.length() = 3 ) {\n return funk.wrap( replace(\n nth_string_arg( "replace", ev, ctx, args, 0 ),\n nth_value_arg( "replace", ev, ctx, args, 1 ),\n nth_string_arg( "replace", ev, ctx, args, 2 ),\n ) );\n }\n return funk.wrap( replace(\n nth_string_arg( "replace", ev, ctx, args, 0 ),\n nth_value_arg( "replace", ev, ctx, args, 1 ),\n nth_string_arg( "replace", ev, ctx, args, 2 ),\n nth_string_arg( "replace", ev, ctx, args, 3 ),\n ) );\n },\n ),\n new Func(\n spelling: "search",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import search;\n die "Too many arguments for search()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( search(\n nth_string_arg( "search", ev, ctx, args, 0 ),\n nth_value_arg( "search", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( search(\n nth_string_arg( "search", ev, ctx, args, 0 ),\n nth_value_arg( "search", ev, ctx, args, 1 ),\n nth_string_arg( "search", ev, ctx, args, 2 ),\n ) );\n },\n ),\n new Func(\n spelling: "starts_with",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import starts_with;\n die "Too many arguments for starts_with()" if args.length() > 2;\n return funk.wrap( starts_with(\n nth_string_arg( "starts_with", ev, ctx, args, 0 ),\n nth_string_arg( "starts_with", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "ends_with",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import ends_with;\n die "Too many arguments for ends_with()" if args.length() > 2;\n return funk.wrap( ends_with(\n nth_string_arg( "ends_with", ev, ctx, args, 0 ),\n nth_string_arg( "ends_with", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "matches",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import matches;\n die "Too many arguments for matches()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( matches(\n nth_string_arg( "matches", ev, ctx, args, 0 ),\n nth_value_arg( "matches", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( matches(\n nth_string_arg( "matches", ev, ctx, args, 0 ),\n nth_value_arg( "matches", ev, ctx, args, 1 ),\n nth_string_arg( "matches", ev, ctx, args, 2 ),\n ) );\n },\n ),\n new Func(\n spelling: "pattern_to_regexp",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import pattern_to_regexp;\n die "Too many arguments for pattern_to_regexp()"\n if args.length() > 2;\n if ( args.length() = 1 ) {\n return funk.wrap( pattern_to_regexp(\n nth_string_arg( "pattern_to_regexp", ev, ctx, args, 0 ),\n ) );\n }\n return funk.wrap( pattern_to_regexp(\n nth_string_arg( "pattern_to_regexp", ev, ctx, args, 0 ),\n nth_value_arg( "pattern_to_regexp", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "quotemeta",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import quotemeta;\n die "Too many arguments for quotemeta()" if args.length() > 1;\n return funk.wrap( quotemeta(\n nth_string_arg( "quotemeta", ev, ctx, args, 0 ),\n ) );\n },\n ),\n new Func(\n spelling: "sprint",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import sprint;\n die "Not enough arguments for sprint()" if args.length() = 0;\n die "Too many arguments for sprint()" if args.length() > 8;\n\n const fmt := nth_string_arg( "sprint", ev, ctx, args, 0 );\n if ( args.length() = 1 ) {\n return funk.wrap( sprint(fmt) );\n }\n if ( args.length() = 2 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n ) );\n }\n if ( args.length() = 3 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n ) );\n }\n if ( args.length() = 4 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n ) );\n }\n if ( args.length() = 5 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n nth_value_arg( "sprint", ev, ctx, args, 4 ),\n ) );\n }\n if ( args.length() = 6 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n nth_value_arg( "sprint", ev, ctx, args, 4 ),\n nth_value_arg( "sprint", ev, ctx, args, 5 ),\n ) );\n }\n if ( args.length() = 7 ) {\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n nth_value_arg( "sprint", ev, ctx, args, 4 ),\n nth_value_arg( "sprint", ev, ctx, args, 5 ),\n nth_value_arg( "sprint", ev, ctx, args, 6 ),\n ) );\n }\n return funk.wrap( sprint(\n fmt,\n nth_value_arg( "sprint", ev, ctx, args, 1 ),\n nth_value_arg( "sprint", ev, ctx, args, 2 ),\n nth_value_arg( "sprint", ev, ctx, args, 3 ),\n nth_value_arg( "sprint", ev, ctx, args, 4 ),\n nth_value_arg( "sprint", ev, ctx, args, 5 ),\n nth_value_arg( "sprint", ev, ctx, args, 6 ),\n nth_value_arg( "sprint", ev, ctx, args, 7 ),\n ) );\n },\n ),\n new Func(\n spelling: "split",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import split;\n die "Too many arguments for split()" if args.length() > 3;\n if ( args.length() = 2 ) {\n return funk.wrap( split(\n nth_string_arg( "split", ev, ctx, args, 0 ),\n nth_value_arg( "split", ev, ctx, args, 1 ),\n ) );\n }\n return funk.wrap( split(\n nth_string_arg( "split", ev, ctx, args, 0 ),\n nth_value_arg( "split", ev, ctx, args, 1 ),\n nth_number_arg( "split", ev, ctx, args, 2 ),\n ) );\n },\n ),\n new Func(\n spelling: "join",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import join;\n die "Too many arguments for join()" if args.length() > 2;\n return funk.wrap( join(\n nth_string_arg( "join", ev, ctx, args, 0 ),\n nth_array_arg( "join", ev, ctx, args, 1 ),\n ) );\n },\n ),\n new Func(\n spelling: "trim",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import trim;\n die "Too many arguments for trim()" if args.length() > 1;\n return funk.wrap( trim( first_string_arg(\n "trim",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "pad",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import pad;\n die "Too many arguments for pad()" if args.length() > 4;\n if ( args.length() = 2 ) {\n return funk.wrap( pad(\n nth_string_arg( "pad", ev, ctx, args, 0 ),\n nth_number_arg( "pad", ev, ctx, args, 1 ),\n ) );\n }\n if ( args.length() = 3 ) {\n return funk.wrap( pad(\n nth_string_arg( "pad", ev, ctx, args, 0 ),\n nth_number_arg( "pad", ev, ctx, args, 1 ),\n nth_string_arg( "pad", ev, ctx, args, 2 ),\n ) );\n }\n return funk.wrap( pad(\n nth_string_arg( "pad", ev, ctx, args, 0 ),\n nth_number_arg( "pad", ev, ctx, args, 1 ),\n nth_string_arg( "pad", ev, ctx, args, 2 ),\n nth_string_arg( "pad", ev, ctx, args, 3 ),\n ) );\n },\n ),\n new Func(\n spelling: "chomp",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import chomp;\n die "Too many arguments for chomp()" if args.length() > 1;\n return funk.wrap( chomp( first_string_arg(\n "chomp",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "title",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import title;\n die "Too many arguments for title()" if args.length() > 1;\n return funk.wrap( title( first_string_arg(\n "title",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "snake",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import snake;\n die "Too many arguments for snake()" if args.length() > 1;\n return funk.wrap( snake( first_string_arg(\n "snake",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "kebab",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import kebab;\n die "Too many arguments for kebab()" if args.length() > 1;\n return funk.wrap( kebab( first_string_arg(\n "kebab",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n new Func(\n spelling: "camel",\n f: function ( funk, ev, ast, ctx, args ) {\n from std/string import camel;\n die "Too many arguments for camel()" if args.length() > 1;\n return funk.wrap( camel( first_string_arg(\n "camel",\n ev,\n ctx,\n args,\n ) ) );\n },\n ),\n);\n';
47413
+ virtualFiles["/modules/std/path/zz/operators.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/path/zz/operators - Operator definitions for ZZPath expressions.\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by all implementations of ZuzuScript.\n\n=head1 DESCRIPTION\n\nThis module extends the base ZPath operator model with\nZuzuScript-flavoured expression operators.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<Operator>\n\nZZPath operator class extending C<std/path/z/operators> C<Operator>.\n\n=back\n\n=head2 Functions\n\n=over\n\n=item C<< string_operand(ev, ctx, expr) >>, C<< string_operands(ev, ctx, left, right) >>\n\nParameters: evaluator, context, and expression AST nodes. Returns:\nC<String> or C<Array>. Evaluates one or two operands as strings.\n\n=item C<< first_eval_value(values) >>, C<< primitive_eval_value(values) >>\n\nParameters: C<values> is an evaluated node array. Returns: value.\nExtracts the first or primitive value used by ZZPath operators.\n\n=item C<< collection_operand(ev, ctx, expr) >>\n\nParameters: evaluator, context, and expression AST node. Returns:\ncollection value. Evaluates an operand for collection operators.\n\n=item C<< builtin_can(value, method_name) >>\n\nParameters: C<value> is any value and C<method_name> is a method name.\nReturns: C<Boolean>. Tests whether built-in values support a method.\n\n=back\n\n=head2 Constants\n\n=over\n\n=item C<STANDARD_OPERATORS>\n\nType: C<Array>. Complete ZZPath operator definition table.\n\n=item C<logical_negation>, C<logical_and>, C<logical_nand>, C<logical_butnot>, C<logical_xor>, C<logical_nor>, C<logical_xnor>, C<logical_onlyif>, C<logical_or>, C<logical_and_value>, C<logical_nand_value>, C<logical_butnot_value>, C<logical_xor_value>, C<logical_nor_value>, C<logical_xnor_value>, C<logical_onlyif_value>, C<logical_or_value>\n\nType: C<Function>. Logical operator implementations.\n\n=item C<numeric_power>, C<numeric_multiplication>, C<numeric_division>, C<numeric_modulus>, C<numeric_addition>, C<numeric_subtraction>, C<numeric_left_shift>, C<numeric_right_shift>, C<numeric_equality>, C<numeric_inequality>, C<numeric_less_than>, C<numeric_greater_than>, C<numeric_less_than_or_equal>, C<numeric_greater_than_or_equal>, C<numeric_compare>, C<numeric_divides>, C<numeric_not_divides>\n\nType: C<Function>. Numeric operator implementations.\n\n=item C<string_concatenation>, C<string_equality>, C<string_inequality>, C<string_greater_than>, C<string_greater_than_or_equal>, C<string_less_than>, C<string_less_than_or_equal>, C<string_compare>, C<string_equality_insensitive>, C<string_inequality_insensitive>, C<string_greater_than_insensitive>, C<string_greater_than_or_equal_insensitive>, C<string_less_than_insensitive>, C<string_less_than_or_equal_insensitive>, C<string_compare_insensitive>\n\nType: C<Function>. String operator implementations.\n\n=item C<regexp_match>, C<bitwise_and>, C<bitwise_xor>, C<bitwise_or>, C<set_union>, C<set_intersection>, C<set_difference>, C<collection_membership>, C<collection_non_membership>, C<set_subsetof>, C<set_supersetof>, C<set_equivalentof>, C<type_aware_equality>, C<type_aware_inequality>, C<object_can>\n\nType: C<Function>. Regular expression, bitwise, collection, type-aware,\nand object capability operator implementations.\n\n=back\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/path/zz/operators >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/path/z/operators import Operator as ZOperator;\n\n\nclass Operator extends ZOperator;\n\nconst logical_negation := function ( op, ev, ast, ctx, expr ) {\n const got := ev.eval_expr( expr, ev.nested_ctx( ctx ) );\n const value := got.length() > 0 ? got[0] : null;\n return op.wrap( not ev.truthy(value) );\n};\n\nconst numeric_power := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val ** right_val );\n};\n\nconst numeric_multiplication := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \xD7 right_val );\n};\n\nconst numeric_division := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \xF7 right_val );\n};\n\nconst numeric_modulus := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val mod right_val );\n};\n\nconst numeric_left_shift := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const first := left_vals.length() > 0 ? left_vals[0] : null;\n const left_val := first \u2261 null ? null : first.primitive_value();\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \xAB right_val );\n};\n\nconst numeric_right_shift := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const first := left_vals.length() > 0 ? left_vals[0] : null;\n const left_val := first \u2261 null ? null : first.primitive_value();\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \xBB right_val );\n};\n\nconst numeric_addition := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val + right_val );\n};\n\nconst numeric_subtraction := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val - right_val );\n};\n\nconst numeric_equality := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val = right_val );\n};\n\nconst numeric_inequality := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \u2260 right_val );\n};\n\nconst numeric_less_than := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val < right_val );\n};\n\nconst numeric_greater_than := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val > right_val );\n};\n\nconst numeric_less_than_or_equal := function (\n op,\n ev,\n ast,\n ctx,\n left,\n right,\n) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \u2264 right_val );\n};\n\nconst numeric_greater_than_or_equal := function (\n op,\n ev,\n ast,\n ctx,\n left,\n right,\n) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val \u2265 right_val );\n};\n\nconst numeric_compare := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val <=> right_val );\n};\n\nconst numeric_divides := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( right_val mod left_val = 0 );\n};\n\nconst numeric_not_divides := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( right_val mod left_val );\n};\n\nfunction string_operand ( ev, ctx, expr ) {\n const result := ev.eval_expr( expr, ev.nested_ctx( ctx ) );\n return "" unless result.length;\n const value := ev.to_string( result[0] );\n return value \u2261 null ? "" : "" _ value;\n}\n\nfunction string_operands ( ev, ctx, left, right ) {\n return [\n string_operand( ev, ctx, left ),\n string_operand( ev, ctx, right ),\n ];\n}\n\nconst string_concatenation := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] _ values[1] );\n};\n\nconst string_equality := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] eq values[1] );\n};\n\nconst string_inequality := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] ne values[1] );\n};\n\nconst string_greater_than := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] gt values[1] );\n};\n\nconst string_greater_than_or_equal := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] ge values[1] );\n};\n\nconst string_less_than := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] lt values[1] );\n};\n\nconst string_less_than_or_equal := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] le values[1] );\n};\n\nconst string_compare := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] cmp values[1] );\n};\n\nconst string_equality_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val eq right_val );\n};\n\nconst string_inequality_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val ne right_val );\n};\n\nconst string_greater_than_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val gt right_val );\n};\n\nconst string_greater_than_or_equal_insensitive := function (\n op,\n ev,\n ast,\n ctx,\n left,\n right,\n) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val ge right_val );\n};\n\nconst string_less_than_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val lt right_val );\n};\n\nconst string_less_than_or_equal_insensitive := function (\n op,\n ev,\n ast,\n ctx,\n left,\n right,\n) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val le right_val );\n};\n\nconst string_compare_insensitive := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n const left_val := lc values[0];\n const right_val := lc values[1];\n return op.wrap( left_val cmp right_val );\n};\n\nconst regexp_match := function ( op, ev, ast, ctx, left, right ) {\n const values := string_operands( ev, ctx, left, right );\n return op.wrap( values[0] ~ values[1] );\n};\n\nconst bitwise_and := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val & right_val );\n};\n\nconst bitwise_xor := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val ^ right_val );\n};\n\nconst bitwise_or := function ( op, ev, ast, ctx, left, right ) {\n const left_val := op._handle_numeric_operand( ev, ctx, left );\n const right_val := op._handle_numeric_operand( ev, ctx, right );\n return op.wrap( left_val | right_val );\n};\n\nfunction first_eval_value ( values ) {\n return values.length() > 0 ? values[0] : null;\n}\n\nfunction primitive_eval_value ( values ) {\n const first := first_eval_value(values);\n return first \u2261 null ? null : first.primitive_value();\n}\n\nfunction collection_operand ( ev, ctx, expr ) {\n const values := ev.eval_expr( expr, ev.nested_ctx( ctx ) );\n if ( values.length() = 1 ) {\n return values[0].primitive_value();\n }\n return values.map( fn value \u2192 value.primitive_value() );\n}\n\nconst logical_and := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( ev.truthy( first_eval_value(left_vals) ) ) {\n return ev.eval_expr( right, ev.nested_ctx( ctx ) );\n }\n return op.wrap(false);\n};\n\nconst logical_nand := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( not ev.truthy( first_eval_value(left_vals) ) ) {\n return op.wrap(true);\n }\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.truthy( first_eval_value(right_vals) ) ? false : true );\n};\n\nconst logical_butnot := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( not ev.truthy( first_eval_value(left_vals) ) ) {\n return op.wrap(false);\n }\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.truthy( first_eval_value(right_vals) ) ? false : true );\n};\n\nconst logical_xor := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const left_truth := ev.truthy( first_eval_value(left_vals) );\n const right_truth := ev.truthy( first_eval_value(right_vals) );\n return op.wrap( ( left_truth xor right_truth ) ? true : false );\n};\n\nconst logical_nor := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( ev.truthy( first_eval_value(left_vals) ) ) {\n return op.wrap(false);\n }\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.truthy( first_eval_value(right_vals) ) ? false : true );\n};\n\nconst logical_xnor := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const left_truth := ev.truthy( first_eval_value(left_vals) );\n const right_truth := ev.truthy( first_eval_value(right_vals) );\n return op.wrap( left_truth \u2261 right_truth );\n};\n\nconst logical_onlyif := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( not ev.truthy( first_eval_value(left_vals) ) ) {\n return op.wrap(true);\n }\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.truthy( first_eval_value(right_vals) ) ? true : false );\n};\n\nconst logical_or := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( ev.truthy( first_eval_value(left_vals) ) ) {\n return op.wrap(true);\n }\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.truthy( first_eval_value(right_vals) ) ? true : false );\n};\n\nconst logical_and_value := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( ev.truthy( first_eval_value(left_vals) ) ) {\n return ev.eval_expr( right, ev.nested_ctx( ctx ) );\n }\n return left_vals;\n};\n\nconst logical_nand_value := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const left_truth := ev.truthy( first_eval_value(left_vals) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const right_truth := ev.truthy( first_eval_value(right_vals) );\n if ( left_truth and right_truth ) {\n return op.wrap(false);\n }\n return right_vals if not left_truth and right_truth;\n return op.wrap(true);\n};\n\nconst logical_butnot_value := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const left_truth := ev.truthy( first_eval_value(left_vals) );\n return left_vals unless left_truth;\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return ev.truthy( first_eval_value(right_vals) )\n ? op.wrap(false)\n : left_vals;\n};\n\nconst logical_xor_value := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const left_truth := ev.truthy( first_eval_value(left_vals) );\n const right_truth := ev.truthy( first_eval_value(right_vals) );\n if ( left_truth xor right_truth ) {\n return left_truth ? left_vals : right_vals;\n }\n return op.wrap(false);\n};\n\nconst logical_nor_value := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const left_truth := ev.truthy( first_eval_value(left_vals) );\n const right_truth := ev.truthy( first_eval_value(right_vals) );\n return op.wrap(false) if left_truth and right_truth;\n return right_vals if left_truth and not right_truth;\n return left_vals if not left_truth and right_truth;\n return op.wrap(true);\n};\n\nconst logical_xnor_value := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const left_truth := ev.truthy( first_eval_value(left_vals) );\n const right_truth := ev.truthy( first_eval_value(right_vals) );\n if ( left_truth \u2261 right_truth ) {\n return left_truth ? right_vals : op.wrap(true);\n }\n return left_truth ? right_vals : left_vals;\n};\n\nconst logical_onlyif_value := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( not ev.truthy( first_eval_value(left_vals) ) ) {\n return op.wrap(true);\n }\n return ev.eval_expr( right, ev.nested_ctx( ctx ) );\n};\n\nconst logical_or_value := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n if ( ev.truthy( first_eval_value(left_vals) ) ) {\n return left_vals;\n }\n return ev.eval_expr( right, ev.nested_ctx( ctx ) );\n};\n\nconst set_union := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val union right_val );\n};\n\nconst set_intersection := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val intersection right_val );\n};\n\nconst set_difference := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val \u2216 right_val );\n};\n\nconst collection_membership := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const left_val := primitive_eval_value(left_vals);\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val in right_val );\n};\n\nconst collection_non_membership := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const left_val := primitive_eval_value(left_vals);\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val \u2209 right_val );\n};\n\nconst set_subsetof := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val subsetof right_val );\n};\n\nconst set_supersetof := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val supersetof right_val );\n};\n\nconst set_equivalentof := function ( op, ev, ast, ctx, left, right ) {\n const left_val := collection_operand( ev, ctx, left );\n const right_val := collection_operand( ev, ctx, right );\n return op.wrap( left_val equivalentof right_val );\n};\n\nconst type_aware_equality := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( ev.equals(\n first_eval_value(left_vals),\n first_eval_value(right_vals),\n ) );\n};\n\nconst type_aware_inequality := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n return op.wrap( not ev.equals(\n first_eval_value(left_vals),\n first_eval_value(right_vals),\n ) );\n};\n\nfunction builtin_can ( value, method_name ) {\n return false if value \u2261 null\n or method_name \u2261 null\n or method_name eq "";\n\n if ( value instanceof Array or value instanceof Bag or value instanceof Set ) {\n return method_name in [\n "length",\n "empty",\n "contains",\n "push",\n "to_Array",\n "to_Bag",\n "to_Set",\n ];\n }\n if ( value instanceof Dict or value instanceof PairList ) {\n return method_name in [\n "length",\n "empty",\n "exists",\n "keys",\n "values",\n "to_Array",\n ];\n }\n if ( value instanceof String or value instanceof BinaryString ) {\n return method_name in [ "length" ];\n }\n return false;\n}\n\nconst object_can := function ( op, ev, ast, ctx, left, right ) {\n const left_vals := ev.eval_expr( left, ev.nested_ctx( ctx ) );\n const right_vals := ev.eval_expr( right, ev.nested_ctx( ctx ) );\n const left_val := primitive_eval_value(left_vals);\n const right_val := primitive_eval_value(right_vals);\n return op.wrap(\n ( left_val can (right_val) ) or builtin_can( left_val, right_val )\n );\n};\n\nconst STANDARD_OPERATORS := [\n new Operator(\n spelling: "?:",\n kind: "ELVIS",\n precedence: 2,\n lex_ignore: true,\n ),\n\n new Operator(\n spelling: "+",\n kind: "UPLUS",\n unary: true,\n precedence: 40,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( +value );\n },\n ),\n\n new Operator(\n spelling: "-",\n kind: "UMINUS",\n unary: true,\n precedence: 40,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( -value );\n },\n ),\n\n new Operator(\n spelling: "\u221A",\n kind: "SQRT",\n unary: true,\n precedence: 40,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( sqrt value );\n },\n ),\n\n new Operator(\n spelling: "!",\n kind: "NOT",\n unary: true,\n precedence: 40,\n f: logical_negation,\n ),\n\n new Operator(\n spelling: "\xAC",\n kind: "NOTSYM",\n unary: true,\n precedence: 40,\n f: logical_negation,\n ),\n\n new Operator(\n spelling: "**",\n kind: "POW",\n require_ws: true,\n right_assoc: true,\n precedence: 26,\n f: numeric_power,\n ),\n\n new Operator(\n spelling: "\xD7",\n kind: "TIMES_SIGN",\n precedence: 24,\n f: numeric_multiplication,\n ),\n\n new Operator(\n spelling: "*",\n kind: "TIMES",\n require_ws: true,\n precedence: 24,\n lex_ignore: true,\n alias: "STAR",\n f: numeric_multiplication,\n ),\n\n new Operator(\n spelling: "\xF7",\n kind: "DIVIDE_SIGN",\n precedence: 24,\n f: numeric_division,\n ),\n\n new Operator(\n spelling: "/",\n kind: "DIVIDE",\n require_ws: true,\n precedence: 24,\n lex_ignore: true,\n alias: "SLASH",\n f: numeric_division,\n ),\n\n new Operator(\n spelling: "mod",\n kind: "MOD",\n precedence: 24,\n f: numeric_modulus,\n ),\n\n new Operator(\n spelling: "+",\n kind: "PLUS",\n require_ws: true,\n precedence: 22,\n f: numeric_addition,\n ),\n\n new Operator(\n spelling: "-",\n kind: "MINUS",\n require_ws: true,\n precedence: 22,\n f: numeric_subtraction,\n ),\n\n new Operator(\n spelling: "=",\n kind: "EQ",\n precedence: 10,\n f: numeric_equality,\n ),\n\n new Operator(\n spelling: "\u2260",\n kind: "NE",\n precedence: 10,\n f: numeric_inequality,\n ),\n\n new Operator(\n spelling: "<",\n kind: "LT",\n precedence: 10,\n f: numeric_less_than,\n ),\n\n new Operator(\n spelling: ">",\n kind: "GT",\n precedence: 10,\n f: numeric_greater_than,\n ),\n\n new Operator(\n spelling: "\u2264",\n kind: "LE_SIGN",\n precedence: 10,\n f: numeric_less_than_or_equal,\n ),\n\n new Operator(\n spelling: "<=",\n kind: "LE",\n precedence: 10,\n f: numeric_less_than_or_equal,\n ),\n\n new Operator(\n spelling: "\u2265",\n kind: "GE_SIGN",\n precedence: 10,\n f: numeric_greater_than_or_equal,\n ),\n\n new Operator(\n spelling: ">=",\n kind: "GE",\n precedence: 10,\n f: numeric_greater_than_or_equal,\n ),\n\n new Operator(\n spelling: "\u2276",\n kind: "CMP_SIGN",\n precedence: 10,\n f: numeric_compare,\n ),\n\n new Operator(\n spelling: "<=>",\n kind: "CMP",\n precedence: 10,\n f: numeric_compare,\n ),\n\n new Operator(\n spelling: "\u2277",\n kind: "CMP_REV_SIGN",\n precedence: 10,\n f: numeric_compare,\n ),\n\n new Operator(\n spelling: "\u2223",\n kind: "DIVIDES_SIGN",\n precedence: 10,\n f: numeric_divides,\n ),\n\n new Operator(\n spelling: "divides",\n kind: "DIVIDES",\n precedence: 10,\n f: numeric_divides,\n ),\n\n new Operator(\n spelling: "\u2224",\n kind: "NOT_DIVIDES_SIGN",\n precedence: 10,\n f: numeric_not_divides,\n ),\n\n new Operator(\n spelling: "_",\n kind: "CONCAT",\n require_ws: true,\n precedence: 21,\n f: string_concatenation,\n ),\n\n new Operator(\n spelling: "eq",\n kind: "STR_EQ",\n precedence: 10,\n f: string_equality,\n ),\n\n new Operator(\n spelling: "ne",\n kind: "STR_NE",\n precedence: 10,\n f: string_inequality,\n ),\n\n new Operator(\n spelling: "gt",\n kind: "STR_GT",\n precedence: 10,\n f: string_greater_than,\n ),\n\n new Operator(\n spelling: "ge",\n kind: "STR_GE",\n precedence: 10,\n f: string_greater_than_or_equal,\n ),\n\n new Operator(\n spelling: "lt",\n kind: "STR_LT",\n precedence: 10,\n f: string_less_than,\n ),\n\n new Operator(\n spelling: "le",\n kind: "STR_LE",\n precedence: 10,\n f: string_less_than_or_equal,\n ),\n\n new Operator(\n spelling: "cmp",\n kind: "STR_CMP",\n precedence: 10,\n f: string_compare,\n ),\n\n new Operator(\n spelling: "eqi",\n kind: "STR_EQI",\n precedence: 10,\n f: string_equality_insensitive,\n ),\n\n new Operator(\n spelling: "nei",\n kind: "STR_NEI",\n precedence: 10,\n f: string_inequality_insensitive,\n ),\n\n new Operator(\n spelling: "gti",\n kind: "STR_GTI",\n precedence: 10,\n f: string_greater_than_insensitive,\n ),\n\n new Operator(\n spelling: "gei",\n kind: "STR_GEI",\n precedence: 10,\n f: string_greater_than_or_equal_insensitive,\n ),\n\n new Operator(\n spelling: "lti",\n kind: "STR_LTI",\n precedence: 10,\n f: string_less_than_insensitive,\n ),\n\n new Operator(\n spelling: "lei",\n kind: "STR_LEI",\n precedence: 10,\n f: string_less_than_or_equal_insensitive,\n ),\n\n new Operator(\n spelling: "cmpi",\n kind: "STR_CMPI",\n precedence: 10,\n f: string_compare_insensitive,\n ),\n\n new Operator(\n spelling: "~",\n kind: "REGEXP_MATCH",\n precedence: 10,\n f: regexp_match,\n ),\n\n new Operator(\n spelling: "\xAB",\n kind: "LSHIFT_SIGN",\n precedence: 18,\n f: numeric_left_shift,\n ),\n\n new Operator(\n spelling: "<<",\n kind: "LSHIFT",\n precedence: 18,\n f: numeric_left_shift,\n ),\n\n new Operator(\n spelling: "\xBB",\n kind: "RSHIFT_SIGN",\n precedence: 18,\n f: numeric_right_shift,\n ),\n\n new Operator(\n spelling: ">>",\n kind: "RSHIFT",\n precedence: 18,\n f: numeric_right_shift,\n ),\n\n new Operator(\n spelling: "&",\n kind: "BAND",\n precedence: 16,\n f: bitwise_and,\n ),\n\n new Operator(\n spelling: "^",\n kind: "BXOR",\n precedence: 14,\n f: bitwise_xor,\n ),\n\n new Operator(\n spelling: "|",\n kind: "BOR",\n precedence: 12,\n f: bitwise_or,\n ),\n\n new Operator(\n spelling: "\u22C0",\n kind: "LAND_SIGN",\n precedence: 7,\n f: logical_and,\n ),\n\n new Operator(\n spelling: "and",\n kind: "LAND",\n precedence: 7,\n f: logical_and,\n ),\n\n new Operator(\n spelling: "\u22C0?",\n kind: "LAND_VALUE_SIGN",\n precedence: 7,\n f: logical_and_value,\n ),\n\n new Operator(\n spelling: "and?",\n kind: "LAND_VALUE",\n precedence: 7,\n f: logical_and_value,\n ),\n\n new Operator(\n spelling: "\u22BC",\n kind: "LNAND_SIGN",\n precedence: 7,\n f: logical_nand,\n ),\n\n new Operator(\n spelling: "nand",\n kind: "LNAND",\n precedence: 7,\n f: logical_nand,\n ),\n\n new Operator(\n spelling: "\u22BC?",\n kind: "LNAND_VALUE_SIGN",\n precedence: 7,\n f: logical_nand_value,\n ),\n\n new Operator(\n spelling: "nand?",\n kind: "LNAND_VALUE",\n precedence: 7,\n f: logical_nand_value,\n ),\n\n new Operator(\n spelling: "\u22AD",\n kind: "LBUTNOT_SIGN",\n precedence: 7,\n f: logical_butnot,\n ),\n\n new Operator(\n spelling: "butnot",\n kind: "LBUTNOT",\n precedence: 7,\n f: logical_butnot,\n ),\n\n new Operator(\n spelling: "\u22AD?",\n kind: "LBUTNOT_VALUE_SIGN",\n precedence: 7,\n f: logical_butnot_value,\n ),\n\n new Operator(\n spelling: "butnot?",\n kind: "LBUTNOT_VALUE",\n precedence: 7,\n f: logical_butnot_value,\n ),\n\n new Operator(\n spelling: "\u22BB",\n kind: "LXOR_SIGN",\n precedence: 5,\n f: logical_xor,\n ),\n\n new Operator(\n spelling: "xor",\n kind: "LXOR",\n precedence: 5,\n f: logical_xor,\n ),\n\n new Operator(\n spelling: "\u22BB?",\n kind: "LXOR_VALUE_SIGN",\n precedence: 5,\n f: logical_xor_value,\n ),\n\n new Operator(\n spelling: "xor?",\n kind: "LXOR_VALUE",\n precedence: 5,\n f: logical_xor_value,\n ),\n\n new Operator(\n spelling: "\u22BD",\n kind: "LNOR_SIGN",\n precedence: 5,\n f: logical_nor,\n ),\n\n new Operator(\n spelling: "nor",\n kind: "LNOR",\n precedence: 5,\n f: logical_nor,\n ),\n\n new Operator(\n spelling: "\u22BD?",\n kind: "LNOR_VALUE_SIGN",\n precedence: 5,\n f: logical_nor_value,\n ),\n\n new Operator(\n spelling: "nor?",\n kind: "LNOR_VALUE",\n precedence: 5,\n f: logical_nor_value,\n ),\n\n new Operator(\n spelling: "\u2194",\n kind: "LXNOR_SIGN",\n precedence: 5,\n f: logical_xnor,\n ),\n\n new Operator(\n spelling: "xnor",\n kind: "LXNOR",\n precedence: 5,\n f: logical_xnor,\n ),\n\n new Operator(\n spelling: "\u2194?",\n kind: "LXNOR_VALUE_SIGN",\n precedence: 5,\n f: logical_xnor_value,\n ),\n\n new Operator(\n spelling: "xnor?",\n kind: "LXNOR_VALUE",\n precedence: 5,\n f: logical_xnor_value,\n ),\n\n new Operator(\n spelling: "\u22A8",\n kind: "LONLYIF_SIGN",\n precedence: 4,\n right_assoc: true,\n f: logical_onlyif,\n ),\n\n new Operator(\n spelling: "onlyif",\n kind: "LONLYIF",\n precedence: 4,\n right_assoc: true,\n f: logical_onlyif,\n ),\n\n new Operator(\n spelling: "\u22A8?",\n kind: "LONLYIF_VALUE_SIGN",\n precedence: 4,\n right_assoc: true,\n f: logical_onlyif_value,\n ),\n\n new Operator(\n spelling: "onlyif?",\n kind: "LONLYIF_VALUE",\n precedence: 4,\n right_assoc: true,\n f: logical_onlyif_value,\n ),\n\n new Operator(\n spelling: "\u22C1",\n kind: "LOR_SIGN",\n precedence: 2,\n f: logical_or,\n ),\n\n new Operator(\n spelling: "or",\n kind: "LOR",\n precedence: 2,\n f: logical_or,\n ),\n\n new Operator(\n spelling: "\u22C1?",\n kind: "LOR_VALUE_SIGN",\n precedence: 2,\n f: logical_or_value,\n ),\n\n new Operator(\n spelling: "or?",\n kind: "LOR_VALUE",\n precedence: 2,\n f: logical_or_value,\n ),\n\n new Operator(\n spelling: "\u22C3",\n kind: "SET_UNION_SIGN",\n precedence: 20,\n f: set_union,\n ),\n\n new Operator(\n spelling: "union",\n kind: "SET_UNION",\n precedence: 20,\n f: set_union,\n ),\n\n new Operator(\n spelling: "\u22C2",\n kind: "SET_INTERSECTION_SIGN",\n precedence: 20,\n f: set_intersection,\n ),\n\n new Operator(\n spelling: "intersection",\n kind: "SET_INTERSECTION",\n precedence: 20,\n f: set_intersection,\n ),\n\n new Operator(\n spelling: "\u2216",\n kind: "SET_DIFFERENCE_SIGN",\n precedence: 20,\n f: set_difference,\n ),\n\n new Operator(\n spelling: "\\\\",\n kind: "SET_DIFFERENCE",\n precedence: 20,\n require_ws: true,\n f: set_difference,\n ),\n\n new Operator(\n spelling: "\u2208",\n kind: "MEMBER_SIGN",\n precedence: 10,\n f: collection_membership,\n ),\n\n new Operator(\n spelling: "in",\n kind: "MEMBER",\n precedence: 10,\n f: collection_membership,\n ),\n\n new Operator(\n spelling: "\u2209",\n kind: "NOT_MEMBER_SIGN",\n precedence: 10,\n f: collection_non_membership,\n ),\n\n new Operator(\n spelling: "\u2282",\n kind: "SUBSETOF_SIGN",\n precedence: 10,\n f: set_subsetof,\n ),\n\n new Operator(\n spelling: "subsetof",\n kind: "SUBSETOF",\n precedence: 10,\n f: set_subsetof,\n ),\n\n new Operator(\n spelling: "\u2283",\n kind: "SUPERSETOF_SIGN",\n precedence: 10,\n f: set_supersetof,\n ),\n\n new Operator(\n spelling: "supersetof",\n kind: "SUPERSETOF",\n precedence: 10,\n f: set_supersetof,\n ),\n\n new Operator(\n spelling: "\u2282\u2283",\n kind: "EQUIVALENTOF_SIGN",\n precedence: 10,\n f: set_equivalentof,\n ),\n\n new Operator(\n spelling: "equivalentof",\n kind: "EQUIVALENTOF",\n precedence: 10,\n f: set_equivalentof,\n ),\n\n new Operator(\n spelling: "\u2261",\n kind: "TYPE_EQ_SIGN",\n precedence: 8,\n f: type_aware_equality,\n ),\n\n new Operator(\n spelling: "==",\n kind: "TYPE_EQ",\n precedence: 8,\n f: type_aware_equality,\n ),\n\n new Operator(\n spelling: "\u2262",\n kind: "TYPE_NE_SIGN",\n precedence: 8,\n f: type_aware_inequality,\n ),\n\n new Operator(\n spelling: "!=",\n kind: "TYPE_NE",\n precedence: 8,\n f: type_aware_inequality,\n ),\n\n new Operator(\n spelling: "can",\n kind: "CAN",\n precedence: 10,\n f: object_can,\n ),\n\n new Operator(\n spelling: "~",\n kind: "BNOT",\n unary: true,\n precedence: 40,\n f: function ( op, ev, ast, ctx, expr ) {\n const value := op._handle_numeric_operand( ev, ctx, expr );\n return op.wrap( ~value );\n },\n ),\n];\n';
46402
47414
  virtualFiles["/modules/std/template/z.zzm"] = '=encoding utf8\n\n=head1 NAME\n\nstd/template/z - Pure ZuzuScript template engine.\n\n=head1 SYNOPSIS\n\n from std/template/z import ZTemplate;\n\n let inline := new ZTemplate(\n string: "Hello {{ user/name }}!",\n );\n say( inline.process( { user: { name: "Ada" } } ) );\n\n let file_tmpl := new ZTemplate(\n file: "templates/page.zt",\n escape: "html",\n );\n say( file_tmpl.process( data ) );\n\n=head1 IMPLEMENTATION SUPPORT\n\nThis module is supported by zuzu.pl, zuzu-rust, and zuzu-js on Node and\nElectron. It is partially supported by zuzu-js in the browser: complex,\nHTTP/JSON/ZPath pipeline, self-contained, and trait-object rendering\ncoverage passes, but filesystem-backed template loading and database row\nrendering coverage are unsupported.\n\n=head1 DESCRIPTION\n\nC<std/template/z> is a pure ZuzuScript template renderer.\n\nThe module compiles template text into a cached parse tree and renders\nit against data using C<std/path/z> expressions.\n\n=head1 EXPORTS\n\n=head2 Classes\n\n=over\n\n=item C<< ZTemplate({ string?: String, file?, escape?: String, includes?: Boolean }) >>\n\nConstructs a template from inline text or a file path. Returns:\nC<ZTemplate>.\n\n=over\n\n=item C<< template.process(data) >>\n\nParameters: C<data> is the model used for path lookups. Returns:\nC<String>. Renders the template.\n\n=back\n\n=back\n\n=head1 TAG FORMS\n\n=over 4\n\n=item * C<{{ expr }}>\n\nRender expression output using template default escaping.\n\n=item * C<{{ expr :: raw }}>, C<{{ expr :: html }}>\n\nRender expression output using per-tag escape override.\n\n=item * C<{{# expr }}> ... C<{{/expr}}>\n\nBlock form. Each truthy match renders child nodes.\n\n=item * C<{{> include.zt }}>\n\nInclude another template file. Relative include paths resolve from the\ncurrent template\'s file directory.\n\n=back\n\n=head1 ESCAPING\n\nDefault escape mode is C<html>. Supported escape modes are C<html> and\nC<raw>.\n\nC<html> escapes C<&>, C<< < >>, C<E<gt>>, double quotes, and single\nquotes.\n\n=head1 INCLUDE RULES\n\n=over 4\n\n=item * Include processing is enabled by default.\n\n=item * Pass C<includes: false> to disable include tags.\n\n=item * Relative includes require a file-backed template source.\n\n=item * Circular include chains throw a deterministic error.\n\n=back\n\n=head1 KNOWN DIFFERENCES\n\nThis module is implemented on top of C<std/path/z>. Parser/rendering\ndeltas across runtimes are covered in ztests and should be treated as\nimplementation bugs unless explicitly documented.\n\n=head1 COPYRIGHT AND LICENCE\n\nB<< std/template/z >> is copyright Toby Inkster.\n\nIt is free software; you may redistribute it and/or modify it under\nthe terms of either the Artistic License 1.0 or the GNU General Public\nLicense version 2.\n\n=cut\n\nfrom std/string import index, substr, trim;\nfrom std/path/z import ZPath;\n\nclass ZTemplate {\n let string;\n let file;\n let String escape := "html";\n let includes := true;\n let source_file := null;\n let Array tree := [];\n let compiled_paths := {};\n\n method __build__ () {\n let has_string := string \u2262 null;\n let has_file := file \u2262 null;\n\n if ( has_string and has_file ) {\n die "Specify only one of \\"string\\" or \\"file\\"";\n }\n if ( not has_string and not has_file ) {\n die "Missing template source: provide \\"string\\" or \\"file\\"";\n }\n\n escape := lc( "" _ escape );\n if ( escape \u2262 "html" and escape \u2262 "raw" ) {\n die `Invalid escape mode "${escape}"`;\n }\n\n if ( has_file ) {\n let loaded := self._read_template_file(file);\n string := loaded{text};\n source_file := loaded{file};\n }\n\n tree := self._parse_template(\n template: string \u2261 null ? "": string,\n current_file: source_file,\n seen_files: {},\n includes: includes,\n );\n }\n\n method process ( data ) {\n die "process() requires a data model" if data \u2261 null;\n\n return self._render_nodes(\n nodes: tree,\n context: data,\n eval_meta: {},\n default_escape: escape,\n );\n }\n\n method _parse_template ( ... PairList args ) {\n let template := args.get( "template", "" );\n let current_file := args.get( "current_file", null );\n let seen_files := args.get( "seen_files", {} );\n let includes_enabled := args.get( "includes", true );\n let root := [];\n\n let stack := [\n {\n expr: null,\n nodes: root,\n },\n ];\n\n let pos := 0;\n while ( true ) {\n let tag_open := index( template, "{{", pos );\n last if tag_open < 0;\n\n let text := substr( template, pos, tag_open - pos );\n self._push_text( stack[ stack.length() - 1 ]{nodes}, text );\n\n let tag_close := index( template, "}}", tag_open + 2 );\n if ( tag_close < 0 ) {\n die `Unterminated tag at character ${tag_open}`;\n }\n\n let raw := substr( template, tag_open + 2, tag_close - tag_open - 2 );\n let tagged := trim(raw);\n\n if ( substr( tagged, 0, 1 ) \u2261 "#" ) {\n let inner := trim( substr( tagged, 1 ) );\n let parsed := self._parse_expression_spec(inner);\n let block := {\n type: "block",\n expr_src: parsed{expr},\n nodes: [],\n };\n stack[ stack.length() - 1 ]{nodes}.push(block);\n stack.push( {\n expr: parsed{expr},\n nodes: block{nodes},\n } );\n }\n else if ( substr( tagged, 0, 1 ) \u2261 "/" ) {\n let inner := trim( substr( tagged, 1 ) );\n let current := stack.pop();\n if ( current \u2261 null or current{expr} \u2261 null ) {\n die `Mismatched close tag {{/${inner}}}`;\n }\n if ( inner \u2262 "" ) {\n let parsed := self._parse_expression_spec(inner);\n if ( current{expr} \u2262 parsed{expr} ) {\n die `Mismatched close tag {{/${inner}}} for {{${current{expr}}}}`;\n }\n }\n }\n else if ( tagged \u2262 "" ) {\n if ( substr( tagged, 0, 1 ) \u2261 ">" ) {\n die "Template includes are disabled"\n if not includes_enabled;\n\n let include_path := trim( substr( tagged, 1 ) );\n die "Empty include path in template tag"\n if include_path \u2261 "";\n\n let resolved := self._resolve_include_path(\n include_path: include_path,\n current_file: current_file,\n );\n\n let key := self._canonical_path(resolved);\n if ( seen_files.exists(key) and seen_files.get(key) ) {\n die `Circular include detected for "${resolved}"`;\n }\n\n seen_files.set( key, true );\n let loaded := self._read_template_file(resolved);\n let include_nodes := self._parse_template(\n template: loaded{text},\n current_file: loaded{file},\n seen_files: seen_files,\n includes: includes_enabled,\n );\n seen_files.remove(key);\n\n for ( let node in include_nodes ) {\n stack[ stack.length() - 1 ]{nodes}.push(node);\n }\n }\n else {\n let parsed := self._parse_expression_spec(tagged);\n stack[ stack.length() - 1 ]{nodes}.push( {\n type: "expr",\n expr_src: parsed{expr},\n escape: parsed{escape},\n } );\n }\n }\n\n pos := tag_close + 2;\n }\n\n let tail := substr( template, pos );\n self._push_text( stack[ stack.length() - 1 ]{nodes}, tail );\n\n if ( stack.length() > 1 ) {\n let missing := stack[ stack.length() - 1 ]{expr};\n die `Missing close tag for {{${missing}}}`;\n }\n\n return root;\n }\n\n method _push_text ( nodes, text ) {\n if ( text \u2262 "" ) {\n nodes.push( {\n type: "text",\n text: text,\n } );\n }\n }\n\n method _read_template_file ( raw_file ) {\n let path_obj := self._to_path(raw_file);\n let text := path_obj.slurp_utf8();\n let canonical := self._canonical_path(path_obj);\n\n return {\n text: text,\n file: canonical,\n };\n }\n\n method _to_path ( raw_file ) {\n from std/io import Path;\n if ( raw_file instanceof Path ) {\n return raw_file;\n }\n return new Path( "" _ raw_file );\n }\n\n method _canonical_path ( path_obj ) {\n let obj := self._to_path(path_obj);\n let canonical := obj.realpath();\n if ( canonical \u2261 null ) {\n canonical := obj.absolute();\n }\n if ( canonical \u2261 null ) {\n return obj.to_String;\n }\n from std/io import Path;\n if ( canonical instanceof Path ) {\n return canonical.to_String;\n }\n return "" _ canonical;\n }\n\n method _resolve_include_path ( ... PairList args ) {\n let include_path := args.get( "include_path" );\n let current_file := args.get( "current_file" );\n\n let include_obj := self._to_path(include_path);\n if ( include_obj.is_absolute() ) {\n return include_obj.to_String;\n }\n\n if ( current_file \u2261 null ) {\n die `Relative include path "${include_path}" requires file-based template source`;\n }\n\n let base := self._to_path(current_file).parent();\n return base.child( include_obj.to_String ).to_String;\n }\n\n method _parse_expression_spec ( raw ) {\n let expr := raw;\n let escape_mode := null;\n\n let split := self._find_escape_separator(raw);\n if ( split \u2262 null ) {\n let lhs := split[0];\n let rhs := split[1];\n if ( rhs \u2261 "html" or rhs \u2261 "raw" ) {\n expr := lhs;\n escape_mode := rhs;\n }\n }\n\n expr := trim(expr);\n if ( expr \u2261 "" ) {\n die "Empty expression in template tag";\n }\n\n return {\n expr: expr,\n escape: escape_mode,\n };\n }\n\n method _find_escape_separator ( text ) {\n let quote := "";\n let i := 0;\n while ( i < length text ) {\n let ch := substr( text, i, 1 );\n if ( quote \u2262 "" ) {\n if ( ch \u2261 "\\\\" ) {\n i += 2;\n next;\n }\n if ( ch \u2261 quote ) {\n quote := "";\n }\n i++;\n next;\n }\n\n if ( ch \u2261 "\\"" or ch \u2261 "\'" ) {\n quote := ch;\n i++;\n next;\n }\n\n if ( ch \u2261 ":" and substr( text, i, 2 ) \u2261 "::" ) {\n let lhs := trim( substr( text, 0, i ) );\n let rhs := lc( trim( substr( text, i + 2 ) ) );\n return [ lhs, rhs ];\n }\n\n i++;\n }\n\n return null;\n }\n\n method _render_nodes ( ... PairList args ) {\n let nodes := args.get( "nodes", [] );\n let context := args.get( "context", null );\n let eval_meta := args.get( "eval_meta", {} );\n let default_escape := args.get( "default_escape", "html" );\n let out := "";\n\n for ( let node in nodes ) {\n if ( node{type} \u2261 "text" ) {\n out _= node{text};\n }\n else if ( node{type} \u2261 "expr" ) {\n let results := self._evaluate_expression(\n expr_src: node{expr_src},\n context: context,\n eval_meta: eval_meta,\n );\n let value := "";\n for ( let matched in results ) {\n let sv := matched.string_value();\n value _= sv \u2261 null ? "" : sv;\n }\n\n let escape_mode := node{escape};\n if ( escape_mode \u2261 null ) {\n escape_mode := default_escape;\n }\n\n if ( escape_mode \u2261 "html" ) {\n out _= self._escape_html(value);\n }\n else {\n out _= value;\n }\n }\n else if ( node{type} \u2261 "block" ) {\n let results := self._evaluate_expression(\n expr_src: node{expr_src},\n context: context,\n eval_meta: eval_meta,\n );\n for ( let matched in results ) {\n let primitive := matched.primitive_value();\n next if not self._truthy(primitive);\n\n let inner_context := context;\n let inner_meta := eval_meta;\n if ( self._node_has_identity(matched) ) {\n inner_context := matched;\n inner_meta := { parentset: results };\n }\n\n out _= self._render_nodes(\n nodes: node{nodes},\n context: inner_context,\n eval_meta: inner_meta,\n default_escape: default_escape,\n );\n }\n }\n }\n\n return out;\n }\n\n method _evaluate_expression ( ... PairList args ) {\n let expr_src := args.get( "expr_src" );\n let context := args.get( "context" );\n let eval_meta := args.get( "eval_meta", {} );\n if ( not compiled_paths.exists(expr_src) ) {\n compiled_paths.set(\n expr_src,\n self._compile_path(expr_src),\n );\n }\n let zpath := compiled_paths.get(expr_src);\n return zpath.evaluate( context, eval_meta );\n }\n\n method _compile_path ( expr_src ) {\n return new ZPath( path: expr_src );\n }\n\n method _node_has_identity ( node ) {\n let parent := node.parent();\n if ( parent \u2261 null ) {\n return false;\n }\n\n return true;\n }\n\n method _truthy ( value ) {\n return value ? true : false;\n }\n\n method _escape_html ( text ) {\n let out := "";\n let i := 0;\n while ( i < length text ) {\n let ch := substr( text, i, 1 );\n if ( ch \u2261 "&" ) {\n out _= "&amp;";\n }\n else if ( ch \u2261 "<" ) {\n out _= "&lt;";\n }\n else if ( ch \u2261 ">" ) {\n out _= "&gt;";\n }\n else if ( ch \u2261 "\\"" ) {\n out _= "&quot;";\n }\n else if ( ch \u2261 "\'" ) {\n out _= "&#39;";\n }\n else {\n out _= ch;\n }\n i++;\n }\n return out;\n }\n}\n';
46403
47415
  virtualFiles["/modules/std/template/zz.zzm"] = `=encoding utf8
46404
47416
 
@@ -46841,7 +47853,7 @@ class ZZTemplate extends ZTemplate {
46841
47853
  }
46842
47854
  });
46843
47855
 
46844
- // ../../../../../tmp/zuzu-browser-build.fADF4k/browser-worker-entry.generated.js
47856
+ // ../../../../../tmp/zuzu-browser-build.UGYrnC/browser-worker-entry.generated.js
46845
47857
  var { createBrowserStdlib } = require_browser_stdlib_generated();
46846
47858
  globalThis.__ZUZU_BROWSER_DEFAULT_RUNTIME_OPTIONS__ = createBrowserStdlib();
46847
47859
  var { installBrowserWorker } = require_browser_worker_entry();