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/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,13 @@
1
+ let TOKENS = {
2
+ "LPAREN": "(",
3
+ "RPAREN": ")",
4
+ "QUOTE": "\"",
5
+ "COMMA": ",",
6
+ "LBRACE": "{",
7
+ "RBRACE": "}",
8
+ "LBRACKET": "[",
9
+ "RBRACKET": "]"
10
+ }
11
+ TOKENS = Object.freeze(TOKENS);
12
+
13
+ export default TOKENS;
@@ -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.
@@ -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}")