@lango-project/lango-js 0.1.7 → 0.1.10
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/README.md +57 -3
- package/package.json +1 -1
- package/src/cli.mjs +1 -1
- package/src/compiler.mjs +227 -40
- package/src/runtime.mjs +44 -10
package/README.md
CHANGED
|
@@ -36,6 +36,24 @@ private `.lango/runtime.mjs` beside the output. Run the current checks with:
|
|
|
36
36
|
npm test
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
Relative Lango imports are compiled under the entry output directory. The build
|
|
40
|
+
preserves their paths relative to the common source directory, and emitted
|
|
41
|
+
imports use the relative path to each actual output. Parent-directory imports
|
|
42
|
+
therefore stay inside the output tree. A shared dependency is emitted once;
|
|
43
|
+
cycles refer back to the configured entry filename. If that filename collides
|
|
44
|
+
with a dependency, dependencies are placed under `.lango/modules/`. Each output
|
|
45
|
+
folder receives its private runtime. Changing the source import graph may change
|
|
46
|
+
its common root and dependency paths; rebuild the complete entry graph rather
|
|
47
|
+
than relying on generated dependency filenames.
|
|
48
|
+
|
|
49
|
+
For a Node command-line entry, use `lango build input.lango --out cli.mjs
|
|
50
|
+
--executable`. It requires `export main(args)`, configures console output and
|
|
51
|
+
invokes main with the command-line arguments, waiting for a returned Promise.
|
|
52
|
+
The generated entry starts with `#!/usr/bin/env node`, so it can be an npm
|
|
53
|
+
`bin` target. npm sets its executable permission during installation; when
|
|
54
|
+
running the file directly outside npm, use `node cli.mjs` or set that permission
|
|
55
|
+
yourself. Ordinary library builds do not invoke main.
|
|
56
|
+
|
|
39
57
|
Create a new application project with:
|
|
40
58
|
|
|
41
59
|
```sh
|
|
@@ -232,8 +250,8 @@ Faz 30 permits a function to return a closure directly. See
|
|
|
232
250
|
|
|
233
251
|
## Faz 31
|
|
234
252
|
|
|
235
|
-
Faz 31
|
|
236
|
-
|
|
253
|
+
Faz 31 retains the original class instance for property access in a returned
|
|
254
|
+
closure. Member capture is now automatic (D-063). See `examples/031-property-capture`.
|
|
237
255
|
|
|
238
256
|
## Faz 32
|
|
239
257
|
|
|
@@ -252,7 +270,7 @@ capture declarations. See `examples/034-static-capture`.
|
|
|
252
270
|
|
|
253
271
|
## Faz 35
|
|
254
272
|
|
|
255
|
-
Faz 35
|
|
273
|
+
Faz 35 retains protected properties in closures; capture is now automatic. See
|
|
256
274
|
`examples/035-protected-capture`.
|
|
257
275
|
|
|
258
276
|
## Faz 36
|
|
@@ -837,6 +855,11 @@ Faz 174 adds `Array#remove_all(value)` for removing every matching value. See
|
|
|
837
855
|
Faz 175 preserves null-safe optional access for array mutation helpers. See
|
|
838
856
|
`examples/175-optional-array-mutations`.
|
|
839
857
|
|
|
858
|
+
On non-array receivers, these helpers delegate to the receiver's own method,
|
|
859
|
+
preserving every supplied argument and the method receiver. This includes
|
|
860
|
+
`remove`, `remove_all`, `clear`, `remove_at` and `insert_at`; an undefined method
|
|
861
|
+
result becomes null. Array mutation semantics remain as described above.
|
|
862
|
+
|
|
840
863
|
## Faz 176
|
|
841
864
|
|
|
842
865
|
Faz 176 requires `to_js_number()` before a Decimal enters a direct host method
|
|
@@ -901,3 +924,34 @@ are native finite numbers.
|
|
|
901
924
|
## Faz 209
|
|
902
925
|
|
|
903
926
|
Faz 209 rejects dynamic non-numeric range bounds.
|
|
927
|
+
|
|
928
|
+
## Implicit method receivers
|
|
929
|
+
|
|
930
|
+
Inside a class method, call another method as `prepare()`; instance and static
|
|
931
|
+
methods resolve within their respective scopes. Use the readonly `@this` only
|
|
932
|
+
when the receiver itself is needed. Callbacks automatically retain the receiver
|
|
933
|
+
for method calls and member access; outer locals and captured context bindings
|
|
934
|
+
still require `capture`. The former `this` and `this.method()` spellings produce migration
|
|
935
|
+
diagnostics. See [the class guide](design/guide.md#public),
|
|
936
|
+
[D-063](design/decisions.md#d-063-method-calls-have-an-implicit-receiver), and
|
|
937
|
+
`examples/261-implicit-receiver` and `examples/262-automatic-receiver` for
|
|
938
|
+
executable examples.
|
|
939
|
+
|
|
940
|
+
## Inheritance across modules
|
|
941
|
+
|
|
942
|
+
A Lango class can extend an imported Lango class, including an aliased import.
|
|
943
|
+
Builds resolve inherited public members, constructor defaults and `super` method
|
|
944
|
+
signatures across module boundaries. Bare inherited method calls and `@property`
|
|
945
|
+
access use the inherited receiver; public fields are not redeclared in the
|
|
946
|
+
subclass. An omitted subclass constructor forwards its arguments. Private method
|
|
947
|
+
access/overrides remain rejected. This supports browser control subclasses in
|
|
948
|
+
separate Lango modules without JavaScript wrappers.
|
|
949
|
+
|
|
950
|
+
Method visibility and implicit-receiver lookup apply to the current Lango
|
|
951
|
+
receiver. Calls to a JavaScript namespace or another object keep their own method
|
|
952
|
+
and boundary normalization, even when the current class declares a private or
|
|
953
|
+
public method with the same name.
|
|
954
|
+
|
|
955
|
+
The CLI test runner waits for a Promise returned by a Lango test before
|
|
956
|
+
reporting its result and resetting state for the next test. Async rejections
|
|
957
|
+
are reported as test failures and remaining tests continue.
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -154,7 +154,7 @@ if (command === "init" && inputPath && rest.length === 0) {
|
|
|
154
154
|
try {
|
|
155
155
|
program.__langoResetTests?.();
|
|
156
156
|
runtime.configure({ output: (value) => console.log(value), catalog, database_adapter: database });
|
|
157
|
-
test.run();
|
|
157
|
+
await test.run();
|
|
158
158
|
console.log(`PASS ${test.name}`);
|
|
159
159
|
} catch (error) {
|
|
160
160
|
failures += 1;
|
package/src/compiler.mjs
CHANGED
|
@@ -30,20 +30,24 @@ export function formatDiagnostics(error) {
|
|
|
30
30
|
}).join("\n");
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export async function buildFile(inputPath, outputPath, seen = new Set(), includeTests = false, profile = {}) {
|
|
33
|
+
export async function buildFile(inputPath, outputPath, seen = new Set(), includeTests = false, profile = {}, layout = null) {
|
|
34
|
+
inputPath = path.resolve(inputPath);
|
|
35
|
+
outputPath = path.resolve(outputPath);
|
|
34
36
|
const decimalProfile = validateDecimalProfile(profile);
|
|
35
37
|
if (decimalProfile.target === "browser" && profile.executable)
|
|
36
38
|
throw new LangoError("LNG1266", 1, "browser builds cannot use --executable", inputPath);
|
|
37
39
|
const isEntryPoint = seen.size === 0;
|
|
38
|
-
if (
|
|
39
|
-
await validateModuleInitializerCycles(inputPath, outputPath);
|
|
40
|
+
if (layout === null) {
|
|
41
|
+
const modules = await validateModuleInitializerCycles(inputPath, outputPath);
|
|
42
|
+
layout = moduleOutputLayout(inputPath, outputPath, modules);
|
|
43
|
+
}
|
|
40
44
|
if (seen.has(inputPath)) return;
|
|
41
45
|
seen.add(inputPath);
|
|
42
46
|
|
|
43
47
|
const source = await readFile(inputPath, "utf8");
|
|
44
|
-
const imports = collectImports(source, inputPath, outputPath);
|
|
48
|
+
const imports = collectImports(source, inputPath, outputPath, layout);
|
|
45
49
|
for (const item of imports)
|
|
46
|
-
await buildFile(item.sourcePath, item.outputPath, seen, includeTests, decimalProfile);
|
|
50
|
+
await buildFile(item.sourcePath, item.outputPath, seen, includeTests, decimalProfile, layout);
|
|
47
51
|
for (const item of imports) {
|
|
48
52
|
const importedProgram = new Parser(
|
|
49
53
|
await readFile(item.sourcePath, "utf8"),
|
|
@@ -77,14 +81,16 @@ export async function buildFile(inputPath, outputPath, seen = new Set(), include
|
|
|
77
81
|
1,
|
|
78
82
|
`${inputPath}: import signature for ${selection.sourceName} does not match ${item.sourcePath}`,
|
|
79
83
|
);
|
|
80
|
-
if (selection.kind === "class")
|
|
84
|
+
if (selection.kind === "class") {
|
|
85
|
+
selection.classMetadata = await importedClassMetadata(item.sourcePath, selection.sourceName);
|
|
81
86
|
validateImportedClassInterface(
|
|
82
87
|
selection,
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
selection.classMetadata,
|
|
89
|
+
{ classes: [selection.classMetadata] },
|
|
85
90
|
inputPath,
|
|
86
91
|
item.sourcePath,
|
|
87
92
|
);
|
|
93
|
+
}
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
|
|
@@ -103,6 +109,7 @@ export async function buildFile(inputPath, outputPath, seen = new Set(), include
|
|
|
103
109
|
`//# sourceMappingURL=${path.basename(outputPath)}.map`,
|
|
104
110
|
`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`,
|
|
105
111
|
);
|
|
112
|
+
output = `#!/usr/bin/env node\n${output}`;
|
|
106
113
|
}
|
|
107
114
|
const directory = path.dirname(outputPath);
|
|
108
115
|
|
|
@@ -283,6 +290,17 @@ class Parser {
|
|
|
283
290
|
this.blank();
|
|
284
291
|
continue;
|
|
285
292
|
}
|
|
293
|
+
const hostImport = /^import js global ([A-Za-z_]\w*(?:\/[A-Za-z_]\w*)*)(?: as ([A-Za-z_]\w*))?$/.exec(line.text);
|
|
294
|
+
if (hostImport) {
|
|
295
|
+
if (declarationSeen)
|
|
296
|
+
this.fail(line, "LNG1121", "imports must precede class and function declarations");
|
|
297
|
+
const name = hostImport[2] ?? hostImport[1].split("/").at(-1);
|
|
298
|
+
if (jsImports.some((item) => item.name === name))
|
|
299
|
+
this.fail(line, "LNG1122", `duplicate JavaScript import name ${name}`);
|
|
300
|
+
jsImports.push({ name, globalPath: hostImport[1].split("/").map(kebabName) });
|
|
301
|
+
this.blank();
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
286
304
|
const jsImport = /^import js "([^"]+)" as ([A-Za-z_]\w*)$/.exec(line.text);
|
|
287
305
|
if (jsImport) {
|
|
288
306
|
if (declarationSeen)
|
|
@@ -1074,8 +1092,12 @@ class Parser {
|
|
|
1074
1092
|
}
|
|
1075
1093
|
captures() {
|
|
1076
1094
|
if (!(this.peek() ?? "").trimStart().startsWith("capture ")) return [];
|
|
1077
|
-
const line = this.
|
|
1078
|
-
const
|
|
1095
|
+
const line = { text: this.peek(), number: this.i + 1 };
|
|
1096
|
+
const indent = spaces(line.text);
|
|
1097
|
+
const names = [];
|
|
1098
|
+
while (spaces(this.peek() ?? "") === indent && (this.peek() ?? "").trimStart().startsWith("capture ")) {
|
|
1099
|
+
names.push(...splitTopLevel(this.line().text.trim().slice(8)));
|
|
1100
|
+
}
|
|
1079
1101
|
if (
|
|
1080
1102
|
names.some(
|
|
1081
1103
|
(name) =>
|
|
@@ -1116,7 +1138,7 @@ class Parser {
|
|
|
1116
1138
|
value:
|
|
1117
1139
|
match[2] === "{"
|
|
1118
1140
|
? this.object(indent + 4)
|
|
1119
|
-
: expr(match[2], line, this),
|
|
1141
|
+
: expr(this.continuedStatement(match[2], line, indent + 4), line, this),
|
|
1120
1142
|
});
|
|
1121
1143
|
}
|
|
1122
1144
|
return { type: "object", fields };
|
|
@@ -1550,6 +1572,8 @@ function expr(source, line, parser) {
|
|
|
1550
1572
|
: [],
|
|
1551
1573
|
line: line.number,
|
|
1552
1574
|
};
|
|
1575
|
+
if (methodCall?.[1] === "this")
|
|
1576
|
+
parser.fail(line, "LNG1270", `use ${methodCall[3]}(...) without this.`);
|
|
1553
1577
|
if (methodCall)
|
|
1554
1578
|
return {
|
|
1555
1579
|
type: "methodCall",
|
|
@@ -1587,6 +1611,8 @@ function expr(source, line, parser) {
|
|
|
1587
1611
|
return { type: "raw", value: text };
|
|
1588
1612
|
}
|
|
1589
1613
|
if (/^(true|false|null)$/.test(text)) return { type: "raw", value: text };
|
|
1614
|
+
if (/^this(?:$|[.\[?])/.test(text))
|
|
1615
|
+
parser.fail(line, "LNG1270", "use @this for the current receiver and method(...) for its methods");
|
|
1590
1616
|
if (/^@{0,2}\$?[A-Za-z_]\w*(?:(?:\.|\?\.)[A-Za-z_]\w*)*(?:\.|\?\.)_[A-Za-z_]\w*$/.test(text))
|
|
1591
1617
|
parser.fail(
|
|
1592
1618
|
line,
|
|
@@ -1803,6 +1829,8 @@ function validate(program, imports = []) {
|
|
|
1803
1829
|
),
|
|
1804
1830
|
);
|
|
1805
1831
|
const jsImportNames = new Set(program.jsImports.map((item) => item.name));
|
|
1832
|
+
const jsModuleNames = new Set(program.jsImports.filter((item) => !item.globalPath).map((item) => item.name));
|
|
1833
|
+
const hostImportNames = new Set(program.jsImports.filter((item) => item.globalPath).map((item) => item.name));
|
|
1806
1834
|
const importedFunctions = new Map(
|
|
1807
1835
|
imports.flatMap((item) =>
|
|
1808
1836
|
(item.selections ?? [])
|
|
@@ -1836,7 +1864,7 @@ function validate(program, imports = []) {
|
|
|
1836
1864
|
["xml2obj", 1],
|
|
1837
1865
|
["start_trans", 0], ["commit_trans", 0], ["rollback_trans", 0], ["trans_state", 0],
|
|
1838
1866
|
]);
|
|
1839
|
-
const hostFunctions = new Set(["fetch"]);
|
|
1867
|
+
const hostFunctions = new Set(["fetch", ...hostImportNames]);
|
|
1840
1868
|
const isDecimalValue = (value) =>
|
|
1841
1869
|
value &&
|
|
1842
1870
|
(value.type === "decimal" ||
|
|
@@ -1988,7 +2016,12 @@ function validate(program, imports = []) {
|
|
|
1988
2016
|
1,
|
|
1989
2017
|
`imported local name ${name} conflicts with a Lango declaration`,
|
|
1990
2018
|
));
|
|
1991
|
-
const classes = new Map(
|
|
2019
|
+
const classes = new Map([
|
|
2020
|
+
...imports.flatMap((item) => (item.selections ?? [])
|
|
2021
|
+
.filter((selection) => selection.classMetadata && program.classes.some((node) => node.ancestor === selection.localName))
|
|
2022
|
+
.map((selection) => [selection.localName, { ...selection.classMetadata, name: selection.localName }])),
|
|
2023
|
+
...program.classes.map((item) => [item.name, item]),
|
|
2024
|
+
]);
|
|
1992
2025
|
duplicateDiagnostics(program.classes, "LNG1205", "class name");
|
|
1993
2026
|
if (nameDiagnostics.length) {
|
|
1994
2027
|
const [primary] = nameDiagnostics;
|
|
@@ -2112,6 +2145,8 @@ function validate(program, imports = []) {
|
|
|
2112
2145
|
if (node.type === "assign" && !node.name.startsWith("@"))
|
|
2113
2146
|
result.add(node.name);
|
|
2114
2147
|
if (node.type === "closureAssign") result.add(node.name);
|
|
2148
|
+
if (node.type === "closureAssign" || node.type === "closure") continue;
|
|
2149
|
+
if (["contextDeclare", "contextSet", "globalDeclare"].includes(node.type)) result.add(node.name);
|
|
2115
2150
|
if (node.type === "for") result.add(node.item);
|
|
2116
2151
|
if (node.type === "forObject") {
|
|
2117
2152
|
result.add(node.key);
|
|
@@ -2260,6 +2295,24 @@ function validate(program, imports = []) {
|
|
|
2260
2295
|
findMethod(classNode, "constructor", false);
|
|
2261
2296
|
const walk = (value, localCallables, classNode = null, staticMethod = false) => {
|
|
2262
2297
|
if (!value || typeof value !== "object") return;
|
|
2298
|
+
if (value.parameters && value.body)
|
|
2299
|
+
localCallables = new Set([...localCallables, ...value.parameters.map((parameter) => parameter.name), ...dynamicBindingNames(value.body)]);
|
|
2300
|
+
const bindings = [
|
|
2301
|
+
...(["assign", "closureAssign", "increment", "contextSet", "contextDeclare", "globalDeclare"].includes(value.type) ? [value.name] : []),
|
|
2302
|
+
...(value.parameters ?? []).map((parameter) => parameter.name),
|
|
2303
|
+
...(value.type === "for" ? [value.item] : []),
|
|
2304
|
+
...(value.type === "forObject" ? [value.key, value.value] : []),
|
|
2305
|
+
...(value.catchName ? [value.catchName] : []),
|
|
2306
|
+
];
|
|
2307
|
+
const readonly = bindings.find((name) => hostImportNames.has(name));
|
|
2308
|
+
if (readonly)
|
|
2309
|
+
throw new LangoError("LNG1268", value.line ?? 1, `${readonly} is a readonly host import and cannot be rebound or shadowed`);
|
|
2310
|
+
if (bindings.includes("this") || bindings.includes("@this"))
|
|
2311
|
+
throw new LangoError("LNG1270", value.line ?? 1, "the current receiver cannot be rebound or shadowed");
|
|
2312
|
+
if (value.type === "methodCall" && value.receiver === "this" && !value.implicitReceiver)
|
|
2313
|
+
throw new LangoError("LNG1270", value.line ?? 1, `use ${value.name}(...) without this.`);
|
|
2314
|
+
if (typeof value.receiver === "string" && /^@this(?:$|[.\[?])/.test(value.receiver) && !classNode)
|
|
2315
|
+
throw new LangoError("LNG1270", value.line ?? 1, "@this is only valid in a class method");
|
|
2263
2316
|
if (value.type === "binary" && ["+", "-", "*", "/", "%", "**", ">", "<", ">=", "<="].includes(value.operator)) {
|
|
2264
2317
|
const string = (item) => item.type === "string";
|
|
2265
2318
|
const number = (item) => item.type === "raw" && /^\d/.test(item.value);
|
|
@@ -2273,6 +2326,21 @@ function validate(program, imports = []) {
|
|
|
2273
2326
|
(leftKind === "string" && value.operator !== "+")))
|
|
2274
2327
|
throw new LangoError("LNG1254", value.line ?? 1, "primitive values do not coerce in operators");
|
|
2275
2328
|
}
|
|
2329
|
+
if (value.type === "call") {
|
|
2330
|
+
const member = classNode && findMethodWithOwner(classNode, value.name, staticMethod);
|
|
2331
|
+
if (member) {
|
|
2332
|
+
const competing = names.has(value.name) || imported.has(value.name) || globals.has(value.name) ||
|
|
2333
|
+
standardFunctions.has(value.name) || hostFunctions.has(value.name) ||
|
|
2334
|
+
localCallables.has(value.name);
|
|
2335
|
+
if (competing)
|
|
2336
|
+
throw new LangoError("LNG1269", value.line, `ambiguous call ${value.name}: a method and another binding have the same name`);
|
|
2337
|
+
value.type = "methodCall";
|
|
2338
|
+
value.receiver = "this";
|
|
2339
|
+
value.implicitReceiver = true;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
if (value.type === "raw" && /^@this(?:$|[.\[?])/.test(value.value) && !classNode)
|
|
2343
|
+
throw new LangoError("LNG1270", value.line ?? 1, "@this is only valid in a class method");
|
|
2276
2344
|
if (value.type === "call") {
|
|
2277
2345
|
const target = names.get(value.name) ?? importedFunctions.get(value.name);
|
|
2278
2346
|
if (
|
|
@@ -2336,7 +2404,7 @@ function validate(program, imports = []) {
|
|
|
2336
2404
|
value.line,
|
|
2337
2405
|
`use / between JavaScript namespace segments in new ${value.name}(...)`,
|
|
2338
2406
|
);
|
|
2339
|
-
if (value.name.includes("/")) validateJavaScriptArguments(value, value.name);
|
|
2407
|
+
if (value.name.includes("/") || hostImportNames.has(value.name)) validateJavaScriptArguments(value, value.name);
|
|
2340
2408
|
const target = classes.get(value.name);
|
|
2341
2409
|
const constructor = target && findConstructor(target);
|
|
2342
2410
|
if (constructor) validateRequiredArguments(constructor, value, value.name);
|
|
@@ -2344,15 +2412,16 @@ function validate(program, imports = []) {
|
|
|
2344
2412
|
if (value.type === "methodCall" && typeof value.receiver === "string") {
|
|
2345
2413
|
if (
|
|
2346
2414
|
value.receiver.includes(".") &&
|
|
2347
|
-
|
|
2415
|
+
jsModuleNames.has(value.receiver.split(".")[0])
|
|
2348
2416
|
)
|
|
2349
2417
|
throw new LangoError(
|
|
2350
2418
|
"LNG1232",
|
|
2351
2419
|
value.line,
|
|
2352
2420
|
`use / between JavaScript namespace segments in ${value.receiver}.${value.name}(...)`,
|
|
2353
2421
|
);
|
|
2422
|
+
const currentReceiver = value.receiver === "this" || value.receiver === "@this";
|
|
2354
2423
|
const targetClass =
|
|
2355
|
-
|
|
2424
|
+
currentReceiver
|
|
2356
2425
|
? classNode
|
|
2357
2426
|
: classes.get(value.receiver) ?? null;
|
|
2358
2427
|
const importedStaticMethod =
|
|
@@ -2379,13 +2448,13 @@ function validate(program, imports = []) {
|
|
|
2379
2448
|
findMethodWithOwner(
|
|
2380
2449
|
targetClass,
|
|
2381
2450
|
value.name,
|
|
2382
|
-
|
|
2451
|
+
currentReceiver ? staticMethod : true,
|
|
2383
2452
|
);
|
|
2384
2453
|
if (targetClass && !target)
|
|
2385
2454
|
throw new LangoError(
|
|
2386
2455
|
"LNG1241",
|
|
2387
2456
|
value.line,
|
|
2388
|
-
`${targetClass.name}: unknown ${
|
|
2457
|
+
`${targetClass.name}: unknown ${currentReceiver && !staticMethod ? "instance" : "static"} method ${value.name}`,
|
|
2389
2458
|
);
|
|
2390
2459
|
if (
|
|
2391
2460
|
target &&
|
|
@@ -2404,6 +2473,8 @@ function validate(program, imports = []) {
|
|
|
2404
2473
|
`${targetClass.name}${target.method.static ? "::" : ":"}${value.name}`,
|
|
2405
2474
|
);
|
|
2406
2475
|
}
|
|
2476
|
+
if (value.type === "methodCall" && hostImportNames.has(receiverRoot(value.receiver)))
|
|
2477
|
+
validateJavaScriptArguments(value, `${receiverRoot(value.receiver)}.${value.name}`);
|
|
2407
2478
|
if (value.type === "methodCall" && receiverRoot(value.receiver) === "args")
|
|
2408
2479
|
validateJavaScriptArguments(value, `args.${value.name}`);
|
|
2409
2480
|
if (value.type === "namespaceMethodCall") {
|
|
@@ -2445,12 +2516,16 @@ function validate(program, imports = []) {
|
|
|
2445
2516
|
);
|
|
2446
2517
|
validateRequiredArguments(target, value, `super.${value.name}`);
|
|
2447
2518
|
}
|
|
2448
|
-
Object.
|
|
2519
|
+
Object.entries(value).forEach(([key, item]) =>
|
|
2449
2520
|
Array.isArray(item)
|
|
2450
|
-
? item.forEach((child) => walk(child, localCallables, classNode, staticMethod))
|
|
2521
|
+
? item.forEach((child) => walk(child, key === "catchBody" && value.catchName ? new Set([...localCallables, value.catchName]) : localCallables, classNode, staticMethod))
|
|
2451
2522
|
: walk(item, localCallables, classNode, staticMethod),
|
|
2452
2523
|
);
|
|
2453
2524
|
};
|
|
2525
|
+
program.globals.forEach((global) => walk(global, new Set()));
|
|
2526
|
+
program.classes.forEach((classNode) =>
|
|
2527
|
+
[...classNode.staticValues, ...classNode.enumValues].forEach((value) => walk(value, new Set())),
|
|
2528
|
+
);
|
|
2454
2529
|
[...program.functions, ...program.tests].forEach((fn) =>
|
|
2455
2530
|
walk(
|
|
2456
2531
|
fn,
|
|
@@ -2496,6 +2571,7 @@ function validate(program, imports = []) {
|
|
|
2496
2571
|
return;
|
|
2497
2572
|
}
|
|
2498
2573
|
if (node.type === "methodCall" || node.type === "namespaceMethodCall") {
|
|
2574
|
+
if (node.implicitReceiver) references.add("@this");
|
|
2499
2575
|
if (typeof node.receiver === "string" && node.receiver !== "this")
|
|
2500
2576
|
references.add(node.receiver.split(/[./]/)[0]);
|
|
2501
2577
|
else if (typeof node.receiver !== "string")
|
|
@@ -2827,17 +2903,20 @@ function validate(program, imports = []) {
|
|
|
2827
2903
|
!hostFunctions.has(name),
|
|
2828
2904
|
);
|
|
2829
2905
|
const captures = new Set(closure.captures);
|
|
2906
|
+
const automaticCaptures = new Set(needed.filter((name) =>
|
|
2907
|
+
classScope && (name.startsWith("@") || name.startsWith("_")),
|
|
2908
|
+
));
|
|
2830
2909
|
const contextCaptures = new Set(
|
|
2831
2910
|
closure.captures.filter((name) => availableContexts.has(name)),
|
|
2832
2911
|
);
|
|
2833
2912
|
for (const name of needed)
|
|
2834
|
-
if (!captures.has(name))
|
|
2913
|
+
if (!captures.has(name) && !automaticCaptures.has(name))
|
|
2835
2914
|
throw new LangoError(
|
|
2836
2915
|
"LNG1210",
|
|
2837
2916
|
closure.line,
|
|
2838
2917
|
`MissingCapture: ${name}; add capture ${name}`,
|
|
2839
2918
|
);
|
|
2840
|
-
for (const name of captures) {
|
|
2919
|
+
for (const name of new Set([...captures, ...automaticCaptures])) {
|
|
2841
2920
|
if (!needed.includes(name) && !contextCaptures.has(name))
|
|
2842
2921
|
throw new LangoError(
|
|
2843
2922
|
"LNG1211",
|
|
@@ -2853,6 +2932,11 @@ function validate(program, imports = []) {
|
|
|
2853
2932
|
`capture ${name} is not assigned before this closure`,
|
|
2854
2933
|
);
|
|
2855
2934
|
if (propertyCapture) {
|
|
2935
|
+
if (name === "@this") {
|
|
2936
|
+
if (!classScope)
|
|
2937
|
+
throw new LangoError("LNG1213", closure.line, "@this is only valid in a class method");
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2856
2940
|
const staticMember = name.startsWith("@@");
|
|
2857
2941
|
const property = name.startsWith("@")
|
|
2858
2942
|
? name.slice(staticMember ? 2 : 1)
|
|
@@ -3088,6 +3172,8 @@ function validate(program, imports = []) {
|
|
|
3088
3172
|
program.functions.forEach((fn) => validateLoopLevels(fn.body));
|
|
3089
3173
|
program.tests.forEach((test) => validateLoopLevels(test.body));
|
|
3090
3174
|
for (const classNode of program.classes) {
|
|
3175
|
+
if (classNode.publicProperties.includes("this"))
|
|
3176
|
+
throw new LangoError("LNG1270", classNode.line, "this is reserved for the current receiver");
|
|
3091
3177
|
if (classNode.ancestor && !classes.has(classNode.ancestor))
|
|
3092
3178
|
throw new LangoError(
|
|
3093
3179
|
"LNG1206",
|
|
@@ -3282,6 +3368,7 @@ function validate(program, imports = []) {
|
|
|
3282
3368
|
);
|
|
3283
3369
|
}
|
|
3284
3370
|
if (name && /^@[A-Za-z_]\w*$/.test(name)) {
|
|
3371
|
+
if (name === "@this") return;
|
|
3285
3372
|
if (!classScope.instanceMembers.has(name.slice(1)))
|
|
3286
3373
|
throw new LangoError(
|
|
3287
3374
|
"LNG1222",
|
|
@@ -3312,7 +3399,7 @@ function validate(program, imports = []) {
|
|
|
3312
3399
|
: null;
|
|
3313
3400
|
if (
|
|
3314
3401
|
name &&
|
|
3315
|
-
(/^@[A-Za-z_]\w*$/.test(name) || /^_[A-Za-z_]\w*$/.test(name))
|
|
3402
|
+
((/^@[A-Za-z_]\w*$/.test(name) && name !== "@this") || /^_[A-Za-z_]\w*$/.test(name))
|
|
3316
3403
|
)
|
|
3317
3404
|
throw new LangoError(
|
|
3318
3405
|
"LNG1231",
|
|
@@ -3389,6 +3476,12 @@ function validate(program, imports = []) {
|
|
|
3389
3476
|
|
|
3390
3477
|
function emit(program, moduleImports = [], includeTests = false) {
|
|
3391
3478
|
const flags = {
|
|
3479
|
+
classes: new Map([
|
|
3480
|
+
...moduleImports.flatMap((item) => (item.selections ?? [])
|
|
3481
|
+
.filter((selection) => selection.classMetadata)
|
|
3482
|
+
.map((selection) => [selection.localName, selection.classMetadata])),
|
|
3483
|
+
...program.classes.map((item) => [item.name, item]),
|
|
3484
|
+
]),
|
|
3392
3485
|
print: false,
|
|
3393
3486
|
translate: false,
|
|
3394
3487
|
context: false,
|
|
@@ -3401,6 +3494,7 @@ function emit(program, moduleImports = [], includeTests = false) {
|
|
|
3401
3494
|
item.names.map((name) => name.split(" as ").at(-1)),
|
|
3402
3495
|
),
|
|
3403
3496
|
),
|
|
3497
|
+
hostGlobals: new Map(program.jsImports.filter((item) => item.globalPath).map((item) => [item.name, item.globalPath])),
|
|
3404
3498
|
};
|
|
3405
3499
|
const classes = program.classes
|
|
3406
3500
|
.map((item) => emitClass(item, flags))
|
|
@@ -3424,7 +3518,18 @@ function emit(program, moduleImports = [], includeTests = false) {
|
|
|
3424
3518
|
.concat("...__langoOwnTests")
|
|
3425
3519
|
.join(", ")}];\n`
|
|
3426
3520
|
: "";
|
|
3521
|
+
const globals = program.globals
|
|
3522
|
+
.map((item) => {
|
|
3523
|
+
const value = emitExpr(item.value, flags);
|
|
3524
|
+
const prefix = item.exported ? "export let" : "let";
|
|
3525
|
+
if (!item.name.startsWith("$")) return `${prefix} ${item.name} = ${value};\n`;
|
|
3526
|
+
flags.decimal = true;
|
|
3527
|
+
return `${prefix} ${item.name} = __decimal(${item.value.type === "raw" ? JSON.stringify(item.value.value) : value});\n`;
|
|
3528
|
+
})
|
|
3529
|
+
.join("");
|
|
3427
3530
|
const helpers = [
|
|
3531
|
+
flags.hostGlobal && "hostGlobal as __hostGlobal",
|
|
3532
|
+
flags.hostGlobalCall && "hostGlobalCall as __hostGlobalCall",
|
|
3428
3533
|
flags.decimal && "decimal as __decimal",
|
|
3429
3534
|
flags.print && "println as __println",
|
|
3430
3535
|
flags.translate && "translate as __translate",
|
|
@@ -3482,20 +3587,12 @@ function emit(program, moduleImports = [], includeTests = false) {
|
|
|
3482
3587
|
)
|
|
3483
3588
|
.join("");
|
|
3484
3589
|
const jsModules = program.jsImports
|
|
3590
|
+
.filter((item) => !item.globalPath)
|
|
3485
3591
|
.map(
|
|
3486
3592
|
(item) =>
|
|
3487
3593
|
`import * as ${item.name} from ${JSON.stringify(item.specifier)};\n`,
|
|
3488
3594
|
)
|
|
3489
3595
|
.join("");
|
|
3490
|
-
const globals = program.globals
|
|
3491
|
-
.map((item) => {
|
|
3492
|
-
const value = emitExpr(item.value, flags);
|
|
3493
|
-
const prefix = item.exported ? "export let" : "let";
|
|
3494
|
-
if (!item.name.startsWith("$")) return `${prefix} ${item.name} = ${value};\n`;
|
|
3495
|
-
flags.decimal = true;
|
|
3496
|
-
return `${prefix} ${item.name} = __decimal(${item.value.type === "raw" ? JSON.stringify(item.value.value) : value});\n`;
|
|
3497
|
-
})
|
|
3498
|
-
.join("");
|
|
3499
3596
|
const testReset = includeTests
|
|
3500
3597
|
? `export function __langoResetTests() {\n${program.globals
|
|
3501
3598
|
.map((item) => ` ${item.name} = ${emitExpr(item.value, flags)};\n`)
|
|
@@ -3575,6 +3672,13 @@ function emitClass(item, flags) {
|
|
|
3575
3672
|
method.public,
|
|
3576
3673
|
]),
|
|
3577
3674
|
);
|
|
3675
|
+
for (let ancestor = flags.classes.get(item.ancestor); ancestor; ancestor = flags.classes.get(ancestor.ancestor)) {
|
|
3676
|
+
ancestor.publicProperties.forEach((name) => flags.publicProperties.add(name));
|
|
3677
|
+
for (const method of ancestor.methods) {
|
|
3678
|
+
const key = `${method.static ? "static" : "instance"}:${method.name}`;
|
|
3679
|
+
if (method.public && !flags.methods.has(key)) flags.methods.set(key, true);
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3578
3682
|
const fields = [
|
|
3579
3683
|
...new Set(
|
|
3580
3684
|
item.methods
|
|
@@ -3585,7 +3689,7 @@ function emitClass(item, flags) {
|
|
|
3585
3689
|
.filter((name) => !name.startsWith("@@"))
|
|
3586
3690
|
.map((name) => name.slice(1)),
|
|
3587
3691
|
),
|
|
3588
|
-
].filter((field) => !flags.publicProperties.has(field));
|
|
3692
|
+
].filter((field) => field !== "this" && !flags.publicProperties.has(field));
|
|
3589
3693
|
const implicitConstructor =
|
|
3590
3694
|
item.ancestor &&
|
|
3591
3695
|
!item.methods.some((method) => method.name === "constructor")
|
|
@@ -3665,7 +3769,8 @@ function emitFunction(fn, flags) {
|
|
|
3665
3769
|
.map((node) => node.name),
|
|
3666
3770
|
);
|
|
3667
3771
|
const hoisted = new Set(
|
|
3668
|
-
[...collectEmitLocals(fn.body)].filter((name) =>
|
|
3772
|
+
[...collectEmitLocals(fn.body)].filter((name) =>
|
|
3773
|
+
!topLevelAssigned.has(name) && !fn.parameters.some((parameter) => parameter.name === name)),
|
|
3669
3774
|
);
|
|
3670
3775
|
const declared = new Set([...fn.parameters.map((p) => p.name), ...hoisted]);
|
|
3671
3776
|
const priorArrayBindings = flags.arrayBindings;
|
|
@@ -3984,6 +4089,12 @@ function emitBoundaryChecks(parameters, depth, flags) {
|
|
|
3984
4089
|
.join("");
|
|
3985
4090
|
}
|
|
3986
4091
|
function emitReceiverName(name, flags) {
|
|
4092
|
+
if (/^@this(?:$|[.\[?])/.test(name)) return `this${name.slice(5)}`;
|
|
4093
|
+
const host = /^([A-Za-z_]\w*)(.*)$/.exec(name);
|
|
4094
|
+
if (host && flags.hostGlobals?.has(host[1])) {
|
|
4095
|
+
flags.hostGlobal = true;
|
|
4096
|
+
return `__hostGlobal(${JSON.stringify(flags.hostGlobals.get(host[1]))})${emitKebabPath(host[2])}`;
|
|
4097
|
+
}
|
|
3987
4098
|
const member = /^(@{1,2})(\$?[A-Za-z_]\w*)(.*)$/.exec(name);
|
|
3988
4099
|
if (member) {
|
|
3989
4100
|
const [, scope, property, suffix] = member;
|
|
@@ -4002,6 +4113,7 @@ function emitReceiverName(name, flags) {
|
|
|
4002
4113
|
}
|
|
4003
4114
|
|
|
4004
4115
|
function emitExpr(node, flags) {
|
|
4116
|
+
if (node.type === "raw" && node.value === "@this") return "this";
|
|
4005
4117
|
const decimalRaw = (value) =>
|
|
4006
4118
|
value?.type === "raw" &&
|
|
4007
4119
|
(value.value.startsWith("$") || value.value.includes(".$"));
|
|
@@ -4014,6 +4126,13 @@ function emitExpr(node, flags) {
|
|
|
4014
4126
|
flags.normalize = true;
|
|
4015
4127
|
return `__normalize(${value})`;
|
|
4016
4128
|
};
|
|
4129
|
+
if (node.type === "raw" && flags.hostGlobals.has(node.value.split(/[.\[?]/)[0]))
|
|
4130
|
+
return normalize(emitReceiverName(node.value, flags));
|
|
4131
|
+
if (node.type === "call" && flags.hostGlobals.has(node.name)) {
|
|
4132
|
+
flags.hostGlobalCall = true;
|
|
4133
|
+
const arguments_ = node.arguments.map((item) => item.type === "closure" ? emitClosure(item, 0, flags) : emitExpr(item, flags)).join(", ");
|
|
4134
|
+
return `__hostGlobalCall(${JSON.stringify(flags.hostGlobals.get(node.name))}${arguments_ ? `, ${arguments_}` : ""})`;
|
|
4135
|
+
}
|
|
4017
4136
|
if (node.type === "raw")
|
|
4018
4137
|
if (/^@{1,2}\$?[A-Za-z_]\w*[.\[?]/.test(node.value))
|
|
4019
4138
|
return normalize(emitReceiverName(node.value, flags));
|
|
@@ -4076,7 +4195,7 @@ function emitExpr(node, flags) {
|
|
|
4076
4195
|
return `__finite(-${numericLiteral ? emitExpr(node.value, flags) : `__requireNumber(${emitExpr(node.value, flags)})`})`;
|
|
4077
4196
|
}
|
|
4078
4197
|
if (node.type === "new")
|
|
4079
|
-
return `new ${node.name.replaceAll("/", ".")}(${node.arguments.map((item) =>
|
|
4198
|
+
return `new ${flags.hostGlobals.has(node.name.split("/")[0]) ? `(${emitReceiverName(node.name.replaceAll("/", "."), flags)})` : node.name.replaceAll("/", ".")}(${node.arguments.map((item) =>
|
|
4080
4199
|
item.type === "closure" ? emitClosure(item, 0, flags) : emitExpr(item, flags),
|
|
4081
4200
|
).join(", ")})`;
|
|
4082
4201
|
if (node.type === "superMethodCall")
|
|
@@ -4092,7 +4211,7 @@ function emitExpr(node, flags) {
|
|
|
4092
4211
|
if (node.type === "methodCall" || node.type === "namespaceMethodCall") {
|
|
4093
4212
|
const namespace = node.type === "namespaceMethodCall";
|
|
4094
4213
|
const sourceReceiver = namespace
|
|
4095
|
-
? node.receiver.replaceAll("/", ".")
|
|
4214
|
+
? emitReceiverName(node.receiver.replaceAll("/", "."), flags)
|
|
4096
4215
|
: typeof node.receiver === "string"
|
|
4097
4216
|
? emitReceiverName(node.receiver, flags)
|
|
4098
4217
|
: emitExpr(node.receiver, flags);
|
|
@@ -4101,7 +4220,8 @@ function emitExpr(node, flags) {
|
|
|
4101
4220
|
(sourceReceiver === "this" && flags.staticMethod)
|
|
4102
4221
|
? "static"
|
|
4103
4222
|
: "instance";
|
|
4104
|
-
const
|
|
4223
|
+
const ownReceiver = !namespace && (sourceReceiver === "this" || sourceReceiver === flags.className);
|
|
4224
|
+
const visibility = ownReceiver ? flags.methods?.get(`${type}:${node.name}`) : undefined;
|
|
4105
4225
|
const receiver =
|
|
4106
4226
|
sourceReceiver === "this" && flags.staticMethod && visibility === false
|
|
4107
4227
|
? flags.className
|
|
@@ -4329,7 +4449,7 @@ function emitString(parts, flags) {
|
|
|
4329
4449
|
function spaces(line) {
|
|
4330
4450
|
return line.length - line.trimStart().length;
|
|
4331
4451
|
}
|
|
4332
|
-
function collectImports(source, inputPath, outputPath) {
|
|
4452
|
+
function collectImports(source, inputPath, outputPath, layout = null) {
|
|
4333
4453
|
const lines = source.split(/\r?\n/);
|
|
4334
4454
|
const items = [];
|
|
4335
4455
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -4378,7 +4498,7 @@ function collectImports(source, inputPath, outputPath) {
|
|
|
4378
4498
|
path.dirname(inputPath),
|
|
4379
4499
|
`${match[1]}.lango`,
|
|
4380
4500
|
);
|
|
4381
|
-
const outputPathForImport = path.resolve(
|
|
4501
|
+
const outputPathForImport = layout?.get(sourcePath) ?? path.resolve(
|
|
4382
4502
|
path.dirname(outputPath),
|
|
4383
4503
|
`${match[1]}.mjs`,
|
|
4384
4504
|
);
|
|
@@ -4387,12 +4507,38 @@ function collectImports(source, inputPath, outputPath) {
|
|
|
4387
4507
|
selections,
|
|
4388
4508
|
sourcePath,
|
|
4389
4509
|
outputPath: outputPathForImport,
|
|
4390
|
-
specifier:
|
|
4510
|
+
specifier: relativeModuleSpecifier(outputPath, outputPathForImport),
|
|
4391
4511
|
});
|
|
4392
4512
|
}
|
|
4393
4513
|
return items;
|
|
4394
4514
|
}
|
|
4395
4515
|
|
|
4516
|
+
function relativeModuleSpecifier(from, to) {
|
|
4517
|
+
const relative = path.relative(path.dirname(from), to).split(path.sep).join("/");
|
|
4518
|
+
return relative.startsWith("../") ? relative : `./${relative}`;
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
function moduleOutputLayout(entryPath, entryOutputPath, modules) {
|
|
4522
|
+
let sourceRoot = path.dirname(entryPath);
|
|
4523
|
+
for (const sourcePath of modules.keys()) {
|
|
4524
|
+
while (true) {
|
|
4525
|
+
const relative = path.relative(sourceRoot, sourcePath);
|
|
4526
|
+
if (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative)) break;
|
|
4527
|
+
sourceRoot = path.dirname(sourceRoot);
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
const outputRoot = path.dirname(entryOutputPath);
|
|
4531
|
+
const dependencyPath = (sourcePath, root) =>
|
|
4532
|
+
path.join(root, path.relative(sourceRoot, sourcePath).replace(/\.lango$/, ".mjs"));
|
|
4533
|
+
const collision = [...modules.keys()].some((sourcePath) =>
|
|
4534
|
+
sourcePath !== entryPath && dependencyPath(sourcePath, outputRoot) === entryOutputPath);
|
|
4535
|
+
const dependencyRoot = collision ? path.join(outputRoot, ".lango/modules") : outputRoot;
|
|
4536
|
+
return new Map([...modules.keys()].map((sourcePath) => [
|
|
4537
|
+
sourcePath,
|
|
4538
|
+
sourcePath === entryPath ? entryOutputPath : dependencyPath(sourcePath, dependencyRoot),
|
|
4539
|
+
]));
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4396
4542
|
async function validateModuleInitializerCycles(entryPath, entryOutputPath) {
|
|
4397
4543
|
const modules = new Map();
|
|
4398
4544
|
const visit = async (modulePath, outputPath) => {
|
|
@@ -4450,6 +4596,7 @@ async function validateModuleInitializerCycles(entryPath, entryOutputPath) {
|
|
|
4450
4596
|
`${modulePath}: cyclic module initializer reads imported ${selection.kind} ${selection.localName}`,
|
|
4451
4597
|
);
|
|
4452
4598
|
}
|
|
4599
|
+
return modules;
|
|
4453
4600
|
}
|
|
4454
4601
|
function collectClassImportInterface(lines, getIndex, setIndex) {
|
|
4455
4602
|
const api = {
|
|
@@ -4494,6 +4641,46 @@ function collectClassImportInterface(lines, getIndex, setIndex) {
|
|
|
4494
4641
|
setIndex(index);
|
|
4495
4642
|
return api;
|
|
4496
4643
|
}
|
|
4644
|
+
async function importedClassMetadata(sourcePath, name, visiting = new Set()) {
|
|
4645
|
+
const key = `${sourcePath}:${name}`;
|
|
4646
|
+
if (visiting.has(key))
|
|
4647
|
+
throw new LangoError("LNG1207", 1, `cyclic class inheritance involving ${name}`, sourcePath);
|
|
4648
|
+
const chain = new Set(visiting).add(key);
|
|
4649
|
+
const source = await readFile(sourcePath, "utf8");
|
|
4650
|
+
const program = new Parser(source, sourcePath).parse();
|
|
4651
|
+
const target = program.classes.find((item) => item.name === name);
|
|
4652
|
+
if (!target) throw new LangoError("LNG1206", 1, `unknown ancestor ${name}`, sourcePath);
|
|
4653
|
+
let ancestor = null;
|
|
4654
|
+
if (target.ancestor) {
|
|
4655
|
+
if (program.classes.some((item) => item.name === target.ancestor))
|
|
4656
|
+
ancestor = await importedClassMetadata(sourcePath, target.ancestor, chain);
|
|
4657
|
+
else {
|
|
4658
|
+
for (const item of collectImports(source, sourcePath, sourcePath)) {
|
|
4659
|
+
const selection = item.selections.find((entry) => entry.kind === "class" && entry.localName === target.ancestor);
|
|
4660
|
+
if (selection) {
|
|
4661
|
+
ancestor = await importedClassMetadata(item.sourcePath, selection.sourceName, chain);
|
|
4662
|
+
break;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
if (!ancestor) throw new LangoError("LNG1206", target.line, `unknown ancestor ${target.ancestor}`, sourcePath);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
const nearest = (own, inherited, keyOf) => [...new Map([
|
|
4669
|
+
...inherited.map((item) => [keyOf(item), item]),
|
|
4670
|
+
...own.map((item) => [keyOf(item), item]),
|
|
4671
|
+
]).values()];
|
|
4672
|
+
const methodKey = (method) => `${method.static ? "static" : "instance"}:${method.name}`;
|
|
4673
|
+
return {
|
|
4674
|
+
...target,
|
|
4675
|
+
ancestor: null,
|
|
4676
|
+
publicProperties: [...new Set([...(ancestor?.publicProperties ?? []), ...target.publicProperties])],
|
|
4677
|
+
methods: nearest(target.methods, ancestor?.methods ?? [], methodKey),
|
|
4678
|
+
publicMethods: nearest(target.publicMethods, ancestor?.publicMethods ?? [], methodKey),
|
|
4679
|
+
staticValues: nearest(target.staticValues, ancestor?.staticValues ?? [], (item) => item.name),
|
|
4680
|
+
enumValues: nearest(target.enumValues, ancestor?.enumValues ?? [], (item) => item.name),
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
|
|
4497
4684
|
function validateImportedClassInterface(
|
|
4498
4685
|
selection,
|
|
4499
4686
|
target,
|
package/src/runtime.mjs
CHANGED
|
@@ -427,7 +427,8 @@ export function add(value, ...items) {
|
|
|
427
427
|
throw new Error("NotAddable");
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
-
export function remove(value,
|
|
430
|
+
export function remove(value, ...items) {
|
|
431
|
+
const [item] = items;
|
|
431
432
|
if (Array.isArray(value)) {
|
|
432
433
|
const index = value.indexOf(item);
|
|
433
434
|
if (index < 0) return false;
|
|
@@ -435,12 +436,13 @@ export function remove(value, item) {
|
|
|
435
436
|
return true;
|
|
436
437
|
}
|
|
437
438
|
if (value !== null && typeof value?.remove === "function") {
|
|
438
|
-
return normalize(value.remove(
|
|
439
|
+
return normalize(value.remove(...items));
|
|
439
440
|
}
|
|
440
441
|
throw new Error("NotRemovable");
|
|
441
442
|
}
|
|
442
443
|
|
|
443
|
-
export function remove_all(value,
|
|
444
|
+
export function remove_all(value, ...items) {
|
|
445
|
+
const [item] = items;
|
|
444
446
|
if (Array.isArray(value)) {
|
|
445
447
|
let removed = 0;
|
|
446
448
|
for (let index = value.length - 1; index >= 0; index -= 1) {
|
|
@@ -451,23 +453,24 @@ export function remove_all(value, item) {
|
|
|
451
453
|
return removed;
|
|
452
454
|
}
|
|
453
455
|
if (value !== null && typeof value?.remove_all === "function") {
|
|
454
|
-
return normalize(value.remove_all(
|
|
456
|
+
return normalize(value.remove_all(...items));
|
|
455
457
|
}
|
|
456
458
|
throw new Error("NotRemovable");
|
|
457
459
|
}
|
|
458
460
|
|
|
459
|
-
export function clear(value) {
|
|
461
|
+
export function clear(value, ...items) {
|
|
460
462
|
if (Array.isArray(value)) {
|
|
461
463
|
value.length = 0;
|
|
462
464
|
return null;
|
|
463
465
|
}
|
|
464
466
|
if (value !== null && typeof value?.clear === "function") {
|
|
465
|
-
return normalize(value.clear());
|
|
467
|
+
return normalize(value.clear(...items));
|
|
466
468
|
}
|
|
467
469
|
throw new Error("NotClearable");
|
|
468
470
|
}
|
|
469
471
|
|
|
470
|
-
export function remove_at(value,
|
|
472
|
+
export function remove_at(value, ...items) {
|
|
473
|
+
const [index] = items;
|
|
471
474
|
if (Array.isArray(value)) {
|
|
472
475
|
if (!Number.isSafeInteger(index) || index < 0 || index >= value.length)
|
|
473
476
|
return false;
|
|
@@ -475,12 +478,13 @@ export function remove_at(value, index) {
|
|
|
475
478
|
return true;
|
|
476
479
|
}
|
|
477
480
|
if (value !== null && typeof value?.remove_at === "function") {
|
|
478
|
-
return normalize(value.remove_at(
|
|
481
|
+
return normalize(value.remove_at(...items));
|
|
479
482
|
}
|
|
480
483
|
throw new Error("NotRemovable");
|
|
481
484
|
}
|
|
482
485
|
|
|
483
|
-
export function insert_at(value,
|
|
486
|
+
export function insert_at(value, ...items) {
|
|
487
|
+
const [index, item] = items;
|
|
484
488
|
if (Array.isArray(value)) {
|
|
485
489
|
if (!Number.isSafeInteger(index) || index < 0 || index > value.length)
|
|
486
490
|
return false;
|
|
@@ -488,7 +492,7 @@ export function insert_at(value, index, item) {
|
|
|
488
492
|
return true;
|
|
489
493
|
}
|
|
490
494
|
if (value !== null && typeof value?.insert_at === "function") {
|
|
491
|
-
return normalize(value.insert_at(
|
|
495
|
+
return normalize(value.insert_at(...items));
|
|
492
496
|
}
|
|
493
497
|
throw new Error("NotInsertable");
|
|
494
498
|
}
|
|
@@ -548,6 +552,36 @@ export function call(value, ...arguments_) {
|
|
|
548
552
|
return normalize(value(...arguments_));
|
|
549
553
|
}
|
|
550
554
|
|
|
555
|
+
export class HostGlobalUnavailable extends Error {
|
|
556
|
+
constructor(path) {
|
|
557
|
+
super("HostGlobalUnavailable: " + path.join("/"));
|
|
558
|
+
this.name = "HostGlobalUnavailable";
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function resolveHostGlobal(path) {
|
|
563
|
+
let owner = globalThis;
|
|
564
|
+
let value = owner;
|
|
565
|
+
for (const segment of path) {
|
|
566
|
+
owner = value;
|
|
567
|
+
if (owner === null || owner === undefined || !(segment in Object(owner)))
|
|
568
|
+
throw new HostGlobalUnavailable(path);
|
|
569
|
+
value = owner[segment];
|
|
570
|
+
}
|
|
571
|
+
if (value === undefined) throw new HostGlobalUnavailable(path);
|
|
572
|
+
return { owner, value };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export function hostGlobal(path) {
|
|
576
|
+
return resolveHostGlobal(path).value;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function hostGlobalCall(path, ...arguments_) {
|
|
580
|
+
const { owner, value } = resolveHostGlobal(path);
|
|
581
|
+
if (typeof value !== "function") throw new Error("NotCallable");
|
|
582
|
+
return normalize(Reflect.apply(value, owner, arguments_));
|
|
583
|
+
}
|
|
584
|
+
|
|
551
585
|
export function callMethod(receiver, name, ...arguments_) {
|
|
552
586
|
const value = receiver?.[name];
|
|
553
587
|
if (typeof value !== "function") throw new Error("NotCallable");
|