@weborigami/language 0.0.39 → 0.0.41
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 +1 -0
- package/package.json +5 -5
- package/src/compiler/origami.pegjs +57 -43
- package/src/compiler/parse.js +602 -226
- package/src/compiler/parserHelpers.js +5 -1
- package/src/runtime/FileLoadersTransform.js +1 -1
- package/src/runtime/concatTreeValues.js +15 -2
- package/src/runtime/evaluate.js +20 -14
- package/src/runtime/expressionFunction.js +1 -0
- package/src/runtime/format.js +2 -1
- package/src/runtime/ops.js +45 -10
- package/test/compiler/parse.test.js +53 -20
- package/test/runtime/InheritScopeMixin.test.js +2 -2
- package/test/runtime/evaluate.test.js +8 -0
- package/test/runtime/format.test.js +1 -1
- package/test/runtime/ops.test.js +17 -4
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
7
7
|
"types": "./index.ts",
|
|
8
8
|
"devDependencies": {
|
|
9
|
-
"@types/node": "20.11.
|
|
9
|
+
"@types/node": "20.11.7",
|
|
10
10
|
"typescript": "5.3.3"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@weborigami/
|
|
14
|
-
"@weborigami/
|
|
13
|
+
"@weborigami/async-tree": "0.0.41",
|
|
14
|
+
"@weborigami/types": "0.0.41",
|
|
15
15
|
"peggy": "3.0.2",
|
|
16
16
|
"watcher": "2.3.0"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "peggy --allowed-start-rules=\"*\" --format es src/compiler/origami.pegjs --output src/compiler/parse.js",
|
|
20
20
|
"prepublishOnly": "npm run build",
|
|
21
|
-
"test": "node --test",
|
|
21
|
+
"test": "node --test --test-reporter=spec",
|
|
22
22
|
"typecheck": "tsc"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -15,54 +15,55 @@ __
|
|
|
15
15
|
= (inlineSpace / newLine / comment)* { return ""; }
|
|
16
16
|
|
|
17
17
|
// A filesystem path that begins with a slash: `/foo/bar`
|
|
18
|
-
absoluteFilePath
|
|
18
|
+
absoluteFilePath "absolute file path"
|
|
19
19
|
= path:leadingSlashPath { return [[ops.filesRoot], ...path]; }
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
args "function arguments"
|
|
22
|
+
= parensArgs
|
|
23
|
+
/ path:leadingSlashPath { return [ops.traverse, ...path]; }
|
|
24
24
|
|
|
25
25
|
// An assignment statement: `foo = 1`
|
|
26
|
-
assignment
|
|
26
|
+
assignment "tree assignment"
|
|
27
27
|
= @identifier __ "=" __ @expr
|
|
28
28
|
|
|
29
29
|
assignmentOrShorthand
|
|
30
30
|
= assignment
|
|
31
31
|
/ key:identifier { return [key, [ops.inherited, key]]; }
|
|
32
32
|
|
|
33
|
-
array
|
|
33
|
+
array "array"
|
|
34
34
|
= "[" __ list:list? "]" { return [ops.array, ...(list ?? [])]; }
|
|
35
35
|
|
|
36
36
|
// Something that can be called. This is more restrictive than the `expr`
|
|
37
37
|
// parser; it doesn't accept regular function calls.
|
|
38
|
-
callTarget
|
|
38
|
+
callTarget "function call"
|
|
39
39
|
= absoluteFilePath
|
|
40
40
|
/ array
|
|
41
41
|
/ object
|
|
42
42
|
/ tree
|
|
43
43
|
/ lambda
|
|
44
|
+
/ parameterizedLambda
|
|
44
45
|
/ protocolCall
|
|
45
46
|
/ group
|
|
46
47
|
/ scopeReference
|
|
47
48
|
|
|
48
49
|
// A single line comment
|
|
49
|
-
comment
|
|
50
|
+
comment "comment"
|
|
50
51
|
= "#" [^\n\r]*
|
|
51
52
|
|
|
52
53
|
digits
|
|
53
54
|
= @[0-9]+
|
|
54
55
|
|
|
55
|
-
doubleQuoteString
|
|
56
|
+
doubleQuoteString "double quote string"
|
|
56
57
|
= '"' chars:doubleQuoteStringChar* '"' { return chars.join(""); }
|
|
57
58
|
|
|
58
59
|
doubleQuoteStringChar
|
|
59
60
|
= !('"' / newLine) @textChar
|
|
60
61
|
|
|
61
|
-
escapedChar
|
|
62
|
+
escapedChar "backslash-escaped character"
|
|
62
63
|
= "\\" @.
|
|
63
64
|
|
|
64
65
|
// An Origami expression, no leading/trailing whitespace
|
|
65
|
-
expr
|
|
66
|
+
expr "expression"
|
|
66
67
|
// Try function calls first, as they can start with expression types that
|
|
67
68
|
// follow (array, object, etc.); we want to parse the largest thing first.
|
|
68
69
|
= implicitParensCall
|
|
@@ -73,8 +74,9 @@ expr
|
|
|
73
74
|
/ array
|
|
74
75
|
/ object
|
|
75
76
|
/ tree
|
|
76
|
-
/ lambda
|
|
77
77
|
/ templateLiteral
|
|
78
|
+
/ lambda
|
|
79
|
+
/ parameterizedLambda
|
|
78
80
|
/ group
|
|
79
81
|
/ string
|
|
80
82
|
/ number
|
|
@@ -87,29 +89,35 @@ expr
|
|
|
87
89
|
expression "Origami expression"
|
|
88
90
|
= __ @expr __
|
|
89
91
|
|
|
90
|
-
float
|
|
92
|
+
float "floating-point number"
|
|
91
93
|
= sign? digits? "." digits {
|
|
92
94
|
return parseFloat(text());
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
// Parse a function and its arguments, e.g. `fn(arg)`, possibly part of a chain
|
|
96
98
|
// of function calls, like `fn(arg1)(arg2)(arg3)`.
|
|
97
|
-
functionComposition
|
|
98
|
-
= target:callTarget chain:argsChain { return makeFunctionCall(target, chain); }
|
|
99
|
+
functionComposition "function composition"
|
|
100
|
+
// = target:callTarget chain:argsChain { return makeFunctionCall(target, chain); }
|
|
101
|
+
= target:callTarget chain:args+ { return makeFunctionCall(target, chain); }
|
|
99
102
|
|
|
100
103
|
// An expression in parentheses: `(foo)`
|
|
101
|
-
group
|
|
104
|
+
group "parenthetical group"
|
|
102
105
|
= "(" __ @expr __ ")"
|
|
103
106
|
|
|
104
|
-
identifier
|
|
107
|
+
identifier "identifier"
|
|
105
108
|
= chars:identifierChar+ { return chars.join(""); }
|
|
106
109
|
|
|
107
110
|
identifierChar
|
|
108
|
-
= [^(){}\[\]
|
|
111
|
+
= [^(){}\[\]<>,/:=\`"'\\# \t\n\r] // No unescaped whitespace or special chars
|
|
109
112
|
/ escapedChar
|
|
110
113
|
|
|
114
|
+
identifierList
|
|
115
|
+
= head:identifier tail:(separator @identifier)* separator? {
|
|
116
|
+
return [head].concat(tail);
|
|
117
|
+
}
|
|
118
|
+
|
|
111
119
|
// A function call with implicit parentheses: `fn 1, 2, 3`
|
|
112
|
-
implicitParensCall
|
|
120
|
+
implicitParensCall "function call with implicit parentheses"
|
|
113
121
|
= target:(functionComposition / callTarget) inlineSpace+ args:list {
|
|
114
122
|
return [target, ...args];
|
|
115
123
|
}
|
|
@@ -117,28 +125,29 @@ implicitParensCall
|
|
|
117
125
|
// A host identifier that may include a colon and port number: `example.com:80`.
|
|
118
126
|
// This is used as a special case at the head of a path, where we want to
|
|
119
127
|
// interpret a colon as part of a text identifier.
|
|
120
|
-
host
|
|
128
|
+
host "HTTP/HTTPS host"
|
|
121
129
|
= identifier (":" number)? { return text(); }
|
|
122
130
|
|
|
123
131
|
inlineSpace
|
|
124
132
|
= [ \t]
|
|
125
133
|
|
|
126
|
-
integer
|
|
134
|
+
integer "integer"
|
|
127
135
|
= sign? digits {
|
|
128
136
|
return parseInt(text());
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
// A lambda expression: `=foo()`
|
|
132
|
-
lambda
|
|
133
|
-
= "=" __ expr:expr { return [ops.lambda, expr]; }
|
|
140
|
+
lambda "lambda function"
|
|
141
|
+
= "=" __ expr:expr { return [ops.lambda, null, expr]; }
|
|
134
142
|
|
|
135
143
|
// A path that begins with a slash: `/foo/bar`
|
|
136
|
-
leadingSlashPath
|
|
144
|
+
leadingSlashPath "path with a leading slash"
|
|
137
145
|
= "/" @path
|
|
138
146
|
/ "/" { return [""]; }
|
|
139
147
|
|
|
140
148
|
// A separated list of expressions
|
|
141
|
-
list
|
|
149
|
+
list "list"
|
|
150
|
+
= head:expr tail:(separator @expr)* separator? { return [head].concat(tail); }
|
|
142
151
|
|
|
143
152
|
newLine
|
|
144
153
|
= "\n"
|
|
@@ -146,7 +155,7 @@ newLine
|
|
|
146
155
|
/ "\r"
|
|
147
156
|
|
|
148
157
|
// A number
|
|
149
|
-
number
|
|
158
|
+
number "number"
|
|
150
159
|
= float
|
|
151
160
|
/ integer
|
|
152
161
|
|
|
@@ -154,7 +163,7 @@ number
|
|
|
154
163
|
//
|
|
155
164
|
// TODO: Use Object.fromEntries with array of key/value pairs
|
|
156
165
|
//
|
|
157
|
-
object
|
|
166
|
+
object "object literal"
|
|
158
167
|
= "{" __ properties:objectProperties? "}" { return [ops.object, ...(properties ?? [])]; }
|
|
159
168
|
|
|
160
169
|
// A separated list of object properties or shorthands
|
|
@@ -164,15 +173,20 @@ objectProperties
|
|
|
164
173
|
}
|
|
165
174
|
|
|
166
175
|
// A single object property with key and value: `x: 1`
|
|
167
|
-
objectProperty
|
|
176
|
+
objectProperty "object property"
|
|
168
177
|
= @identifier __ ":" __ @expr
|
|
169
178
|
|
|
170
179
|
objectPropertyOrShorthand
|
|
171
180
|
= objectProperty
|
|
172
181
|
/ key:identifier { return [key, [ops.scope, key]]; }
|
|
173
182
|
|
|
183
|
+
parameterizedLambda
|
|
184
|
+
= "(" __ parameters:identifierList? ")" __ ("=>"/"⇒") __ expr:expr {
|
|
185
|
+
return [ops.lambda, parameters ?? [], expr];
|
|
186
|
+
}
|
|
187
|
+
|
|
174
188
|
// Function arguments in parentheses
|
|
175
|
-
parensArgs
|
|
189
|
+
parensArgs "function arguments in parentheses"
|
|
176
190
|
= "(" __ list:list? ")" { return list ?? [undefined]; }
|
|
177
191
|
|
|
178
192
|
separator
|
|
@@ -183,26 +197,26 @@ sign
|
|
|
183
197
|
= [+\-]
|
|
184
198
|
|
|
185
199
|
// A slash-separated path of keys
|
|
186
|
-
path
|
|
200
|
+
path "slash-separated path"
|
|
187
201
|
= head:pathKey "/" tail:path { return [head].concat(tail); }
|
|
188
202
|
/ key:pathKey { return [key]; }
|
|
189
203
|
|
|
190
204
|
// A single key in a slash-separated path
|
|
191
|
-
pathKey
|
|
205
|
+
pathKey "path element"
|
|
192
206
|
= key:identifierChar* { return key.join(""); }
|
|
193
207
|
|
|
194
208
|
// Parse a protocol call like `fn://foo/bar`.
|
|
195
209
|
// There can be zero, one, or two slashes after the colon.
|
|
196
|
-
protocolCall
|
|
210
|
+
protocolCall "function call using protocol: syntax"
|
|
197
211
|
= protocol:protocol ":" "/"|0..2| host:host path:leadingSlashPath? {
|
|
198
212
|
return [protocol, host, ...(path ?? [])];
|
|
199
213
|
}
|
|
200
214
|
|
|
201
|
-
protocol
|
|
215
|
+
protocol "protocol"
|
|
202
216
|
= reservedProtocol
|
|
203
217
|
/ scopeReference
|
|
204
218
|
|
|
205
|
-
reservedProtocol
|
|
219
|
+
reservedProtocol "reserved protocol"
|
|
206
220
|
= "https" { return ops.https; }
|
|
207
221
|
/ "http" { return ops.http; }
|
|
208
222
|
/ "package" { return [ops.scope, "@package"] } // Alias
|
|
@@ -210,10 +224,10 @@ reservedProtocol
|
|
|
210
224
|
/ "treehttp" { return ops.treeHttp; }
|
|
211
225
|
/ "tree" { return ops.treeHttps; } // Alias
|
|
212
226
|
|
|
213
|
-
scopeReference
|
|
227
|
+
scopeReference "scope reference"
|
|
214
228
|
= key:identifier { return [ops.scope, key]; }
|
|
215
229
|
|
|
216
|
-
singleQuoteString
|
|
230
|
+
singleQuoteString "single quote string"
|
|
217
231
|
= "'" chars:singleQuoteStringChar* "'" { return chars.join(""); }
|
|
218
232
|
|
|
219
233
|
singleQuoteStringChar
|
|
@@ -222,14 +236,14 @@ singleQuoteStringChar
|
|
|
222
236
|
start
|
|
223
237
|
= number
|
|
224
238
|
|
|
225
|
-
string
|
|
239
|
+
string "string"
|
|
226
240
|
= doubleQuoteString
|
|
227
241
|
/ singleQuoteString
|
|
228
242
|
|
|
229
243
|
// A top-level document defining a template. This is the same as a template
|
|
230
244
|
// literal, but can contain backticks at the top level.
|
|
231
|
-
templateDocument "
|
|
232
|
-
= contents:templateDocumentContents { return [ops.lambda, contents]; }
|
|
245
|
+
templateDocument "template"
|
|
246
|
+
= contents:templateDocumentContents { return [ops.lambda, null, contents]; }
|
|
233
247
|
|
|
234
248
|
// Template documents can contain backticks at the top level.
|
|
235
249
|
templateDocumentChar
|
|
@@ -239,11 +253,11 @@ templateDocumentChar
|
|
|
239
253
|
templateDocumentContents
|
|
240
254
|
= parts:(templateDocumentText / templateSubstitution)* { return makeTemplate(parts); }
|
|
241
255
|
|
|
242
|
-
templateDocumentText
|
|
256
|
+
templateDocumentText "template text"
|
|
243
257
|
= chars:templateDocumentChar+ { return chars.join(""); }
|
|
244
258
|
|
|
245
259
|
// A backtick-quoted template literal
|
|
246
|
-
templateLiteral
|
|
260
|
+
templateLiteral "template literal"
|
|
247
261
|
= "`" @templateLiteralContents "`"
|
|
248
262
|
|
|
249
263
|
templateLiteralChar
|
|
@@ -258,14 +272,14 @@ templateLiteralText
|
|
|
258
272
|
= chars:templateLiteralChar+ { return chars.join(""); }
|
|
259
273
|
|
|
260
274
|
// A substitution in a template literal: `{{ fn() }}`
|
|
261
|
-
templateSubstitution
|
|
275
|
+
templateSubstitution "template substitution"
|
|
262
276
|
= "{{" @expression "}}"
|
|
263
277
|
|
|
264
278
|
textChar
|
|
265
279
|
= escapedChar / .
|
|
266
280
|
|
|
267
281
|
// A tree literal: `{ index.html = "Hello" }`
|
|
268
|
-
tree
|
|
282
|
+
tree "tree literal"
|
|
269
283
|
= "{" __ assignments:treeAssignments? "}" { return [ops.tree, ...(assignments ?? [])]; }
|
|
270
284
|
|
|
271
285
|
// A separated list of assignments or shorthands
|