dal-ast-js 0.0.1-dev
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/LICENSE +201 -0
- package/README.md +26 -0
- package/dist/index.cjs +696 -0
- package/dist/index.esm.js +694 -0
- package/docs/notes.md +36 -0
- package/package.json +28 -0
- package/rollup.config.js +18 -0
- package/src/ArgsParser.js +152 -0
- package/src/DalAstGenerator.js +35 -0
- package/src/DesignValidator.js +37 -0
- package/src/KEYWORDS.js +11 -0
- package/src/Lexer.js +174 -0
- package/src/Parser.js +281 -0
- package/src/TOKENS.js +13 -0
- package/src/Untokenizer.js +34 -0
- package/src/docs/DesignValidatorNotes.md +6 -0
- package/src/grammar.json +57 -0
- package/synthesizer/Synthesizer.py +88 -0
- package/synthesizer/asts/lib_manager_ast.json +842 -0
- package/synthesizer/dal_ast_synthesizer.py +31 -0
- package/synthesizer/docs/commands.json +8 -0
- package/synthesizer/getSynthesizedNode.py +362 -0
- package/synthesizer/helper.py +13 -0
- package/synthesizer/output/LoggingHelper.py +57 -0
- package/synthesizer/output/synthesized.py +76 -0
- package/synthesizer/output_helpers/LoggingHelper.py +57 -0
- package/synthesizer/output_helpers/readme.md +3 -0
- package/synthesizer/readme.md +9 -0
- package/tests/Lexer.test.js +45 -0
- package/tests/designs/library_manager.dal +79 -0
- package/tests/designs/test.dal +19 -0
- package/tests/designs/test3.dal +16 -0
- package/tests/output/ast.json +842 -0
- package/tests/output/ast_direct_gen.json +842 -0
- package/tests/output/test_tokens.json +3628 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
let TOKENS$1 = {
|
|
2
|
+
"LPAREN": "(",
|
|
3
|
+
"RPAREN": ")",
|
|
4
|
+
"QUOTE": "\"",
|
|
5
|
+
"COMMA": ",",
|
|
6
|
+
"LBRACE": "{",
|
|
7
|
+
"RBRACE": "}",
|
|
8
|
+
"LBRACKET": "[",
|
|
9
|
+
"RBRACKET": "]"
|
|
10
|
+
};
|
|
11
|
+
TOKENS$1 = Object.freeze(TOKENS$1);
|
|
12
|
+
|
|
13
|
+
var TOKENS = TOKENS$1;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* This lexer works as follows:
|
|
17
|
+
* - Reads character at current position
|
|
18
|
+
* - If it is not a token:
|
|
19
|
+
* - adds to identifier accumulator
|
|
20
|
+
* - If it is a token:
|
|
21
|
+
* - saves what is in the accumulator to scannedTokens
|
|
22
|
+
* - saves the token to scannedTokens
|
|
23
|
+
* - If a token is a quote:
|
|
24
|
+
* - saves quote to scanned token
|
|
25
|
+
* - scans forward to end of quote while accumulating identifier
|
|
26
|
+
* - saves accumulator to scanned tokens
|
|
27
|
+
* - saves quote to scanned tokens
|
|
28
|
+
*
|
|
29
|
+
* I am not scanning for keywords here, I just save them as
|
|
30
|
+
* identifiers and then in the parse stage I will classify them.
|
|
31
|
+
* This makes the algorithm very simple.
|
|
32
|
+
*
|
|
33
|
+
* Example:
|
|
34
|
+
* design ("name")
|
|
35
|
+
* IDENTIFIER LPAREN QUOTE IDENTIFIER QUOTE RPAREN
|
|
36
|
+
*
|
|
37
|
+
* I also save the line/col number (starting and ending). This will be
|
|
38
|
+
* useful for visualization in the workbench.
|
|
39
|
+
*/
|
|
40
|
+
class DalLexer {
|
|
41
|
+
constructor (source) {
|
|
42
|
+
this.currPos = 0;
|
|
43
|
+
this.source = [...source];
|
|
44
|
+
this.scannedTokens = [];
|
|
45
|
+
|
|
46
|
+
// Current line and colno
|
|
47
|
+
this.lineno = 1;
|
|
48
|
+
this.colno = 0;
|
|
49
|
+
|
|
50
|
+
// Accumulates identifiers until tokens are visited.
|
|
51
|
+
this.accumulatedIdentifier = [];
|
|
52
|
+
this.startColIdentifier;
|
|
53
|
+
this.startLineIdentifier;
|
|
54
|
+
|
|
55
|
+
this.run();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Runs the lexer from the current position.
|
|
60
|
+
*/
|
|
61
|
+
run() {
|
|
62
|
+
do {
|
|
63
|
+
const character = this.source[this.currPos];
|
|
64
|
+
this.processCurrentPosition(character);
|
|
65
|
+
} while (++this.currPos < this.source.length);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Processes the character at the current position.
|
|
70
|
+
*/
|
|
71
|
+
processCurrentPosition(character) {
|
|
72
|
+
this.colno++;
|
|
73
|
+
|
|
74
|
+
if (character == " ") {
|
|
75
|
+
this.addAccumulatedIdentifierToken();
|
|
76
|
+
return;
|
|
77
|
+
} else if (character == "\n") {
|
|
78
|
+
this.lineno++;
|
|
79
|
+
this.colno = 0;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const token = this.getToken(character);
|
|
84
|
+
if (token) {
|
|
85
|
+
this.addAccumulatedIdentifierToken();
|
|
86
|
+
this.addToken(token);
|
|
87
|
+
if (token === "QUOTE") {
|
|
88
|
+
this.extractStringFromQuotes();
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.addToAccumulator(character);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Finds the identifier given the character.
|
|
97
|
+
* @param {String} character Character from scan.
|
|
98
|
+
* @returns {String|null} Identifier if valid token, else null.
|
|
99
|
+
*/
|
|
100
|
+
getToken (character) {
|
|
101
|
+
for (const [identifier, value] of Object.entries(TOKENS)) {
|
|
102
|
+
if (value[0] !== character) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
return identifier;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* If a quote is encountered, scan forward until end of quote is found.
|
|
111
|
+
* Everything inside the quote is part of the string, this ignores any
|
|
112
|
+
* of the tokens like comma.
|
|
113
|
+
*
|
|
114
|
+
* TODO: Add support for escaped quotes inside quotes.
|
|
115
|
+
*/
|
|
116
|
+
extractStringFromQuotes () {
|
|
117
|
+
this.colno++;
|
|
118
|
+
this.currPos++;
|
|
119
|
+
do {
|
|
120
|
+
const character = this.source[this.currPos];
|
|
121
|
+
const identifier = this.getToken(character);
|
|
122
|
+
if (character === "\n") {
|
|
123
|
+
this.lineno++;
|
|
124
|
+
this.colno = 0;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (identifier !== "QUOTE") {
|
|
128
|
+
this.addToAccumulator(character);
|
|
129
|
+
this.colno++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
this.addAccumulatedIdentifierToken();
|
|
133
|
+
this.addToken(identifier);
|
|
134
|
+
break;
|
|
135
|
+
} while (this.currPos++ < this.source.length)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Adds to accumulator. Saves starting position of
|
|
140
|
+
* identifier being accumulated.
|
|
141
|
+
*
|
|
142
|
+
* @param {String} character Character to add to accumulate.
|
|
143
|
+
*/
|
|
144
|
+
addToAccumulator (character) {
|
|
145
|
+
if (this.accumulatedIdentifier.length === 0) {
|
|
146
|
+
this.startColIdentifier = this.colno;
|
|
147
|
+
this.startLineIdentifier = this.lineno;
|
|
148
|
+
}
|
|
149
|
+
this.accumulatedIdentifier.push(character);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Add the accumulated identifier to the scannedTokens list.
|
|
154
|
+
*/
|
|
155
|
+
addAccumulatedIdentifierToken () {
|
|
156
|
+
// Subtract 1 from endColno beause we have to reach token
|
|
157
|
+
// to identify that the accumulator is done.
|
|
158
|
+
if (this.accumulatedIdentifier.length > 0) {
|
|
159
|
+
this.scannedTokens.push({
|
|
160
|
+
type: "IDENTIFIER",
|
|
161
|
+
value: this.accumulatedIdentifier.join(""),
|
|
162
|
+
startLineno: this.startLineIdentifier,
|
|
163
|
+
startColno: this.startColIdentifier,
|
|
164
|
+
endLineno: this.lineno,
|
|
165
|
+
endColno: this.colno - 1
|
|
166
|
+
});
|
|
167
|
+
this.accumulatedIdentifier = [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Adds the token to the scannedTokens list.
|
|
173
|
+
* @param {String} type Type for the token.
|
|
174
|
+
* @param {Number} value Value of token (example identifier)
|
|
175
|
+
*/
|
|
176
|
+
addToken (type, value) {
|
|
177
|
+
this.scannedTokens.push({
|
|
178
|
+
type: type,
|
|
179
|
+
value: value,
|
|
180
|
+
startLineno: this.lineno,
|
|
181
|
+
startColno: this.colno,
|
|
182
|
+
endLineno: this.lineno,
|
|
183
|
+
endColno: this.colno
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let KEYWORDS = [
|
|
189
|
+
"design",
|
|
190
|
+
"behavior",
|
|
191
|
+
"if",
|
|
192
|
+
"else",
|
|
193
|
+
"for",
|
|
194
|
+
"while"
|
|
195
|
+
];
|
|
196
|
+
KEYWORDS = Object.freeze(KEYWORDS);
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Builds string back from the tokens.
|
|
200
|
+
*
|
|
201
|
+
* Ex:
|
|
202
|
+
* LBRACKET QUOTE IDENTIFIER("test") QUOTE RBRACKET
|
|
203
|
+
* ("test")
|
|
204
|
+
*/
|
|
205
|
+
class Untokenizer {
|
|
206
|
+
|
|
207
|
+
constructor (tokens) {
|
|
208
|
+
this.tokens = tokens;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Builds string from tokens.
|
|
213
|
+
* @param {Array} tokens
|
|
214
|
+
* @returns
|
|
215
|
+
*/
|
|
216
|
+
buildString(tokens) {
|
|
217
|
+
let str = "";
|
|
218
|
+
|
|
219
|
+
for (const token of this.tokens) {
|
|
220
|
+
if (token.type === "IDENTIFIER") {
|
|
221
|
+
str = str + token.value;
|
|
222
|
+
} else {
|
|
223
|
+
str = str + TOKENS[token.type];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return str;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Structure
|
|
233
|
+
* command(args...)
|
|
234
|
+
*
|
|
235
|
+
* Example:
|
|
236
|
+
* set(book, ["name"], "Harry Potter")
|
|
237
|
+
*
|
|
238
|
+
* Returns:
|
|
239
|
+
* [
|
|
240
|
+
* {
|
|
241
|
+
* "type": "name",
|
|
242
|
+
* "value": "book"
|
|
243
|
+
* },
|
|
244
|
+
* {
|
|
245
|
+
* "type": "list",
|
|
246
|
+
* "value": ["name"]
|
|
247
|
+
* },
|
|
248
|
+
* {
|
|
249
|
+
* "type": "string",
|
|
250
|
+
* "value": "Harry Potter"
|
|
251
|
+
* }
|
|
252
|
+
* ]
|
|
253
|
+
*
|
|
254
|
+
* This class parses the arguments from parser
|
|
255
|
+
* for a keyword or command instruction.
|
|
256
|
+
*/
|
|
257
|
+
class ArgsParser {
|
|
258
|
+
constructor (tokens) {
|
|
259
|
+
this.tokens = tokens;
|
|
260
|
+
this.groupedTokens = [];
|
|
261
|
+
this.parsedArgs = [];
|
|
262
|
+
|
|
263
|
+
this.pairs = [
|
|
264
|
+
{
|
|
265
|
+
start:"QUOTE",
|
|
266
|
+
end:"QUOTE",
|
|
267
|
+
type:"string"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
start:"LBRACE",
|
|
271
|
+
end:"RBRACE",
|
|
272
|
+
type:"object"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
start:"LBRACKET",
|
|
276
|
+
end:"RBRACKET",
|
|
277
|
+
type: "list"
|
|
278
|
+
}
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
run () {
|
|
283
|
+
if (this.tokens.length === 0) {
|
|
284
|
+
return this.parsedArgs;
|
|
285
|
+
}
|
|
286
|
+
this.splitByComma(this.tokens);
|
|
287
|
+
for (const group of this.groupedTokens) {
|
|
288
|
+
this.parsedArgs.push(this.processGroup(group));
|
|
289
|
+
}
|
|
290
|
+
return this.parsedArgs;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Splits the tokens by commas so they can be processed independently.
|
|
295
|
+
* For objects, arrays and strings, it flags that pair is being tracked
|
|
296
|
+
* so that any commmas inside the object or array is ignored.
|
|
297
|
+
* @param {Array} tokens
|
|
298
|
+
*/
|
|
299
|
+
splitByComma (tokens) {
|
|
300
|
+
let pos = 0;
|
|
301
|
+
let group = [];
|
|
302
|
+
this.trackingPair = null;
|
|
303
|
+
do {
|
|
304
|
+
const token = this.tokens[pos];
|
|
305
|
+
if (token.type === "COMMA" && !this.trackingPair) {
|
|
306
|
+
this.groupedTokens.push(group);
|
|
307
|
+
group = [];
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (this.trackingPair && token.type === this.trackingPair) {
|
|
312
|
+
this.trackingPair = null;
|
|
313
|
+
} else if (!this.trackingPair) {
|
|
314
|
+
this.trackingPair = this.isPair(token);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
group.push(token);
|
|
318
|
+
|
|
319
|
+
} while (++pos < this.tokens.length);
|
|
320
|
+
|
|
321
|
+
this.groupedTokens.push(group);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Checks if the token is a pair.
|
|
326
|
+
* @param {Object} token Token being checked.
|
|
327
|
+
* @returns {String|null}
|
|
328
|
+
*/
|
|
329
|
+
isPair (token) {
|
|
330
|
+
for(const pair of this.pairs) {
|
|
331
|
+
if (token.type === pair.start) {
|
|
332
|
+
return pair.end;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Process the grouped tokens and returns
|
|
339
|
+
* the type and value of the arguments.
|
|
340
|
+
*
|
|
341
|
+
* Types:
|
|
342
|
+
* ------
|
|
343
|
+
* Name (this is a participant name)
|
|
344
|
+
* String
|
|
345
|
+
* List or Array
|
|
346
|
+
* Object
|
|
347
|
+
* Number
|
|
348
|
+
*
|
|
349
|
+
* @param {Array} tokens
|
|
350
|
+
*/
|
|
351
|
+
processGroup (tokens) {
|
|
352
|
+
let type;
|
|
353
|
+
let value = new Untokenizer(tokens).buildString();
|
|
354
|
+
if (tokens[0].type === "QUOTE") {
|
|
355
|
+
type = "string";
|
|
356
|
+
value = JSON.parse(value);
|
|
357
|
+
} else if (tokens[0].type === "LBRACKET") {
|
|
358
|
+
type = "list";
|
|
359
|
+
value = JSON.parse(value);
|
|
360
|
+
} else if (tokens[0].type === "LBRACE") {
|
|
361
|
+
type = "object";
|
|
362
|
+
value = JSON.parse(value);
|
|
363
|
+
} else if (!Number.isNaN(Number(value))) {
|
|
364
|
+
type = "number";
|
|
365
|
+
value = parseFloat(value);
|
|
366
|
+
} else {
|
|
367
|
+
type = "name";
|
|
368
|
+
if (value === "true") {
|
|
369
|
+
value = true;
|
|
370
|
+
} else if (value === "false") {
|
|
371
|
+
value = false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
type: type,
|
|
377
|
+
value: value
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* This class produces an AST given the scanned tokens.
|
|
384
|
+
*
|
|
385
|
+
* Steps:
|
|
386
|
+
* - Process current token
|
|
387
|
+
* - Visit keyword and create node
|
|
388
|
+
* - Parse the args and save node metadata
|
|
389
|
+
* - If node has a body (if, behavior), add to stack
|
|
390
|
+
* - If node doesn't have body, add to body of stack top
|
|
391
|
+
* - When rbrace is encountered, pop stack and add
|
|
392
|
+
* to body of top of stack.
|
|
393
|
+
*
|
|
394
|
+
* It is a very simple algorithm because my language is basic.
|
|
395
|
+
* I create stack to track the nested body being processed.
|
|
396
|
+
* I use rbrace as marker to finish a nested block and build the tree.
|
|
397
|
+
*
|
|
398
|
+
* Note: This was the first way I thought to implement this
|
|
399
|
+
* but I think there is a better way, I am going to iterate
|
|
400
|
+
* on this. This approach does not identify syntax errors
|
|
401
|
+
* properly.
|
|
402
|
+
*
|
|
403
|
+
* The actual commands are very simple in this language
|
|
404
|
+
* because I am always following the format shown below:
|
|
405
|
+
*
|
|
406
|
+
* command(args)
|
|
407
|
+
*
|
|
408
|
+
* So every identifier that isn't a keyword while scanning
|
|
409
|
+
* forward is a command.
|
|
410
|
+
*/
|
|
411
|
+
class DalParser {
|
|
412
|
+
|
|
413
|
+
constructor (tokens) {
|
|
414
|
+
this.tokens = tokens;
|
|
415
|
+
this.currPos = 0;
|
|
416
|
+
this.ast = {
|
|
417
|
+
type: "root",
|
|
418
|
+
body: []
|
|
419
|
+
};
|
|
420
|
+
this.stack = [this.ast];
|
|
421
|
+
this.run();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
run () {
|
|
425
|
+
do {
|
|
426
|
+
const token = this.tokens[this.currPos];
|
|
427
|
+
this.processToken(token);
|
|
428
|
+
} while (++this.currPos < this.tokens.length);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Process the current token.
|
|
433
|
+
*
|
|
434
|
+
* RBRACE indicates current block is over.
|
|
435
|
+
* behavior and if are blocks and have a body
|
|
436
|
+
* design is a statement.
|
|
437
|
+
*
|
|
438
|
+
* @param {String} token
|
|
439
|
+
*/
|
|
440
|
+
processToken(token) {
|
|
441
|
+
if (token.type === "RBRACE") {
|
|
442
|
+
this.closeBlock();
|
|
443
|
+
} else if (token.value === "design") {
|
|
444
|
+
this.processDesignKeyword();
|
|
445
|
+
} else if (token.value === "behavior") {
|
|
446
|
+
this.processBehavior();
|
|
447
|
+
} else if (token.value === "if") {
|
|
448
|
+
this.processIf();
|
|
449
|
+
} else if (token.value === "else") {
|
|
450
|
+
this.processElse();
|
|
451
|
+
} else if (token.value === "for") {
|
|
452
|
+
this.processFor();
|
|
453
|
+
} else if (token.value === "while") {
|
|
454
|
+
this.processWhile();
|
|
455
|
+
}else if (token.type === "IDENTIFIER") {
|
|
456
|
+
this.processCmd(token.value);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Closes the nested block by removing it from
|
|
463
|
+
* the top of the stack and adding it to the new
|
|
464
|
+
* node at the top of the stack.
|
|
465
|
+
*
|
|
466
|
+
* It uses the RBRACE identifier to determine if
|
|
467
|
+
* the top block is closed.
|
|
468
|
+
*
|
|
469
|
+
* For if statements, it appends the elif and else
|
|
470
|
+
* blocks to the if block that was already processed
|
|
471
|
+
* so that they can be synthesized in a single AST node.
|
|
472
|
+
*/
|
|
473
|
+
closeBlock () {
|
|
474
|
+
const node = this.stack.pop();
|
|
475
|
+
|
|
476
|
+
if (node.type === "else") {
|
|
477
|
+
const body = this.stack[this.stack.length - 1].body;
|
|
478
|
+
const ifNode = body[body.length - 1];
|
|
479
|
+
ifNode["else"] = node;
|
|
480
|
+
} else {
|
|
481
|
+
this.stack[this.stack.length - 1].body.push(node);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Processes the behavior block.
|
|
487
|
+
*
|
|
488
|
+
* behavior <behavior_name>(args1, arg2...argN) {
|
|
489
|
+
*
|
|
490
|
+
* }
|
|
491
|
+
*/
|
|
492
|
+
processBehavior () {
|
|
493
|
+
const behaviorName = this.tokens[++this.currPos].value;
|
|
494
|
+
const node = {
|
|
495
|
+
type: "behavior",
|
|
496
|
+
behaviorName: behaviorName,
|
|
497
|
+
body: []
|
|
498
|
+
};
|
|
499
|
+
this.stack.push(node);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Processes if block.
|
|
504
|
+
*
|
|
505
|
+
* if (condition) {
|
|
506
|
+
*
|
|
507
|
+
* }
|
|
508
|
+
*/
|
|
509
|
+
processIf () {
|
|
510
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
511
|
+
const node = {
|
|
512
|
+
type: "if",
|
|
513
|
+
args: parsedArgs,
|
|
514
|
+
body: []
|
|
515
|
+
};
|
|
516
|
+
this.stack.push(node);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Processes while block.
|
|
521
|
+
*
|
|
522
|
+
* while (condition) {
|
|
523
|
+
*
|
|
524
|
+
* }
|
|
525
|
+
*/
|
|
526
|
+
processWhile () {
|
|
527
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
528
|
+
const node = {
|
|
529
|
+
type: "while",
|
|
530
|
+
args: parsedArgs,
|
|
531
|
+
body: []
|
|
532
|
+
};
|
|
533
|
+
this.stack.push(node);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Processes else block.
|
|
538
|
+
*
|
|
539
|
+
* else (condition) {
|
|
540
|
+
*
|
|
541
|
+
* }
|
|
542
|
+
*/
|
|
543
|
+
processElse () {
|
|
544
|
+
const node = {
|
|
545
|
+
type: "else",
|
|
546
|
+
body: []
|
|
547
|
+
};
|
|
548
|
+
this.stack.push(node);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Processes for block.
|
|
553
|
+
*
|
|
554
|
+
* for (<participan>, <start>,<end>) {
|
|
555
|
+
*
|
|
556
|
+
* }
|
|
557
|
+
*/
|
|
558
|
+
processFor () {
|
|
559
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
560
|
+
const node = {
|
|
561
|
+
type: "for",
|
|
562
|
+
participant: parsedArgs[0],
|
|
563
|
+
start: parsedArgs[1],
|
|
564
|
+
end: parsedArgs[2],
|
|
565
|
+
body: []
|
|
566
|
+
};
|
|
567
|
+
this.stack.push(node);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Process design keyword.
|
|
572
|
+
*
|
|
573
|
+
* design(<design_name>)
|
|
574
|
+
*/
|
|
575
|
+
processDesignKeyword () {
|
|
576
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
577
|
+
const node = {
|
|
578
|
+
"type": "design",
|
|
579
|
+
"design_name": parsedArgs
|
|
580
|
+
};
|
|
581
|
+
this.stack[this.stack.length - 1].body.push(node);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Process design keyword.
|
|
586
|
+
*
|
|
587
|
+
* cmd(args1, arg2...argN)
|
|
588
|
+
*/
|
|
589
|
+
processCmd (cmd) {
|
|
590
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
591
|
+
const node = {
|
|
592
|
+
"type": "cmd",
|
|
593
|
+
"command": cmd,
|
|
594
|
+
"args": parsedArgs
|
|
595
|
+
};
|
|
596
|
+
this.stack[this.stack.length - 1].body.push(node);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Parse the args.
|
|
601
|
+
*
|
|
602
|
+
* command("test",varName, 123,["count"])
|
|
603
|
+
*
|
|
604
|
+
* "args": [
|
|
605
|
+
* {
|
|
606
|
+
* "type": "string",
|
|
607
|
+
* "value": "test"
|
|
608
|
+
* },
|
|
609
|
+
* {
|
|
610
|
+
* "type": "name",
|
|
611
|
+
* "value": "varName"
|
|
612
|
+
* },
|
|
613
|
+
* {
|
|
614
|
+
* "type": "number",
|
|
615
|
+
* "value": 123
|
|
616
|
+
* },
|
|
617
|
+
* {
|
|
618
|
+
* "type": "list",
|
|
619
|
+
* "value": [
|
|
620
|
+
* "count"
|
|
621
|
+
* ]
|
|
622
|
+
* }
|
|
623
|
+
* ]
|
|
624
|
+
*
|
|
625
|
+
* @returns {Object} Returns the processed args.
|
|
626
|
+
*/
|
|
627
|
+
getArgs () {
|
|
628
|
+
const token = this.tokens[++this.currPos];
|
|
629
|
+
if (token.type !== "LPAREN") {
|
|
630
|
+
throw new Error("Expected LPAREN, Syntax error")
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const args = [];
|
|
634
|
+
let foundRParen = false;
|
|
635
|
+
do {
|
|
636
|
+
const token = this.tokens[++this.currPos];
|
|
637
|
+
if (token.type === "RPAREN") {
|
|
638
|
+
foundRParen = true;
|
|
639
|
+
break;
|
|
640
|
+
} else {
|
|
641
|
+
args.push(token);
|
|
642
|
+
}
|
|
643
|
+
} while (this.currPos < this.tokens.length)
|
|
644
|
+
|
|
645
|
+
// This is a crude attempt to see if brackets are closed
|
|
646
|
+
// It has obvious flaws, if the parenthesis isn't closed and
|
|
647
|
+
// then another identifier opens and closes parenthesis, it
|
|
648
|
+
// will keep moving forward until it reaches it.
|
|
649
|
+
|
|
650
|
+
// There is a better way to do this, so this will get replaced.
|
|
651
|
+
if (!foundRParen) {
|
|
652
|
+
throw new Error("Expected RPAREN, Syntax error")
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return args;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* This class produces an AST output given a DAL source file.
|
|
662
|
+
*
|
|
663
|
+
* Internally it invokes:
|
|
664
|
+
* - Lexer
|
|
665
|
+
* - Parser
|
|
666
|
+
* - AST output
|
|
667
|
+
*
|
|
668
|
+
* The AST output is sent to the synthesizer and synthesized
|
|
669
|
+
* into a python program.
|
|
670
|
+
*
|
|
671
|
+
* In the future, this will also:
|
|
672
|
+
* - Identify syntax errors
|
|
673
|
+
* - Lint
|
|
674
|
+
* - Validate the internal consistency of the design
|
|
675
|
+
*/
|
|
676
|
+
class DalAstGenerator {
|
|
677
|
+
|
|
678
|
+
constructor() {
|
|
679
|
+
this.ast = null;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
run (source) {
|
|
683
|
+
try {
|
|
684
|
+
const lexer = new DalLexer(source);
|
|
685
|
+
const parser = new DalParser(lexer.scannedTokens);
|
|
686
|
+
this.ast = parser.ast;
|
|
687
|
+
} catch(e) {
|
|
688
|
+
console.error("Error generating the AST:", e);
|
|
689
|
+
}
|
|
690
|
+
return this.ast;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export { DalAstGenerator };
|
package/docs/notes.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
## Planning
|
|
2
|
+
|
|
3
|
+
- Lexer tokenizes the script
|
|
4
|
+
- Parser uses the grammar to build the AST.
|
|
5
|
+
- Grammar establishes how to build tree from tokens.
|
|
6
|
+
|
|
7
|
+
## Sample Script
|
|
8
|
+
```
|
|
9
|
+
design(sample_design)
|
|
10
|
+
|
|
11
|
+
behavior(getValue) {
|
|
12
|
+
create("basket","list",[])
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Tree Output
|
|
17
|
+
```
|
|
18
|
+
SemanticModel
|
|
19
|
+
- Design
|
|
20
|
+
- name:sample Design
|
|
21
|
+
- Behavior
|
|
22
|
+
- name:getValue
|
|
23
|
+
- block
|
|
24
|
+
- Call
|
|
25
|
+
- name:create
|
|
26
|
+
- arguments
|
|
27
|
+
- participant: basket
|
|
28
|
+
- type: list
|
|
29
|
+
- value: []
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Synthesis Output
|
|
33
|
+
```
|
|
34
|
+
def getValue():
|
|
35
|
+
basket = []
|
|
36
|
+
```
|