@lango-project/lango-js 0.1.1 → 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 +338 -37
- package/src/runtime.mjs +102 -2
- 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/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 = {}) {
|
|
@@ -231,7 +254,7 @@ function formatLine(line) {
|
|
|
231
254
|
|
|
232
255
|
class Parser {
|
|
233
256
|
constructor(source, name) {
|
|
234
|
-
this.lines = source.split(/\r?\n/).map((line) => stripComment(line).trimEnd());
|
|
257
|
+
this.lines = normalizeKebabIdentifiers(source).split(/\r?\n/).map((line) => stripComment(line).trimEnd());
|
|
235
258
|
this.name = name;
|
|
236
259
|
this.i = 0;
|
|
237
260
|
}
|
|
@@ -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,
|
|
@@ -518,7 +537,16 @@ class Parser {
|
|
|
518
537
|
`expected ${indent} spaces`,
|
|
519
538
|
);
|
|
520
539
|
const line = this.line();
|
|
521
|
-
const
|
|
540
|
+
const initialText = line.text.slice(indent);
|
|
541
|
+
const text = /^([A-Za-z_]\w*) = \{$/.test(initialText)
|
|
542
|
+
? initialText
|
|
543
|
+
: this.continuedStatement(initialText, line, indent);
|
|
544
|
+
if (text === "else")
|
|
545
|
+
this.fail(
|
|
546
|
+
line,
|
|
547
|
+
"LNG1259",
|
|
548
|
+
"else must be indented 2 spaces more than its if",
|
|
549
|
+
);
|
|
522
550
|
if (text.startsWith("assert ")) {
|
|
523
551
|
body.push({
|
|
524
552
|
type: "assert",
|
|
@@ -540,7 +568,16 @@ class Parser {
|
|
|
540
568
|
this.blank();
|
|
541
569
|
let elseBody = null;
|
|
542
570
|
if (this.peek() === `${" ".repeat(indent + 2)}else`) {
|
|
571
|
+
const elseLine = { text: this.peek(), number: this.i + 1 };
|
|
543
572
|
this.i += 1;
|
|
573
|
+
this.blank();
|
|
574
|
+
const elseStart = this.peek();
|
|
575
|
+
if (!elseStart || spaces(elseStart) !== indent + 6)
|
|
576
|
+
this.fail(
|
|
577
|
+
elseStart ? { text: elseStart, number: this.i + 1 } : elseLine,
|
|
578
|
+
"LNG1260",
|
|
579
|
+
"else body must be indented 4 spaces more than else",
|
|
580
|
+
);
|
|
544
581
|
elseBody = this.block(indent + 6);
|
|
545
582
|
}
|
|
546
583
|
body.push({
|
|
@@ -739,7 +776,80 @@ class Parser {
|
|
|
739
776
|
});
|
|
740
777
|
else if (/^[A-Za-z_]\w*\+\+$/.test(text))
|
|
741
778
|
body.push({ type: "increment", name: text.slice(0, -2) });
|
|
742
|
-
else if (/^(
|
|
779
|
+
else if (/^(\$?[A-Za-z_]\w*) = new ([A-Za-z_]\w*(?:[./][A-Za-z_]\w*)*)\(\(([^)]*)\)$/.test(text)) {
|
|
780
|
+
const [, name, constructor, parameters] =
|
|
781
|
+
/^(\$?[A-Za-z_]\w*) = new ([A-Za-z_]\w*(?:[./][A-Za-z_]\w*)*)\(\(([^)]*)\)$/.exec(text);
|
|
782
|
+
const closure = {
|
|
783
|
+
type: "closure",
|
|
784
|
+
parameters: this.parameters(parameters, line),
|
|
785
|
+
captures: this.captures(),
|
|
786
|
+
line: line.number,
|
|
787
|
+
body: this.block(indent + 4),
|
|
788
|
+
};
|
|
789
|
+
this.blank();
|
|
790
|
+
if (this.peek() !== `${" ".repeat(indent)})`)
|
|
791
|
+
this.fail(line, "LNG1263", "constructor callback requires a closing parenthesis");
|
|
792
|
+
this.i += 1;
|
|
793
|
+
body.push({
|
|
794
|
+
type: "assign",
|
|
795
|
+
name,
|
|
796
|
+
value: { type: "new", name: constructor, arguments: [closure], line: line.number },
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
else if (!text.includes(" = ") && /^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.test(text)) {
|
|
800
|
+
const [, receiver, name, argumentsText, parameters] =
|
|
801
|
+
/^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.exec(text);
|
|
802
|
+
const closure = {
|
|
803
|
+
type: "closure",
|
|
804
|
+
parameters: this.parameters(parameters, line),
|
|
805
|
+
captures: this.captures(),
|
|
806
|
+
line: line.number,
|
|
807
|
+
body: this.block(indent + 4),
|
|
808
|
+
};
|
|
809
|
+
this.blank();
|
|
810
|
+
if (this.peek() !== `${" ".repeat(indent)})`)
|
|
811
|
+
this.fail(line, "LNG1264", "method callback requires a closing parenthesis");
|
|
812
|
+
this.i += 1;
|
|
813
|
+
body.push({
|
|
814
|
+
type: "expression",
|
|
815
|
+
value: {
|
|
816
|
+
type: "methodCall",
|
|
817
|
+
receiver,
|
|
818
|
+
optional: false,
|
|
819
|
+
name,
|
|
820
|
+
arguments: [...splitTopLevel(argumentsText).map((item) => expr(item, line, this)), closure],
|
|
821
|
+
line: line.number,
|
|
822
|
+
},
|
|
823
|
+
});
|
|
824
|
+
}
|
|
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)) {
|
|
743
853
|
const [, first, name, parameters] = /^(.+)\.([A-Za-z_]\w*)\(\(([^)]*)\)$/.exec(text);
|
|
744
854
|
const steps = [];
|
|
745
855
|
let step = { name, parameters: this.parameters(parameters, line), captures: this.captures(), line: line.number, body: this.block(indent + 4) };
|
|
@@ -895,7 +1005,7 @@ class Parser {
|
|
|
895
1005
|
operator: suffix === "++" ? "+" : "-",
|
|
896
1006
|
});
|
|
897
1007
|
} else {
|
|
898
|
-
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);
|
|
899
1009
|
if (!assignment)
|
|
900
1010
|
this.fail(line, "LNG1106", `unsupported Faz 2 statement: ${text}`);
|
|
901
1011
|
body.push({
|
|
@@ -937,6 +1047,17 @@ class Parser {
|
|
|
937
1047
|
xml += `\n${this.lines[this.i++]}`;
|
|
938
1048
|
}
|
|
939
1049
|
}
|
|
1050
|
+
continuedStatement(source, line, indent) {
|
|
1051
|
+
let depth = curlyDelimiterDepth(source);
|
|
1052
|
+
while (depth > 0) {
|
|
1053
|
+
const next = this.peek();
|
|
1054
|
+
if (next === undefined || spaces(next) < indent)
|
|
1055
|
+
this.fail(line, "LNG1262", "unterminated multi-line expression");
|
|
1056
|
+
source += ` ${this.line().text.trim()}`;
|
|
1057
|
+
depth = curlyDelimiterDepth(source);
|
|
1058
|
+
}
|
|
1059
|
+
return source;
|
|
1060
|
+
}
|
|
940
1061
|
captures() {
|
|
941
1062
|
if (!(this.peek() ?? "").trimStart().startsWith("capture ")) return [];
|
|
942
1063
|
const line = this.line();
|
|
@@ -1072,6 +1193,66 @@ function topLevelBinary(text, operators, rightAssociative = false) {
|
|
|
1072
1193
|
return match;
|
|
1073
1194
|
}
|
|
1074
1195
|
|
|
1196
|
+
function curlyDelimiterDepth(text) {
|
|
1197
|
+
let quote = null;
|
|
1198
|
+
let depth = 0;
|
|
1199
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1200
|
+
const character = text[index];
|
|
1201
|
+
if (quote !== null) {
|
|
1202
|
+
if (character === "\\") { index += 1; continue; }
|
|
1203
|
+
if (character === quote) quote = null;
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
if (character === "'" || character === '"') { quote = character; continue; }
|
|
1207
|
+
if (character === "{") depth += 1;
|
|
1208
|
+
if (character === "}") depth -= 1;
|
|
1209
|
+
}
|
|
1210
|
+
return depth;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const KEBAB_MARKER = "__lango_dash__";
|
|
1214
|
+
|
|
1215
|
+
function normalizeKebabIdentifiers(source) {
|
|
1216
|
+
let result = "";
|
|
1217
|
+
let quote = null;
|
|
1218
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1219
|
+
const character = source[index];
|
|
1220
|
+
if (quote !== null) {
|
|
1221
|
+
result += character;
|
|
1222
|
+
if (character === "\\") result += source[++index] ?? "";
|
|
1223
|
+
else if (character === quote) quote = null;
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (character === "'" || character === '"') { quote = character; result += character; continue; }
|
|
1227
|
+
const match = /^[A-Za-z_]\w*(?:-[A-Za-z_]\w*)+/.exec(source.slice(index));
|
|
1228
|
+
if (match) {
|
|
1229
|
+
result += match[0].replaceAll("-", KEBAB_MARKER);
|
|
1230
|
+
index += match[0].length - 1;
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
result += character;
|
|
1234
|
+
}
|
|
1235
|
+
return result;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function kebabName(name) {
|
|
1239
|
+
return name.replaceAll(KEBAB_MARKER, "-");
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function emitKebabMember(receiver, operator, name) {
|
|
1243
|
+
return name.includes(KEBAB_MARKER)
|
|
1244
|
+
? `${receiver}${operator === "?." ? "?." : ""}[${JSON.stringify(kebabName(name))}]`
|
|
1245
|
+
: `${receiver}${operator}${name}`;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function emitKebabPath(value) {
|
|
1249
|
+
return value.replace(/(\?\.|\.)([A-Za-z_]\w*)/g, (all, operator, name) =>
|
|
1250
|
+
name.includes(KEBAB_MARKER)
|
|
1251
|
+
? emitKebabMember("", operator, name)
|
|
1252
|
+
: all,
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1075
1256
|
function splitTopLevel(text, separator = ", ") {
|
|
1076
1257
|
const parts = [];
|
|
1077
1258
|
let start = 0;
|
|
@@ -1153,10 +1334,18 @@ function expr(source, line, parser) {
|
|
|
1153
1334
|
}),
|
|
1154
1335
|
};
|
|
1155
1336
|
}
|
|
1337
|
+
if (text.startsWith("not "))
|
|
1338
|
+
return {
|
|
1339
|
+
type: "unary",
|
|
1340
|
+
operator: "not",
|
|
1341
|
+
value: expr(text.slice(4), line, parser),
|
|
1342
|
+
};
|
|
1156
1343
|
for (const operators of [
|
|
1157
1344
|
["??"],
|
|
1158
|
-
["or"
|
|
1159
|
-
["and"
|
|
1345
|
+
["or"],
|
|
1346
|
+
["and"],
|
|
1347
|
+
["||"],
|
|
1348
|
+
["&&"],
|
|
1160
1349
|
["==", "!="],
|
|
1161
1350
|
[">=", "<=", ">", "<", "not in", "in"],
|
|
1162
1351
|
["+", "-"],
|
|
@@ -1179,12 +1368,6 @@ function expr(source, line, parser) {
|
|
|
1179
1368
|
left: expr(text.slice(0, exponent.index), line, parser),
|
|
1180
1369
|
right: expr(text.slice(exponent.index + exponent.operator.length + 2), line, parser),
|
|
1181
1370
|
};
|
|
1182
|
-
if (text.startsWith("not "))
|
|
1183
|
-
return {
|
|
1184
|
-
type: "unary",
|
|
1185
|
-
operator: "not",
|
|
1186
|
-
value: expr(text.slice(4), line, parser),
|
|
1187
|
-
};
|
|
1188
1371
|
if (/^-[^\s]/.test(text))
|
|
1189
1372
|
return {
|
|
1190
1373
|
type: "unary",
|
|
@@ -1200,6 +1383,13 @@ function expr(source, line, parser) {
|
|
|
1200
1383
|
const localize = /^_\((.*)\)$/.exec(text);
|
|
1201
1384
|
if (localize)
|
|
1202
1385
|
return { type: "translate", literal: literal(localize[1], line, parser) };
|
|
1386
|
+
const decimal = /^\$\((.*)\)$/.exec(text);
|
|
1387
|
+
if (decimal) {
|
|
1388
|
+
const arguments_ = decimal[1] === "" ? [] : splitTopLevel(decimal[1]);
|
|
1389
|
+
if (arguments_.length !== 1)
|
|
1390
|
+
parser.fail(line, "LNG1261", "$() requires exactly one value");
|
|
1391
|
+
return { type: "decimal", value: expr(arguments_[0], line, parser) };
|
|
1392
|
+
}
|
|
1203
1393
|
const construct =
|
|
1204
1394
|
/^new ([A-Za-z_]\w*(?:[./][A-Za-z_]\w*)*)\((.*)\)$/.exec(text);
|
|
1205
1395
|
if (construct)
|
|
@@ -1313,6 +1503,17 @@ function expr(source, line, parser) {
|
|
|
1313
1503
|
: [],
|
|
1314
1504
|
line: line.number,
|
|
1315
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
|
+
};
|
|
1316
1517
|
const methodCall =
|
|
1317
1518
|
/^([A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w*)*?)(\?\.|\.)([A-Za-z_]\w*)\((.*)\)$/.exec(
|
|
1318
1519
|
text,
|
|
@@ -1618,12 +1819,15 @@ function validate(program, imports = []) {
|
|
|
1618
1819
|
["is_array", 1],
|
|
1619
1820
|
["range", [1, 2, 3]],
|
|
1620
1821
|
["to_js_number", 1],
|
|
1822
|
+
["import_module", 1],
|
|
1823
|
+
["xml2obj", 1],
|
|
1621
1824
|
["start_trans", 0], ["commit_trans", 0], ["rollback_trans", 0], ["trans_state", 0],
|
|
1622
1825
|
]);
|
|
1623
1826
|
const hostFunctions = new Set(["fetch"]);
|
|
1624
1827
|
const isDecimalValue = (value) =>
|
|
1625
1828
|
value &&
|
|
1626
|
-
(
|
|
1829
|
+
(value.type === "decimal" ||
|
|
1830
|
+
(value.type === "raw" && (value.value.startsWith("$") || value.value.includes(".$"))) ||
|
|
1627
1831
|
(value.type === "unary" &&
|
|
1628
1832
|
value.operator === "negate" &&
|
|
1629
1833
|
isDecimalValue(value.value)) ||
|
|
@@ -1635,6 +1839,21 @@ function validate(program, imports = []) {
|
|
|
1635
1839
|
((value.left.type === "raw" && value.left.value === "null" && isDecimalValue(value.right)) ||
|
|
1636
1840
|
(value.right.type === "raw" && value.right.value === "null" && isDecimalValue(value.left)) ||
|
|
1637
1841
|
(isDecimalValue(value.left) && isDecimalValue(value.right)))));
|
|
1842
|
+
const decimalParameters = (fn) => {
|
|
1843
|
+
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
|
1844
|
+
const accepted = new Set();
|
|
1845
|
+
const visit = (node) => {
|
|
1846
|
+
if (!node || typeof node !== "object") return;
|
|
1847
|
+
if (node.type === "decimal" && node.value?.type === "raw" && parameterNames.has(node.value.value))
|
|
1848
|
+
accepted.add(node.value.value);
|
|
1849
|
+
Object.values(node).forEach((child) => {
|
|
1850
|
+
if (Array.isArray(child)) child.forEach(visit);
|
|
1851
|
+
else visit(child);
|
|
1852
|
+
});
|
|
1853
|
+
};
|
|
1854
|
+
visit(fn.body);
|
|
1855
|
+
return accepted;
|
|
1856
|
+
};
|
|
1638
1857
|
const returns = (body, result = []) => {
|
|
1639
1858
|
for (const node of body) {
|
|
1640
1859
|
if (node.type === "return") result.push(node.value);
|
|
@@ -2057,11 +2276,13 @@ function validate(program, imports = []) {
|
|
|
2057
2276
|
);
|
|
2058
2277
|
if (target) {
|
|
2059
2278
|
validateRequiredArguments(target, value, value.name);
|
|
2279
|
+
const decimalArguments = decimalParameters(target);
|
|
2060
2280
|
value.arguments.forEach((argument, index) => {
|
|
2061
2281
|
const parameter = target.parameters[index];
|
|
2062
2282
|
if (
|
|
2063
2283
|
parameter &&
|
|
2064
2284
|
!parameter.name.startsWith("$") &&
|
|
2285
|
+
!decimalArguments.has(parameter.name) &&
|
|
2065
2286
|
isDecimalValue(argument)
|
|
2066
2287
|
)
|
|
2067
2288
|
throw new LangoError(
|
|
@@ -2348,6 +2569,15 @@ function validate(program, imports = []) {
|
|
|
2348
2569
|
return names;
|
|
2349
2570
|
};
|
|
2350
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
|
+
|
|
2351
2581
|
const globalDeclarations = (body, names = new Set()) => {
|
|
2352
2582
|
for (const node of body) {
|
|
2353
2583
|
if (node.type === "globalDeclare") names.add(node.name);
|
|
@@ -2403,9 +2633,14 @@ function validate(program, imports = []) {
|
|
|
2403
2633
|
outerBindings,
|
|
2404
2634
|
classScope = null,
|
|
2405
2635
|
inheritedExpiredCatchBindings = new Set(),
|
|
2636
|
+
outerContextBindings = new Set(),
|
|
2406
2637
|
) => {
|
|
2407
2638
|
const available = new Set(outerBindings);
|
|
2408
2639
|
const expiredCatchBindings = new Set(inheritedExpiredCatchBindings);
|
|
2640
|
+
const availableContexts = new Set([
|
|
2641
|
+
...outerContextBindings,
|
|
2642
|
+
...contextBindings(body),
|
|
2643
|
+
]);
|
|
2409
2644
|
for (const node of body) {
|
|
2410
2645
|
const references = new Set();
|
|
2411
2646
|
[node.value, node.condition, node.collection, node.arguments, node.tests].forEach((value) => {
|
|
@@ -2425,6 +2660,7 @@ function validate(program, imports = []) {
|
|
|
2425
2660
|
new Set(available),
|
|
2426
2661
|
classScope,
|
|
2427
2662
|
expiredCatchBindings,
|
|
2663
|
+
availableContexts,
|
|
2428
2664
|
);
|
|
2429
2665
|
const right = node.elseBody
|
|
2430
2666
|
? validateClosures(
|
|
@@ -2432,6 +2668,7 @@ function validate(program, imports = []) {
|
|
|
2432
2668
|
new Set(available),
|
|
2433
2669
|
classScope,
|
|
2434
2670
|
expiredCatchBindings,
|
|
2671
|
+
availableContexts,
|
|
2435
2672
|
)
|
|
2436
2673
|
: available;
|
|
2437
2674
|
const exits = [];
|
|
@@ -2450,6 +2687,7 @@ function validate(program, imports = []) {
|
|
|
2450
2687
|
new Set(available),
|
|
2451
2688
|
classScope,
|
|
2452
2689
|
expiredCatchBindings,
|
|
2690
|
+
availableContexts,
|
|
2453
2691
|
),
|
|
2454
2692
|
);
|
|
2455
2693
|
if (!node.elseBody || reachesEnd(node.elseBody))
|
|
@@ -2460,6 +2698,7 @@ function validate(program, imports = []) {
|
|
|
2460
2698
|
new Set(available),
|
|
2461
2699
|
classScope,
|
|
2462
2700
|
expiredCatchBindings,
|
|
2701
|
+
availableContexts,
|
|
2463
2702
|
)
|
|
2464
2703
|
: new Set(available),
|
|
2465
2704
|
);
|
|
@@ -2475,6 +2714,7 @@ function validate(program, imports = []) {
|
|
|
2475
2714
|
new Set(available),
|
|
2476
2715
|
classScope,
|
|
2477
2716
|
expiredCatchBindings,
|
|
2717
|
+
availableContexts,
|
|
2478
2718
|
);
|
|
2479
2719
|
if (reachesEnd(node.tryBody)) exits.push(tryExit);
|
|
2480
2720
|
if (node.catchBody) {
|
|
@@ -2485,6 +2725,7 @@ function validate(program, imports = []) {
|
|
|
2485
2725
|
new Set(
|
|
2486
2726
|
[...expiredCatchBindings].filter((name) => name !== node.catchName),
|
|
2487
2727
|
),
|
|
2728
|
+
availableContexts,
|
|
2488
2729
|
);
|
|
2489
2730
|
if (reachesEnd(node.catchBody)) exits.push(catchExit);
|
|
2490
2731
|
}
|
|
@@ -2498,6 +2739,7 @@ function validate(program, imports = []) {
|
|
|
2498
2739
|
certain,
|
|
2499
2740
|
classScope,
|
|
2500
2741
|
new Set([...expiredCatchBindings, node.catchName].filter(Boolean)),
|
|
2742
|
+
availableContexts,
|
|
2501
2743
|
)
|
|
2502
2744
|
: certain;
|
|
2503
2745
|
if (!node.finallyBody || reachesEnd(node.finallyBody))
|
|
@@ -2511,6 +2753,7 @@ function validate(program, imports = []) {
|
|
|
2511
2753
|
new Set([...available, node.item]),
|
|
2512
2754
|
classScope,
|
|
2513
2755
|
expiredCatchBindings,
|
|
2756
|
+
availableContexts,
|
|
2514
2757
|
);
|
|
2515
2758
|
continue;
|
|
2516
2759
|
}
|
|
@@ -2520,6 +2763,7 @@ function validate(program, imports = []) {
|
|
|
2520
2763
|
new Set([...available, node.key, node.value]),
|
|
2521
2764
|
classScope,
|
|
2522
2765
|
expiredCatchBindings,
|
|
2766
|
+
availableContexts,
|
|
2523
2767
|
);
|
|
2524
2768
|
continue;
|
|
2525
2769
|
}
|
|
@@ -2535,6 +2779,7 @@ function validate(program, imports = []) {
|
|
|
2535
2779
|
new Set(available),
|
|
2536
2780
|
classScope,
|
|
2537
2781
|
expiredCatchBindings,
|
|
2782
|
+
availableContexts,
|
|
2538
2783
|
);
|
|
2539
2784
|
continue;
|
|
2540
2785
|
}
|
|
@@ -2543,6 +2788,10 @@ function validate(program, imports = []) {
|
|
|
2543
2788
|
? node
|
|
2544
2789
|
: node.type === "return" && node.value?.type === "closure"
|
|
2545
2790
|
? node.value
|
|
2791
|
+
: node.type === "assign" && node.value?.type === "new" && node.value.arguments.length === 1 && node.value.arguments[0].type === "closure"
|
|
2792
|
+
? node.value.arguments[0]
|
|
2793
|
+
: node.type === "expression" && node.value?.type === "methodCall" && node.value.arguments.at(-1)?.type === "closure"
|
|
2794
|
+
? node.value.arguments.at(-1)
|
|
2546
2795
|
: null;
|
|
2547
2796
|
if (closure) {
|
|
2548
2797
|
const localBindings = new Set([
|
|
@@ -2562,9 +2811,14 @@ function validate(program, imports = []) {
|
|
|
2562
2811
|
(name) =>
|
|
2563
2812
|
!localBindings.has(name) &&
|
|
2564
2813
|
!contextNames.has(name) &&
|
|
2565
|
-
!globalNames.has(name)
|
|
2814
|
+
!globalNames.has(name) &&
|
|
2815
|
+
!standardFunctions.has(name) &&
|
|
2816
|
+
!hostFunctions.has(name),
|
|
2566
2817
|
);
|
|
2567
2818
|
const captures = new Set(closure.captures);
|
|
2819
|
+
const contextCaptures = new Set(
|
|
2820
|
+
closure.captures.filter((name) => availableContexts.has(name)),
|
|
2821
|
+
);
|
|
2568
2822
|
for (const name of needed)
|
|
2569
2823
|
if (!captures.has(name))
|
|
2570
2824
|
throw new LangoError(
|
|
@@ -2573,7 +2827,7 @@ function validate(program, imports = []) {
|
|
|
2573
2827
|
`MissingCapture: ${name}; add capture ${name}`,
|
|
2574
2828
|
);
|
|
2575
2829
|
for (const name of captures) {
|
|
2576
|
-
if (!needed.includes(name))
|
|
2830
|
+
if (!needed.includes(name) && !contextCaptures.has(name))
|
|
2577
2831
|
throw new LangoError(
|
|
2578
2832
|
"LNG1211",
|
|
2579
2833
|
closure.line,
|
|
@@ -2581,7 +2835,7 @@ function validate(program, imports = []) {
|
|
|
2581
2835
|
);
|
|
2582
2836
|
const protectedMember = Boolean(classScope && name.startsWith("_"));
|
|
2583
2837
|
const propertyCapture = name.startsWith("@") || protectedMember;
|
|
2584
|
-
if (!propertyCapture && !available.has(name))
|
|
2838
|
+
if (!propertyCapture && !contextCaptures.has(name) && !available.has(name))
|
|
2585
2839
|
throw new LangoError(
|
|
2586
2840
|
"LNG1212",
|
|
2587
2841
|
closure.line,
|
|
@@ -2614,8 +2868,9 @@ function validate(program, imports = []) {
|
|
|
2614
2868
|
new Set([...available, ...localBindings]),
|
|
2615
2869
|
classScope,
|
|
2616
2870
|
expiredCatchBindings,
|
|
2871
|
+
new Set([...availableContexts, ...contextCaptures]),
|
|
2617
2872
|
);
|
|
2618
|
-
if (node.type === "closureAssign") available.add(node.name);
|
|
2873
|
+
if (node.type === "closureAssign" || node.type === "assign") available.add(node.name);
|
|
2619
2874
|
continue;
|
|
2620
2875
|
}
|
|
2621
2876
|
if (node.type === "assign" && !node.name.startsWith("@"))
|
|
@@ -3163,6 +3418,7 @@ function emit(program, moduleImports = [], includeTests = false) {
|
|
|
3163
3418
|
flags.print && "println as __println",
|
|
3164
3419
|
flags.translate && "translate as __translate",
|
|
3165
3420
|
flags.xml && "xmlEscape as __xmlEscape",
|
|
3421
|
+
flags.xmlObject && "xml2obj as __xml2obj",
|
|
3166
3422
|
flags.context && "contextGet as __contextGet",
|
|
3167
3423
|
flags.context && "contextSet as __contextSet",
|
|
3168
3424
|
flags.context && "contextRestore as __contextRestore",
|
|
@@ -3192,6 +3448,7 @@ function emit(program, moduleImports = [], includeTests = false) {
|
|
|
3192
3448
|
flags.finite && "finite as __finite",
|
|
3193
3449
|
flags.number && "requireNumber as __requireNumber",
|
|
3194
3450
|
flags.call && "call as __call",
|
|
3451
|
+
flags.callMethod && "callMethod as __callMethod",
|
|
3195
3452
|
flags.boundary && "boundary as __boundary",
|
|
3196
3453
|
flags.database && "startTrans as __startTrans",
|
|
3197
3454
|
flags.database && "commitTrans as __commitTrans",
|
|
@@ -3442,6 +3699,7 @@ function collectEmitLocals(body, names = new Set(), nested = false) {
|
|
|
3442
3699
|
}
|
|
3443
3700
|
function emitDecimalExpression(node, flags) {
|
|
3444
3701
|
const operand = (value) => {
|
|
3702
|
+
if (value.type === "decimal") return emitExpr(value, flags);
|
|
3445
3703
|
if (value.type === "binary" && ["+", "-", "*", "/", "%", "**"].includes(value.operator))
|
|
3446
3704
|
return emitDecimalExpression(value, flags);
|
|
3447
3705
|
if (value.type === "raw" && /^\d+(?:\.\d+)?$/.test(value.value)) {
|
|
@@ -3497,7 +3755,7 @@ function emitStatement(node, depth, declared, flags) {
|
|
|
3497
3755
|
if (node.type === "assign") {
|
|
3498
3756
|
const decimalBinding = node.name.startsWith("$") || /^@{1,2}\$/.test(node.name);
|
|
3499
3757
|
const property = node.name.slice(node.name.startsWith("@@") ? 2 : 1);
|
|
3500
|
-
const target = /^[A-Za-z_]\w
|
|
3758
|
+
const target = /^[A-Za-z_]\w*(?:\.\$?[A-Za-z_]\w*)+$/.test(node.name)
|
|
3501
3759
|
? node.name
|
|
3502
3760
|
: node.name.startsWith("@@")
|
|
3503
3761
|
? `${flags.className}.${property}`
|
|
@@ -3509,7 +3767,7 @@ function emitStatement(node, depth, declared, flags) {
|
|
|
3509
3767
|
? `this.${node.name}`
|
|
3510
3768
|
: node.name;
|
|
3511
3769
|
const prefix =
|
|
3512
|
-
/^[A-Za-z_]\w
|
|
3770
|
+
/^[A-Za-z_]\w*(?:\.\$?[A-Za-z_]\w*)+$/.test(node.name)
|
|
3513
3771
|
? ""
|
|
3514
3772
|
: node.name.startsWith("@") ||
|
|
3515
3773
|
(flags.className && node.name.startsWith("_")) ||
|
|
@@ -3643,7 +3901,11 @@ function emitClosure(node, depth, flags) {
|
|
|
3643
3901
|
const priorContextNames = flags.contextNames;
|
|
3644
3902
|
const priorGlobalNames = flags.globalNames;
|
|
3645
3903
|
const priorArrayBindings = flags.arrayBindings;
|
|
3646
|
-
const
|
|
3904
|
+
const contextCaptures = node.captures.filter((name) => priorContextNames?.has(name));
|
|
3905
|
+
const contextNames = new Set([
|
|
3906
|
+
...collectContextNames(node.body),
|
|
3907
|
+
...contextCaptures,
|
|
3908
|
+
]);
|
|
3647
3909
|
flags.contextNames = contextNames;
|
|
3648
3910
|
flags.globalNames = collectGlobalNames(node.body);
|
|
3649
3911
|
flags.arrayBindings = new Set();
|
|
@@ -3671,7 +3933,16 @@ function emitClosure(node, depth, flags) {
|
|
|
3671
3933
|
if (!contextNames.size)
|
|
3672
3934
|
return wrap(`(${parameters}) => {\n${boundary}${body}${pad} return null;\n${pad}}`);
|
|
3673
3935
|
flags.context = true;
|
|
3674
|
-
|
|
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);
|
|
3675
3946
|
}
|
|
3676
3947
|
function emitArityGuard(parameters, actual, name, depth, flags) {
|
|
3677
3948
|
const positions = parameters
|
|
@@ -3695,11 +3966,18 @@ function emitBoundaryChecks(parameters, depth, flags) {
|
|
|
3695
3966
|
})
|
|
3696
3967
|
.join("");
|
|
3697
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
|
+
|
|
3698
3975
|
function emitExpr(node, flags) {
|
|
3699
3976
|
const decimalRaw = (value) =>
|
|
3700
3977
|
value?.type === "raw" &&
|
|
3701
3978
|
(value.value.startsWith("$") || value.value.includes(".$"));
|
|
3702
3979
|
const decimalValue = (value) =>
|
|
3980
|
+
value?.type === "decimal" ||
|
|
3703
3981
|
decimalRaw(value) ||
|
|
3704
3982
|
(value?.type === "binary" && (decimalValue(value.left) || decimalValue(value.right))) ||
|
|
3705
3983
|
(value?.type === "unary" && decimalValue(value.value));
|
|
@@ -3731,9 +4009,13 @@ function emitExpr(node, flags) {
|
|
|
3731
4009
|
: normalize(`this.#${node.value.slice(1)}`)
|
|
3732
4010
|
: flags.className && node.value.startsWith("_")
|
|
3733
4011
|
? normalize(`this.${node.value}`)
|
|
3734
|
-
: (node.value.includes("?.") || /^(?:[A-Za-z_]\w*\.[A-Za-z_]\w*|[A-Za-z_]\w*\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\])$/.test(node.value))
|
|
3735
|
-
? normalize(node.value)
|
|
3736
|
-
: node.value;
|
|
4012
|
+
: (node.value.includes("?.") || node.value.includes(KEBAB_MARKER) || /^(?:[A-Za-z_]\w*\.[A-Za-z_]\w*|[A-Za-z_]\w*\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\])$/.test(node.value))
|
|
4013
|
+
? normalize(emitKebabPath(node.value))
|
|
4014
|
+
: emitKebabPath(node.value);
|
|
4015
|
+
if (node.type === "decimal") {
|
|
4016
|
+
flags.decimal = true;
|
|
4017
|
+
return `__decimal(${emitExpr(node.value, flags)})`;
|
|
4018
|
+
}
|
|
3737
4019
|
if (node.type === "sql") {
|
|
3738
4020
|
flags.sql = true;
|
|
3739
4021
|
const text = node.parts
|
|
@@ -3758,23 +4040,25 @@ function emitExpr(node, flags) {
|
|
|
3758
4040
|
return `__finite(-${numericLiteral ? emitExpr(node.value, flags) : `__requireNumber(${emitExpr(node.value, flags)})`})`;
|
|
3759
4041
|
}
|
|
3760
4042
|
if (node.type === "new")
|
|
3761
|
-
return `new ${node.name.replaceAll("/", ".")}(${node.arguments.map((item) =>
|
|
4043
|
+
return `new ${node.name.replaceAll("/", ".")}(${node.arguments.map((item) =>
|
|
4044
|
+
item.type === "closure" ? emitClosure(item, 0, flags) : emitExpr(item, flags),
|
|
4045
|
+
).join(", ")})`;
|
|
3762
4046
|
if (node.type === "superMethodCall")
|
|
3763
4047
|
return `super.${node.name}(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
3764
4048
|
if (node.type === "index")
|
|
3765
4049
|
return normalize(
|
|
3766
|
-
`${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)}]`,
|
|
3767
4051
|
);
|
|
3768
4052
|
if (node.type === "property")
|
|
3769
4053
|
return normalize(
|
|
3770
|
-
|
|
4054
|
+
emitKebabMember(emitExpr(node.receiver, flags), node.optional ? "?." : ".", node.name),
|
|
3771
4055
|
);
|
|
3772
4056
|
if (node.type === "methodCall" || node.type === "namespaceMethodCall") {
|
|
3773
4057
|
const namespace = node.type === "namespaceMethodCall";
|
|
3774
4058
|
const sourceReceiver = namespace
|
|
3775
4059
|
? node.receiver.replaceAll("/", ".")
|
|
3776
4060
|
: typeof node.receiver === "string"
|
|
3777
|
-
? node.receiver
|
|
4061
|
+
? emitReceiverName(node.receiver, flags)
|
|
3778
4062
|
: emitExpr(node.receiver, flags);
|
|
3779
4063
|
const type =
|
|
3780
4064
|
sourceReceiver === flags.className ||
|
|
@@ -3793,7 +4077,9 @@ function emitExpr(node, flags) {
|
|
|
3793
4077
|
flags.arrayBindings?.has(node.receiver)
|
|
3794
4078
|
? "push"
|
|
3795
4079
|
: node.name;
|
|
3796
|
-
const arguments_ = node.arguments.map((item) =>
|
|
4080
|
+
const arguments_ = node.arguments.map((item) =>
|
|
4081
|
+
item.type === "closure" ? emitClosure(item, 0, flags) : emitExpr(item, flags),
|
|
4082
|
+
).join(", ");
|
|
3797
4083
|
const helperCall = (helper) => {
|
|
3798
4084
|
const call = `${helper}(${receiver}${arguments_ ? `, ${arguments_}` : ""})`;
|
|
3799
4085
|
if (!node.optional) return call;
|
|
@@ -3824,7 +4110,9 @@ function emitExpr(node, flags) {
|
|
|
3824
4110
|
flags.insertAt = true;
|
|
3825
4111
|
return helperCall("__insertAt");
|
|
3826
4112
|
}
|
|
3827
|
-
const call =
|
|
4113
|
+
const call = visibility === false
|
|
4114
|
+
? `${receiver}.#${method}(${arguments_})`
|
|
4115
|
+
: emitKebabMember(receiver, node.optional ? "?." : ".", method) + `(${arguments_})`;
|
|
3828
4116
|
if (node.optional) return normalize(call);
|
|
3829
4117
|
if (
|
|
3830
4118
|
visibility === undefined &&
|
|
@@ -3840,7 +4128,8 @@ function emitExpr(node, flags) {
|
|
|
3840
4128
|
if (node.type === "object")
|
|
3841
4129
|
return `{ ${node.fields.map((item) => {
|
|
3842
4130
|
const value = emitExpr(item.value, flags);
|
|
3843
|
-
if (!item.key.startsWith("$"))
|
|
4131
|
+
if (!item.key.startsWith("$"))
|
|
4132
|
+
return `${item.key.includes(KEBAB_MARKER) ? JSON.stringify(kebabName(item.key)) : item.key}: ${value}`;
|
|
3844
4133
|
flags.decimal = true;
|
|
3845
4134
|
return `${JSON.stringify(item.key)}: __decimal(${item.value.type === "raw" ? JSON.stringify(item.value.value) : value})`;
|
|
3846
4135
|
}).join(", ")} }`;
|
|
@@ -3912,6 +4201,14 @@ function emitExpr(node, flags) {
|
|
|
3912
4201
|
flags.toJsNumber = true;
|
|
3913
4202
|
return `__toJsNumber(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
3914
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
|
+
}
|
|
3915
4212
|
if (node.type === "call")
|
|
3916
4213
|
if (node.name === "from_json" || node.name === "to_json") {
|
|
3917
4214
|
flags.json = true;
|
|
@@ -3951,6 +4248,10 @@ function emitExpr(node, flags) {
|
|
|
3951
4248
|
return `__call(${node.name}, ${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
3952
4249
|
} else
|
|
3953
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
|
+
}
|
|
3954
4255
|
if (node.type === "string") return emitString(node.literal, flags);
|
|
3955
4256
|
if (node.type === "xml") {
|
|
3956
4257
|
flags.xml = true;
|