-
Notifications
You must be signed in to change notification settings - Fork 27
/
askscript.grammar.pegjs
341 lines (235 loc) · 11.8 KB
/
askscript.grammar.pegjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
{
const ask = require('./askscript.grammar.pegjs.classes');
/**
* Checks all operValues use the same operator, otherwise throws an error.
*
* In AskScript for different operators in one expression user must use braces to state precedence explicitly.
*/
function assertAllOperatorsMatch(operValues) {
if (operValues.length == 0) return;
const operator = operValues[0].operator;
for (const operValue of operValues) {
if (operValue.operator.text != operator.text) {
const descr =
`Expected all operators in an expression to be the same ` +
`(e.g. a${operator.text}b${operator.text}c), ` +
`but ${input.substring(peg$savedPos, peg$currPos)}, which uses operator '${operValue.operator.text}', found instead. ` +
`Use brackets to separate different operators, `+
`e.g. a${operator.text}b${operator.text}(c${operValue.operator.text}d)`;
const expectedDescr = 'all operators in an expression to be the same';
const foundDescr = input.substring(peg$savedPos, peg$currPos);
throw new peg$SyntaxError(
descr,
expectedDescr,
foundDescr,
location());
}
}
}
}
// === ask { ===
ask =
lineWithoutCode* aH:askHeader aB:askBody askFooter lineWithoutCode* eof { return new ask.Ask(aH, aB); }
/ lineWithoutCode* ws* v:value ws* lineWithoutCode* eof { return v }
askForRepl = lineWithoutCode* ws* 'ask' aL:askHeader_argList? aRT:askHeader_retType? ws* '{' .*
askHeader = ws* 'ask' aL:askHeader_argList? aRT:askHeader_retType? ws* '{' ws* lineComment? {
return new ask.AskHeader(aL === null ? [] : aL, aRT);
}
askHeader_argList = ws* '(' aL:argList ')' { return aL }
askHeader_retType = ws* ':' ws* t:type { return t }
askFooter = blockFooter ws*
askBody = sL:statementList { return new ask.AskBody(sL) }
// === statements ===
statementList =
lineWithoutCode* sL:statementList_NoEmptyLines lineWithoutCode* { return sL }
/ lineWithoutCode* { return [] }
statementList_NoEmptyLines =
s:statement sL:statementList_NoEmptyLines { return sL.unshift(s), sL }
/ s:lastStatement { return [s] }
// statement is at least one full line
// statement does NOT include the trailing newline
statement = lineWithoutCode* wsnonl* s:statement_NoWs wsnonl* (';'? (lineComment / wsnonl* nl) / ';') { return s }
lastStatement = lineWithoutCode* wsnonl* s:statement_NoWs wsnonl* (';'? (lineComment / wsnonl* nl) / ';'?) { return s }
statement_NoWs =
s:(
variableDefinition
/ if
/ elseif
/ while
/ forOf
/ forIn
/ for3
/ return
/ break
/ assignment
/ value
) { return new ask.Statement(s) }
statementKeyword = 'const' / 'let' / 'if' / 'else if' / 'while' / 'for' / 'return'
// === variables ===
// variables other than of function type
variableDefinition =
vD:variableDeclaration ws* '=' ws* v:value { return new ask.VariableDefinition(vD, v) }
/ vD:variableDeclaration { return new ask.VariableDefinition(vD) }
variableDeclaration =
m:modifier ws+ i:(identifier/operator) t:variableDefinition_type? { return new ask.VariableDeclaration(m, i, t === null ? ask.anyType : t) }
variableDefinition_type = ws* ':' ws* t:type { return t }
// === value ===
value = !(statementKeyword (ws / eof)) e:nonArithmExpression opVals:operValue* { assertAllOperatorsMatch(opVals); return new ask.Value(e, opVals) }
// We don't allow newline before an operator because:
// 123
// -123
// should be treated as 2 statements not as '123 - 123'.
operValue = wsnonl* op:operator ws* v:nonArithmExpression { return new ask.OperNAValue(op, v) }
// === non-arithm expressions ===
nonArithmExpression =
e:nonArithmNoMethodsExpression mCAs:methodCallApplied* { return new ask.NonArithmValue(e, mCAs) }
nonArithmNoMethodsExpression =
unaryOperator
/ brackets
/ functionObject
/ remote
/ functionCall
/ query
/ valueLiteral
/ identifier
// === unary operator ===
unaryOperator = op:operator v:nonArithmNoMethodsExpression { return new ask.UnaryOperator(op, v) }
// === brackets ===
brackets = '(' ws* v:value ws* ')' { return v }
// === function object ===
functionObject = fH:functionHeader cB:codeBlock functionFooter { return new ask.FunctionObject(fH, cB) }
functionHeader = 'fun' aL:argBrackets? rTD:functionHeader_returnTypeDecl? ws* '{' ws* lineComment? { return new ask.FunctionHeader(aL === null ? [] : aL, rTD === null ? ask.anyType : rTD) }
functionHeader_returnTypeDecl = ws* ':' ws* t2:type { return t2 } // this is the optional return type declaration
functionFooter = blockFooter
// === code block ===
codeBlockWithBraces = '{' ws* cB:codeBlock ws* '}' { return cB; }
codeBlock = statementList
// === query ===
query = queryHeader qFL:queryFieldList queryFooter { return new ask.Query(qFL) }
queryHeader = 'query' ws* '{' ws* lineComment?
queryFieldList =
lineWithoutCode* qF:queryField ws* lineComment? lineWithoutCode* qFL:queryFieldList { return qFL.unshift(qF), qFL }
/ lineWithoutCode* qF:queryField ws* lineComment? lineWithoutCode* { return [qF] }
/ lineWithoutCode* { return [] }
queryField =
ws* i:identifier ws* ':' ws* v:value qFL:queryFieldBlock? { return new ask.QueryField(i, v, qFL) }
// This is double quote in fact (the second ':' is leading the methodCallApplied rule)
/ ws* i:identifier ws* ':' ws* mCAs:methodCallApplied* qFL:queryFieldBlock? { return new ask.QueryField(i, new ask.NonArithmValue(i, mCAs), qFL) }
/ ws* i:identifier qFL:queryFieldBlock? { return new ask.QueryField(i, new ask.NonArithmValue(i, []), qFL) }
queryFieldBlock = ws* '{' ws* lineComment? lineWithoutCode* qFL:queryFieldList ws* '}' { return qFL }
queryFooter = blockFooter
// === remote ===
remote = rH:remoteHeader cB:codeBlockWithBraces { return new ask.Remote(rH, cB) }
remoteHeader =
'remote(' ws* url:value ws* ',' ws* args:map ws* ')' ws* { return new ask.RemoteHeader(url, args) }
/ 'remote(' ws* url:value ws* ')' ws* { return new ask.RemoteHeader(url, new ask.Map([])) }
// === lists: arg list, call list, value list ===
argBrackets = ws* '(' aL:argList ')' { return aL }
argList = // TODO(lc): check all the *List constructs for handling empty lists
aL:nonEmptyArgList { return aL }
/ '' { return [] }
nonEmptyArgList =
a:arg ',' aL:argList { return aL.unshift(a), aL }
/ a:arg { return [a] }
arg = ws* i:identifier ws* t:argType? { return new ask.Arg(i, t === null ? ask.anyType : t) }
argType = ':' ws* t:type ws* { return t }
callArgList = v:valueList { return v }
valueList =
vL:nonEmptyValueList { return vL}
/ ws* { return [] }
nonEmptyValueList =
ws* v:value ws* ',' vL:nonEmptyValueList { vL.unshift(v); return vL }
/ ws* v:value ws* { return [v] }
// === control flow ===
if = 'if' ws* '(' ws* v:value ws* ')' ws* cB:codeBlockWithBraces eB:elseif { return new ask.If(v, cB, eB) }
/ 'if' ws* '(' ws* v:value ws* ')' ws* cB:codeBlockWithBraces eB:elseBlock? { return new ask.If(v, cB, eB) }
elseif = ws* 'else if' ws* '(' ws* v:value ws* ')' ws* cB:codeBlockWithBraces eB:elseif { return new ask.ElseIf(v, cB, eB) }
/ ws* 'else if' ws* '(' ws* v:value ws* ')' ws* cB:codeBlockWithBraces eB:elseBlock? { return new ask.ElseIf(v, cB, eB) }
while = 'while' ws* '(' ws* v:value ws* ')' ws* cB:codeBlockWithBraces { return new ask.While(v, cB) }
forOf = 'for' ws* '(' ws* vD:variableDeclaration ws+ 'of' ws+ v:value ws* ')' ws* cB:codeBlockWithBraces { return new ask.ForOf(vD, v, cB)}
forIn = 'for' ws* '(' ws* vD:variableDeclaration ws+ 'in' ws+ v:value ws* ')' ws* cB:codeBlockWithBraces { return new ask.ForIn(vD, v, cB)}
for3 = 'for' ws* '(' ws* s1:statement_NoWs? ws* ';' ws* s2:statement_NoWs ws* ';' ws* s3:statement_NoWs ws* ')' ws* cB:codeBlockWithBraces { return new ask.For3(s1, s2, s3, cB)}
elseBlock = ws* 'else' ws* cB:codeBlockWithBraces { return new ask.Else(cB) }
break = 'break' wsnonl* { return new ask.Break() }
return =
'return' wsnonl+ v:value { return new ask.Return(v) }
/ 'return' wsnonl* { return new ask.Return(ask.nullValue) }
// === ====
assignment = i:identifier ws* '=' ws* v:value { return new ask.Assignment(i, v) }
// === function and method calls ===
functionCall = i:identifier ws* '(' cAL:callArgList ')' { return new ask.FunctionCall(i, cAL) }
methodCallApplied =
ws* ':' ws* iop:(identifier/operator) cAL:methodCallAppliedArgList? { return new ask.MethodCallApplied(iop, cAL === null ? [] : cAL)}
/ ws* '.' ws* i:identifier { return new ask.KeyIdentifierApplied(i) }
/ wsnonl* '[' ws* v:value ']' { return new ask.KeyExpressionApplied(v) }
methodCallAppliedArgList = ws* '(' cAL:callArgList ')' { return cAL }
// === simple elements ===
// TODO:
// - [ ] function type sugar (a1, a2, a3, ...) -> retType for fun(retType, a1, a2, a3(, true))
type = t:(functionCall/identifier) { return t }
valueLiteral =
v:(
null
/ boolean
/ float // float needs to go before int
/ int
/ string
/ array
/ map
) { return new ask.ValueLiteral(v) }
string = "'" sC:stringContents "'" { return sC }
stringContents = ch* { return new ask.StringLiteral(text()) }
array = '[' vL:valueList ']' { return new ask.Array(vL) }
map = '{' mEL:mapEntryList '}' { return new ask.Map(mEL) }
mapEntryList =
ws* mE:mapEntry ws* ',' mEL:mapEntryList { return mEL.unshift(mE), mEL }
/ ws* mE:mapEntry ws* { return [mE] }
/ ws* { return [] }
mapEntry =
i:identifier ws* ':' ws* v:value { return new ask.MapEntry(i, v) }
/ i:string ws* ':' ws* v:value { return new ask.MapEntry(i, v) }
// This is double quote in fact (the second ':' is leading the methodCallApplied rule)
/ i:identifier ws* ':' ws* mCAs:methodCallApplied* { return new ask.MapEntry(i, new ask.NonArithmValue(i, mCAs)) }
/ ws* i:identifier { return new ask.MapEntry(i, new ask.NonArithmValue(i, [])) }
modifier = const / let
const = 'const' { return new ask.Const() }
let = 'let' { return new ask.Let() }
blockFooter = ws* '}'
lineWithoutCode =
lineWithComment
/ emptyLine
lineWithComment = lineComment
lineComment = wsnonl* '//' (!nl .)* (nl / eof)
emptyLine = wsnonl* nl
// === literals ===
identifier = [_$a-zA-Z][_$a-zA-Z0-9]* { return new ask.Identifier(text()) } // TODO(lc): add Unicode here one day
// operators consist of chars: -<>+*/^%=&|, but they cannot contain // sequence
operator = ([-!<>+*^%=&|] / ([/] &[^/]))+ { return new ask.Identifier(text(), true) }
null = 'null' { return new ask.Null() }
boolean = true / false
true = 'true' { return new ask.True() }
false = 'false' { return new ask.False() }
int = [-]?[0-9]+ { return new ask.Int(text()) } // TODO(lc): yes, multiple leading zeros possible, I might fix one day
float = [-]?[0-9]+ '.' [0-9]+ { return new ask.Float(text()) } // TODO(lc): yes, multiple leading zeros possible, I might fix one day
// === character classes ===
// character (in string)
ch =
'\\' escape
/ [\x20-\x26\x28-\x5B\x5D-\xff] // all printable characters except ' (\x27) and \ (\x5C)
escape =
"'"
/ '\\' // this is one backslash
/ 'u' hex hex hex hex
/ 'x' hex hex
// digits (dec and hex)
hex = [0-9A-Fa-f]
digit = [0-9]
onenine = [1-9]
// whitespace
ws = wsnonl / nl
// same-line whitespace
wsnonl = ' ' / '\t'
// new line
nl = '\n' / '\r'
// end of file
eof = (!.)