@weborigami/language 0.3.3-jse.2 → 0.3.3-jse.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/main.js +2 -0
- package/package.json +3 -3
- package/src/compiler/compile.js +8 -2
- package/src/compiler/optimize.js +147 -93
- package/src/compiler/origami.pegjs +74 -60
- package/src/compiler/parse.js +946 -766
- package/src/compiler/parserHelpers.js +148 -48
- package/src/runtime/HandleExtensionsTransform.js +10 -1
- package/src/runtime/evaluate.js +28 -35
- package/src/runtime/expressionObject.js +4 -4
- package/src/runtime/getHandlers.js +10 -0
- package/src/runtime/handlers.js +14 -53
- package/src/runtime/jsGlobals.js +99 -0
- package/src/runtime/mergeTrees.js +0 -5
- package/src/runtime/ops.js +69 -148
- package/src/runtime/symbols.js +1 -0
- package/test/compiler/codeHelpers.js +3 -1
- package/test/compiler/compile.test.js +52 -27
- package/test/compiler/optimize.test.js +92 -23
- package/test/compiler/parse.test.js +480 -366
- package/test/runtime/evaluate.test.js +4 -20
- package/test/runtime/expressionObject.test.js +5 -4
- package/test/runtime/handlers.test.js +19 -10
- package/test/runtime/mergeTrees.test.js +0 -5
- package/test/runtime/ops.test.js +94 -82
package/main.js
CHANGED
|
@@ -7,10 +7,12 @@ export { default as evaluate } from "./src/runtime/evaluate.js";
|
|
|
7
7
|
export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
|
|
8
8
|
export * as expressionFunction from "./src/runtime/expressionFunction.js";
|
|
9
9
|
export { default as functionResultsMap } from "./src/runtime/functionResultsMap.js";
|
|
10
|
+
export { default as getHandlers } from "./src/runtime/getHandlers.js";
|
|
10
11
|
export { default as HandleExtensionsTransform } from "./src/runtime/HandleExtensionsTransform.js";
|
|
11
12
|
export * from "./src/runtime/handlers.js";
|
|
12
13
|
export { default as ImportModulesMixin } from "./src/runtime/ImportModulesMixin.js";
|
|
13
14
|
export { default as InvokeFunctionsTransform } from "./src/runtime/InvokeFunctionsTransform.js";
|
|
15
|
+
export { default as jsGlobals } from "./src/runtime/jsGlobals.js";
|
|
14
16
|
export * as moduleCache from "./src/runtime/moduleCache.js";
|
|
15
17
|
export { default as OrigamiFiles } from "./src/runtime/OrigamiFiles.js";
|
|
16
18
|
export * as symbols from "./src/runtime/symbols.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.3.3-jse.
|
|
3
|
+
"version": "0.3.3-jse.3",
|
|
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.3-jse.
|
|
15
|
-
"@weborigami/types": "0.3.3-jse.
|
|
14
|
+
"@weborigami/async-tree": "0.3.3-jse.3",
|
|
15
|
+
"@weborigami/types": "0.3.3-jse.3",
|
|
16
16
|
"watcher": "2.3.1",
|
|
17
17
|
"yaml": "2.7.0"
|
|
18
18
|
},
|
package/src/compiler/compile.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { createExpressionFunction } from "../runtime/expressionFunction.js";
|
|
2
|
+
import jsGlobals from "../runtime/jsGlobals.js";
|
|
2
3
|
import optimize from "./optimize.js";
|
|
3
4
|
import { parse } from "./parse.js";
|
|
4
5
|
|
|
5
6
|
function compile(source, options) {
|
|
6
|
-
const {
|
|
7
|
+
const { startRule } = options;
|
|
7
8
|
const mode = options.mode ?? "shell";
|
|
9
|
+
const globals = options.globals ?? jsGlobals;
|
|
8
10
|
const enableCaching = options.scopeCaching ?? true;
|
|
9
11
|
if (typeof source === "string") {
|
|
10
12
|
source = { text: source };
|
|
@@ -14,7 +16,11 @@ function compile(source, options) {
|
|
|
14
16
|
mode,
|
|
15
17
|
startRule,
|
|
16
18
|
});
|
|
17
|
-
const optimized = optimize(code,
|
|
19
|
+
const optimized = optimize(code, {
|
|
20
|
+
enableCaching,
|
|
21
|
+
globals,
|
|
22
|
+
mode,
|
|
23
|
+
});
|
|
18
24
|
const fn = createExpressionFunction(optimized);
|
|
19
25
|
return fn;
|
|
20
26
|
}
|
package/src/compiler/optimize.js
CHANGED
|
@@ -1,40 +1,60 @@
|
|
|
1
1
|
import { pathFromKeys, trailingSlash } from "@weborigami/async-tree";
|
|
2
2
|
import { ops } from "../runtime/internal.js";
|
|
3
|
-
import
|
|
3
|
+
import jsGlobals from "../runtime/jsGlobals.js";
|
|
4
|
+
import { annotate, markers } from "./parserHelpers.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Optimize an Origami code instruction:
|
|
7
8
|
*
|
|
8
|
-
* - Transform any remaining
|
|
9
|
+
* - Transform any remaining reference references to scope references.
|
|
9
10
|
* - Transform those or explicit ops.scope calls to ops.external calls unless
|
|
10
11
|
* they refer to local variables (variables defined by object literals or
|
|
11
12
|
* lambda parameters).
|
|
12
|
-
* - Apply any macros to the code.
|
|
13
13
|
*
|
|
14
14
|
* @typedef {import("./parserHelpers.js").AnnotatedCode} AnnotatedCode
|
|
15
15
|
* @typedef {import("./parserHelpers.js").Code} Code
|
|
16
16
|
*
|
|
17
17
|
* @param {AnnotatedCode} code
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {Record<string, AnnotatedCode>} macros
|
|
20
|
-
* @param {Record<string, AnnotatedCode>} cache
|
|
21
|
-
* @param {Record<string, boolean>} locals
|
|
18
|
+
* @param {any} options
|
|
22
19
|
* @returns {AnnotatedCode}
|
|
23
20
|
*/
|
|
24
|
-
export default function optimize(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
cache = {}
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
export default function optimize(code, options = {}) {
|
|
22
|
+
const enableCaching = options.enableCaching ?? true;
|
|
23
|
+
const globals = options.globals ?? jsGlobals;
|
|
24
|
+
const mode = options.mode ?? "shell";
|
|
25
|
+
const cache = options.cache ?? {};
|
|
26
|
+
|
|
27
|
+
// The locals is an array, one item for each function or object context that
|
|
28
|
+
// has been entered. The array grows to the right. The array items are
|
|
29
|
+
// subarrays containing the names of local variables defined in that context.
|
|
30
|
+
const locals = options.locals ? options.locals.slice() : [];
|
|
31
|
+
|
|
32
|
+
const externalScope =
|
|
33
|
+
mode === "shell"
|
|
34
|
+
? // External scope is parent scope + globals
|
|
35
|
+
annotate(
|
|
36
|
+
[ops.merge, globals, annotate([ops.scope], code.location)],
|
|
37
|
+
code.location
|
|
38
|
+
)
|
|
39
|
+
: annotate([ops.scope], code.location);
|
|
40
|
+
|
|
31
41
|
// See if we can optimize this level of the code
|
|
32
42
|
const [fn, ...args] = code;
|
|
33
|
-
let
|
|
43
|
+
let optimized = code;
|
|
44
|
+
let externalReference = fn instanceof Array && fn[0] === ops.scope;
|
|
45
|
+
let depth;
|
|
34
46
|
switch (fn) {
|
|
47
|
+
case markers.global:
|
|
48
|
+
// Replace global op with the globals
|
|
49
|
+
optimized = annotate([globals, args[0]], code.location);
|
|
50
|
+
break;
|
|
51
|
+
|
|
35
52
|
case ops.lambda:
|
|
36
53
|
const parameters = args[0];
|
|
37
|
-
|
|
54
|
+
if (parameters.length > 0) {
|
|
55
|
+
const names = parameters.map((param) => param[1]);
|
|
56
|
+
locals.push(names);
|
|
57
|
+
}
|
|
38
58
|
break;
|
|
39
59
|
|
|
40
60
|
case ops.literal:
|
|
@@ -46,97 +66,131 @@ export default function optimize(
|
|
|
46
66
|
|
|
47
67
|
case ops.object:
|
|
48
68
|
const entries = args;
|
|
49
|
-
|
|
69
|
+
const keys = entries.map(([key]) => propertyName(key));
|
|
70
|
+
locals.push(keys);
|
|
50
71
|
break;
|
|
51
72
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Apply macro
|
|
59
|
-
const macro = macros?.[normalizedKey];
|
|
60
|
-
return applyMacro(macro, code, enableCaching, macros, cache, locals);
|
|
61
|
-
} else if (enableCaching && !locals[normalizedKey]) {
|
|
62
|
-
// Upgrade to cached external scope reference
|
|
63
|
-
return annotate(
|
|
64
|
-
[ops.external, key, annotate([ops.scope, key], code.location), cache],
|
|
65
|
-
code.location
|
|
66
|
-
);
|
|
67
|
-
} else if (fn === undetermined) {
|
|
68
|
-
// Transform undetermined reference to regular scope call
|
|
69
|
-
return annotate([ops.scope, key], code.location);
|
|
70
|
-
} else {
|
|
71
|
-
// Internal ops.scope call; leave as is
|
|
72
|
-
return code;
|
|
73
|
+
case markers.reference:
|
|
74
|
+
// Determine whether reference is local and, if so, transform to
|
|
75
|
+
// ops.local call. Otherwise transform to ops.scope call.
|
|
76
|
+
let key = args[0];
|
|
77
|
+
if (key instanceof Array && key[0] === ops.literal) {
|
|
78
|
+
key = key[1];
|
|
73
79
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!locals[normalizedKey]) {
|
|
83
|
-
// Are the remaining arguments all literals?
|
|
84
|
-
const allLiterals = args
|
|
85
|
-
.slice(1)
|
|
86
|
-
.every((arg) => arg[0] === ops.literal);
|
|
87
|
-
if (allLiterals) {
|
|
88
|
-
// Convert to ops.external
|
|
89
|
-
const keys = args.map((arg) => arg[1]);
|
|
90
|
-
const path = pathFromKeys(keys);
|
|
91
|
-
/** @type {Code} */
|
|
92
|
-
const optimized = [ops.external, path, code, cache];
|
|
93
|
-
return annotate(optimized, code.location);
|
|
94
|
-
}
|
|
80
|
+
const normalizedKey = trailingSlash.remove(key);
|
|
81
|
+
let target;
|
|
82
|
+
depth = getLocalReferenceDepth(locals, normalizedKey);
|
|
83
|
+
if (depth >= 0) {
|
|
84
|
+
// Transform local reference
|
|
85
|
+
const contextCode = [ops.context];
|
|
86
|
+
if (depth > 0) {
|
|
87
|
+
contextCode.push(depth);
|
|
95
88
|
}
|
|
89
|
+
target = annotate(contextCode, code.location);
|
|
90
|
+
} else if (mode === "shell") {
|
|
91
|
+
// Transform non-local reference
|
|
92
|
+
target = externalScope;
|
|
93
|
+
externalReference = true;
|
|
94
|
+
} else if (mode === "jse") {
|
|
95
|
+
target = globals;
|
|
96
96
|
}
|
|
97
|
+
optimized = annotate([target, ...args], code.location);
|
|
97
98
|
break;
|
|
98
|
-
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
case ops.scope:
|
|
101
|
+
depth = locals.length;
|
|
102
|
+
if (depth === 0) {
|
|
103
|
+
// Use scope call as is
|
|
104
|
+
optimized = code;
|
|
105
|
+
} else {
|
|
106
|
+
// Add context for appropriate depth to scope call
|
|
107
|
+
const contextCode = annotate([ops.context, depth], code.location);
|
|
108
|
+
optimized = annotate([ops.scope, contextCode], code.location);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
// Optimize children
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
optimized = annotate(
|
|
115
|
+
optimized.map((child, index) => {
|
|
116
|
+
// Don't optimize lambda parameter names
|
|
117
|
+
if (fn === ops.lambda && index === 1) {
|
|
118
|
+
return child;
|
|
119
|
+
} else if (fn === ops.object && index > 0) {
|
|
120
|
+
// Code that defines a property `x` that contains references to `x`
|
|
121
|
+
// shouldn't find this context but look further up.
|
|
122
|
+
const [key, value] = child;
|
|
123
|
+
const normalizedKey = trailingSlash.remove(key);
|
|
124
|
+
let adjustedLocals;
|
|
125
|
+
if (locals.at(-1)?.includes(normalizedKey)) {
|
|
126
|
+
adjustedLocals = locals.slice();
|
|
127
|
+
// Remove the key from the current context's locals
|
|
128
|
+
adjustedLocals[adjustedLocals.length - 1] = locals
|
|
129
|
+
.at(-1)
|
|
130
|
+
.filter((name) => name !== normalizedKey);
|
|
131
|
+
} else {
|
|
132
|
+
adjustedLocals = locals;
|
|
133
|
+
}
|
|
134
|
+
return [
|
|
135
|
+
key,
|
|
136
|
+
optimize(/** @type {AnnotatedCode} */ (value), {
|
|
137
|
+
...options,
|
|
138
|
+
locals: adjustedLocals,
|
|
139
|
+
}),
|
|
140
|
+
];
|
|
141
|
+
} else if (Array.isArray(child) && "location" in child) {
|
|
142
|
+
// Review: Aside from ops.object (above), what non-instruction arrays
|
|
143
|
+
// does this descend into?
|
|
144
|
+
return optimize(/** @type {AnnotatedCode} */ (child), {
|
|
145
|
+
...options,
|
|
146
|
+
locals,
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
return child;
|
|
150
|
+
}
|
|
151
|
+
}),
|
|
152
|
+
optimized.location
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Cache external scope or merged globals + scope references
|
|
156
|
+
if (enableCaching && externalReference) {
|
|
157
|
+
// Get all the keys so we can construct a path as a cache key
|
|
158
|
+
const keys = optimized
|
|
159
|
+
.slice(1)
|
|
160
|
+
.map((arg) =>
|
|
161
|
+
typeof arg === "string" || typeof arg === "number"
|
|
162
|
+
? arg
|
|
163
|
+
: arg instanceof Array && arg[0] === ops.literal
|
|
164
|
+
? arg[1]
|
|
165
|
+
: null
|
|
128
166
|
);
|
|
129
|
-
|
|
130
|
-
|
|
167
|
+
if (keys.some((key) => key === null)) {
|
|
168
|
+
throw new Error("Internal error: scope reference with non-literal key");
|
|
131
169
|
}
|
|
132
|
-
|
|
170
|
+
const path = pathFromKeys(keys);
|
|
171
|
+
optimized = annotate(
|
|
172
|
+
[ops.cache, cache, path, optimized],
|
|
173
|
+
optimized.location
|
|
174
|
+
);
|
|
175
|
+
}
|
|
133
176
|
|
|
134
177
|
return annotate(optimized, code.location);
|
|
135
178
|
}
|
|
136
179
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
180
|
+
// Determine how many contexts up we need to go for a local
|
|
181
|
+
function getLocalReferenceDepth(locals, key) {
|
|
182
|
+
const contextIndex = locals.findLastIndex((names) => names.includes(key));
|
|
183
|
+
if (contextIndex < 0) {
|
|
184
|
+
return -1; // Not a local reference
|
|
185
|
+
}
|
|
186
|
+
const depth = locals.length - contextIndex - 1;
|
|
187
|
+
return depth;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function propertyName(key) {
|
|
191
|
+
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
192
|
+
// Non-enumerable property, remove parentheses
|
|
193
|
+
key = key.slice(1, -1);
|
|
194
|
+
}
|
|
195
|
+
return trailingSlash.remove(key);
|
|
142
196
|
}
|
|
@@ -20,14 +20,15 @@ import {
|
|
|
20
20
|
makeBinaryOperation,
|
|
21
21
|
makeCall,
|
|
22
22
|
makeDeferredArguments,
|
|
23
|
+
makeDocument,
|
|
23
24
|
makeJsPropertyAccess,
|
|
24
25
|
makeObject,
|
|
25
26
|
makePipeline,
|
|
26
27
|
makeProperty,
|
|
27
|
-
makeReference,
|
|
28
28
|
makeTemplate,
|
|
29
29
|
makeUnaryOperation,
|
|
30
|
-
makeYamlObject
|
|
30
|
+
makeYamlObject,
|
|
31
|
+
markers,
|
|
31
32
|
} from "./parserHelpers.js";
|
|
32
33
|
import isOrigamiFrontMatter from "./isOrigamiFrontMatter.js";
|
|
33
34
|
|
|
@@ -49,31 +50,32 @@ additiveOperator
|
|
|
49
50
|
/ "-"
|
|
50
51
|
|
|
51
52
|
angleBracketLiteral
|
|
52
|
-
= "<" __ protocol:angleBracketProtocol "//" path:angleBracketPath __ ">" {
|
|
53
|
+
= "<" __ protocol:angleBracketProtocol "//"? path:angleBracketPath __ ">" {
|
|
53
54
|
return annotate([protocol, ...path], location());
|
|
54
55
|
}
|
|
55
|
-
/ "<"
|
|
56
|
-
const [
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
/ "<" __ "/" path:angleBracketPath __ ">" {
|
|
57
|
+
const root = annotate([ops.rootDirectory], location());
|
|
58
|
+
return path.length > 0 ? annotate([root, ...path], location()) : root;
|
|
59
|
+
}
|
|
60
|
+
/ "<" __ "~" "/"? path:angleBracketPath __ ">" {
|
|
61
|
+
const home = annotate([ops.homeDirectory], location());
|
|
62
|
+
return path.length > 0 ? annotate([home, ...path], location()) : home;
|
|
59
63
|
}
|
|
60
64
|
/ "<" __ path:angleBracketPath __ ">" {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
: annotate([ops.traverse, root, ...tail], location())
|
|
66
|
-
}
|
|
65
|
+
// Angle bracket paths always reference scope
|
|
66
|
+
const scope = annotate([ops.scope], location());
|
|
67
|
+
return annotate([scope, ...path], location());
|
|
68
|
+
}
|
|
67
69
|
|
|
68
70
|
angleBracketPath
|
|
69
71
|
= @angleBracketPathKey|0.., "/"| "/"?
|
|
70
72
|
|
|
71
73
|
angleBracketPathKey
|
|
72
74
|
= chars:angleBracketPathChar+ slashFollows:slashFollows? {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// Append a trailing slash if one follows (but don't consume it)
|
|
76
|
+
const key = chars.join("") + (slashFollows ? "/" : "");
|
|
77
|
+
return annotate([ops.literal, key], location());
|
|
78
|
+
}
|
|
77
79
|
|
|
78
80
|
// A single character in a slash-separated path segment
|
|
79
81
|
angleBracketPathChar
|
|
@@ -82,7 +84,7 @@ angleBracketPathChar
|
|
|
82
84
|
|
|
83
85
|
angleBracketProtocol
|
|
84
86
|
= protocol:jsIdentifier ":" {
|
|
85
|
-
return annotate([
|
|
87
|
+
return annotate([markers.global, `${protocol[1]}:`], location());
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
arguments "function arguments"
|
|
@@ -150,7 +152,7 @@ bitwiseXorOperator
|
|
|
150
152
|
// `fn(arg1)(arg2)(arg3)`.
|
|
151
153
|
callExpression "function call"
|
|
152
154
|
= head:protocolExpression tail:arguments* {
|
|
153
|
-
return tail.reduce(makeCall, head);
|
|
155
|
+
return tail.reduce((target, args) => makeCall(target, args, options.mode), head);
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
// A comma-separated list of expressions: `x, y, z`
|
|
@@ -169,7 +171,7 @@ comment "comment"
|
|
|
169
171
|
|
|
170
172
|
computedPropertyAccess
|
|
171
173
|
= __ "[" expression:expression expectClosingBracket {
|
|
172
|
-
return annotate([
|
|
174
|
+
return annotate([markers.traverse, expression], location());
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
conditionalExpression
|
|
@@ -362,7 +364,7 @@ identifierChar
|
|
|
362
364
|
|
|
363
365
|
implicitParenthesesCallExpression "function call with implicit parentheses"
|
|
364
366
|
= head:arrowFunction args:(inlineSpace+ @implicitParensthesesArguments)? {
|
|
365
|
-
return args ? makeCall(head, args) : head;
|
|
367
|
+
return args ? makeCall(head, args, options.mode) : head;
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
// A separated list of values for an implicit parens call. This differs from
|
|
@@ -384,7 +386,9 @@ jseMode
|
|
|
384
386
|
= &{ return options.mode === "jse" }
|
|
385
387
|
|
|
386
388
|
jsIdentifier
|
|
387
|
-
=
|
|
389
|
+
= id:$( jsIdentifierStart jsIdentifierPart* ) {
|
|
390
|
+
return annotate([ops.literal, id], location());
|
|
391
|
+
}
|
|
388
392
|
|
|
389
393
|
// Continuation of a JavaScript identifier
|
|
390
394
|
// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#prod-IdentifierPart
|
|
@@ -398,13 +402,12 @@ jsIdentifierStart "JavaScript identifier start"
|
|
|
398
402
|
|
|
399
403
|
jsPropertyAccess
|
|
400
404
|
= __ "." __ property:jsIdentifier {
|
|
401
|
-
|
|
402
|
-
return annotate([ops.traverse, literal], location());
|
|
405
|
+
return annotate([markers.traverse, property], location());
|
|
403
406
|
}
|
|
404
407
|
|
|
405
408
|
jsReference "identifier reference"
|
|
406
409
|
= id:jsIdentifier {
|
|
407
|
-
return annotate([
|
|
410
|
+
return annotate([markers.reference, id], location());
|
|
408
411
|
}
|
|
409
412
|
|
|
410
413
|
// A separated list of values
|
|
@@ -449,7 +452,7 @@ multiplicativeOperator
|
|
|
449
452
|
// A namespace reference is a string of letters only, followed by a colon.
|
|
450
453
|
namespace
|
|
451
454
|
= chars:[A-Za-z]+ ":" {
|
|
452
|
-
return annotate([
|
|
455
|
+
return annotate([markers.global, chars.join("") + ":"], location());
|
|
453
456
|
}
|
|
454
457
|
|
|
455
458
|
// A new expression: `new Foo()`
|
|
@@ -458,6 +461,10 @@ newExpression
|
|
|
458
461
|
const args = tail?.[0] !== undefined ? tail : [];
|
|
459
462
|
return annotate([ops.construct, head, ...args], location());
|
|
460
463
|
}
|
|
464
|
+
/ "new:" head:jsReference tail:parenthesesArguments {
|
|
465
|
+
const args = tail?.[0] !== undefined ? tail : [];
|
|
466
|
+
return annotate([ops.construct, head, ...args], location());
|
|
467
|
+
}
|
|
461
468
|
|
|
462
469
|
newLine
|
|
463
470
|
= "\n"
|
|
@@ -522,9 +529,16 @@ objectProperty "object property"
|
|
|
522
529
|
// A shorthand reference inside an object literal: `foo`
|
|
523
530
|
objectShorthandProperty "object identifier"
|
|
524
531
|
= key:objectPublicKey {
|
|
525
|
-
const
|
|
526
|
-
return annotate([key,
|
|
532
|
+
const reference = annotate([markers.reference, key], location());
|
|
533
|
+
return annotate([key, reference], location());
|
|
527
534
|
}
|
|
535
|
+
/ jseMode path:angleBracketLiteral {
|
|
536
|
+
let lastKey = path.at(-1);
|
|
537
|
+
if (lastKey instanceof Array) {
|
|
538
|
+
lastKey = lastKey[1]; // get scope identifier or literal
|
|
539
|
+
}
|
|
540
|
+
return annotate([lastKey, path], location());
|
|
541
|
+
}
|
|
528
542
|
|
|
529
543
|
objectPublicKey
|
|
530
544
|
= identifier:identifier slash:"/"? {
|
|
@@ -537,11 +551,12 @@ objectPublicKey
|
|
|
537
551
|
|
|
538
552
|
optionalChaining
|
|
539
553
|
= __ "?." __ property:jsIdentifier {
|
|
540
|
-
|
|
541
|
-
return annotate([ops.optionalTraverse, literal], location());
|
|
554
|
+
return annotate([ops.optionalTraverse, property], location());
|
|
542
555
|
}
|
|
556
|
+
|
|
543
557
|
parameter
|
|
544
|
-
=
|
|
558
|
+
= jseMode @jsIdentifier
|
|
559
|
+
/ shellMode identifier:identifier {
|
|
545
560
|
return annotate([ops.literal, identifier], location());
|
|
546
561
|
}
|
|
547
562
|
|
|
@@ -577,7 +592,7 @@ path "slash-separated path"
|
|
|
577
592
|
// A slash-separated path of keys that follows a call target
|
|
578
593
|
pathArguments
|
|
579
594
|
= path:path {
|
|
580
|
-
return annotate([
|
|
595
|
+
return annotate([markers.traverse, ...path], location());
|
|
581
596
|
}
|
|
582
597
|
|
|
583
598
|
// A single key in a slash-separated path: `/a`
|
|
@@ -603,7 +618,7 @@ pathSegmentChar
|
|
|
603
618
|
pipelineExpression
|
|
604
619
|
= head:shorthandFunction tail:(__ singleArrow __ @shorthandFunction)* {
|
|
605
620
|
return annotate(
|
|
606
|
-
tail.reduce(makePipeline, downgradeReference(head)),
|
|
621
|
+
tail.reduce((arg, fn) => makePipeline(arg, fn, options.mode), downgradeReference(head)),
|
|
607
622
|
location()
|
|
608
623
|
);
|
|
609
624
|
}
|
|
@@ -639,7 +654,7 @@ program "Origami program"
|
|
|
639
654
|
protocolExpression
|
|
640
655
|
= fn:namespace "//" host:(host / slash) path:path? {
|
|
641
656
|
const keys = annotate([host, ...(path ?? [])], location());
|
|
642
|
-
return makeCall(fn, keys);
|
|
657
|
+
return makeCall(fn, keys, options.mode);
|
|
643
658
|
}
|
|
644
659
|
/ newExpression
|
|
645
660
|
/ primary
|
|
@@ -647,8 +662,7 @@ protocolExpression
|
|
|
647
662
|
// A namespace followed by a key: `foo:x`
|
|
648
663
|
qualifiedReference
|
|
649
664
|
= fn:namespace reference:scopeReference {
|
|
650
|
-
|
|
651
|
-
return makeCall(fn, [literal]);
|
|
665
|
+
return makeCall(fn, [reference[1]], options.mode);
|
|
652
666
|
}
|
|
653
667
|
|
|
654
668
|
regexFlags
|
|
@@ -667,7 +681,11 @@ regexLiteralChar
|
|
|
667
681
|
/ escapedChar
|
|
668
682
|
|
|
669
683
|
relationalExpression
|
|
670
|
-
|
|
684
|
+
// We disallow a newline before the relational operator to support a newline
|
|
685
|
+
// as a separator in an object literal that has an object shorthand property
|
|
686
|
+
// with an angle bracket path. Otherwise the opening angle bracket would be
|
|
687
|
+
// interpreted as a relational operator.
|
|
688
|
+
= head:shiftExpression tail:(inlineSpace @relationalOperator __ @shiftExpression)* {
|
|
671
689
|
return tail.reduce(makeBinaryOperation, head);
|
|
672
690
|
}
|
|
673
691
|
|
|
@@ -677,25 +695,22 @@ relationalOperator
|
|
|
677
695
|
/ ">="
|
|
678
696
|
/ ">"
|
|
679
697
|
|
|
680
|
-
//
|
|
681
|
-
// or the root folder itself: `/`
|
|
698
|
+
// The root folder: `/`
|
|
682
699
|
rootDirectory
|
|
683
|
-
= "/"
|
|
684
|
-
return annotate([ops.rootDirectory, key], location());
|
|
685
|
-
}
|
|
686
|
-
/ "/" !"/" {
|
|
700
|
+
= &("/" !"/") {
|
|
687
701
|
return annotate([ops.rootDirectory], location());
|
|
688
702
|
}
|
|
689
703
|
|
|
690
704
|
scopeReference "scope reference"
|
|
691
705
|
= identifier:identifier slashFollows:slashFollows? {
|
|
692
706
|
const id = identifier + (slashFollows ? "/" : "");
|
|
693
|
-
|
|
707
|
+
const idCode = annotate([ops.literal, identifier], location());
|
|
708
|
+
return annotate([markers.reference, idCode], location());
|
|
694
709
|
}
|
|
695
710
|
|
|
696
711
|
separator
|
|
697
712
|
= __ "," __
|
|
698
|
-
/
|
|
713
|
+
/ @whitespaceWithNewLine
|
|
699
714
|
|
|
700
715
|
shebang
|
|
701
716
|
= "#!" [^\n\r]* { return null; }
|
|
@@ -754,7 +769,7 @@ slashFollows
|
|
|
754
769
|
}
|
|
755
770
|
|
|
756
771
|
spreadElement
|
|
757
|
-
= ellipsis __ value:
|
|
772
|
+
= ellipsis __ value:expectPipelineExpression {
|
|
758
773
|
return annotate([ops.spread, value], location());
|
|
759
774
|
}
|
|
760
775
|
|
|
@@ -767,14 +782,7 @@ stringLiteral "string"
|
|
|
767
782
|
// contain backticks at the top level.
|
|
768
783
|
templateBody "template"
|
|
769
784
|
= head:templateBodyText tail:(templateSubstitution templateBodyText)* {
|
|
770
|
-
|
|
771
|
-
[annotate([ops.literal, "_"], location())],
|
|
772
|
-
location()
|
|
773
|
-
);
|
|
774
|
-
return annotate(
|
|
775
|
-
[ops.lambda, lambdaParameters, makeTemplate(ops.templateIndent, head, tail, location())],
|
|
776
|
-
location()
|
|
777
|
-
);
|
|
785
|
+
return makeTemplate(ops.templateIndent, head, tail, location());
|
|
778
786
|
}
|
|
779
787
|
|
|
780
788
|
// Template document bodies can contain backticks at the top level
|
|
@@ -791,10 +799,15 @@ templateDocument "template document"
|
|
|
791
799
|
const macroName = options.mode === "jse" ? "_template" : "@template";
|
|
792
800
|
return annotate(applyMacro(front, macroName, body), location());
|
|
793
801
|
}
|
|
794
|
-
/ front:frontMatterYaml
|
|
795
|
-
return front
|
|
796
|
-
|
|
797
|
-
|
|
802
|
+
/ front:frontMatterYaml body:templateBody {
|
|
803
|
+
return makeDocument(options.mode, front, body, location());
|
|
804
|
+
}
|
|
805
|
+
/ body:templateBody {
|
|
806
|
+
const lambdaParameters = annotate(
|
|
807
|
+
[annotate([ops.literal, "_"], location())],
|
|
808
|
+
location()
|
|
809
|
+
);
|
|
810
|
+
return annotate([ops.lambda, lambdaParameters, body], location());
|
|
798
811
|
}
|
|
799
812
|
|
|
800
813
|
// A backtick-quoted template literal
|
|
@@ -836,14 +849,15 @@ unaryOperator
|
|
|
836
849
|
// Don't match a front matter delimiter. For some reason, the negative
|
|
837
850
|
// lookahead !"--\n" doesn't work.
|
|
838
851
|
/ @"-" !"-\n"
|
|
839
|
-
|
|
852
|
+
// Don't match a path that starts with a tilde: ~/foo
|
|
853
|
+
/ @"~" !"/"
|
|
840
854
|
|
|
841
855
|
whitespace
|
|
842
856
|
= inlineSpace
|
|
843
857
|
/ newLine
|
|
844
858
|
/ comment
|
|
845
859
|
|
|
846
|
-
// Whitespace
|
|
860
|
+
// Whitespace required in shell mode, optional in JSE mode
|
|
847
861
|
whitespaceShell
|
|
848
862
|
= shellMode whitespace
|
|
849
863
|
/ jseMode __
|