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
package/src/Parser.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import KEYWORDS from "./KEYWORDS";
|
|
2
|
+
import { ArgsParser } from "./ArgsParser";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This class produces an AST given the scanned tokens.
|
|
6
|
+
*
|
|
7
|
+
* Steps:
|
|
8
|
+
* - Process current token
|
|
9
|
+
* - Visit keyword and create node
|
|
10
|
+
* - Parse the args and save node metadata
|
|
11
|
+
* - If node has a body (if, behavior), add to stack
|
|
12
|
+
* - If node doesn't have body, add to body of stack top
|
|
13
|
+
* - When rbrace is encountered, pop stack and add
|
|
14
|
+
* to body of top of stack.
|
|
15
|
+
*
|
|
16
|
+
* It is a very simple algorithm because my language is basic.
|
|
17
|
+
* I create stack to track the nested body being processed.
|
|
18
|
+
* I use rbrace as marker to finish a nested block and build the tree.
|
|
19
|
+
*
|
|
20
|
+
* Note: This was the first way I thought to implement this
|
|
21
|
+
* but I think there is a better way, I am going to iterate
|
|
22
|
+
* on this. This approach does not identify syntax errors
|
|
23
|
+
* properly.
|
|
24
|
+
*
|
|
25
|
+
* The actual commands are very simple in this language
|
|
26
|
+
* because I am always following the format shown below:
|
|
27
|
+
*
|
|
28
|
+
* command(args)
|
|
29
|
+
*
|
|
30
|
+
* So every identifier that isn't a keyword while scanning
|
|
31
|
+
* forward is a command.
|
|
32
|
+
*/
|
|
33
|
+
export class DalParser {
|
|
34
|
+
|
|
35
|
+
constructor (tokens) {
|
|
36
|
+
this.tokens = tokens;
|
|
37
|
+
this.currPos = 0;
|
|
38
|
+
this.ast = {
|
|
39
|
+
type: "root",
|
|
40
|
+
body: []
|
|
41
|
+
}
|
|
42
|
+
this.stack = [this.ast];
|
|
43
|
+
this.run();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
run () {
|
|
47
|
+
do {
|
|
48
|
+
const token = this.tokens[this.currPos];
|
|
49
|
+
this.processToken(token);
|
|
50
|
+
} while (++this.currPos < this.tokens.length);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Process the current token.
|
|
55
|
+
*
|
|
56
|
+
* RBRACE indicates current block is over.
|
|
57
|
+
* behavior and if are blocks and have a body
|
|
58
|
+
* design is a statement.
|
|
59
|
+
*
|
|
60
|
+
* @param {String} token
|
|
61
|
+
*/
|
|
62
|
+
processToken(token) {
|
|
63
|
+
let output;
|
|
64
|
+
if (token.type === "RBRACE") {
|
|
65
|
+
this.closeBlock();
|
|
66
|
+
} else if (token.value === "design") {
|
|
67
|
+
this.processDesignKeyword();
|
|
68
|
+
} else if (token.value === "behavior") {
|
|
69
|
+
this.processBehavior();
|
|
70
|
+
} else if (token.value === "if") {
|
|
71
|
+
this.processIf();
|
|
72
|
+
} else if (token.value === "else") {
|
|
73
|
+
this.processElse();
|
|
74
|
+
} else if (token.value === "for") {
|
|
75
|
+
this.processFor();
|
|
76
|
+
} else if (token.value === "while") {
|
|
77
|
+
this.processWhile();
|
|
78
|
+
}else if (token.type === "IDENTIFIER") {
|
|
79
|
+
this.processCmd(token.value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Closes the nested block by removing it from
|
|
86
|
+
* the top of the stack and adding it to the new
|
|
87
|
+
* node at the top of the stack.
|
|
88
|
+
*
|
|
89
|
+
* It uses the RBRACE identifier to determine if
|
|
90
|
+
* the top block is closed.
|
|
91
|
+
*
|
|
92
|
+
* For if statements, it appends the elif and else
|
|
93
|
+
* blocks to the if block that was already processed
|
|
94
|
+
* so that they can be synthesized in a single AST node.
|
|
95
|
+
*/
|
|
96
|
+
closeBlock () {
|
|
97
|
+
const node = this.stack.pop();
|
|
98
|
+
|
|
99
|
+
if (node.type === "else") {
|
|
100
|
+
const body = this.stack[this.stack.length - 1].body;
|
|
101
|
+
const ifNode = body[body.length - 1]
|
|
102
|
+
ifNode["else"] = node;
|
|
103
|
+
} else {
|
|
104
|
+
this.stack[this.stack.length - 1].body.push(node);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Processes the behavior block.
|
|
110
|
+
*
|
|
111
|
+
* behavior <behavior_name>(args1, arg2...argN) {
|
|
112
|
+
*
|
|
113
|
+
* }
|
|
114
|
+
*/
|
|
115
|
+
processBehavior () {
|
|
116
|
+
const behaviorName = this.tokens[++this.currPos].value;
|
|
117
|
+
const node = {
|
|
118
|
+
type: "behavior",
|
|
119
|
+
behaviorName: behaviorName,
|
|
120
|
+
body: []
|
|
121
|
+
}
|
|
122
|
+
this.stack.push(node)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Processes if block.
|
|
127
|
+
*
|
|
128
|
+
* if (condition) {
|
|
129
|
+
*
|
|
130
|
+
* }
|
|
131
|
+
*/
|
|
132
|
+
processIf () {
|
|
133
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
134
|
+
const node = {
|
|
135
|
+
type: "if",
|
|
136
|
+
args: parsedArgs,
|
|
137
|
+
body: []
|
|
138
|
+
}
|
|
139
|
+
this.stack.push(node)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Processes while block.
|
|
144
|
+
*
|
|
145
|
+
* while (condition) {
|
|
146
|
+
*
|
|
147
|
+
* }
|
|
148
|
+
*/
|
|
149
|
+
processWhile () {
|
|
150
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
151
|
+
const node = {
|
|
152
|
+
type: "while",
|
|
153
|
+
args: parsedArgs,
|
|
154
|
+
body: []
|
|
155
|
+
}
|
|
156
|
+
this.stack.push(node)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Processes else block.
|
|
161
|
+
*
|
|
162
|
+
* else (condition) {
|
|
163
|
+
*
|
|
164
|
+
* }
|
|
165
|
+
*/
|
|
166
|
+
processElse () {
|
|
167
|
+
const node = {
|
|
168
|
+
type: "else",
|
|
169
|
+
body: []
|
|
170
|
+
}
|
|
171
|
+
this.stack.push(node)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Processes for block.
|
|
176
|
+
*
|
|
177
|
+
* for (<participan>, <start>,<end>) {
|
|
178
|
+
*
|
|
179
|
+
* }
|
|
180
|
+
*/
|
|
181
|
+
processFor () {
|
|
182
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
183
|
+
const node = {
|
|
184
|
+
type: "for",
|
|
185
|
+
participant: parsedArgs[0],
|
|
186
|
+
start: parsedArgs[1],
|
|
187
|
+
end: parsedArgs[2],
|
|
188
|
+
body: []
|
|
189
|
+
}
|
|
190
|
+
this.stack.push(node)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Process design keyword.
|
|
195
|
+
*
|
|
196
|
+
* design(<design_name>)
|
|
197
|
+
*/
|
|
198
|
+
processDesignKeyword () {
|
|
199
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
200
|
+
const node = {
|
|
201
|
+
"type": "design",
|
|
202
|
+
"design_name": parsedArgs
|
|
203
|
+
}
|
|
204
|
+
this.stack[this.stack.length - 1].body.push(node);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Process design keyword.
|
|
209
|
+
*
|
|
210
|
+
* cmd(args1, arg2...argN)
|
|
211
|
+
*/
|
|
212
|
+
processCmd (cmd) {
|
|
213
|
+
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
214
|
+
const node = {
|
|
215
|
+
"type": "cmd",
|
|
216
|
+
"command": cmd,
|
|
217
|
+
"args": parsedArgs
|
|
218
|
+
}
|
|
219
|
+
this.stack[this.stack.length - 1].body.push(node);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Parse the args.
|
|
224
|
+
*
|
|
225
|
+
* command("test",varName, 123,["count"])
|
|
226
|
+
*
|
|
227
|
+
* "args": [
|
|
228
|
+
* {
|
|
229
|
+
* "type": "string",
|
|
230
|
+
* "value": "test"
|
|
231
|
+
* },
|
|
232
|
+
* {
|
|
233
|
+
* "type": "name",
|
|
234
|
+
* "value": "varName"
|
|
235
|
+
* },
|
|
236
|
+
* {
|
|
237
|
+
* "type": "number",
|
|
238
|
+
* "value": 123
|
|
239
|
+
* },
|
|
240
|
+
* {
|
|
241
|
+
* "type": "list",
|
|
242
|
+
* "value": [
|
|
243
|
+
* "count"
|
|
244
|
+
* ]
|
|
245
|
+
* }
|
|
246
|
+
* ]
|
|
247
|
+
*
|
|
248
|
+
* @returns {Object} Returns the processed args.
|
|
249
|
+
*/
|
|
250
|
+
getArgs () {
|
|
251
|
+
const token = this.tokens[++this.currPos];
|
|
252
|
+
if (token.type !== "LPAREN") {
|
|
253
|
+
throw new Error("Expected LPAREN, Syntax error")
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const args = [];
|
|
257
|
+
let foundRParen = false;
|
|
258
|
+
do {
|
|
259
|
+
const token = this.tokens[++this.currPos];
|
|
260
|
+
if (token.type === "RPAREN") {
|
|
261
|
+
foundRParen = true;
|
|
262
|
+
break;
|
|
263
|
+
} else {
|
|
264
|
+
args.push(token);
|
|
265
|
+
}
|
|
266
|
+
} while (this.currPos < this.tokens.length)
|
|
267
|
+
|
|
268
|
+
// This is a crude attempt to see if brackets are closed
|
|
269
|
+
// It has obvious flaws, if the parenthesis isn't closed and
|
|
270
|
+
// then another identifier opens and closes parenthesis, it
|
|
271
|
+
// will keep moving forward until it reaches it.
|
|
272
|
+
|
|
273
|
+
// There is a better way to do this, so this will get replaced.
|
|
274
|
+
if (!foundRParen) {
|
|
275
|
+
throw new Error("Expected RPAREN, Syntax error")
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return args;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
}
|
package/src/TOKENS.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import TOKENS from "./TOKENS";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds string back from the tokens.
|
|
5
|
+
*
|
|
6
|
+
* Ex:
|
|
7
|
+
* LBRACKET QUOTE IDENTIFIER("test") QUOTE RBRACKET
|
|
8
|
+
* ("test")
|
|
9
|
+
*/
|
|
10
|
+
export class Untokenizer {
|
|
11
|
+
|
|
12
|
+
constructor (tokens) {
|
|
13
|
+
this.tokens = tokens;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Builds string from tokens.
|
|
18
|
+
* @param {Array} tokens
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
buildString(tokens) {
|
|
22
|
+
let str = "";
|
|
23
|
+
|
|
24
|
+
for (const token of this.tokens) {
|
|
25
|
+
if (token.type === "IDENTIFIER") {
|
|
26
|
+
str = str + token.value
|
|
27
|
+
} else {
|
|
28
|
+
str = str + TOKENS[token.type];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return str;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Writing down some notes here as I am working through the design validator:
|
|
2
|
+
|
|
3
|
+
- As I was processing this, I decided that I would create a participant declaration block and in it, I would establish the participants, their types, allowed transformations domain structure etc.
|
|
4
|
+
- When I call the create command, it will create a participant of a particular type and assign it a UID.
|
|
5
|
+
- Then when I save that participant to the world state or access it anywhere else, I have the UID that traces it back to the creation.
|
|
6
|
+
- When a participant interacts with a transformation, for example, insert book into basket, their types will be checked to see if book is allowed to be inserted into basket.
|
package/src/grammar.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"token": "IDENTIFIER",
|
|
4
|
+
"value": "design",
|
|
5
|
+
"description": "design(\"lexer\")",
|
|
6
|
+
"order": [
|
|
7
|
+
{
|
|
8
|
+
"token": "LPAREN",
|
|
9
|
+
"endToken": "RPAREN",
|
|
10
|
+
"contentType": "args"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"token": "IDENTIFIER",
|
|
16
|
+
"value": "behavior",
|
|
17
|
+
"order": [
|
|
18
|
+
{
|
|
19
|
+
"token": "IDENTIFIER",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"identity": "behaviorName"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"token": "LBRACE",
|
|
25
|
+
"endToken": "RBRACE",
|
|
26
|
+
"contentType": "body"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"token": "IDENTIFIER",
|
|
32
|
+
"value": "if",
|
|
33
|
+
"order": [
|
|
34
|
+
{
|
|
35
|
+
"token": "LPAREN",
|
|
36
|
+
"endToken": "RPAREN",
|
|
37
|
+
"contentType": "args"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"token": "LBRACE",
|
|
41
|
+
"endToken": "RBRACE",
|
|
42
|
+
"contentType": "body"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"token": "IDENTIFIER",
|
|
48
|
+
"value": "else",
|
|
49
|
+
"order": [
|
|
50
|
+
{
|
|
51
|
+
"token": "LBRACE",
|
|
52
|
+
"endToken": "RBRACE",
|
|
53
|
+
"contentType": "body"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from getSynthesizedNode import getSynthesizedNode
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
class Synthesizer:
|
|
7
|
+
|
|
8
|
+
def __init__(self, dalAst):
|
|
9
|
+
self.dalAst = dalAst
|
|
10
|
+
self.pythonAst = ast.Module(
|
|
11
|
+
body=[],
|
|
12
|
+
type_ignores=[]
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def run(self):
|
|
16
|
+
'''
|
|
17
|
+
Run the synthesizer
|
|
18
|
+
'''
|
|
19
|
+
# Process each node in the DAL ast.
|
|
20
|
+
for node in self.dalAst["body"]:
|
|
21
|
+
self.processTree(node, self.pythonAst, 0)
|
|
22
|
+
|
|
23
|
+
importNode = ast.parse("from LoggingHelper import semanticLogger").body[0]
|
|
24
|
+
self.pythonAst.body.insert(0, importNode)
|
|
25
|
+
|
|
26
|
+
self.writeToOutputFolder()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def writeToOutputFolder(self):
|
|
30
|
+
'''
|
|
31
|
+
Writes the synthesized output to output folder and
|
|
32
|
+
also adds the logging helper.
|
|
33
|
+
|
|
34
|
+
If the output folder has files, clear it.
|
|
35
|
+
'''
|
|
36
|
+
outputFolder = Path(__file__).parent / "output"
|
|
37
|
+
|
|
38
|
+
# Make folder if it doesn't exist
|
|
39
|
+
outputFolder.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
|
|
41
|
+
# Clear the output folder (assuming it existed)
|
|
42
|
+
for item in outputFolder.iterdir():
|
|
43
|
+
if item.is_dir():
|
|
44
|
+
shutil.rmtree(item)
|
|
45
|
+
else:
|
|
46
|
+
item.unlink()
|
|
47
|
+
|
|
48
|
+
# Write the synthesized output
|
|
49
|
+
outputFile = Path(__file__).parent / "output" / "synthesized.py"
|
|
50
|
+
with open(outputFile,"w+") as f:
|
|
51
|
+
f.write(ast.unparse(self.pythonAst))
|
|
52
|
+
|
|
53
|
+
# Copyt logging helper
|
|
54
|
+
src = Path(__file__).parent / "output_helpers" / "LoggingHelper.py"
|
|
55
|
+
dst = Path(__file__).parent / "output" / "LoggingHelper.py"
|
|
56
|
+
shutil.copy(src, dst)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def processTree(self, dalAstNode, pythonAstNode, indent):
|
|
60
|
+
'''
|
|
61
|
+
Process the tree node. If there is a body, process
|
|
62
|
+
each node in the body.
|
|
63
|
+
|
|
64
|
+
Writes the synthesized ast node to the ast tree.
|
|
65
|
+
'''
|
|
66
|
+
# self.printTree(indent, dalAstNode["type"])
|
|
67
|
+
astNodeBody = getSynthesizedNode(dalAstNode)
|
|
68
|
+
|
|
69
|
+
if astNodeBody is None:
|
|
70
|
+
if dalAstNode['type'] == "cmd":
|
|
71
|
+
type = dalAstNode['type'] + "," + dalAstNode['command']
|
|
72
|
+
else:
|
|
73
|
+
type = dalAstNode['type']
|
|
74
|
+
print(f"Unable to synthesize node of type {type}")
|
|
75
|
+
else:
|
|
76
|
+
ast.fix_missing_locations(astNodeBody)
|
|
77
|
+
pythonAstNode.body.append(astNodeBody)
|
|
78
|
+
|
|
79
|
+
if "body" in dalAstNode:
|
|
80
|
+
for node in dalAstNode["body"]:
|
|
81
|
+
self.processTree(node, astNodeBody, indent + 1)
|
|
82
|
+
|
|
83
|
+
def printTree(self, indent, value):
|
|
84
|
+
'''
|
|
85
|
+
Prints Tree with indentation for inspection.
|
|
86
|
+
'''
|
|
87
|
+
spaces = (indent * 4) * " "
|
|
88
|
+
print(f"{spaces}{value}")
|