@lango-project/lango-js 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/compiler.mjs CHANGED
@@ -32,7 +32,8 @@ export function formatDiagnostics(error) {
32
32
 
33
33
  export async function buildFile(inputPath, outputPath, seen = new Set(), includeTests = false, profile = {}) {
34
34
  const decimalProfile = validateDecimalProfile(profile);
35
- if (seen.size === 0)
35
+ const isEntryPoint = seen.size === 0;
36
+ if (isEntryPoint)
36
37
  await validateModuleInitializerCycles(inputPath, outputPath);
37
38
  if (seen.has(inputPath)) return;
38
39
  seen.add(inputPath);
@@ -86,10 +87,19 @@ export async function buildFile(inputPath, outputPath, seen = new Set(), include
86
87
  }
87
88
 
88
89
  const compiled = compile(source, inputPath, imports, includeTests, decimalProfile);
89
- const output = compiled.javascript.replace(
90
+ let output = compiled.javascript.replace(
90
91
  "//# sourceMappingURL=main.mjs.map",
91
92
  `//# sourceMappingURL=${path.basename(outputPath)}.map`,
92
93
  );
94
+ if (isEntryPoint && profile.executable) {
95
+ const main = compiled.program.functions.find((item) => item.name === "main" && item.exported);
96
+ if (!main)
97
+ throw new LangoError("LNG1265", 1, "an executable build requires export main(args)", inputPath);
98
+ output = output.replace(
99
+ `//# sourceMappingURL=${path.basename(outputPath)}.map`,
100
+ `const { configure } = await import("./.lango/runtime.mjs");\nconfigure({ output: (value) => console.log(value) });\nawait main(process.argv.slice(2));\n//# sourceMappingURL=${path.basename(outputPath)}.map`,
101
+ );
102
+ }
93
103
  const directory = path.dirname(outputPath);
94
104
 
95
105
  await mkdir(path.join(directory, ".lango"), { recursive: true });
@@ -111,6 +121,19 @@ export async function buildFile(inputPath, outputPath, seen = new Set(), include
111
121
  await readFile(new URL("./vendor/bignumber.mjs", import.meta.url), "utf8"),
112
122
  "utf8",
113
123
  );
124
+ const xmlVendor = new URL("./vendor/xmldom/", import.meta.url);
125
+ await mkdir(path.join(directory, ".lango/xmldom"), { recursive: true });
126
+ for (const name of ["conventions.js", "dom-parser.js", "dom.js", "entities.js", "errors.js", "grammar.js", "index.js", "sax.js"])
127
+ await writeFile(
128
+ path.join(directory, ".lango/xmldom", name),
129
+ await readFile(new URL(name, xmlVendor), "utf8"),
130
+ "utf8",
131
+ );
132
+ await writeFile(
133
+ path.join(directory, ".lango/xmldom.LICENSE.md"),
134
+ await readFile(new URL("./vendor/xmldom.LICENSE.md", import.meta.url), "utf8"),
135
+ "utf8",
136
+ );
114
137
  }
115
138
 
116
139
  export function compile(source, sourceName = "<source>", imports = [], includeTests = false, profile = {}) {
@@ -243,7 +266,6 @@ class Parser {
243
266
  const tests = [];
244
267
  const jsImports = [];
245
268
  let declarationSeen = false;
246
- let exportedClassSeen = false;
247
269
  this.blank();
248
270
  while (this.i < this.lines.length) {
249
271
  const line = this.line();
@@ -293,9 +315,6 @@ class Parser {
293
315
  );
294
316
  if (classHeader) {
295
317
  declarationSeen = true;
296
- if (classHeader[1] && exportedClassSeen)
297
- this.fail(line, "LNG1114", "a file may export one class");
298
- exportedClassSeen ||= Boolean(classHeader[1]);
299
318
  const classNode = {
300
319
  name: classHeader[2],
301
320
  ancestor: classHeader[3] ?? null,
@@ -538,6 +557,8 @@ class Parser {
538
557
  } else if (/^global (\$?[A-Za-z_]\w*)$/.test(text)) {
539
558
  const [, name] = /^global (\$?[A-Za-z_]\w*)$/.exec(text);
540
559
  body.push({ type: "globalDeclare", name, line: line.number });
560
+ } else if (/^@{1,2}\$?[A-Za-z_]\w*.*\(.*\)$/.test(text) && !text.includes(" = ")) {
561
+ body.push({ type: "expression", value: expr(text, line, this) });
541
562
  } else if (/^context ([A-Za-z_]\w*) = (.+)$/.test(text)) {
542
563
  const [, name, value] = /^context ([A-Za-z_]\w*) = (.+)$/.exec(text);
543
564
  body.push({ type: "contextSet", name, value: expr(value, line, this) });
@@ -777,7 +798,7 @@ class Parser {
777
798
  value: { type: "new", name: constructor, arguments: [closure], line: line.number },
778
799
  });
779
800
  }
780
- else if (/^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.test(text)) {
801
+ else if (!text.includes(" = ") && /^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.test(text)) {
781
802
  const [, receiver, name, argumentsText, parameters] =
782
803
  /^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.exec(text);
783
804
  const closure = {
@@ -803,7 +824,34 @@ class Parser {
803
824
  },
804
825
  });
805
826
  }
806
- else if (/^(.+)\.([A-Za-z_]\w*)\(\(([^)]*)\)$/.test(text)) {
827
+ else if (/^(\$?[A-Za-z_]\w*) = (.+)\.([A-Za-z_]\w*)\((?:(.*), )?\(([^)]*)\)$/.test(text)) {
828
+ const [, name, receiver, method, argumentsText, parameters] =
829
+ /^(\$?[A-Za-z_]\w*) = (.+)\.([A-Za-z_]\w*)\((?:(.*), )?\(([^)]*)\)$/.exec(text);
830
+ const closure = {
831
+ type: "closure",
832
+ parameters: this.parameters(parameters, line),
833
+ captures: this.captures(),
834
+ line: line.number,
835
+ body: this.block(indent + 4),
836
+ };
837
+ this.blank();
838
+ if (this.peek() !== `${" ".repeat(indent)})`)
839
+ this.fail(line, "LNG1266", "assigned method callback requires a closing parenthesis");
840
+ this.i += 1;
841
+ body.push({
842
+ type: "assign",
843
+ name,
844
+ value: {
845
+ type: "methodCall",
846
+ receiver,
847
+ optional: false,
848
+ name: method,
849
+ arguments: [...(argumentsText ? splitTopLevel(argumentsText).map((item) => expr(item, line, this)) : []), closure],
850
+ line: line.number,
851
+ },
852
+ });
853
+ }
854
+ else if (!text.includes(" = ") && /^(.+)\.([A-Za-z_]\w*)\(\(([^)]*)\)$/.test(text)) {
807
855
  const [, first, name, parameters] = /^(.+)\.([A-Za-z_]\w*)\(\(([^)]*)\)$/.exec(text);
808
856
  const steps = [];
809
857
  let step = { name, parameters: this.parameters(parameters, line), captures: this.captures(), line: line.number, body: this.block(indent + 4) };
@@ -898,9 +946,9 @@ class Parser {
898
946
  name,
899
947
  value: { type: "sql", text: sqlText, parts, parameters },
900
948
  });
901
- } else if (/^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\] = (.+)$/.test(text)) {
949
+ } else if (/^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\] = (.+)$/.test(text)) {
902
950
  const [, receiver, firstIndex, secondIndex, sourceValue] =
903
- /^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\] = (.+)$/.exec(text);
951
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\] = (.+)$/.exec(text);
904
952
  body.push({
905
953
  type: "indexAssign",
906
954
  receiver,
@@ -908,9 +956,9 @@ class Parser {
908
956
  value: this.expression(sourceValue, line),
909
957
  operator: null,
910
958
  });
911
- } else if (/^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\] (\+|-|\*|\/|%|\*\*)= (.+)$/.test(text)) {
959
+ } else if (/^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\] (\+|-|\*|\/|%|\*\*)= (.+)$/.test(text)) {
912
960
  const [, receiver, firstIndex, secondIndex, operator, sourceValue] =
913
- /^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\] (\+|-|\*|\/|%|\*\*)= (.+)$/.exec(text);
961
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\] (\+|-|\*|\/|%|\*\*)= (.+)$/.exec(text);
914
962
  body.push({
915
963
  type: "indexAssign",
916
964
  receiver,
@@ -918,9 +966,9 @@ class Parser {
918
966
  value: this.expression(sourceValue, line),
919
967
  operator,
920
968
  });
921
- } else if (/^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\](\+\+|--)$/.test(text)) {
969
+ } else if (/^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\](\+\+|--)$/.test(text)) {
922
970
  const [, receiver, firstIndex, secondIndex, suffix] =
923
- /^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\](\+\+|--)$/.exec(text);
971
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\]\[([^\]]+)\](\+\+|--)$/.exec(text);
924
972
  body.push({
925
973
  type: "indexAssign",
926
974
  receiver,
@@ -928,9 +976,9 @@ class Parser {
928
976
  value: { type: "raw", value: "1" },
929
977
  operator: suffix === "++" ? "+" : "-",
930
978
  });
931
- } else if (/^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\] = (.+)$/.test(text)) {
979
+ } else if (/^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\] = (.+)$/.test(text)) {
932
980
  const [, receiver, sourceIndex, sourceValue] =
933
- /^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\] = (.+)$/.exec(text);
981
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\] = (.+)$/.exec(text);
934
982
  body.push({
935
983
  type: "indexAssign",
936
984
  receiver,
@@ -938,9 +986,9 @@ class Parser {
938
986
  value: this.expression(sourceValue, line),
939
987
  operator: null,
940
988
  });
941
- } else if (/^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\] (\+|-|\*|\/|%|\*\*)= (.+)$/.test(text)) {
989
+ } else if (/^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\] (\+|-|\*|\/|%|\*\*)= (.+)$/.test(text)) {
942
990
  const [, receiver, sourceIndex, operator, sourceValue] =
943
- /^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\] (\+|-|\*|\/|%|\*\*)= (.+)$/.exec(text);
991
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\] (\+|-|\*|\/|%|\*\*)= (.+)$/.exec(text);
944
992
  body.push({
945
993
  type: "indexAssign",
946
994
  receiver,
@@ -948,9 +996,9 @@ class Parser {
948
996
  value: this.expression(sourceValue, line),
949
997
  operator,
950
998
  });
951
- } else if (/^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\](\+\+|--)$/.test(text)) {
999
+ } else if (/^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\](\+\+|--)$/.test(text)) {
952
1000
  const [, receiver, sourceIndex, suffix] =
953
- /^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\](\+\+|--)$/.exec(text);
1001
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\[([^\]]+)\](\+\+|--)$/.exec(text);
954
1002
  body.push({
955
1003
  type: "indexAssign",
956
1004
  receiver,
@@ -959,7 +1007,7 @@ class Parser {
959
1007
  operator: suffix === "++" ? "+" : "-",
960
1008
  });
961
1009
  } else {
962
- const assignment = /^(\$[A-Za-z_]\w*|@{0,2}\$?[A-Za-z_]\w*|[A-Za-z_]\w*\.\$?[A-Za-z_]\w*) = (.+)$/.exec(text);
1010
+ const assignment = /^(\$[A-Za-z_]\w*|@{0,2}\$?[A-Za-z_]\w*(?:\.\$?[A-Za-z_]\w*)*) = (.+)$/.exec(text);
963
1011
  if (!assignment)
964
1012
  this.fail(line, "LNG1106", `unsupported Faz 2 statement: ${text}`);
965
1013
  body.push({
@@ -1288,10 +1336,18 @@ function expr(source, line, parser) {
1288
1336
  }),
1289
1337
  };
1290
1338
  }
