@lango-project/lango-js 0.1.2 → 0.1.3
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/NOTICE +5 -0
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/cli.mjs +8 -2
- package/src/compiler.mjs +150 -26
- package/src/runtime.mjs +92 -1
- package/src/template/package.json +4 -0
- package/src/vendor/xmldom/conventions.js +429 -0
- package/src/vendor/xmldom/dom-parser.js +591 -0
- package/src/vendor/xmldom/dom.js +3759 -0
- package/src/vendor/xmldom/entities.js +2171 -0
- package/src/vendor/xmldom/errors.js +205 -0
- package/src/vendor/xmldom/grammar.js +559 -0
- package/src/vendor/xmldom/index.js +41 -0
- package/src/vendor/xmldom/sax.js +983 -0
- package/src/vendor/xmldom.LICENSE.md +8 -0
package/NOTICE
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
Lango
|
|
2
2
|
Copyright 2026 Savas Alparslan
|
|
3
|
+
|
|
4
|
+
This product includes @xmldom/xmldom 0.9.12.
|
|
5
|
+
Copyright 2019-present Christopher J. Brody and other contributors.
|
|
6
|
+
Copyright 2012-2017 @jindw <jindw@xidea.org> and other contributors.
|
|
7
|
+
Licensed under the MIT License; see src/vendor/xmldom.LICENSE.md.
|
package/README.md
CHANGED
|
@@ -77,7 +77,9 @@ test:lango -- input.lango` when Lango test blocks need host services.
|
|
|
77
77
|
|
|
78
78
|
`buildFile()` writes a matched private runtime next to the generated module.
|
|
79
79
|
Before calling exported Lango entry points, import that runtime and configure
|
|
80
|
-
it.
|
|
80
|
+
it. Runtimes generated into separate packages share dynamic Lango contexts
|
|
81
|
+
within one JavaScript process, so an application host can provide a context to
|
|
82
|
+
an exported function in a compiled dependency. `configure()` accepts an object with optional `output`, `catalog`, and
|
|
81
83
|
`database_adapter` values; it returns `null`, resets dynamic context and
|
|
82
84
|
transaction-failure state, and rejects an invalid configuration with
|
|
83
85
|
`RuntimeConfigurationError`. `output` is a function (or `null`), `catalog` is
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -15,8 +15,13 @@ const displayError = (error) => error instanceof LangoError
|
|
|
15
15
|
const buildOptions = (arguments_) => {
|
|
16
16
|
if (arguments_[0] !== "--out" || !arguments_[1]) return null;
|
|
17
17
|
const profile = {};
|
|
18
|
-
for (let index = 2; index < arguments_.length;
|
|
18
|
+
for (let index = 2; index < arguments_.length;) {
|
|
19
19
|
const option = arguments_[index];
|
|
20
|
+
if (option === "--executable") {
|
|
21
|
+
profile.executable = true;
|
|
22
|
+
index += 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
20
25
|
const value = arguments_[index + 1];
|
|
21
26
|
if (!value) return null;
|
|
22
27
|
if (option === "--decimal-division-scale")
|
|
@@ -24,6 +29,7 @@ const buildOptions = (arguments_) => {
|
|
|
24
29
|
else if (option === "--decimal-division-rounding")
|
|
25
30
|
profile.decimal_division_rounding = Decimal.Rounding[value] ?? value;
|
|
26
31
|
else return null;
|
|
32
|
+
index += 2;
|
|
27
33
|
}
|
|
28
34
|
return { outputPath: arguments_[1], profile };
|
|
29
35
|
};
|
|
@@ -167,6 +173,6 @@ if (command === "init" && inputPath && rest.length === 0) {
|
|
|
167
173
|
process.exitCode = error instanceof LangoError ? 1 : 2;
|
|
168
174
|
}
|
|
169
175
|
} else {
|
|
170
|
-
console.error("Usage: node src/cli.mjs init <directory>\n node src/cli.mjs build <input.lango> --out <output.mjs> [--decimal-division-scale <n>] [--decimal-division-rounding <EnumName>]\n node src/cli.mjs run <input.lango> [--database <sqlite-file>] [--catalog <catalog.json>] [-- args...]\n node src/cli.mjs test <input.lango> [--database <sqlite-file>] [--catalog <catalog.json>]\n node src/cli.mjs format <input.lango>");
|
|
176
|
+
console.error("Usage: node src/cli.mjs init <directory>\n node src/cli.mjs build <input.lango> --out <output.mjs> [--executable] [--decimal-division-scale <n>] [--decimal-division-rounding <EnumName>]\n node src/cli.mjs run <input.lango> [--database <sqlite-file>] [--catalog <catalog.json>] [-- args...]\n node src/cli.mjs test <input.lango> [--database <sqlite-file>] [--catalog <catalog.json>]\n node src/cli.mjs format <input.lango>");
|
|
171
177
|
process.exitCode = 2;
|
|
172
178
|
}
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -777,7 +796,7 @@ class Parser {
|
|
|
777
796
|
value: { type: "new", name: constructor, arguments: [closure], line: line.number },
|
|
778
797
|
});
|
|
779
798
|
}
|
|
780
|
-
else if (/^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.test(text)) {
|
|
799
|
+
else if (!text.includes(" = ") && /^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.test(text)) {
|
|
781
800
|
const [, receiver, name, argumentsText, parameters] =
|
|
782
801
|
/^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.exec(text);
|
|
783
802
|
const closure = {
|
|
@@ -803,7 +822,34 @@ class Parser {
|
|
|
803
822
|
},
|
|
804
823
|
});
|
|
805
824
|
}
|
|
806
|
-
else if (/^(.+)\.([A-Za-z_]\w*)\(
|
|
825
|
+
else if (/^(\$?[A-Za-z_]\w*) = (.+)\.([A-Za-z_]\w*)\((?:(.*), )?\(([^)]*)\)$/.test(text)) {
|
|
826
|
+
const [, name, receiver, method, argumentsText, parameters] =
|
|
827
|
+
/^(\$?[A-Za-z_]\w*) = (.+)\.([A-Za-z_]\w*)\((?:(.*), )?\(([^)]*)\)$/.exec(text);
|
|
828
|
+
const closure = {
|
|
829
|
+
type: "closure",
|
|
830
|
+
parameters: this.parameters(parameters, line),
|
|
831
|
+
captures: this.captures(),
|
|
832
|
+
line: line.number,
|
|
833
|
+
body: this.block(indent + 4),
|
|
834
|
+
};
|
|
835
|
+
this.blank();
|
|
836
|
+
if (this.peek() !== `${" ".repeat(indent)})`)
|
|
837
|
+
this.fail(line, "LNG1266", "assigned method callback requires a closing parenthesis");
|
|
838
|
+
this.i += 1;
|
|
839
|
+
body.push({
|
|
840
|
+
type: "assign",
|
|
841
|
+
name,
|
|
842
|
+
value: {
|
|
843
|
+
type: "methodCall",
|
|
844
|
+
receiver,
|
|
845
|
+
optional: false,
|
|
846
|
+
name: method,
|
|
847
|
+
arguments: [...(argumentsText ? splitTopLevel(argumentsText).map((item) => expr(item, line, this)) : []), closure],
|
|
848
|
+
line: line.number,
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
else if (!text.includes(" = ") && /^(.+)\.([A-Za-z_]\w*)\(\(([^)]*)\)$/.test(text)) {
|
|
807
853
|
const [, first, name, parameters] = /^(.+)\.([A-Za-z_]\w*)\(\(([^)]*)\)$/.exec(text);
|
|
808
854
|
const steps = [];
|
|
809
855
|
let step = { name, parameters: this.parameters(parameters, line), captures: this.captures(), line: line.number, body: this.block(indent + 4) };
|
|
@@ -959,7 +1005,7 @@ class Parser {
|
|
|
959
1005
|
operator: suffix === "++" ? "+" : "-",
|
|
960
1006
|
});
|
|
961
1007
|
} else {
|
|
962
|
-
const assignment = /^(\$[A-Za-z_]\w*|@{0,2}\$?[A-Za-z_]\w*|[A-Za-z_]\w
|
|
1008
|
+
const assignment = /^(\$[A-Za-z_]\w*|@{0,2}\$?[A-Za-z_]\w*|[A-Za-z_]\w*(?:\.\$?[A-Za-z_]\w*)+) = (.+)$/.exec(text);
|
|
963
1009
|
if (!assignment)
|
|
964
1010
|
this.fail(line, "LNG1106", `unsupported Faz 2 statement: ${text}`);
|
|
965
1011
|
body.push({
|
|
@@ -1288,10 +1334,18 @@ function expr(source, line, parser) {
|
|
|
1288
1334
|
}),
|
|
1289
1335
|
};
|
|
1290
1336
|
}
|
|
1337
|
+
if (text.startsWith("not "))
|
|
1338
|
+
return {
|
|
1339
|
+
type: "unary",
|
|
1340
|
+
operator: "not",
|
|
1341
|
+
value: expr(text.slice(4), line, parser),
|
|
1342
|
+
};
|
|
1291
1343
|
for (const operators of [
|
|
1292
1344
|
["??"],
|
|
1293
|
-
["or"
|
|
1294
|
-
["and"
|
|
1345
|
+
["or"],
|
|
1346
|
+
["and"],
|
|
1347
|
+
["||"],
|
|
1348
|
+
["&&"],
|
|
1295
1349
|
["==", "!="],
|
|
1296
1350
|
[">=", "<=", ">", "<", "not in", "in"],
|
|
1297
1351
|
["+", "-"],
|
|
@@ -1314,12 +1368,6 @@ function expr(source, line, parser) {
|
|
|
1314
1368
|
left: expr(text.slice(0, exponent.index), line, parser),
|
|
1315
1369
|
right: expr(text.slice(exponent.index + exponent.operator.length + 2), line, parser),
|
|
1316
1370
|
};
|
|
1317
|
-
if (text.startsWith("not "))
|
|
1318
|
-
return {
|
|
1319
|
-
type: "unary",
|
|
1320
|
-
operator: "not",
|
|
1321
|
-
value: expr(text.slice(4), line, parser),
|
|
1322
|
-
};
|
|
1323
1371
|
if (/^-[^\s]/.test(text))
|
|
1324
1372
|
return {
|
|
1325
1373
|
type: "unary",
|
|
@@ -1455,6 +1503,17 @@ function expr(source, line, parser) {
|
|
|
1455
1503
|
: [],
|
|
1456
1504
|
line: line.number,
|
|
1457
1505
|
};
|
|
1506
|
+
const indexedCall = /^(.+)\[(.+)\]\((.*)\)$/.exec(text);
|
|
1507
|
+
if (indexedCall)
|
|
1508
|
+
return {
|
|
1509
|
+
type: "indexedCall",
|
|
1510
|
+
receiver: expr(indexedCall[1], line, parser),
|
|
1511
|
+
index: expr(indexedCall[2], line, parser),
|
|
1512
|
+
arguments: indexedCall[3]
|
|
1513
|
+
? splitTopLevel(indexedCall[3]).map((item) => expr(item, line, parser))
|
|
1514
|
+
: [],
|
|
1515
|
+
line: line.number,
|
|
1516
|
+
};
|
|
1458
1517
|
const methodCall =
|
|
1459
1518
|
/^([A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w*)*?)(\?\.|\.)([A-Za-z_]\w*)\((.*)\)$/.exec(
|
|
1460
1519
|
text,
|
|
@@ -1760,6 +1819,8 @@ function validate(program, imports = []) {
|
|
|
1760
1819
|
["is_array", 1],
|
|
1761
1820
|
["range", [1, 2, 3]],
|
|
1762
1821
|
["to_js_number", 1],
|
|
1822
|
+
["import_module", 1],
|
|
1823
|
+
["xml2obj", 1],
|
|
1763
1824
|
["start_trans", 0], ["commit_trans", 0], ["rollback_trans", 0], ["trans_state", 0],
|
|
1764
1825
|
]);
|
|
1765
1826
|
const hostFunctions = new Set(["fetch"]);
|
|
@@ -2508,6 +2569,15 @@ function validate(program, imports = []) {
|
|
|
2508
2569
|
return names;
|
|
2509
2570
|
};
|
|
2510
2571
|
|
|
2572
|
+
const contextBindings = (body, names = new Set()) => {
|
|
2573
|
+
for (const node of body) {
|
|
2574
|
+
if (node.type === "contextDeclare" || node.type === "contextSet") names.add(node.name);
|
|
2575
|
+
if (node.type !== "closureAssign")
|
|
2576
|
+
nestedBodies(node).forEach((child) => contextBindings(child, names));
|
|
2577
|
+
}
|
|
2578
|
+
return names;
|
|
2579
|
+
};
|
|
2580
|
+
|
|
2511
2581
|
const globalDeclarations = (body, names = new Set()) => {
|
|
2512
2582
|
for (const node of body) {
|
|
2513
2583
|
if (node.type === "globalDeclare") names.add(node.name);
|
|
@@ -2563,9 +2633,14 @@ function validate(program, imports = []) {
|
|
|
2563
2633
|
outerBindings,
|
|
2564
2634
|
classScope = null,
|
|
2565
2635
|
inheritedExpiredCatchBindings = new Set(),
|
|
2636
|
+
outerContextBindings = new Set(),
|
|
2566
2637
|
) => {
|
|
2567
2638
|
const available = new Set(outerBindings);
|
|
2568
2639
|
const expiredCatchBindings = new Set(inheritedExpiredCatchBindings);
|
|
2640
|
+
const availableContexts = new Set([
|
|
2641
|
+
...outerContextBindings,
|
|
2642
|
+
...contextBindings(body),
|
|
2643
|
+
]);
|
|
2569
2644
|
for (const node of body) {
|
|
2570
2645
|
const references = new Set();
|
|
2571
2646
|
[node.value, node.condition, node.collection, node.arguments, node.tests].forEach((value) => {
|
|
@@ -2585,6 +2660,7 @@ function validate(program, imports = []) {
|
|
|
2585
2660
|
new Set(available),
|
|
2586
2661
|
classScope,
|
|
2587
2662
|
expiredCatchBindings,
|
|
2663
|
+
availableContexts,
|
|
2588
2664
|
);
|
|
2589
2665
|
const right = node.elseBody
|
|
2590
2666
|
? validateClosures(
|
|
@@ -2592,6 +2668,7 @@ function validate(program, imports = []) {
|
|
|
2592
2668
|
new Set(available),
|
|
2593
2669
|
classScope,
|
|
2594
2670
|
expiredCatchBindings,
|
|
2671
|
+
availableContexts,
|
|
2595
2672
|
)
|
|
2596
2673
|
: available;
|
|
2597
2674
|
const exits = [];
|
|
@@ -2610,6 +2687,7 @@ function validate(program, imports = []) {
|
|
|
2610
2687
|
new Set(available),
|
|
2611
2688
|
classScope,
|
|
2612
2689
|
expiredCatchBindings,
|
|
2690
|
+
availableContexts,
|
|
2613
2691
|
),
|
|
2614
2692
|
);
|
|
2615
2693
|
if (!node.elseBody || reachesEnd(node.elseBody))
|
|
@@ -2620,6 +2698,7 @@ function validate(program, imports = []) {
|
|
|
2620
2698
|
new Set(available),
|
|
2621
2699
|
classScope,
|
|
2622
2700
|
expiredCatchBindings,
|
|
2701
|
+
availableContexts,
|
|
2623
2702
|
)
|
|
2624
2703
|
: new Set(available),
|
|
2625
2704
|
);
|
|
@@ -2635,6 +2714,7 @@ function validate(program, imports = []) {
|
|
|
2635
2714
|
new Set(available),
|
|
2636
2715
|
classScope,
|
|
2637
2716
|
expiredCatchBindings,
|
|
2717
|
+
availableContexts,
|
|
2638
2718
|
);
|
|
2639
2719
|
if (reachesEnd(node.tryBody)) exits.push(tryExit);
|
|
2640
2720
|
if (node.catchBody) {
|
|
@@ -2645,6 +2725,7 @@ function validate(program, imports = []) {
|
|
|
2645
2725
|
new Set(
|
|
2646
2726
|
[...expiredCatchBindings].filter((name) => name !== node.catchName),
|
|
2647
2727
|
),
|
|
2728
|
+
availableContexts,
|
|
2648
2729
|
);
|
|
2649
2730
|
if (reachesEnd(node.catchBody)) exits.push(catchExit);
|
|
2650
2731
|
}
|
|
@@ -2658,6 +2739,7 @@ function validate(program, imports = []) {
|
|
|
2658
2739
|
certain,
|
|
2659
2740
|
classScope,
|
|
2660
2741
|
new Set([...expiredCatchBindings, node.catchName].filter(Boolean)),
|
|
2742
|
+
availableContexts,
|
|
2661
2743
|
)
|
|
2662
2744
|
: certain;
|
|
2663
2745
|
if (!node.finallyBody || reachesEnd(node.finallyBody))
|
|
@@ -2671,6 +2753,7 @@ function validate(program, imports = []) {
|
|
|
2671
2753
|
new Set([...available, node.item]),
|
|
2672
2754
|
classScope,
|
|
2673
2755
|
expiredCatchBindings,
|
|
2756
|
+
availableContexts,
|
|
2674
2757
|
);
|
|
2675
2758
|
continue;
|
|
2676
2759
|
}
|
|
@@ -2680,6 +2763,7 @@ function validate(program, imports = []) {
|
|
|
2680
2763
|
new Set([...available, node.key, node.value]),
|
|
2681
2764
|
classScope,
|
|
2682
2765
|
expiredCatchBindings,
|
|
2766
|
+
availableContexts,
|
|
2683
2767
|
);
|
|
2684
2768
|
continue;
|
|
2685
2769
|
}
|
|
@@ -2695,6 +2779,7 @@ function validate(program, imports = []) {
|
|
|
2695
2779
|
new Set(available),
|
|
2696
2780
|
classScope,
|
|
2697
2781
|
expiredCatchBindings,
|
|
2782
|
+
availableContexts,
|
|
2698
2783
|
);
|
|
2699
2784
|
continue;
|
|
2700
2785
|
}
|
|
@@ -2726,9 +2811,14 @@ function validate(program, imports = []) {
|
|
|
2726
2811
|
(name) =>
|
|
2727
2812
|
!localBindings.has(name) &&
|
|
2728
2813
|
!contextNames.has(name) &&
|
|
2729
|
-
!globalNames.has(name)
|
|
2814
|
+
!globalNames.has(name) &&
|
|
2815
|
+
!standardFunctions.has(name) &&
|
|
2816
|
+
!hostFunctions.has(name),
|
|
2730
2817
|
);
|
|
2731
2818
|
const captures = new Set(closure.captures);
|
|
2819
|
+
const contextCaptures = new Set(
|
|
2820
|
+
closure.captures.filter((name) => availableContexts.has(name)),
|
|
2821
|
+
);
|
|
2732
2822
|
for (const name of needed)
|
|
2733
2823
|
if (!captures.has(name))
|
|
2734
2824
|
throw new LangoError(
|
|
@@ -2737,7 +2827,7 @@ function validate(program, imports = []) {
|
|
|
2737
2827
|
`MissingCapture: ${name}; add capture ${name}`,
|
|
2738
2828
|
);
|
|
2739
2829
|
for (const name of captures) {
|
|
2740
|
-
if (!needed.includes(name))
|
|
2830
|
+
if (!needed.includes(name) && !contextCaptures.has(name))
|
|
2741
2831
|
throw new LangoError(
|
|
2742
2832
|
"LNG1211",
|
|
2743
2833
|
closure.line,
|
|
@@ -2745,7 +2835,7 @@ function validate(program, imports = []) {
|
|
|
2745
2835
|
);
|
|
2746
2836
|
const protectedMember = Boolean(classScope && name.startsWith("_"));
|
|
2747
2837
|
const propertyCapture = name.startsWith("@") || protectedMember;
|
|
2748
|
-
if (!propertyCapture && !available.has(name))
|
|
2838
|
+
if (!propertyCapture && !contextCaptures.has(name) && !available.has(name))
|
|
2749
2839
|
throw new LangoError(
|
|
2750
2840
|
"LNG1212",
|
|
2751
2841
|
closure.line,
|
|
@@ -2778,6 +2868,7 @@ function validate(program, imports = []) {
|
|
|
2778
2868
|
new Set([...available, ...localBindings]),
|
|
2779
2869
|
classScope,
|
|
2780
2870
|
expiredCatchBindings,
|
|
2871
|
+
new Set([...availableContexts, ...contextCaptures]),
|
|
2781
2872
|
);
|
|
2782
2873
|
if (node.type === "closureAssign" || node.type === "assign") available.add(node.name);
|
|
2783
2874
|
continue;
|
|
@@ -3327,6 +3418,7 @@ function emit(program, moduleImports = [], includeTests = false) {
|
|
|
3327
3418
|
flags.print && "println as __println",
|
|
3328
3419
|
flags.translate && "translate as __translate",
|
|
3329
3420
|
flags.xml && "xmlEscape as __xmlEscape",
|
|
3421
|
+
flags.xmlObject && "xml2obj as __xml2obj",
|
|
3330
3422
|
flags.context && "contextGet as __contextGet",
|
|
3331
3423
|
flags.context && "contextSet as __contextSet",
|
|
3332
3424
|
flags.context && "contextRestore as __contextRestore",
|
|
@@ -3356,6 +3448,7 @@ function emit(program, moduleImports = [], includeTests = false) {
|
|
|
3356
3448
|
flags.finite && "finite as __finite",
|
|
3357
3449
|
flags.number && "requireNumber as __requireNumber",
|
|
3358
3450
|
flags.call && "call as __call",
|
|
3451
|
+
flags.callMethod && "callMethod as __callMethod",
|
|
3359
3452
|
flags.boundary && "boundary as __boundary",
|
|
3360
3453
|
flags.database && "startTrans as __startTrans",
|
|
3361
3454
|
flags.database && "commitTrans as __commitTrans",
|
|
@@ -3662,7 +3755,7 @@ function emitStatement(node, depth, declared, flags) {
|
|
|
3662
3755
|
if (node.type === "assign") {
|
|
3663
3756
|
const decimalBinding = node.name.startsWith("$") || /^@{1,2}\$/.test(node.name);
|
|
3664
3757
|
const property = node.name.slice(node.name.startsWith("@@") ? 2 : 1);
|
|
3665
|
-
const target = /^[A-Za-z_]\w
|
|
3758
|
+
const target = /^[A-Za-z_]\w*(?:\.\$?[A-Za-z_]\w*)+$/.test(node.name)
|
|
3666
3759
|
? node.name
|
|
3667
3760
|
: node.name.startsWith("@@")
|
|
3668
3761
|
? `${flags.className}.${property}`
|
|
@@ -3674,7 +3767,7 @@ function emitStatement(node, depth, declared, flags) {
|
|
|
3674
3767
|
? `this.${node.name}`
|
|
3675
3768
|
: node.name;
|
|
3676
3769
|
const prefix =
|
|
3677
|
-
/^[A-Za-z_]\w
|
|
3770
|
+
/^[A-Za-z_]\w*(?:\.\$?[A-Za-z_]\w*)+$/.test(node.name)
|
|
3678
3771
|
? ""
|
|
3679
3772
|
: node.name.startsWith("@") ||
|
|
3680
3773
|
(flags.className && node.name.startsWith("_")) ||
|
|
@@ -3808,7 +3901,11 @@ function emitClosure(node, depth, flags) {
|
|
|
3808
3901
|
const priorContextNames = flags.contextNames;
|
|
3809
3902
|
const priorGlobalNames = flags.globalNames;
|
|
3810
3903
|
const priorArrayBindings = flags.arrayBindings;
|
|
3811
|
-
const
|
|
3904
|
+
const contextCaptures = node.captures.filter((name) => priorContextNames?.has(name));
|
|
3905
|
+
const contextNames = new Set([
|
|
3906
|
+
...collectContextNames(node.body),
|
|
3907
|
+
...contextCaptures,
|
|
3908
|
+
]);
|
|
3812
3909
|
flags.contextNames = contextNames;
|
|
3813
3910
|
flags.globalNames = collectGlobalNames(node.body);
|
|
3814
3911
|
flags.arrayBindings = new Set();
|
|
@@ -3836,7 +3933,16 @@ function emitClosure(node, depth, flags) {
|
|
|
3836
3933
|
if (!contextNames.size)
|
|
3837
3934
|
return wrap(`(${parameters}) => {\n${boundary}${body}${pad} return null;\n${pad}}`);
|
|
3838
3935
|
flags.context = true;
|
|
3839
|
-
|
|
3936
|
+
const capturedParameters = contextCaptures.map((_, index) => `__capturedContext${index}`);
|
|
3937
|
+
const capturedValues = contextCaptures.map((name) => `__contextGet(${JSON.stringify(name)})`);
|
|
3938
|
+
const capturedSetup = contextCaptures
|
|
3939
|
+
.map((name, index) => `${pad} __contextScope.push(__contextSet(${JSON.stringify(name)}, __capturedContext${index}));\n`)
|
|
3940
|
+
.join("");
|
|
3941
|
+
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}}`;
|
|
3942
|
+
const wrappedCallback = contextCaptures.length
|
|
3943
|
+
? `((${capturedParameters.join(", ")}) => ${callback})(${capturedValues.join(", ")})`
|
|
3944
|
+
: callback;
|
|
3945
|
+
return wrap(wrappedCallback);
|
|
3840
3946
|
}
|
|
3841
3947
|
function emitArityGuard(parameters, actual, name, depth, flags) {
|
|
3842
3948
|
const positions = parameters
|
|
@@ -3860,6 +3966,12 @@ function emitBoundaryChecks(parameters, depth, flags) {
|
|
|
3860
3966
|
})
|
|
3861
3967
|
.join("");
|
|
3862
3968
|
}
|
|
3969
|
+
function emitReceiverName(name, flags) {
|
|
3970
|
+
if (!flags.contextNames?.has(name)) return name;
|
|
3971
|
+
flags.context = true;
|
|
3972
|
+
return `__contextGet(${JSON.stringify(name)})`;
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3863
3975
|
function emitExpr(node, flags) {
|
|
3864
3976
|
const decimalRaw = (value) =>
|
|
3865
3977
|
value?.type === "raw" &&
|
|
@@ -3935,7 +4047,7 @@ function emitExpr(node, flags) {
|
|
|
3935
4047
|
return `super.${node.name}(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
3936
4048
|
if (node.type === "index")
|
|
3937
4049
|
return normalize(
|
|
3938
|
-
`${typeof node.receiver === "string" ? node.receiver : emitExpr(node.receiver, flags)}${node.optional ? "?." : ""}[${emitExpr(node.index, flags)}]`,
|
|
4050
|
+
`${typeof node.receiver === "string" ? emitReceiverName(node.receiver, flags) : emitExpr(node.receiver, flags)}${node.optional ? "?." : ""}[${emitExpr(node.index, flags)}]`,
|
|
3939
4051
|
);
|
|
3940
4052
|
if (node.type === "property")
|
|
3941
4053
|
return normalize(
|
|
@@ -3946,7 +4058,7 @@ function emitExpr(node, flags) {
|
|
|
3946
4058
|
const sourceReceiver = namespace
|
|
3947
4059
|
? node.receiver.replaceAll("/", ".")
|
|
3948
4060
|
: typeof node.receiver === "string"
|
|
3949
|
-
? node.receiver
|
|
4061
|
+
? emitReceiverName(node.receiver, flags)
|
|
3950
4062
|
: emitExpr(node.receiver, flags);
|
|
3951
4063
|
const type =
|
|
3952
4064
|
sourceReceiver === flags.className ||
|
|
@@ -4089,6 +4201,14 @@ function emitExpr(node, flags) {
|
|
|
4089
4201
|
flags.toJsNumber = true;
|
|
4090
4202
|
return `__toJsNumber(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
4091
4203
|
}
|
|
4204
|
+
if (node.type === "call")
|
|
4205
|
+
if (node.name === "import_module")
|
|
4206
|
+
return `import(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
4207
|
+
if (node.type === "call")
|
|
4208
|
+
if (node.name === "xml2obj") {
|
|
4209
|
+
flags.xmlObject = true;
|
|
4210
|
+
return `__xml2obj(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
4211
|
+
}
|
|
4092
4212
|
if (node.type === "call")
|
|
4093
4213
|
if (node.name === "from_json" || node.name === "to_json") {
|
|
4094
4214
|
flags.json = true;
|
|
@@ -4128,6 +4248,10 @@ function emitExpr(node, flags) {
|
|
|
4128
4248
|
return `__call(${node.name}, ${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
4129
4249
|
} else
|
|
4130
4250
|
return `${node.name}(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
4251
|
+
if (node.type === "indexedCall") {
|
|
4252
|
+
flags.callMethod = true;
|
|
4253
|
+
return `__callMethod(${emitExpr(node.receiver, flags)}, ${emitExpr(node.index, flags)}, ${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
4254
|
+
}
|
|
4131
4255
|
if (node.type === "string") return emitString(node.literal, flags);
|
|
4132
4256
|
if (node.type === "xml") {
|
|
4133
4257
|
flags.xml = true;
|
package/src/runtime.mjs
CHANGED
|
@@ -22,6 +22,9 @@ export function runtimeSource(profile = {}) {
|
|
|
22
22
|
if (!(rounding in ROUNDING_MODES))
|
|
23
23
|
throw new Error(`unknown decimal_division_rounding ${rounding}`);
|
|
24
24
|
return `import BigNumber from "./bignumber.mjs";
|
|
25
|
+
import { createRequire } from "node:module";
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
const { DOMParser } = require("./xmldom/index.js");
|
|
25
28
|
|
|
26
29
|
const Decimal = BigNumber.clone({
|
|
27
30
|
DECIMAL_PLACES: ${scale},
|
|
@@ -56,7 +59,9 @@ let output = null;
|
|
|
56
59
|
let database = null;
|
|
57
60
|
let failedTransaction = false;
|
|
58
61
|
export const DB = Object.freeze({ Active: "active", RollbackOnly: "rollback_only", Failed: "failed" });
|
|
59
|
-
const
|
|
62
|
+
const contextStore = Symbol.for("lango.contexts");
|
|
63
|
+
const contexts = globalThis[contextStore] ?? new Map();
|
|
64
|
+
globalThis[contextStore] = contexts;
|
|
60
65
|
|
|
61
66
|
export class RuntimeConfigurationError extends Error {
|
|
62
67
|
constructor(message) {
|
|
@@ -188,6 +193,73 @@ export function xmlEscape(value) {
|
|
|
188
193
|
.replaceAll("'", "'");
|
|
189
194
|
}
|
|
190
195
|
|
|
196
|
+
export function xml2obj(xml) {
|
|
197
|
+
if (typeof xml !== "string") throw new Error("XmlParseError: XML input must be a string");
|
|
198
|
+
const errors = [];
|
|
199
|
+
let document;
|
|
200
|
+
try {
|
|
201
|
+
document = new DOMParser({
|
|
202
|
+
onError(level, message) {
|
|
203
|
+
if (level !== "warning") errors.push(message);
|
|
204
|
+
},
|
|
205
|
+
}).parseFromString(xml, "application/xml");
|
|
206
|
+
} catch (error) {
|
|
207
|
+
throw new Error(\`XmlParseError: \${error.message}\`);
|
|
208
|
+
}
|
|
209
|
+
if (errors.length) throw new Error(\`XmlParseError: \${errors[0]}\`);
|
|
210
|
+
if (!document.documentElement)
|
|
211
|
+
throw new Error("XmlParseError: XML must contain one root tag");
|
|
212
|
+
return new Tag(document.documentElement);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export class Tag {
|
|
216
|
+
constructor(element) {
|
|
217
|
+
this.element = element;
|
|
218
|
+
return new Proxy(this, {
|
|
219
|
+
get: (target, name, receiver) => target.#property(name, receiver),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
tag() {
|
|
224
|
+
return this.element.tagName;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
innerText() {
|
|
228
|
+
let text = "";
|
|
229
|
+
for (const child of this.element.childNodes) {
|
|
230
|
+
if (child.nodeType === 3 || child.nodeType === 4) text += child.data;
|
|
231
|
+
}
|
|
232
|
+
return text;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
children() {
|
|
236
|
+
return Array.from(this.element.childNodes)
|
|
237
|
+
.filter((child) => child.nodeType === 1)
|
|
238
|
+
.map((child) => new Tag(child));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
attribute(name) {
|
|
242
|
+
const value = this.element.getAttribute(String(name));
|
|
243
|
+
return value === null ? null : value;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
attributes() {
|
|
247
|
+
const values = {};
|
|
248
|
+
for (const attribute of Array.from(this.element.attributes)) values[attribute.name] = attribute.value;
|
|
249
|
+
return values;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#property(name, receiver) {
|
|
253
|
+
if (typeof name !== "string") return Reflect.get(this, name, receiver);
|
|
254
|
+
if (name === "tag" || name === "innerText" || name === "children" || name === "attribute" || name === "attributes" || name === "element")
|
|
255
|
+
return Reflect.get(this, name, receiver);
|
|
256
|
+
if (this.element.hasAttribute(name)) return this.element.getAttribute(name);
|
|
257
|
+
const matching = Array.from(this.element.childNodes)
|
|
258
|
+
.filter((child) => child.nodeType === 1 && child.tagName === name);
|
|
259
|
+
return matching.length === 1 ? new Tag(matching[0]) : undefined;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
191
263
|
export function println(value) {
|
|
192
264
|
if (output === null) {
|
|
193
265
|
throw new Error("Lango runtime output is not configured");
|
|
@@ -197,6 +269,18 @@ export function println(value) {
|
|
|
197
269
|
return null;
|
|
198
270
|
}
|
|
199
271
|
|
|
272
|
+
const defaultLog = Object.freeze({
|
|
273
|
+
debug(value) { return writeLog(process.stdout, "DEBUG", value); },
|
|
274
|
+
info(value) { return writeLog(process.stdout, "INFO", value); },
|
|
275
|
+
warn(value) { return writeLog(process.stderr, "WARN", value); },
|
|
276
|
+
err(value) { return writeLog(process.stderr, "ERROR", value); },
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
function writeLog(stream, level, value) {
|
|
280
|
+
stream.write(\`[\${level}] \${String(value)}\\n\`);
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
200
284
|
export function fromJson(text) {
|
|
201
285
|
try {
|
|
202
286
|
return decimalJson(JSON.parse(text));
|
|
@@ -448,6 +532,12 @@ export function call(value, ...arguments_) {
|
|
|
448
532
|
return normalize(value(...arguments_));
|
|
449
533
|
}
|
|
450
534
|
|
|
535
|
+
export function callMethod(receiver, name, ...arguments_) {
|
|
536
|
+
const value = receiver?.[name];
|
|
537
|
+
if (typeof value !== "function") throw new Error("NotCallable");
|
|
538
|
+
return normalize(value.call(receiver, ...arguments_));
|
|
539
|
+
}
|
|
540
|
+
|
|
451
541
|
export function boundary(value) {
|
|
452
542
|
return finite(normalize(value));
|
|
453
543
|
}
|
|
@@ -469,6 +559,7 @@ export function withArity(callback, requiredPositions, name) {
|
|
|
469
559
|
|
|
470
560
|
export function contextGet(name) {
|
|
471
561
|
const stack = contexts.get(name);
|
|
562
|
+
if ((!stack || stack.length === 0) && name === "log") return defaultLog;
|
|
472
563
|
if (!stack || stack.length === 0) {
|
|
473
564
|
throw new Error(\`Lango context is unavailable: \${name}\`);
|
|
474
565
|
}
|