@weborigami/language 0.0.41 → 0.0.43
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/index.ts +18 -0
- package/main.js +1 -1
- package/package.json +3 -3
- package/src/compiler/compile.js +20 -14
- package/src/compiler/origami.pegjs +157 -77
- package/src/compiler/parse.d.ts +3 -0
- package/src/compiler/parse.js +1223 -800
- package/src/compiler/parserHelpers.js +10 -0
- package/src/runtime/concatTreeValues.js +6 -0
- package/src/runtime/evaluate.js +24 -20
- package/src/runtime/expressionFunction.js +3 -4
- package/src/runtime/formatError.js +52 -0
- package/src/runtime/internal.js +0 -2
- package/src/runtime/ops.js +1 -1
- package/test/compiler/compile.test.js +1 -1
- package/test/compiler/parse.test.js +124 -31
- package/src/compiler/code.d.ts +0 -3
- package/src/runtime/format.js +0 -127
- package/test/runtime/format.test.js +0 -66
package/index.ts
CHANGED
|
@@ -3,6 +3,15 @@ import { StringLike } from "../async-tree/index.js";
|
|
|
3
3
|
|
|
4
4
|
export * from "./main.js";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* A chunk of compiled Origami code. This is just an Array with an additional
|
|
8
|
+
* `source` property.
|
|
9
|
+
*/
|
|
10
|
+
interface ArrayWithSource extends Array<any> {
|
|
11
|
+
source?: Source;
|
|
12
|
+
}
|
|
13
|
+
export type Code = ArrayWithSource;
|
|
14
|
+
|
|
6
15
|
/**
|
|
7
16
|
* A class constructor is an object with a `new` method that returns an
|
|
8
17
|
* instance of the indicated type.
|
|
@@ -32,3 +41,12 @@ export type FileUnpackFunction = (
|
|
|
32
41
|
export type Mixin<MixinMembers> = <T>(
|
|
33
42
|
Base: Constructor<T>
|
|
34
43
|
) => Constructor<T & MixinMembers>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Source code representation used by the parser.
|
|
47
|
+
*/
|
|
48
|
+
export type Source = {
|
|
49
|
+
name: string;
|
|
50
|
+
text: string;
|
|
51
|
+
url: URL;
|
|
52
|
+
}
|
package/main.js
CHANGED
|
@@ -17,5 +17,5 @@ export { default as concatTreeValues } from "./src/runtime/concatTreeValues.js";
|
|
|
17
17
|
export { default as evaluate } from "./src/runtime/evaluate.js";
|
|
18
18
|
export * as expressionFunction from "./src/runtime/expressionFunction.js";
|
|
19
19
|
export { default as extname } from "./src/runtime/extname.js";
|
|
20
|
-
export { default as
|
|
20
|
+
export { default as formatError } from "./src/runtime/formatError.js";
|
|
21
21
|
export { default as functionResultsMap } from "./src/runtime/functionResultsMap.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.43",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"typescript": "5.3.3"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@weborigami/async-tree": "0.0.
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
13
|
+
"@weborigami/async-tree": "0.0.43",
|
|
14
|
+
"@weborigami/types": "0.0.43",
|
|
15
15
|
"peggy": "3.0.2",
|
|
16
16
|
"watcher": "2.3.0"
|
|
17
17
|
},
|
package/src/compiler/compile.js
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import { createExpressionFunction } from "../runtime/expressionFunction.js";
|
|
2
2
|
import { parse } from "./parse.js";
|
|
3
3
|
|
|
4
|
-
function compile(
|
|
4
|
+
function compile(source, startRule) {
|
|
5
|
+
if (typeof source === "string") {
|
|
6
|
+
source = { text: source };
|
|
7
|
+
}
|
|
5
8
|
// Trim whitespace from template blocks before we begin lexing, as our
|
|
6
9
|
// heuristics are non-local and hard to implement in our parser.
|
|
7
|
-
const preprocessed = trimTemplateWhitespace(text);
|
|
8
|
-
const code = parse(preprocessed, {
|
|
10
|
+
const preprocessed = trimTemplateWhitespace(source.text);
|
|
11
|
+
const code = parse(preprocessed, {
|
|
12
|
+
grammarSource: source,
|
|
13
|
+
startRule,
|
|
14
|
+
});
|
|
9
15
|
const fn = createExpressionFunction(code);
|
|
10
16
|
return fn;
|
|
11
17
|
}
|
|
12
18
|
|
|
13
|
-
export function expression(
|
|
14
|
-
return compile(
|
|
19
|
+
export function expression(source) {
|
|
20
|
+
return compile(source, "expression");
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
export function templateDocument(
|
|
18
|
-
return compile(
|
|
23
|
+
export function templateDocument(source) {
|
|
24
|
+
return compile(source, "templateDocument");
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
// Trim the whitespace around and in substitution blocks in a template. There's
|
|
@@ -24,14 +30,14 @@ export function templateDocument(text) {
|
|
|
24
30
|
//
|
|
25
31
|
// Example:
|
|
26
32
|
//
|
|
27
|
-
// {
|
|
33
|
+
// ${ if `
|
|
28
34
|
// true text
|
|
29
35
|
// `, `
|
|
30
36
|
// false text
|
|
31
|
-
// ` }
|
|
37
|
+
// ` }
|
|
32
38
|
//
|
|
33
39
|
// Case 1: a substitution that starts the text or starts a line (there's only
|
|
34
|
-
// whitespace before the
|
|
40
|
+
// whitespace before the `${`), and has the line end with the start of a
|
|
35
41
|
// template literal (there's only whitespace after the backtick) marks the start
|
|
36
42
|
// of a block.
|
|
37
43
|
//
|
|
@@ -39,17 +45,17 @@ export function templateDocument(text) {
|
|
|
39
45
|
// another is an internal break in the block. Edge case: three backticks in a
|
|
40
46
|
// row, like ```, are common in markdown and are not treated as a break.
|
|
41
47
|
//
|
|
42
|
-
// Case 3: a line that ends a template literal and ends with `}
|
|
48
|
+
// Case 3: a line that ends a template literal and ends with `}` or ends the
|
|
43
49
|
// text marks the end of the block.
|
|
44
50
|
//
|
|
45
51
|
// In all three cases, we trim spaces and tabs from the start and end of the
|
|
46
52
|
// line. In case 1, we also remove the preceding newline.
|
|
47
53
|
function trimTemplateWhitespace(text) {
|
|
48
|
-
const regex1 = /(^|\n)[ \t]*({{.*?`)[ \t]*\n/g;
|
|
54
|
+
const regex1 = /(^|\n)[ \t]*((?:{{|\${).*?`)[ \t]*\n/g;
|
|
49
55
|
const regex2 = /\n[ \t]*(`(?!`).*?`)[ \t]*\n/g;
|
|
50
|
-
const
|
|
56
|
+
const regex3js = /\n[ \t]*(`(?!`).*?(?:}}|[^\\]}))[ \t]*(?:\n|$)/g;
|
|
51
57
|
const trimBlockStarts = text.replace(regex1, "$1$2");
|
|
52
58
|
const trimBlockBreaks = trimBlockStarts.replace(regex2, "\n$1");
|
|
53
|
-
const trimBlockEnds = trimBlockBreaks.replace(
|
|
59
|
+
const trimBlockEnds = trimBlockBreaks.replace(regex3js, "\n$1");
|
|
54
60
|
return trimBlockEnds;
|
|
55
61
|
}
|
|
@@ -7,7 +7,17 @@
|
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
import * as ops from "../runtime/ops.js";
|
|
10
|
-
import { makeFunctionCall, makeTemplate } from "./parserHelpers.js";
|
|
10
|
+
import { makeFunctionCall, makePipeline, makeTemplate } from "./parserHelpers.js";
|
|
11
|
+
|
|
12
|
+
// If a parse result is an object that will be evaluated at runtime, attach the
|
|
13
|
+
// location of the source code that produced it for debugging and error messages.
|
|
14
|
+
function annotate(parseResult, location) {
|
|
15
|
+
if (typeof parseResult === "object" && parseResult !== null) {
|
|
16
|
+
parseResult.location = location;
|
|
17
|
+
}
|
|
18
|
+
return parseResult;
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
}}
|
|
12
22
|
|
|
13
23
|
// A block of optional whitespace
|
|
@@ -15,12 +25,23 @@ __
|
|
|
15
25
|
= (inlineSpace / newLine / comment)* { return ""; }
|
|
16
26
|
|
|
17
27
|
// A filesystem path that begins with a slash: `/foo/bar`
|
|
28
|
+
// We take care to avoid treating two consecutive leading slashes as a path;
|
|
29
|
+
// that starts a comment.
|
|
18
30
|
absoluteFilePath "absolute file path"
|
|
19
|
-
= path:leadingSlashPath {
|
|
31
|
+
= !"//" path:leadingSlashPath {
|
|
32
|
+
return annotate([[ops.filesRoot], ...path], location());
|
|
33
|
+
}
|
|
20
34
|
|
|
21
35
|
args "function arguments"
|
|
22
36
|
= parensArgs
|
|
23
|
-
/ path:leadingSlashPath {
|
|
37
|
+
/ path:leadingSlashPath {
|
|
38
|
+
return annotate([ops.traverse, ...path], location());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
array "array"
|
|
42
|
+
= "[" __ list:list? __ closingBracket {
|
|
43
|
+
return annotate([ops.array, ...(list ?? [])], location());
|
|
44
|
+
}
|
|
24
45
|
|
|
25
46
|
// An assignment statement: `foo = 1`
|
|
26
47
|
assignment "tree assignment"
|
|
@@ -28,10 +49,9 @@ assignment "tree assignment"
|
|
|
28
49
|
|
|
29
50
|
assignmentOrShorthand
|
|
30
51
|
= assignment
|
|
31
|
-
/ key:identifier {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
= "[" __ list:list? "]" { return [ops.array, ...(list ?? [])]; }
|
|
52
|
+
/ key:identifier {
|
|
53
|
+
return annotate([key, [ops.inherited, key]], location());
|
|
54
|
+
}
|
|
35
55
|
|
|
36
56
|
// Something that can be called. This is more restrictive than the `expr`
|
|
37
57
|
// parser; it doesn't accept regular function calls.
|
|
@@ -46,13 +66,41 @@ callTarget "function call"
|
|
|
46
66
|
/ group
|
|
47
67
|
/ scopeReference
|
|
48
68
|
|
|
69
|
+
// Required closing curly brace. We use this for the `tree` term: it's the last
|
|
70
|
+
// term in the `step` parser that starts with a curly brace, so if that parser
|
|
71
|
+
// sees a left curly brace, here we must see a right curly brace.
|
|
72
|
+
closingBrace
|
|
73
|
+
= "}"
|
|
74
|
+
/ .? {
|
|
75
|
+
error("Expected right curly brace");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Required closing bracket
|
|
79
|
+
closingBracket
|
|
80
|
+
= "]"
|
|
81
|
+
/ .? {
|
|
82
|
+
error("Expected right bracket");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Required closing parenthesis. We use this for the `group` term: it's the last
|
|
86
|
+
// term in the `step` parser that starts with a parenthesis, so if that parser
|
|
87
|
+
// sees a left parenthesis, here we must see a right parenthesis.
|
|
88
|
+
closingParen
|
|
89
|
+
= ")"
|
|
90
|
+
/ .? {
|
|
91
|
+
error("Expected right parenthesis");
|
|
92
|
+
}
|
|
93
|
+
|
|
49
94
|
// A single line comment
|
|
50
95
|
comment "comment"
|
|
51
|
-
=
|
|
96
|
+
= multiLineComment
|
|
97
|
+
/ singleLineComment
|
|
52
98
|
|
|
53
99
|
digits
|
|
54
100
|
= @[0-9]+
|
|
55
101
|
|
|
102
|
+
doubleArrow = "⇒" / "=>"
|
|
103
|
+
|
|
56
104
|
doubleQuoteString "double quote string"
|
|
57
105
|
= '"' chars:doubleQuoteStringChar* '"' { return chars.join(""); }
|
|
58
106
|
|
|
@@ -63,27 +111,8 @@ escapedChar "backslash-escaped character"
|
|
|
63
111
|
= "\\" @.
|
|
64
112
|
|
|
65
113
|
// An Origami expression, no leading/trailing whitespace
|
|
66
|
-
expr
|
|
67
|
-
|
|
68
|
-
// follow (array, object, etc.); we want to parse the largest thing first.
|
|
69
|
-
= implicitParensCall
|
|
70
|
-
/ functionComposition
|
|
71
|
-
// Then try parsers that look for a distinctive token at the start: an opening
|
|
72
|
-
// slash, bracket, curly brace, etc.
|
|
73
|
-
/ absoluteFilePath
|
|
74
|
-
/ array
|
|
75
|
-
/ object
|
|
76
|
-
/ tree
|
|
77
|
-
/ templateLiteral
|
|
78
|
-
/ lambda
|
|
79
|
-
/ parameterizedLambda
|
|
80
|
-
/ group
|
|
81
|
-
/ string
|
|
82
|
-
/ number
|
|
83
|
-
// Protocol calls are distinguished by a colon, but it's not at the start.
|
|
84
|
-
/ protocolCall
|
|
85
|
-
// Least distinctive option is a simple scope reference, so it comes last.
|
|
86
|
-
/ scopeReference
|
|
114
|
+
expr
|
|
115
|
+
= pipeline
|
|
87
116
|
|
|
88
117
|
// Top-level Origami expression, possible leading/trailing whitepsace.
|
|
89
118
|
expression "Origami expression"
|
|
@@ -97,36 +126,36 @@ float "floating-point number"
|
|
|
97
126
|
// Parse a function and its arguments, e.g. `fn(arg)`, possibly part of a chain
|
|
98
127
|
// of function calls, like `fn(arg1)(arg2)(arg3)`.
|
|
99
128
|
functionComposition "function composition"
|
|
100
|
-
|
|
101
|
-
|
|
129
|
+
= target:callTarget chain:args* end:implicitParensArgs? {
|
|
130
|
+
if (end) {
|
|
131
|
+
chain.push(end);
|
|
132
|
+
}
|
|
133
|
+
return annotate(makeFunctionCall(target, chain), location());
|
|
134
|
+
}
|
|
102
135
|
|
|
103
136
|
// An expression in parentheses: `(foo)`
|
|
104
137
|
group "parenthetical group"
|
|
105
|
-
= "(" __ @expr __
|
|
138
|
+
= "(" __ @expr __ closingParen
|
|
139
|
+
|
|
140
|
+
// A host identifier that may include a colon and port number: `example.com:80`.
|
|
141
|
+
// This is used as a special case at the head of a path, where we want to
|
|
142
|
+
// interpret a colon as part of a text identifier.
|
|
143
|
+
host "HTTP/HTTPS host"
|
|
144
|
+
= identifier (":" number)? { return text(); }
|
|
106
145
|
|
|
107
146
|
identifier "identifier"
|
|
108
147
|
= chars:identifierChar+ { return chars.join(""); }
|
|
109
148
|
|
|
110
149
|
identifierChar
|
|
111
|
-
= [^(){}\[\]
|
|
150
|
+
= [^(){}\[\]<>\-=,/:\`"'\\# →⇒\t\n\r] // No unescaped whitespace or special chars
|
|
151
|
+
/ @'-' !'>' // Accept a hyphen but not in a single arrow combination
|
|
112
152
|
/ escapedChar
|
|
113
153
|
|
|
114
154
|
identifierList
|
|
115
|
-
=
|
|
116
|
-
return [head].concat(tail);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// A function call with implicit parentheses: `fn 1, 2, 3`
|
|
120
|
-
implicitParensCall "function call with implicit parentheses"
|
|
121
|
-
= target:(functionComposition / callTarget) inlineSpace+ args:list {
|
|
122
|
-
return [target, ...args];
|
|
123
|
-
}
|
|
155
|
+
= @identifier|1.., separator| separator?
|
|
124
156
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// interpret a colon as part of a text identifier.
|
|
128
|
-
host "HTTP/HTTPS host"
|
|
129
|
-
= identifier (":" number)? { return text(); }
|
|
157
|
+
implicitParensArgs "arguments with implicit parentheses"
|
|
158
|
+
= inlineSpace+ @list
|
|
130
159
|
|
|
131
160
|
inlineSpace
|
|
132
161
|
= [ \t]
|
|
@@ -138,16 +167,21 @@ integer "integer"
|
|
|
138
167
|
|
|
139
168
|
// A lambda expression: `=foo()`
|
|
140
169
|
lambda "lambda function"
|
|
141
|
-
= "=" __ expr:expr {
|
|
170
|
+
= "=" __ expr:expr {
|
|
171
|
+
return annotate([ops.lambda, null, expr], location());
|
|
172
|
+
}
|
|
142
173
|
|
|
143
174
|
// A path that begins with a slash: `/foo/bar`
|
|
144
175
|
leadingSlashPath "path with a leading slash"
|
|
145
176
|
= "/" @path
|
|
146
|
-
/ "/" { return [""]; }
|
|
177
|
+
/ "/" { return annotate([""], location()); }
|
|
147
178
|
|
|
148
179
|
// A separated list of expressions
|
|
149
180
|
list "list"
|
|
150
|
-
=
|
|
181
|
+
= @expr|1.., separator| separator?
|
|
182
|
+
|
|
183
|
+
multiLineComment
|
|
184
|
+
= "/*" (!"*/" .)* "*/" { return null; }
|
|
151
185
|
|
|
152
186
|
newLine
|
|
153
187
|
= "\n"
|
|
@@ -164,13 +198,13 @@ number "number"
|
|
|
164
198
|
// TODO: Use Object.fromEntries with array of key/value pairs
|
|
165
199
|
//
|
|
166
200
|
object "object literal"
|
|
167
|
-
= "{" __ properties:objectProperties? "}" {
|
|
201
|
+
= "{" __ properties:objectProperties? __ "}" {
|
|
202
|
+
return annotate([ops.object, ...(properties ?? [])], location());
|
|
203
|
+
}
|
|
168
204
|
|
|
169
205
|
// A separated list of object properties or shorthands
|
|
170
206
|
objectProperties
|
|
171
|
-
=
|
|
172
|
-
return [head].concat(tail);
|
|
173
|
-
}
|
|
207
|
+
= @objectPropertyOrShorthand|1.., separator| separator?
|
|
174
208
|
|
|
175
209
|
// A single object property with key and value: `x: 1`
|
|
176
210
|
objectProperty "object property"
|
|
@@ -178,28 +212,29 @@ objectProperty "object property"
|
|
|
178
212
|
|
|
179
213
|
objectPropertyOrShorthand
|
|
180
214
|
= objectProperty
|
|
181
|
-
/ key:identifier {
|
|
215
|
+
/ key:identifier {
|
|
216
|
+
return annotate([key, [ops.scope, key]], location());
|
|
217
|
+
}
|
|
182
218
|
|
|
183
219
|
parameterizedLambda
|
|
184
|
-
= "(" __ parameters:identifierList? ")" __
|
|
185
|
-
return [ops.lambda, parameters ?? [], expr];
|
|
220
|
+
= "(" __ parameters:identifierList? __ ")" __ doubleArrow __ expr:expr {
|
|
221
|
+
return annotate([ops.lambda, parameters ?? [], expr], location());
|
|
186
222
|
}
|
|
187
223
|
|
|
188
224
|
// Function arguments in parentheses
|
|
189
225
|
parensArgs "function arguments in parentheses"
|
|
190
|
-
= "(" __ list:list? ")" {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
= __ "," __
|
|
194
|
-
/ whitespaceWithNewLine
|
|
226
|
+
= "(" __ list:list? __ ")" {
|
|
227
|
+
return list ?? annotate([undefined], location());
|
|
228
|
+
}
|
|
195
229
|
|
|
196
|
-
|
|
197
|
-
=
|
|
230
|
+
pipeline
|
|
231
|
+
= steps:(@step|1.., __ singleArrow __ |) {
|
|
232
|
+
return annotate(makePipeline(steps), location());
|
|
233
|
+
}
|
|
198
234
|
|
|
199
235
|
// A slash-separated path of keys
|
|
200
236
|
path "slash-separated path"
|
|
201
|
-
=
|
|
202
|
-
/ key:pathKey { return [key]; }
|
|
237
|
+
= pathKey|1.., "/"|
|
|
203
238
|
|
|
204
239
|
// A single key in a slash-separated path
|
|
205
240
|
pathKey "path element"
|
|
@@ -209,7 +244,7 @@ pathKey "path element"
|
|
|
209
244
|
// There can be zero, one, or two slashes after the colon.
|
|
210
245
|
protocolCall "function call using protocol: syntax"
|
|
211
246
|
= protocol:protocol ":" "/"|0..2| host:host path:leadingSlashPath? {
|
|
212
|
-
return [protocol, host, ...(path ?? [])];
|
|
247
|
+
return annotate([protocol, host, ...(path ?? [])], location());
|
|
213
248
|
}
|
|
214
249
|
|
|
215
250
|
protocol "protocol"
|
|
@@ -225,7 +260,22 @@ reservedProtocol "reserved protocol"
|
|
|
225
260
|
/ "tree" { return ops.treeHttps; } // Alias
|
|
226
261
|
|
|
227
262
|
scopeReference "scope reference"
|
|
228
|
-
= key:identifier {
|
|
263
|
+
= key:identifier {
|
|
264
|
+
return annotate([ops.scope, key], location());
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
separator
|
|
268
|
+
= __ "," __
|
|
269
|
+
/ whitespaceWithNewLine
|
|
270
|
+
|
|
271
|
+
sign
|
|
272
|
+
= [+\-]
|
|
273
|
+
|
|
274
|
+
singleArrow = "→" / "->"
|
|
275
|
+
|
|
276
|
+
singleLineComment
|
|
277
|
+
= "#" [^\n\r]* { return null; }
|
|
278
|
+
/ "//" [^\n\r]* { return null; }
|
|
229
279
|
|
|
230
280
|
singleQuoteString "single quote string"
|
|
231
281
|
= "'" chars:singleQuoteStringChar* "'" { return chars.join(""); }
|
|
@@ -233,6 +283,29 @@ singleQuoteString "single quote string"
|
|
|
233
283
|
singleQuoteStringChar
|
|
234
284
|
= !("'" / newLine) @textChar
|
|
235
285
|
|
|
286
|
+
// A single step in a pipeline, or a top-level expression
|
|
287
|
+
step
|
|
288
|
+
// Literals that can't start a function call
|
|
289
|
+
= number
|
|
290
|
+
// Try functions next; they can start with expression types that follow
|
|
291
|
+
// (array, object, etc.), and we want to parse the larger thing first.
|
|
292
|
+
/ functionComposition
|
|
293
|
+
// Then try parsers that look for a distinctive token at the start: an opening
|
|
294
|
+
// slash, bracket, curly brace, etc.
|
|
295
|
+
/ absoluteFilePath
|
|
296
|
+
/ array
|
|
297
|
+
/ object
|
|
298
|
+
/ tree
|
|
299
|
+
/ lambda
|
|
300
|
+
/ parameterizedLambda
|
|
301
|
+
/ templateLiteral
|
|
302
|
+
/ string
|
|
303
|
+
/ group
|
|
304
|
+
// Protocol calls are distinguished by a colon, but it's not at the start.
|
|
305
|
+
/ protocolCall
|
|
306
|
+
// Least distinctive option is a simple scope reference, so it comes last.
|
|
307
|
+
/ scopeReference
|
|
308
|
+
|
|
236
309
|
start
|
|
237
310
|
= number
|
|
238
311
|
|
|
@@ -243,15 +316,19 @@ string "string"
|
|
|
243
316
|
// A top-level document defining a template. This is the same as a template
|
|
244
317
|
// literal, but can contain backticks at the top level.
|
|
245
318
|
templateDocument "template"
|
|
246
|
-
= contents:templateDocumentContents {
|
|
319
|
+
= contents:templateDocumentContents {
|
|
320
|
+
return annotate([ops.lambda, null, contents], location());
|
|
321
|
+
}
|
|
247
322
|
|
|
248
323
|
// Template documents can contain backticks at the top level.
|
|
249
324
|
templateDocumentChar
|
|
250
|
-
= !"{{" @textChar
|
|
325
|
+
= !("{{" / "${") @textChar
|
|
251
326
|
|
|
252
327
|
// The contents of a template document containing plain text and substitutions
|
|
253
328
|
templateDocumentContents
|
|
254
|
-
= parts:(templateDocumentText / templateSubstitution)* {
|
|
329
|
+
= parts:(templateDocumentText / templateSubstitution)* {
|
|
330
|
+
return annotate(makeTemplate(parts), location());
|
|
331
|
+
}
|
|
255
332
|
|
|
256
333
|
templateDocumentText "template text"
|
|
257
334
|
= chars:templateDocumentChar+ { return chars.join(""); }
|
|
@@ -261,11 +338,13 @@ templateLiteral "template literal"
|
|
|
261
338
|
= "`" @templateLiteralContents "`"
|
|
262
339
|
|
|
263
340
|
templateLiteralChar
|
|
264
|
-
= !("`" / "{{") @textChar
|
|
341
|
+
= !("`" / "{{" / "${") @textChar
|
|
265
342
|
|
|
266
343
|
// The contents of a template literal containing plain text and substitutions
|
|
267
344
|
templateLiteralContents
|
|
268
|
-
= parts:(templateLiteralText / templateSubstitution)* {
|
|
345
|
+
= parts:(templateLiteralText / templateSubstitution)* {
|
|
346
|
+
return annotate(makeTemplate(parts), location());
|
|
347
|
+
}
|
|
269
348
|
|
|
270
349
|
// Plain text in a template literal
|
|
271
350
|
templateLiteralText
|
|
@@ -274,19 +353,20 @@ templateLiteralText
|
|
|
274
353
|
// A substitution in a template literal: `{{ fn() }}`
|
|
275
354
|
templateSubstitution "template substitution"
|
|
276
355
|
= "{{" @expression "}}"
|
|
356
|
+
/ "${" @expression "}"
|
|
277
357
|
|
|
278
358
|
textChar
|
|
279
359
|
= escapedChar / .
|
|
280
360
|
|
|
281
361
|
// A tree literal: `{ index.html = "Hello" }`
|
|
282
362
|
tree "tree literal"
|
|
283
|
-
= "{" __ assignments:treeAssignments?
|
|
363
|
+
= "{" __ assignments:treeAssignments? __ closingBrace {
|
|
364
|
+
return annotate([ops.tree, ...(assignments ?? [])], location());
|
|
365
|
+
}
|
|
284
366
|
|
|
285
367
|
// A separated list of assignments or shorthands
|
|
286
368
|
treeAssignments
|
|
287
|
-
=
|
|
288
|
-
return [head].concat(tail);
|
|
289
|
-
}
|
|
369
|
+
= @assignmentOrShorthand|1.., separator| separator?
|
|
290
370
|
|
|
291
371
|
whitespaceWithNewLine
|
|
292
372
|
= inlineSpace* comment? newLine __
|