1339
+ if (text.startsWith("not "))
1340
+ return {
1341
+ type: "unary",
1342
+ operator: "not",
1343
+ value: expr(text.slice(4), line, parser),
1344
+ };
1291
1345
  for (const operators of [
1292
1346
  ["??"],
1293
- ["or", "||"],
1294
- ["and", "&&"],
1347
+ ["or"],
1348
+ ["and"],
1349
+ ["||"],
1350
+ ["&&"],
1295
1351
  ["==", "!="],
1296
1352
  [">=", "<=", ">", "<", "not in", "in"],
1297
1353
  ["+", "-"],
@@ -1314,12 +1370,6 @@ function expr(source, line, parser) {
1314
1370
  left: expr(text.slice(0, exponent.index), line, parser),
1315
1371
  right: expr(text.slice(exponent.index + exponent.operator.length + 2), line, parser),
1316
1372
  };
1317
- if (text.startsWith("not "))
1318
- return {
1319
- type: "unary",
1320
- operator: "not",
1321
- value: expr(text.slice(4), line, parser),
1322
- };
1323
1373
  if (/^-[^\s]/.test(text))
1324
1374
  return {
1325
1375
  type: "unary",
@@ -1364,7 +1414,7 @@ function expr(source, line, parser) {
1364
1414
  line: line.number,
1365
1415
  };
1366
1416
  const nestedIndex =
1367
- /^([A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w)*(?:\?\.)?\[.+\])(\?\.)?\[(.+)\]$/.exec(
1417
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w)*(?:\?\.)?\[.+\])(\?\.)?\[(.+)\]$/.exec(
1368
1418
  text,
1369
1419
  );
1370
1420
  if (nestedIndex)
@@ -1375,7 +1425,7 @@ function expr(source, line, parser) {
1375
1425
  index: expr(nestedIndex[3], line, parser),
1376
1426
  };
1377
1427
  const computedIndex =
1378
- /^([A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w)*)(\?\.)?\[([^\]]+)\]$/.exec(
1428
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w)*)(\?\.)?\[([^\]]+)\]$/.exec(
1379
1429
  text,
1380
1430
  );
