@weborigami/language 0.0.41 → 0.0.42
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 +13 -7
- package/src/compiler/origami.pegjs +143 -74
- package/src/compiler/parse.d.ts +3 -0
- package/src/compiler/parse.js +901 -720
- 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/parse.test.js +74 -28
- 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.42",
|
|
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": "
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
13
|
+
"@weborigami/async-tree": "https://gitpkg.now.sh/WebOrigami/origami/async-tree?e08c2451",
|
|
14
|
+
"@weborigami/types": "0.0.42",
|
|
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
|
|
@@ -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
|
|
@@ -16,11 +26,20 @@ __
|
|
|
16
26
|
|
|
17
27
|
// A filesystem path that begins with a slash: `/foo/bar`
|
|
18
28
|
absoluteFilePath "absolute file path"
|
|
19
|
-
= path:leadingSlashPath {
|
|
29
|
+
= path:leadingSlashPath {
|
|
30
|
+
return annotate([[ops.filesRoot], ...path], location());
|
|
31
|
+
}
|
|
20
32
|
|
|
21
33
|
args "function arguments"
|
|
22
34
|
= parensArgs
|
|
23
|
-
/ path:leadingSlashPath {
|
|
35
|
+
/ path:leadingSlashPath {
|
|
36
|
+
return annotate([ops.traverse, ...path], location());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
array "array"
|
|
40
|
+
= "[" __ list:list? __ closingBracket {
|
|
41
|
+
return annotate([ops.array, ...(list ?? [])], location());
|
|
42
|
+
}
|
|
24
43
|
|
|
25
44
|
// An assignment statement: `foo = 1`
|
|
26
45
|
assignment "tree assignment"
|
|
@@ -28,10 +47,9 @@ assignment "tree assignment"
|
|
|
28
47
|
|
|
29
48
|
assignmentOrShorthand
|
|
30
49
|
= assignment
|
|
31
|
-
/ key:identifier {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
= "[" __ list:list? "]" { return [ops.array, ...(list ?? [])]; }
|
|
50
|
+
/ key:identifier {
|
|
51
|
+
return annotate([key, [ops.inherited, key]], location());
|
|
52
|
+
}
|
|
35
53
|
|
|
36
54
|
// Something that can be called. This is more restrictive than the `expr`
|
|
37
55
|
// parser; it doesn't accept regular function calls.
|
|
@@ -46,6 +64,31 @@ callTarget "function call"
|
|
|
46
64
|
/ group
|
|
47
65
|
/ scopeReference
|
|
48
66
|
|
|
67
|
+
// Required closing curly brace. We use this for the `tree` term: it's the last
|
|
68
|
+
// term in the `step` parser that starts with a curly brace, so if that parser
|
|
69
|
+
// sees a left curly brace, here we must see a right curly brace.
|
|
70
|
+
closingBrace
|
|
71
|
+
= "}"
|
|
72
|
+
/ .? {
|
|
73
|
+
error("Expected right curly brace");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Required closing bracket
|
|
77
|
+
closingBracket
|
|
78
|
+
= "]"
|
|
79
|
+
/ .? {
|
|
80
|
+
error("Expected right bracket");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Required closing parenthesis. We use this for the `group` term: it's the last
|
|
84
|
+
// term in the `step` parser that starts with a parenthesis, so if that parser
|
|
85
|
+
// sees a left parenthesis, here we must see a right parenthesis.
|
|
86
|
+
closingParen
|
|
87
|
+
= ")"
|
|
88
|
+
/ .? {
|
|
89
|
+
error("Expected right parenthesis");
|
|
90
|
+
}
|
|
91
|
+
|
|
49
92
|
// A single line comment
|
|
50
93
|
comment "comment"
|
|
51
94
|
= "#" [^\n\r]*
|
|
@@ -53,6 +96,8 @@ comment "comment"
|
|
|
53
96
|
digits
|
|
54
97
|
= @[0-9]+
|
|
55
98
|
|
|
99
|
+
doubleArrow = "⇒" / "=>"
|
|
100
|
+
|
|
56
101
|
doubleQuoteString "double quote string"
|
|
57
102
|
= '"' chars:doubleQuoteStringChar* '"' { return chars.join(""); }
|
|
58
103
|
|
|
@@ -63,27 +108,8 @@ escapedChar "backslash-escaped character"
|
|
|
63
108
|
= "\\" @.
|
|
64
109
|
|
|
65
110
|
// 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
|
|
111
|
+
expr
|
|
112
|
+
= pipeline
|
|
87
113
|
|
|
88
114
|
// Top-level Origami expression, possible leading/trailing whitepsace.
|
|
89
115
|
expression "Origami expression"
|
|
@@ -97,36 +123,36 @@ float "floating-point number"
|
|
|
97
123
|
// Parse a function and its arguments, e.g. `fn(arg)`, possibly part of a chain
|
|
98
124
|
// of function calls, like `fn(arg1)(arg2)(arg3)`.
|
|
99
125
|
functionComposition "function composition"
|
|
100
|
-
|
|
101
|
-
|
|
126
|
+
= target:callTarget chain:args* end:implicitParensArgs? {
|
|
127
|
+
if (end) {
|
|
128
|
+
chain.push(end);
|
|
129
|
+
}
|
|
130
|
+
return annotate(makeFunctionCall(target, chain), location());
|
|
131
|
+
}
|
|
102
132
|
|
|
103
133
|
// An expression in parentheses: `(foo)`
|
|
104
134
|
group "parenthetical group"
|
|
105
|
-
= "(" __ @expr __
|
|
135
|
+
= "(" __ @expr __ closingParen
|
|
136
|
+
|
|
137
|
+
// A host identifier that may include a colon and port number: `example.com:80`.
|
|
138
|
+
// This is used as a special case at the head of a path, where we want to
|
|
139
|
+
// interpret a colon as part of a text identifier.
|
|
140
|
+
host "HTTP/HTTPS host"
|
|
141
|
+
= identifier (":" number)? { return text(); }
|
|
106
142
|
|
|
107
143
|
identifier "identifier"
|
|
108
144
|
= chars:identifierChar+ { return chars.join(""); }
|
|
109
145
|
|
|
110
146
|
identifierChar
|
|
111
|
-
= [^(){}\[\]
|
|
147
|
+
= [^(){}\[\]<>\-=,/:\`"'\\# →⇒\t\n\r] // No unescaped whitespace or special chars
|
|
148
|
+
/ @'-' !'>' // Accept a hyphen but not in a single arrow combination
|
|
112
149
|
/ escapedChar
|
|
113
150
|
|
|
114
151
|
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
|
-
}
|
|
152
|
+
= @identifier|1.., separator| separator?
|
|
124
153
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// interpret a colon as part of a text identifier.
|
|
128
|
-
host "HTTP/HTTPS host"
|
|
129
|
-
= identifier (":" number)? { return text(); }
|
|
154
|
+
implicitParensArgs "arguments with implicit parentheses"
|
|
155
|
+
= inlineSpace+ @list
|
|
130
156
|
|
|
131
157
|
inlineSpace
|
|
132
158
|
= [ \t]
|
|
@@ -138,16 +164,18 @@ integer "integer"
|
|
|
138
164
|
|
|
139
165
|
// A lambda expression: `=foo()`
|
|
140
166
|
lambda "lambda function"
|
|
141
|
-
= "=" __ expr:expr {
|
|
167
|
+
= "=" __ expr:expr {
|
|
168
|
+
return annotate([ops.lambda, null, expr], location());
|
|
169
|
+
}
|
|
142
170
|
|
|
143
171
|
// A path that begins with a slash: `/foo/bar`
|
|
144
172
|
leadingSlashPath "path with a leading slash"
|
|
145
173
|
= "/" @path
|
|
146
|
-
/ "/" { return [""]; }
|
|
174
|
+
/ "/" { return annotate([""], location()); }
|
|
147
175
|
|
|
148
176
|
// A separated list of expressions
|
|
149
177
|
list "list"
|
|
150
|
-
=
|
|
178
|
+
= @expr|1.., separator| separator?
|
|
151
179
|
|
|
152
180
|
newLine
|
|
153
181
|
= "\n"
|
|
@@ -164,13 +192,13 @@ number "number"
|
|
|
164
192
|
// TODO: Use Object.fromEntries with array of key/value pairs
|
|
165
193
|
//
|
|
166
194
|
object "object literal"
|
|
167
|
-
= "{" __ properties:objectProperties? "}" {
|
|
195
|
+
= "{" __ properties:objectProperties? __ "}" {
|
|
196
|
+
return annotate([ops.object, ...(properties ?? [])], location());
|
|
197
|
+
}
|
|
168
198
|
|
|
169
199
|
// A separated list of object properties or shorthands
|
|
170
200
|
objectProperties
|
|
171
|
-
=
|
|
172
|
-
return [head].concat(tail);
|
|
173
|
-
}
|
|
201
|
+
= @objectPropertyOrShorthand|1.., separator| separator?
|
|
174
202
|
|
|
175
203
|
// A single object property with key and value: `x: 1`
|
|
176
204
|
objectProperty "object property"
|
|
@@ -178,28 +206,29 @@ objectProperty "object property"
|
|
|
178
206
|
|
|
179
207
|
objectPropertyOrShorthand
|
|
180
208
|
= objectProperty
|
|
181
|
-
/ key:identifier {
|
|
209
|
+
/ key:identifier {
|
|
210
|
+
return annotate([key, [ops.scope, key]], location());
|
|
211
|
+
}
|
|
182
212
|
|
|
183
213
|
parameterizedLambda
|
|
184
|
-
= "(" __ parameters:identifierList? ")" __
|
|
185
|
-
return [ops.lambda, parameters ?? [], expr];
|
|
214
|
+
= "(" __ parameters:identifierList? __ ")" __ doubleArrow __ expr:expr {
|
|
215
|
+
return annotate([ops.lambda, parameters ?? [], expr], location());
|
|
186
216
|
}
|
|
187
217
|
|
|
188
218
|
// Function arguments in parentheses
|
|
189
219
|
parensArgs "function arguments in parentheses"
|
|
190
|
-
= "(" __ list:list? ")" {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
= __ "," __
|
|
194
|
-
/ whitespaceWithNewLine
|
|
220
|
+
= "(" __ list:list? __ ")" {
|
|
221
|
+
return list ?? annotate([undefined], location());
|
|
222
|
+
}
|
|
195
223
|
|
|
196
|
-
|
|
197
|
-
=
|
|
224
|
+
pipeline
|
|
225
|
+
= steps:(@step|1.., __ singleArrow __ |) {
|
|
226
|
+
return annotate(makePipeline(steps), location());
|
|
227
|
+
}
|
|
198
228
|
|
|
199
229
|
// A slash-separated path of keys
|
|
200
230
|
path "slash-separated path"
|
|
201
|
-
=
|
|
202
|
-
/ key:pathKey { return [key]; }
|
|
231
|
+
= pathKey|1.., "/"|
|
|
203
232
|
|
|
204
233
|
// A single key in a slash-separated path
|
|
205
234
|
pathKey "path element"
|
|
@@ -209,7 +238,7 @@ pathKey "path element"
|
|
|
209
238
|
// There can be zero, one, or two slashes after the colon.
|
|
210
239
|
protocolCall "function call using protocol: syntax"
|
|
211
240
|
= protocol:protocol ":" "/"|0..2| host:host path:leadingSlashPath? {
|
|
212
|
-
return [protocol, host, ...(path ?? [])];
|
|
241
|
+
return annotate([protocol, host, ...(path ?? [])], location());
|
|
213
242
|
}
|
|
214
243
|
|
|
215
244
|
protocol "protocol"
|
|
@@ -225,7 +254,18 @@ reservedProtocol "reserved protocol"
|
|
|
225
254
|
/ "tree" { return ops.treeHttps; } // Alias
|
|
226
255
|
|
|
227
256
|
scopeReference "scope reference"
|
|
228
|
-
= key:identifier {
|
|
257
|
+
= key:identifier {
|
|
258
|
+
return annotate([ops.scope, key], location());
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
separator
|
|
262
|
+
= __ "," __
|
|
263
|
+
/ whitespaceWithNewLine
|
|
264
|
+
|
|
265
|
+
sign
|
|
266
|
+
= [+\-]
|
|
267
|
+
|
|
268
|
+
singleArrow = "→" / "->"
|
|
229
269
|
|
|
230
270
|
singleQuoteString "single quote string"
|
|
231
271
|
= "'" chars:singleQuoteStringChar* "'" { return chars.join(""); }
|
|
@@ -233,6 +273,29 @@ singleQuoteString "single quote string"
|
|
|
233
273
|
singleQuoteStringChar
|
|
234
274
|
= !("'" / newLine) @textChar
|
|
235
275
|
|
|
276
|
+
// A single step in a pipeline, or a top-level expression
|
|
277
|
+
step
|
|
278
|
+
// Literals that can't start a function call
|
|
279
|
+
= number
|
|
280
|
+
// Try functions next; they can start with expression types that follow
|
|
281
|
+
// (array, object, etc.), and we want to parse the larger thing first.
|
|
282
|
+
/ functionComposition
|
|
283
|
+
// Then try parsers that look for a distinctive token at the start: an opening
|
|
284
|
+
// slash, bracket, curly brace, etc.
|
|
285
|
+
/ absoluteFilePath
|
|
286
|
+
/ array
|
|
287
|
+
/ object
|
|
288
|
+
/ tree
|
|
289
|
+
/ lambda
|
|
290
|
+
/ parameterizedLambda
|
|
291
|
+
/ templateLiteral
|
|
292
|
+
/ string
|
|
293
|
+
/ group
|
|
294
|
+
// Protocol calls are distinguished by a colon, but it's not at the start.
|
|
295
|
+
/ protocolCall
|
|
296
|
+
// Least distinctive option is a simple scope reference, so it comes last.
|
|
297
|
+
/ scopeReference
|
|
298
|
+
|
|
236
299
|
start
|
|
237
300
|
= number
|
|
238
301
|
|
|
@@ -243,7 +306,9 @@ string "string"
|
|
|
243
306
|
// A top-level document defining a template. This is the same as a template
|
|
244
307
|
// literal, but can contain backticks at the top level.
|
|
245
308
|
templateDocument "template"
|
|
246
|
-
= contents:templateDocumentContents {
|
|
309
|
+
= contents:templateDocumentContents {
|
|
310
|
+
return annotate([ops.lambda, null, contents], location());
|
|
311
|
+
}
|
|
247
312
|
|
|
248
313
|
// Template documents can contain backticks at the top level.
|
|
249
314
|
templateDocumentChar
|
|
@@ -251,7 +316,9 @@ templateDocumentChar
|
|
|
251
316
|
|
|
252
317
|
// The contents of a template document containing plain text and substitutions
|
|
253
318
|
templateDocumentContents
|
|
254
|
-
= parts:(templateDocumentText / templateSubstitution)* {
|
|
319
|
+
= parts:(templateDocumentText / templateSubstitution)* {
|
|
320
|
+
return annotate(makeTemplate(parts), location());
|
|
321
|
+
}
|
|
255
322
|
|
|
256
323
|
templateDocumentText "template text"
|
|
257
324
|
= chars:templateDocumentChar+ { return chars.join(""); }
|
|
@@ -265,7 +332,9 @@ templateLiteralChar
|
|
|
265
332
|
|
|
266
333
|
// The contents of a template literal containing plain text and substitutions
|
|
267
334
|
templateLiteralContents
|
|
268
|
-
= parts:(templateLiteralText / templateSubstitution)* {
|
|
335
|
+
= parts:(templateLiteralText / templateSubstitution)* {
|
|
336
|
+
return annotate(makeTemplate(parts), location());
|
|
337
|
+
}
|
|
269
338
|
|
|
270
339
|
// Plain text in a template literal
|
|
271
340
|
templateLiteralText
|
|
@@ -280,13 +349,13 @@ textChar
|
|
|
280
349
|
|
|
281
350
|
// A tree literal: `{ index.html = "Hello" }`
|
|
282
351
|
tree "tree literal"
|
|
283
|
-
= "{" __ assignments:treeAssignments?
|
|
352
|
+
= "{" __ assignments:treeAssignments? __ closingBrace {
|
|
353
|
+
return annotate([ops.tree, ...(assignments ?? [])], location());
|
|
354
|
+
}
|
|
284
355
|
|
|
285
356
|
// A separated list of assignments or shorthands
|
|
286
357
|
treeAssignments
|
|
287
|
-
=
|
|
288
|
-
return [head].concat(tail);
|
|
289
|
-
}
|
|
358
|
+
= @assignmentOrShorthand|1.., separator| separator?
|
|
290
359
|
|
|
291
360
|
whitespaceWithNewLine
|
|
292
361
|
= inlineSpace* comment? newLine __
|