@weborigami/language 0.3.4-jse.8 → 0.3.4-jse.9
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/package.json +3 -3
- package/src/compiler/optimize.js +13 -8
- package/src/compiler/origami.pegjs +2 -2
- package/src/compiler/parse.js +45 -14
- package/src/compiler/parserHelpers.js +4 -3
- package/src/runtime/getHandlers.js +1 -1
- package/src/runtime/ops.js +2 -2
- package/test/compiler/codeHelpers.js +9 -0
- package/test/compiler/optimize.test.js +6 -1
- package/test/compiler/parse.test.js +2 -10
- package/test/runtime/expressionObject.test.js +1 -1
- package/test/runtime/ops.test.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.3.4-jse.
|
|
3
|
+
"version": "0.3.4-jse.9",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"typescript": "5.8.2"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/async-tree": "0.3.4-jse.
|
|
15
|
-
"@weborigami/types": "0.3.4-jse.
|
|
14
|
+
"@weborigami/async-tree": "0.3.4-jse.9",
|
|
15
|
+
"@weborigami/types": "0.3.4-jse.9",
|
|
16
16
|
"watcher": "2.3.1",
|
|
17
17
|
"yaml": "2.7.0"
|
|
18
18
|
},
|
package/src/compiler/optimize.js
CHANGED
|
@@ -56,7 +56,7 @@ export default function optimize(code, options = {}) {
|
|
|
56
56
|
|
|
57
57
|
case ops.object:
|
|
58
58
|
const entries = args;
|
|
59
|
-
const keys = entries.map(entryKey);
|
|
59
|
+
const keys = entries.map((entry) => entryKey(entry));
|
|
60
60
|
locals.push(keys);
|
|
61
61
|
break;
|
|
62
62
|
}
|
|
@@ -70,13 +70,16 @@ export default function optimize(code, options = {}) {
|
|
|
70
70
|
} else if (op === ops.object && index > 0) {
|
|
71
71
|
const [key, value] = child;
|
|
72
72
|
const adjustedLocals = avoidLocalRecursion(locals, key);
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
return annotate(
|
|
74
|
+
[
|
|
75
|
+
key,
|
|
76
|
+
optimize(/** @type {AnnotatedCode} */ (value), {
|
|
77
|
+
...options,
|
|
78
|
+
locals: adjustedLocals,
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
81
|
+
child.location
|
|
82
|
+
);
|
|
80
83
|
} else if (Array.isArray(child) && "location" in child) {
|
|
81
84
|
// Review: Aside from ops.object (above), what non-instruction arrays
|
|
82
85
|
// does this descend into?
|
|
@@ -191,6 +194,7 @@ function inlineLiteral(code) {
|
|
|
191
194
|
function localReference(key, locals, location) {
|
|
192
195
|
const normalized = trailingSlash.remove(key);
|
|
193
196
|
const depth = getLocalReferenceDepth(locals, normalized);
|
|
197
|
+
/** @type {any[]} */
|
|
194
198
|
const context = [ops.context];
|
|
195
199
|
if (depth > 0) {
|
|
196
200
|
context.push(depth);
|
|
@@ -310,6 +314,7 @@ function resolvePath(code, globals, locals, cache) {
|
|
|
310
314
|
|
|
311
315
|
function scopeCall(locals, location) {
|
|
312
316
|
const depth = locals.length;
|
|
317
|
+
/** @type {any[]} */
|
|
313
318
|
const code = [ops.scope];
|
|
314
319
|
if (depth > 0) {
|
|
315
320
|
// Add context for appropriate depth to scope call
|
|
@@ -866,8 +866,8 @@ uriKey
|
|
|
866
866
|
// A single character in a URI key
|
|
867
867
|
uriKeyChar
|
|
868
868
|
// Accept anything that doesn't end the URI key or path
|
|
869
|
-
//
|
|
870
|
-
= [^/,\)\]\}
|
|
869
|
+
// Reject whitespace; see notes for `whitespace` term
|
|
870
|
+
= char:[^/,\)\]\}] !&{ return /\s/.test(char); } { return char; }
|
|
871
871
|
/ escapedChar
|
|
872
872
|
|
|
873
873
|
// A slash-separated path of keys: `a/b/c`
|
package/src/compiler/parse.js
CHANGED
|
@@ -285,7 +285,7 @@ function peg$parse(input, options) {
|
|
|
285
285
|
var peg$r7 = /^[gimuy]/;
|
|
286
286
|
var peg$r8 = /^[^\/\n\r]/;
|
|
287
287
|
var peg$r9 = /^[^\n\r]/;
|
|
288
|
-
var peg$r10 = /^[^\/,)\]}
|
|
288
|
+
var peg$r10 = /^[^\/,)\]}]/;
|
|
289
289
|
var peg$r11 = /^[a-z]/;
|
|
290
290
|
var peg$r12 = /^[a-z0-9+-.]/;
|
|
291
291
|
var peg$r13 = /^[:]/;
|
|
@@ -399,7 +399,7 @@ function peg$parse(input, options) {
|
|
|
399
399
|
var peg$e105 = peg$otherExpectation("template document");
|
|
400
400
|
var peg$e106 = peg$otherExpectation("template literal");
|
|
401
401
|
var peg$e107 = peg$otherExpectation("template substitution");
|
|
402
|
-
var peg$e108 = peg$classExpectation(["/", ",", ")", "]", "}"
|
|
402
|
+
var peg$e108 = peg$classExpectation(["/", ",", ")", "]", "}"], true, false);
|
|
403
403
|
var peg$e109 = peg$otherExpectation("slash-separated path");
|
|
404
404
|
var peg$e110 = peg$classExpectation([["a", "z"]], false, false);
|
|
405
405
|
var peg$e111 = peg$classExpectation([["a", "z"], ["0", "9"], ["+", "."]], false, false);
|
|
@@ -780,14 +780,16 @@ function peg$parse(input, options) {
|
|
|
780
780
|
// A single slash is a path key
|
|
781
781
|
return annotate([ops.literal, ""], location());
|
|
782
782
|
};
|
|
783
|
-
var peg$f110 = function(
|
|
783
|
+
var peg$f110 = function(char) { return /\s/.test(char); };
|
|
784
|
+
var peg$f111 = function(char) { return char; };
|
|
785
|
+
var peg$f112 = function(keys) {
|
|
784
786
|
return annotate(keys, location());
|
|
785
787
|
};
|
|
786
|
-
var peg$
|
|
788
|
+
var peg$f113 = function() {
|
|
787
789
|
return annotate([markers.global, text()], location());
|
|
788
790
|
};
|
|
789
|
-
var peg$
|
|
790
|
-
var peg$
|
|
791
|
+
var peg$f114 = function(char) { return /\s/.test(char); };
|
|
792
|
+
var peg$f115 = function(char) { return char; };
|
|
791
793
|
var peg$currPos = options.peg$currPos | 0;
|
|
792
794
|
var peg$savedPos = peg$currPos;
|
|
793
795
|
var peg$posDetailsCache = [{ line: 1, column: 1 }];
|
|
@@ -6050,15 +6052,44 @@ function peg$parse(input, options) {
|
|
|
6050
6052
|
}
|
|
6051
6053
|
|
|
6052
6054
|
function peg$parseuriKeyChar() {
|
|
6053
|
-
var s0;
|
|
6055
|
+
var s0, s1, s2, s3;
|
|
6054
6056
|
|
|
6055
|
-
s0 =
|
|
6056
|
-
|
|
6057
|
+
s0 = peg$currPos;
|
|
6058
|
+
s1 = input.charAt(peg$currPos);
|
|
6059
|
+
if (peg$r10.test(s1)) {
|
|
6057
6060
|
peg$currPos++;
|
|
6058
6061
|
} else {
|
|
6059
|
-
|
|
6062
|
+
s1 = peg$FAILED;
|
|
6060
6063
|
if (peg$silentFails === 0) { peg$fail(peg$e108); }
|
|
6061
6064
|
}
|
|
6065
|
+
if (s1 !== peg$FAILED) {
|
|
6066
|
+
s2 = peg$currPos;
|
|
6067
|
+
peg$silentFails++;
|
|
6068
|
+
peg$savedPos = peg$currPos;
|
|
6069
|
+
s3 = peg$f110(s1);
|
|
6070
|
+
if (s3) {
|
|
6071
|
+
s3 = undefined;
|
|
6072
|
+
} else {
|
|
6073
|
+
s3 = peg$FAILED;
|
|
6074
|
+
}
|
|
6075
|
+
peg$silentFails--;
|
|
6076
|
+
if (s3 === peg$FAILED) {
|
|
6077
|
+
s2 = undefined;
|
|
6078
|
+
} else {
|
|
6079
|
+
peg$currPos = s2;
|
|
6080
|
+
s2 = peg$FAILED;
|
|
6081
|
+
}
|
|
6082
|
+
if (s2 !== peg$FAILED) {
|
|
6083
|
+
peg$savedPos = s0;
|
|
6084
|
+
s0 = peg$f111(s1);
|
|
6085
|
+
} else {
|
|
6086
|
+
peg$currPos = s0;
|
|
6087
|
+
s0 = peg$FAILED;
|
|
6088
|
+
}
|
|
6089
|
+
} else {
|
|
6090
|
+
peg$currPos = s0;
|
|
6091
|
+
s0 = peg$FAILED;
|
|
6092
|
+
}
|
|
6062
6093
|
if (s0 === peg$FAILED) {
|
|
6063
6094
|
s0 = peg$parseescapedChar();
|
|
6064
6095
|
}
|
|
@@ -6086,7 +6117,7 @@ function peg$parse(input, options) {
|
|
|
6086
6117
|
}
|
|
6087
6118
|
if (s1 !== peg$FAILED) {
|
|
6088
6119
|
peg$savedPos = s0;
|
|
6089
|
-
s1 = peg$
|
|
6120
|
+
s1 = peg$f112(s1);
|
|
6090
6121
|
}
|
|
6091
6122
|
s0 = s1;
|
|
6092
6123
|
peg$silentFails--;
|
|
@@ -6137,7 +6168,7 @@ function peg$parse(input, options) {
|
|
|
6137
6168
|
}
|
|
6138
6169
|
if (s3 !== peg$FAILED) {
|
|
6139
6170
|
peg$savedPos = s0;
|
|
6140
|
-
s0 = peg$
|
|
6171
|
+
s0 = peg$f113();
|
|
6141
6172
|
} else {
|
|
6142
6173
|
peg$currPos = s0;
|
|
6143
6174
|
s0 = peg$FAILED;
|
|
@@ -6217,7 +6248,7 @@ function peg$parse(input, options) {
|
|
|
6217
6248
|
}
|
|
6218
6249
|
if (s1 !== peg$FAILED) {
|
|
6219
6250
|
peg$savedPos = peg$currPos;
|
|
6220
|
-
s2 = peg$
|
|
6251
|
+
s2 = peg$f114(s1);
|
|
6221
6252
|
if (s2) {
|
|
6222
6253
|
s2 = undefined;
|
|
6223
6254
|
} else {
|
|
@@ -6225,7 +6256,7 @@ function peg$parse(input, options) {
|
|
|
6225
6256
|
}
|
|
6226
6257
|
if (s2 !== peg$FAILED) {
|
|
6227
6258
|
peg$savedPos = s0;
|
|
6228
|
-
s0 = peg$
|
|
6259
|
+
s0 = peg$f115(s1);
|
|
6229
6260
|
} else {
|
|
6230
6261
|
peg$currPos = s0;
|
|
6231
6262
|
s0 = peg$FAILED;
|
|
@@ -28,7 +28,7 @@ export const markers = {
|
|
|
28
28
|
* If a parse result is an object that will be evaluated at runtime, attach the
|
|
29
29
|
* location of the source code that produced it for debugging and error messages.
|
|
30
30
|
*
|
|
31
|
-
* @param {
|
|
31
|
+
* @param {any[]} code
|
|
32
32
|
* @param {CodeLocation} location
|
|
33
33
|
*/
|
|
34
34
|
export function annotate(code, location) {
|
|
@@ -375,11 +375,12 @@ export function makePath(keys) {
|
|
|
375
375
|
const reference = annotate([markers.reference, headKey], head.location);
|
|
376
376
|
|
|
377
377
|
let code = [markers.traverse, reference, ...tail];
|
|
378
|
-
|
|
378
|
+
const location = spanLocations(code);
|
|
379
|
+
code = annotate(code, location);
|
|
379
380
|
|
|
380
381
|
// Last key has trailing slash implies unpack operation
|
|
381
382
|
if (trailingSlash.has(args.at(-1)[1])) {
|
|
382
|
-
code = annotate([ops.unpack, code],
|
|
383
|
+
code = annotate([ops.unpack, code], location);
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
return code;
|
package/src/runtime/ops.js
CHANGED
|
@@ -204,7 +204,7 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
|
|
|
204
204
|
export async function homeDirectory(...keys) {
|
|
205
205
|
const tree = new OrigamiFiles(os.homedir());
|
|
206
206
|
// Use the same handlers as the current tree
|
|
207
|
-
tree.handlers = getHandlers(this);
|
|
207
|
+
/** @type {any} */ (tree).handlers = getHandlers(this);
|
|
208
208
|
return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
|
|
209
209
|
}
|
|
210
210
|
addOpLabel(homeDirectory, "«ops.homeDirectory»");
|
|
@@ -424,7 +424,7 @@ addOpLabel(remainder, "«ops.remainder»");
|
|
|
424
424
|
export async function rootDirectory(...keys) {
|
|
425
425
|
const tree = new OrigamiFiles("/");
|
|
426
426
|
// Use the same handlers as the current tree
|
|
427
|
-
tree.handlers = getHandlers(this);
|
|
427
|
+
/** @type {any} */ (tree).handlers = getHandlers(this);
|
|
428
428
|
return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
|
|
429
429
|
}
|
|
430
430
|
addOpLabel(rootDirectory, "«ops.rootDirectory»");
|
|
@@ -7,6 +7,15 @@ export function assertCodeEqual(actual, expected) {
|
|
|
7
7
|
assert.deepStrictEqual(actualStripped, expectedStripped);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export function assertCodeLocations(code) {
|
|
11
|
+
assert(code.location, "no location");
|
|
12
|
+
for (const item of code) {
|
|
13
|
+
if (Array.isArray(item)) {
|
|
14
|
+
assertCodeLocations(item);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
/**
|
|
11
20
|
* Adds a fake source to code.
|
|
12
21
|
*
|
|
@@ -4,7 +4,11 @@ import * as compile from "../../src/compiler/compile.js";
|
|
|
4
4
|
import optimize from "../../src/compiler/optimize.js";
|
|
5
5
|
import { markers } from "../../src/compiler/parserHelpers.js";
|
|
6
6
|
import { ops } from "../../src/runtime/internal.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
assertCodeEqual,
|
|
9
|
+
assertCodeLocations,
|
|
10
|
+
createCode,
|
|
11
|
+
} from "./codeHelpers.js";
|
|
8
12
|
|
|
9
13
|
describe("optimize", () => {
|
|
10
14
|
test("change local references to context references", () => {
|
|
@@ -277,5 +281,6 @@ function assertCompile(expression, expected, mode = "shell") {
|
|
|
277
281
|
const globals = new ObjectTree({});
|
|
278
282
|
const fn = compile.expression(expression, { globals, mode, parent });
|
|
279
283
|
const actual = fn.code;
|
|
284
|
+
assertCodeLocations(actual);
|
|
280
285
|
assertCodeEqual(actual, expected);
|
|
281
286
|
}
|
|
@@ -3,7 +3,7 @@ import { describe, test } from "node:test";
|
|
|
3
3
|
import { parse } from "../../src/compiler/parse.js";
|
|
4
4
|
import { markers } from "../../src/compiler/parserHelpers.js";
|
|
5
5
|
import * as ops from "../../src/runtime/ops.js";
|
|
6
|
-
import { assertCodeEqual } from "./codeHelpers.js";
|
|
6
|
+
import { assertCodeEqual, assertCodeLocations } from "./codeHelpers.js";
|
|
7
7
|
|
|
8
8
|
describe("Origami parser", () => {
|
|
9
9
|
test("additiveExpression", () => {
|
|
@@ -1596,16 +1596,8 @@ function assertParse(
|
|
|
1596
1596
|
assertCodeEqual(code, expected);
|
|
1597
1597
|
}
|
|
1598
1598
|
|
|
1599
|
-
function assertCodeLocations(code) {
|
|
1600
|
-
assert(code.location, "no location");
|
|
1601
|
-
for (const item of code) {
|
|
1602
|
-
if (Array.isArray(item)) {
|
|
1603
|
-
assertCodeLocations(item);
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
1599
|
function assertThrows(startRule, source, message, position, mode = "shell") {
|
|
1600
|
+
// @ts-ignore We declare this so we can inspect it in debugger
|
|
1609
1601
|
let code;
|
|
1610
1602
|
try {
|
|
1611
1603
|
code = parse(source, {
|
|
@@ -54,7 +54,7 @@ describe("expressionObject", () => {
|
|
|
54
54
|
test("returned object values can be unpacked", async () => {
|
|
55
55
|
const entries = [["data.json", `{ "a": 1 }`]];
|
|
56
56
|
const parent = new ObjectTree({});
|
|
57
|
-
parent.handlers = {
|
|
57
|
+
/** @type {any} */ (parent).handlers = {
|
|
58
58
|
"json.handler": {
|
|
59
59
|
unpack: JSON.parse,
|
|
60
60
|
},
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -358,7 +358,7 @@ describe("ops", () => {
|
|
|
358
358
|
const a = await tree.get("a");
|
|
359
359
|
const b = await a.get("b");
|
|
360
360
|
const scope = await ops.scope.call(b);
|
|
361
|
-
assert.equal(await scope
|
|
361
|
+
assert.equal(await scope?.get("c"), 1);
|
|
362
362
|
});
|
|
363
363
|
|
|
364
364
|
test("accepts an optional context", async () => {
|
|
@@ -372,7 +372,7 @@ describe("ops", () => {
|
|
|
372
372
|
const a = await tree.get("a");
|
|
373
373
|
const b = await a.get("b");
|
|
374
374
|
const scope = await ops.scope.call(b, tree);
|
|
375
|
-
assert.equal(await scope
|
|
375
|
+
assert.equal(await scope?.get("c"), 1);
|
|
376
376
|
});
|
|
377
377
|
});
|
|
378
378
|
|