1381
1431
  if (
@@ -1389,7 +1439,7 @@ function expr(source, line, parser) {
1389
1439
  index: expr(computedIndex[3], line, parser),
1390
1440
  };
1391
1441
  const indexedProperty =
1392
- /^([A-Za-z_]\w*.*\])(\?\.|\.)(\$?[A-Za-z_]\w*)$/.exec(text);
1442
+ /^(@{0,2}\$?[A-Za-z_]\w*.*\])(\?\.|\.)(\$?[A-Za-z_]\w*)$/.exec(text);
1393
1443
  if (
1394
1444
  indexedProperty &&
1395
1445
  !/^[A-Za-z_]\w*\?\.\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\]$/.test(
@@ -1403,7 +1453,7 @@ function expr(source, line, parser) {
1403
1453
  name: indexedProperty[3],
1404
1454
  };
1405
1455
  const indexedMethodCall =
1406
- /^([A-Za-z_]\w*.*\])(\?\.|\.)([A-Za-z_]\w*)\((.*)\)$/.exec(text);
1456
+ /^(@{0,2}\$?[A-Za-z_]\w*.*\])(\?\.|\.)([A-Za-z_]\w*)\((.*)\)$/.exec(text);
1407
1457
  if (indexedMethodCall)
1408
1458
  return {
1409
1459
  type: "methodCall",
@@ -1416,7 +1466,7 @@ function expr(source, line, parser) {
1416
1466
  line: line.number,
1417
1467
  };
1418
1468
  const nestedCallResultIndex =
1419
- /^([A-Za-z_]\w*.*\)\?\.\[.+\])(\?\.)?\[(.+)\]$/.exec(text);
1469
+ /^(@{0,2}\$?[A-Za-z_]\w*.*\)\?\.\[.+\])(\?\.)?\[(.+)\]$/.exec(text);
1420
1470
  if (nestedCallResultIndex)
1421
1471
  return {
1422
1472
  type: "index",
@@ -1425,7 +1475,7 @@ function expr(source, line, parser) {
1425
1475
  index: expr(nestedCallResultIndex[3], line, parser),
1426
1476
  };
1427
1477
  const callResultIndex =
1428
- /^([A-Za-z_]\w*.*\))(\?\.)?\[(.+)\]$/.exec(text);
1478
+ /^(@{0,2}\$?[A-Za-z_]\w*.*\))(\?\.)?\[(.+)\]$/.exec(text);
1429
1479
  if (callResultIndex)
1430
1480
  return {
1431
1481
  type: "index",
@@ -1434,7 +1484,7 @@ function expr(source, line, parser) {
1434
1484
  index: expr(callResultIndex[3], line, parser),
1435
1485
  };
1436
1486
  const callResultProperty =
1437
- /^([A-Za-z_]\w*.*\))(\?\.|\.)(\$?[A-Za-z_]\w*)$/.exec(text);
1487
+ /^(@{0,2}\$?[A-Za-z_]\w*.*\))(\?\.|\.)(\$?[A-Za-z_]\w*)$/.exec(text);
1438
1488
  if (callResultProperty)
1439
1489
  return {
1440
1490
  type: "property",
@@ -1443,7 +1493,7 @@ function expr(source, line, parser) {
1443
1493
  name: callResultProperty[3],
1444
1494
  };
1445
1495
  const callResultMethod =
1446
- /^([A-Za-z_]\w*.*\))(\?\.|\.)([A-Za-z_]\w*)\((.*)\)$/.exec(text);
1496
+ /^(@{0,2}\$?[A-Za-z_]\w*.*\))(\?\.|\.)([A-Za-z_]\w*)\((.*)\)$/.exec(text);
1447
1497
  if (callResultMethod)
1448
1498
  return {
1449
1499
  type: "methodCall",
@@ -1455,8 +1505,19 @@ function expr(source, line, parser) {
1455
1505
  : [],
1456
1506
  line: line.number,
1457
1507
  };
1508
+ const indexedCall = /^(.+)\[(.+)\]\((.*)\)$/.exec(text);
1509
+ if (indexedCall)
1510
+ return {
1511
+ type: "indexedCall",
1512
+ receiver: expr(indexedCall[1], line, parser),
1513
+ index: expr(indexedCall[2], line, parser),
1514
+ arguments: indexedCall[3]
1515
+ ? splitTopLevel(indexedCall[3]).map((item) => expr(item, line, parser))
1516
+ : [],
1517
+ line: line.number,
1518
+ };
1458
1519
  const methodCall =
1459
- /^([A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w*)*?)(\?\.|\.)([A-Za-z_]\w*)\((.*)\)$/.exec(
1520
+ /^(@{0,2}\$?[A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w*)*?)(\?\.|\.)([A-Za-z_]\w*)\((.*)\)$/.exec(
1460
1521
  text,
1461
1522
  );
1462
1523
  const namespaceMethodCall =
@@ -1510,15 +1571,15 @@ function expr(source, line, parser) {
1510
1571
  return { type: "raw", value: text };
1511
1572
  }
1512
1573
  if (/^(true|false|null)$/.test(text)) return { type: "raw", value: text };
1513
- if (/^[A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w*)*(?:\.|\?\.)_[A-Za-z_]\w*$/.test(text))
1574
+ if (/^@{0,2}\$?[A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w*)*(?:\.|\?\.)_[A-Za-z_]\w*$/.test(text))
1514
1575
  parser.fail(
1515
1576
  line,
1516
1577
  "LNG1115",
1517
1578
  "protected properties are not accessible outside a class",
1518
1579
  );
1519
- if (/^[A-Za-z_]\w*(?:(?:\.|\?\.)\$?[A-Za-z_]\w*|(?:\?\.)?\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\])+$/.test(text))
1580
+ if (/^@{0,2}\$?[A-Za-z_]\w*(?:(?:\.|\?\.)\$?[A-Za-z_]\w*|(?:\?\.)?\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\])+$/.test(text))
1520
1581
  return { type: "raw", value: text };
1521
- if (/^[A-Za-z_]\w*\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\]$/.test(text))
1582
+ if (/^@{0,2}\$?[A-Za-z_]\w*\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\]$/.test(text))
1522
1583
  return { type: "raw", value: text };
1523
1584
  if (/^(?:\$[A-Za-z_]\w*|@{0,2}\$?[A-Za-z_]\w*)$/.test(text)) return { type: "raw", value: text };
1524
1585
  parser.fail(line, "LNG1108", `unsupported Faz 2 expression: ${text}`);
@@ -1760,6 +1821,8 @@ function validate(program, imports = []) {
1760
1821
  ["is_array", 1],
1761
1822
  ["range", [1, 2, 3]],
1762
1823
  ["to_js_number", 1],
1824
+ ["import_module", 1],
1825
+ ["xml2obj", 1],
1763
1826
  ["start_trans", 0], ["commit_trans", 0], ["rollback_trans", 0], ["trans_state", 0],
1764
1827
  ]);
1765
1828
  const hostFunctions = new Set(["fetch"]);
@@ -2508,6 +2571,15 @@ function validate(program, imports = []) {
2508
2571
  return names;
2509
2572
  };
2510
2573
 
2574
+ const contextBindings = (body, names = new Set()) => {
2575
+ for (const node of body) {
2576
+ if (node.type === "contextDeclare" || node.type === "contextSet") names.add(node.name);
2577
+ if (node.type !== "closureAssign")
2578
+ nestedBodies(node).forEach((child) => contextBindings(child, names));
2579
+ }
2580
+ return names;
2581
+ };
2582
+
2511
2583
  const globalDeclarations = (body, names = new Set()) => {
2512
2584
  for (const node of body) {
2513
2585
  if (node.type === "globalDeclare") names.add(node.name);
@@ -2563,9 +2635,14 @@ function validate(program, imports = []) {
2563
2635
  outerBindings,
2564
2636
  classScope = null,
2565
2637
  inheritedExpiredCatchBindings = new Set(),
2638
+ outerContextBindings = new Set(),
2566
2639
  ) => {
2567
2640
  const available = new Set(outerBindings);
2568
2641
  const expiredCatchBindings = new Set(inheritedExpiredCatchBindings);
2642
+ const availableContexts = new Set([
2643
+ ...outerContextBindings,
2644
+ ...contextBindings(body),
2645
+ ]);
2569
2646
  for (const node of body) {
2570
2647
  const references = new Set();
2571
2648
  [node.value, node.condition, node.collection, node.arguments, node.tests].forEach((value) => {
@@ -2585,6 +2662,7 @@ function validate(program, imports = []) {
2585
2662
  new Set(available),
2586
2663
  classScope,
2587
2664
  expiredCatchBindings,
2665
+ availableContexts,
2588
2666
  );
2589
2667
  const right = node.elseBody
2590
2668
  ? validateClosures(
@@ -2592,6 +2670,7 @@ function validate(program, imports = []) {
2592
2670
  new Set(available),
2593
2671
  classScope,
2594
2672
  expiredCatchBindings,
2673
+ availableContexts,
2595
2674
  )
2596
2675
  : available;
2597
2676
  const exits = [];
@@ -2610,6 +2689,7 @@ function validate(program, imports = []) {
2610
2689
  new Set(available),
2611
2690
  classScope,
2612
2691
  expiredCatchBindings,
2692
+ availableContexts,
2613
2693
  ),
2614
2694
  );
2615
2695
  if (!node.elseBody || reachesEnd(node.elseBody))
@@ -2620,6 +2700,7 @@ function validate(program, imports = []) {
2620
2700
  new Set(available),
2621
2701
  classScope,
2622
2702
  expiredCatchBindings,
2703
+ availableContexts,
2623
2704
  )
2624
2705
  : new Set(available),
2625
2706
  );
@@ -2635,6 +2716,7 @@ function validate(program, imports = []) {
2635
2716
  new Set(available),
2636
2717
  classScope,
2637
2718
  expiredCatchBindings,
2719
+ availableContexts,
2638
2720
  );
2639
2721
  if (reachesEnd(node.tryBody)) exits.push(tryExit);
2640
2722
  if (node.catchBody) {
@@ -2645,6 +2727,7 @@ function validate(program, imports = []) {
2645
2727
  new Set(
2646
2728
  [...expiredCatchBindings].filter((name) => name !== node.catchName),
2647
2729
  ),
2730
+ availableContexts,
2648
2731
  );
2649
2732
  if (reachesEnd(node.catchBody)) exits.push(catchExit);
2650
2733
  }
@@ -2658,6 +2741,7 @@ function validate(program, imports = []) {
2658
2741
  certain,
2659
2742
  classScope,
2660
2743
  new Set([...expiredCatchBindings, node.catchName].filter(Boolean)),
2744
+ availableContexts,
2661
2745
  )
2662
2746
  : certain;
2663
2747
  if (!node.finallyBody || reachesEnd(node.finallyBody))
@@ -2671,6 +2755,7 @@ function validate(program, imports = []) {
2671
2755
  new Set([...available, node.item]),
2672
2756
  classScope,
2673
2757
  expiredCatchBindings,
2758
+ availableContexts,
2674
2759
  );
2675
2760
  continue;
2676
2761
  }
@@ -2680,6 +2765,7 @@ function validate(program, imports = []) {
2680
2765
  new Set([...available, node.key, node.value]),
2681
2766
  classScope,
2682
2767
  expiredCatchBindings,
2768
+ availableContexts,
2683
2769
  );
2684
2770
  continue;
2685
2771
  }
@@ -2695,6 +2781,7 @@ function validate(program, imports = []) {
2695
2781
  new Set(available),
2696
2782
  classScope,
2697
2783
  expiredCatchBindings,
2784
+ availableContexts,
2698
2785
  );
2699
2786
  continue;
2700
2787
  }
@@ -2726,9 +2813,14 @@ function validate(program, imports = []) {
2726
2813
  (name) =>
2727
2814
  !localBindings.has(name) &&
2728
2815
  !contextNames.has(name) &&
2729
- !globalNames.has(name),
2816
+ !globalNames.has(name) &&
2817
+ !standardFunctions.has(name) &&
2818
+ !hostFunctions.has(name),
2730
2819
  );
2731
2820
  const captures = new Set(closure.captures);
2821
+ const contextCaptures = new Set(
2822
+ closure.captures.filter((name) => availableContexts.has(name)),
2823
+ );
2732
2824
  for (const name of needed)
2733
2825
  if (!captures.has(name))
2734
2826
  throw new LangoError(
@@ -2737,7 +2829,7 @@ function validate(program, imports = []) {
2737
2829
  `MissingCapture: ${name}; add capture ${name}`,
2738
2830
  );
2739
2831
  for (const name of captures) {
2740
- if (!needed.includes(name))
2832
+ if (!needed.includes(name) && !contextCaptures.has(name))
2741
2833
  throw new LangoError(
2742
2834
  "LNG1211",
2743
2835
  closure.line,
@@ -2745,7 +2837,7 @@ function validate(program, imports = []) {
2745
2837
  );
2746
2838
  const protectedMember = Boolean(classScope && name.startsWith("_"));
2747
2839
  const propertyCapture = name.startsWith("@") || protectedMember;
2748
- if (!propertyCapture && !available.has(name))
2840
+ if (!propertyCapture && !contextCaptures.has(name) && !available.has(name))
2749
2841
  throw new LangoError(
2750
2842
  "LNG1212",
2751
2843
  closure.line,
@@ -2778,6 +2870,7 @@ function validate(program, imports = []) {
2778
2870
  new Set([...available, ...localBindings]),
2779
2871
  classScope,
2780
2872
  expiredCatchBindings,
2873
+ new Set([...availableContexts, ...contextCaptures]),
2781
2874
  );
2782
2875
  if (node.type === "closureAssign" || node.type === "assign") available.add(node.name);
2783
2876
  continue;
@@ -3327,6 +3420,7 @@ function emit(program, moduleImports = [], includeTests = false) {
3327
3420
  flags.print && "println as __println",
3328
3421
  flags.translate && "translate as __translate",
3329
3422
  flags.xml && "xmlEscape as __xmlEscape",
3423
+ flags.xmlObject && "xml2obj as __xml2obj",
3330
3424
  flags.context && "contextGet as __contextGet",
3331
3425
  flags.context && "contextSet as __contextSet",
3332
3426
  flags.context && "contextRestore as __contextRestore",
@@ -3356,6 +3450,7 @@ function emit(program, moduleImports = [], includeTests = false) {
3356
3450
  flags.finite && "finite as __finite",
3357
3451
  flags.number && "requireNumber as __requireNumber",
3358
3452
  flags.call && "call as __call",
3453
+ flags.callMethod && "callMethod as __callMethod",
3359
3454
  flags.boundary && "boundary as __boundary",
3360
3455
  flags.database && "startTrans as __startTrans",
3361
3456
  flags.database && "commitTrans as __commitTrans",
@@ -3661,20 +3756,15 @@ function emitStatement(node, depth, declared, flags) {
3661
3756
  }
3662
3757
  if (node.type === "assign") {
3663
3758
  const decimalBinding = node.name.startsWith("$") || /^@{1,2}\$/.test(node.name);
3664
- const property = node.name.slice(node.name.startsWith("@@") ? 2 : 1);
3665
- const target = /^[A-Za-z_]\w*\.\$?[A-Za-z_]\w*$/.test(node.name)
3666
- ? node.name
3667
- : node.name.startsWith("@@")
3668
- ? `${flags.className}.${property}`
3759
+ const target = /^[A-Za-z_]\w*(?:\.\$?[A-Za-z_]\w*)+$/.test(node.name)
3760
+ ? emitReceiverName(node.name, flags)
3669
3761
  : node.name.startsWith("@")
3670
- ? flags.publicProperties?.has(property)
3671
- ? `this.${property}`
3672
- : `this.#${property}`
3762
+ ? emitReceiverName(node.name, flags)
3673
3763
  : flags.className && node.name.startsWith("_")
3674
3764
  ? `this.${node.name}`
3675
3765
  : node.name;
3676
3766
  const prefix =
3677
- /^[A-Za-z_]\w*\.\$?[A-Za-z_]\w*$/.test(node.name)
3767
+ /^[A-Za-z_]\w*(?:\.\$?[A-Za-z_]\w*)+$/.test(node.name)
3678
3768
  ? ""
3679
3769
  : node.name.startsWith("@") ||
3680
3770
  (flags.className && node.name.startsWith("_")) ||
@@ -3696,7 +3786,7 @@ function emitStatement(node, depth, declared, flags) {
3696
3786
  return `${pad}${prefix}${target} = ${decimalBinding && node.value.type !== "binary" && !decimalLiteral ? `__decimal(${decimalInput})` : value};\n`;
3697
3787
  }
3698
3788
  if (node.type === "indexAssign") {
3699
- const target = `${node.receiver}${(node.indexes ?? [node.index]).map((index) => `[${emitExpr(index, flags)}]`).join("")}`;
3789
+ const target = `${emitReceiverName(node.receiver, flags)}${(node.indexes ?? [node.index]).map((index) => `[${emitExpr(index, flags)}]`).join("")}`;
3700
3790
  const value = emitExpr(node.value, flags);
3701
3791
  if (node.operator) flags.finite = true;
3702
3792
  return `${pad}${target} = ${node.operator ? `__finite(${target} ${node.operator} ${value})` : value};\n`;
@@ -3808,7 +3898,11 @@ function emitClosure(node, depth, flags) {
3808
3898
  const priorContextNames = flags.contextNames;
3809
3899
  const priorGlobalNames = flags.globalNames;
3810
3900
  const priorArrayBindings = flags.arrayBindings;
3811
- const contextNames = collectContextNames(node.body);
3901
+ const contextCaptures = node.captures.filter((name) => priorContextNames?.has(name));
3902
+ const contextNames = new Set([
3903
+ ...collectContextNames(node.body),
3904
+ ...contextCaptures,
3905
+ ]);
3812
3906
  flags.contextNames = contextNames;
3813
3907
  flags.globalNames = collectGlobalNames(node.body);
3814
3908
  flags.arrayBindings = new Set();
@@ -3836,7 +3930,16 @@ function emitClosure(node, depth, flags) {
3836
3930
  if (!contextNames.size)
3837
3931
  return wrap(`(${parameters}) => {\n${boundary}${body}${pad} return null;\n${pad}}`);
3838
3932
  flags.context = true;
3839
- return wrap(`(${parameters}) => {\n${pad} const __contextScope = [];\n${pad} try {\n${boundary}${body}${pad} return null;\n${pad} } finally {\n${pad} __contextRestore(__contextScope);\n${pad} }\n${pad}}`);
3933
+ const capturedParameters = contextCaptures.map((_, index) => `__capturedContext${index}`);
3934
+ const capturedValues = contextCaptures.map((name) => `__contextGet(${JSON.stringify(name)})`);
3935
+ const capturedSetup = contextCaptures
3936
+ .map((name, index) => `${pad} __contextScope.push(__contextSet(${JSON.stringify(name)}, __capturedContext${index}));\n`)
3937
+ .join("");
3938
+ const callback = `(${parameters}) => {\n${pad} const __contextScope = [];\n${pad} try {\n${capturedSetup}${boundary}${body}${pad} return null;\n${pad} } finally {\n${pad} __contextRestore(__contextScope);\n${pad} }\n${pad}}`;
3939
+ const wrappedCallback = contextCaptures.length
3940
+ ? `((${capturedParameters.join(", ")}) => ${callback})(${capturedValues.join(", ")})`
3941
+ : callback;
3942
+ return wrap(wrappedCallback);
3840
3943
  }
3841
3944
  function emitArityGuard(parameters, actual, name, depth, flags) {
3842
3945
  const positions = parameters
@@ -3860,6 +3963,24 @@ function emitBoundaryChecks(parameters, depth, flags) {
3860
3963
  })
3861
3964
  .join("");
3862
3965
  }
3966
+ function emitReceiverName(name, flags) {
3967
+ const member = /^(@{1,2})(\$?[A-Za-z_]\w*)(.*)$/.exec(name);
3968
+ if (member) {
3969
+ const [, scope, property, suffix] = member;
3970
+ const owner = scope === "@@" ? flags.className : "this";
3971
+ const visibility = scope === "@@" || flags.publicProperties?.has(property) ? "" : "#";
3972
+ return `${owner}.${visibility}${property}${suffix}`;
3973
+ }
3974
+ const base = /^([A-Za-z_]\w*)([.\[].*)$/.exec(name);
3975
+ if (base && flags.contextNames?.has(base[1])) {
3976
+ flags.context = true;
3977
+ return `__contextGet(${JSON.stringify(base[1])})${base[2]}`;
3978
+ }
3979
+ if (!flags.contextNames?.has(name)) return name;
3980
+ flags.context = true;
3981
+ return `__contextGet(${JSON.stringify(name)})`;
3982
+ }
3983
+
3863
3984
  function emitExpr(node, flags) {
3864
3985
  const decimalRaw = (value) =>
3865
3986
  value?.type === "raw" &&
@@ -3873,11 +3994,18 @@ function emitExpr(node, flags) {
3873
3994
  flags.normalize = true;
3874
3995
  return `__normalize(${value})`;
3875
3996
  };
3997
+ if (node.type === "raw")
3998
+ if (/^@{1,2}\$?[A-Za-z_]\w*[.\[?]/.test(node.value))
3999
+ return normalize(emitReceiverName(node.value, flags));
3876
4000
  if (node.type === "raw")
3877
4001
  if (flags.contextNames?.has(node.value)) {
3878
4002
  flags.context = true;
3879
4003
  return `__contextGet(${JSON.stringify(node.value)})`;
3880
4004
  }
4005
+ if (node.type === "raw")
4006
+ if (/^[A-Za-z_]\w*[.\[]/.test(node.value) && flags.contextNames?.has(node.value.split(/[.\[]/)[0])) {
4007
+ return normalize(emitReceiverName(node.value, flags));
4008
+ }
3881
4009
  if (node.type === "raw")
3882
4010
  if (/^DB\.(Active|RollbackOnly|Failed)$/.test(node.value)) {
3883
4011
  flags.databaseEnum = true;
@@ -3935,7 +4063,7 @@ function emitExpr(node, flags) {
3935
4063
  return `super.${node.name}(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
3936
4064
  if (node.type === "index")
3937
4065
  return normalize(
3938
- `${typeof node.receiver === "string" ? node.receiver : emitExpr(node.receiver, flags)}${node.optional ? "?." : ""}[${emitExpr(node.index, flags)}]`,
4066
+ `${typeof node.receiver === "string" ? emitReceiverName(node.receiver, flags) : emitExpr(node.receiver, flags)}${node.optional ? "?." : ""}[${emitExpr(node.index, flags)}]`,
3939
4067
  );
3940
4068
  if (node.type === "property")
3941
4069
  return normalize(
@@ -3946,7 +4074,7 @@ function emitExpr(node, flags) {
3946
4074
  const sourceReceiver = namespace
3947
4075
  ? node.receiver.replaceAll("/", ".")
3948
4076
  : typeof node.receiver === "string"
3949
- ? node.receiver
4077
+ ? emitReceiverName(node.receiver, flags)
3950
4078
  : emitExpr(node.receiver, flags);
3951
4079
  const type =
3952
4080
  sourceReceiver === flags.className ||
@@ -4089,6 +4217,14 @@ function emitExpr(node, flags) {
4089
4217
  flags.toJsNumber = true;
4090
4218
  return `__toJsNumber(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
4091
4219
  }
4220
+ if (node.type === "call")
4221
+ if (node.name === "import_module")
4222
+ return `import(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
4223
+ if (node.type === "call")
4224
+ if (node.name === "xml2obj") {
4225
+ flags.xmlObject = true;
4226
+ return `__xml2obj(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
4227
+ }
4092
4228
  if (node.type === "call")
4093
4229
  if (node.name === "from_json" || node.name === "to_json") {
4094
4230
  flags.json = true;
@@ -4128,6 +4264,10 @@ function emitExpr(node, flags) {
4128
4264
  return `__call(${node.name}, ${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
4129
4265
  } else
4130
4266
  return `${node.name}(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
4267
+ if (node.type === "indexedCall") {
4268
+ flags.callMethod = true;
4269
+ return `__callMethod(${emitExpr(node.receiver, flags)}, ${emitExpr(node.index, flags)}, ${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
4270
+ }
4131
4271
  if (node.type === "string") return emitString(node.literal, flags);
4132
4272
  if (node.type === "xml") {
4133
4273
  flags.xml = true;