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/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "dal-ast-js",
3
+ "version": "0.0.1-dev",
4
+ "main": "dist/index.cjs",
5
+ "module": "dist/index.esm.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "rollup -c",
9
+ "test": "vitest",
10
+ "test:watch": "vitest --watch",
11
+ "test:run": "vitest run"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/vishalpalaniappan/program-synthesizer-python.git"
16
+ },
17
+ "author": "Vishal Palaniappan",
18
+ "license": "MIT",
19
+ "bugs": {
20
+ "url": "https://github.com/vishalpalaniappan/program-synthesizer-python/issues"
21
+ },
22
+ "homepage": "https://github.com/vishalpalaniappan/program-synthesizer-python",
23
+ "devDependencies": {
24
+ "@rollup/plugin-node-resolve": "^16.0.3",
25
+ "vitest": "^4.0.18",
26
+ "rollup": "^4.57.1"
27
+ }
28
+ }
@@ -0,0 +1,18 @@
1
+ import resolve from "@rollup/plugin-node-resolve";
2
+
3
+ export default {
4
+ input: "src/DalAstGenerator.js",
5
+ output: [
6
+ {
7
+ file: "dist/index.cjs",
8
+ format: "cjs",
9
+ inlineDynamicImports: true,
10
+ },
11
+ {
12
+ file: "dist/index.esm.js",
13
+ format: "esm",
14
+ inlineDynamicImports: true,
15
+ },
16
+ ],
17
+ plugins: [resolve()],
18
+ };
@@ -0,0 +1,152 @@
1
+ import { Untokenizer } from "./Untokenizer";
2
+
3
+ /**
4
+ * Structure
5
+ * command(args...)
6
+ *
7
+ * Example:
8
+ * set(book, ["name"], "Harry Potter")
9
+ *
10
+ * Returns:
11
+ * [
12
+ * {
13
+ * "type": "name",
14
+ * "value": "book"
15
+ * },
16
+ * {
17
+ * "type": "list",
18
+ * "value": ["name"]
19
+ * },
20
+ * {
21
+ * "type": "string",
22
+ * "value": "Harry Potter"
23
+ * }
24
+ * ]
25
+ *
26
+ * This class parses the arguments from parser
27
+ * for a keyword or command instruction.
28
+ */
29
+ export class ArgsParser {
30
+ constructor (tokens) {
31
+ this.tokens = tokens;
32
+ this.groupedTokens = [];
33
+ this.parsedArgs = [];
34
+
35
+ this.pairs = [
36
+ {
37
+ start:"QUOTE",
38
+ end:"QUOTE",
39
+ type:"string"
40
+ },
41
+ {
42
+ start:"LBRACE",
43
+ end:"RBRACE",
44
+ type:"object"
45
+ },
46
+ {
47
+ start:"LBRACKET",
48
+ end:"RBRACKET",
49
+ type: "list"
50
+ }
51
+ ]
52
+ }
53
+
54
+ run () {
55
+ if (this.tokens.length === 0) {
56
+ return this.parsedArgs;
57
+ }
58
+ this.splitByComma(this.tokens);
59
+ for (const group of this.groupedTokens) {
60
+ this.parsedArgs.push(this.processGroup(group));
61
+ }
62
+ return this.parsedArgs;
63
+ }
64
+
65
+ /**
66
+ * Splits the tokens by commas so they can be processed independently.
67
+ * For objects, arrays and strings, it flags that pair is being tracked
68
+ * so that any commmas inside the object or array is ignored.
69
+ * @param {Array} tokens
70
+ */
71
+ splitByComma (tokens) {
72
+ let pos = 0;
73
+ let group = [];
74
+ this.trackingPair = null;
75
+ do {
76
+ const token = this.tokens[pos];
77
+ if (token.type === "COMMA" && !this.trackingPair) {
78
+ this.groupedTokens.push(group);
79
+ group = [];
80
+ continue;
81
+ }
82
+
83
+ if (this.trackingPair && token.type === this.trackingPair) {
84
+ this.trackingPair = null;
85
+ } else if (!this.trackingPair) {
86
+ this.trackingPair = this.isPair(token);
87
+ }
88
+
89
+ group.push(token);
90
+
91
+ } while (++pos < this.tokens.length);
92
+
93
+ this.groupedTokens.push(group);
94
+ }
95
+
96
+ /**
97
+ * Checks if the token is a pair.
98
+ * @param {Object} token Token being checked.
99
+ * @returns {String|null}
100
+ */
101
+ isPair (token) {
102
+ for(const pair of this.pairs) {
103
+ if (token.type === pair.start) {
104
+ return pair.end;
105
+ }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Process the grouped tokens and returns
111
+ * the type and value of the arguments.
112
+ *
113
+ * Types:
114
+ * ------
115
+ * Name (this is a participant name)
116
+ * String
117
+ * List or Array
118
+ * Object
119
+ * Number
120
+ *
121
+ * @param {Array} tokens
122
+ */
123
+ processGroup (tokens) {
124
+ let type;
125
+ let value = new Untokenizer(tokens).buildString();
126
+ if (tokens[0].type === "QUOTE") {
127
+ type = "string";
128
+ value = JSON.parse(value)
129
+ } else if (tokens[0].type === "LBRACKET") {
130
+ type = "list"
131
+ value = JSON.parse(value)
132
+ } else if (tokens[0].type === "LBRACE") {
133
+ type = "object"
134
+ value = JSON.parse(value)
135
+ } else if (!Number.isNaN(Number(value))) {
136
+ type = "number"
137
+ value = parseFloat(value)
138
+ } else {
139
+ type = "name";
140
+ if (value === "true") {
141
+ value = true;
142
+ } else if (value === "false") {
143
+ value = false;
144
+ }
145
+ }
146
+
147
+ return {
148
+ type: type,
149
+ value: value
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,35 @@
1
+ import { DalLexer } from "./Lexer";
2
+ import { DalParser } from "./Parser";
3
+ /**
4
+ * This class produces an AST output given a DAL source file.
5
+ *
6
+ * Internally it invokes:
7
+ * - Lexer
8
+ * - Parser
9
+ * - AST output
10
+ *
11
+ * The AST output is sent to the synthesizer and synthesized
12
+ * into a python program.
13
+ *
14
+ * In the future, this will also:
15
+ * - Identify syntax errors
16
+ * - Lint
17
+ * - Validate the internal consistency of the design
18
+ */
19
+ export class DalAstGenerator {
20
+
21
+ constructor() {
22
+ this.ast = null;
23
+ }
24
+
25
+ run (source) {
26
+ try {
27
+ const lexer = new DalLexer(source);
28
+ const parser = new DalParser(lexer.scannedTokens);
29
+ this.ast = parser.ast;
30
+ } catch(e) {
31
+ console.error("Error generating the AST:", e);
32
+ }
33
+ return this.ast;
34
+ }
35
+ }
@@ -0,0 +1,37 @@
1
+ // This will evaluate the semantic world for inconsistencies.
2
+ // Example: Type mistmatch, invalid participants etc.
3
+
4
+ // This requires knowledge about the actual transformations semantics, so I will establish that before moving forward with the validation.
5
+ // The synthesis development can happen in parallel because it doesn't impact this process.
6
+
7
+ export class DesignValidator {
8
+
9
+ constructor (ast) {
10
+ this.ast = ast;
11
+ this.currentBehavior;
12
+ }
13
+
14
+ run() {
15
+ this.processTree(this.ast);
16
+ }
17
+
18
+ processTree(node) {
19
+ if ("body" in node) {
20
+ for (const child of node["body"]) {
21
+ if (child["type"] === "behavior") {
22
+ this.currentBehavior = child;
23
+ }
24
+ this.processTree(child)
25
+ }
26
+ } else {
27
+ this.processNode(node);
28
+ }
29
+
30
+ }
31
+
32
+ processNode(node) {
33
+ if (node.type === "cmd" && node.command === "create") {
34
+ console.log(`Participant ${node.args[0].value} created in behavior ${this.currentBehavior["behaviorName"]}`, )
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,11 @@
1
+ let KEYWORDS = [
2
+ "design",
3
+ "behavior",
4
+ "if",
5
+ "else",
6
+ "for",
7
+ "while"
8
+ ]
9
+ KEYWORDS = Object.freeze(KEYWORDS);
10
+
11
+ export default KEYWORDS;
package/src/Lexer.js ADDED
@@ -0,0 +1,174 @@
1
+ import TOKENS from "./TOKENS";
2
+
3
+ /**
4
+ * This lexer works as follows:
5
+ * - Reads character at current position
6
+ * - If it is not a token:
7
+ * - adds to identifier accumulator
8
+ * - If it is a token:
9
+ * - saves what is in the accumulator to scannedTokens
10
+ * - saves the token to scannedTokens
11
+ * - If a token is a quote:
12
+ * - saves quote to scanned token
13
+ * - scans forward to end of quote while accumulating identifier
14
+ * - saves accumulator to scanned tokens
15
+ * - saves quote to scanned tokens
16
+ *
17
+ * I am not scanning for keywords here, I just save them as
18
+ * identifiers and then in the parse stage I will classify them.
19
+ * This makes the algorithm very simple.
20
+ *
21
+ * Example:
22
+ * design ("name")
23
+ * IDENTIFIER LPAREN QUOTE IDENTIFIER QUOTE RPAREN
24
+ *
25
+ * I also save the line/col number (starting and ending). This will be
26
+ * useful for visualization in the workbench.
27
+ */
28
+ export class DalLexer {
29
+ constructor (source) {
30
+ this.currPos = 0;
31
+ this.source = [...source];
32
+ this.scannedTokens = [];
33
+
34
+ // Current line and colno
35
+ this.lineno = 1;
36
+ this.colno = 0;
37
+
38
+ // Accumulates identifiers until tokens are visited.
39
+ this.accumulatedIdentifier = [];
40
+ this.startColIdentifier;
41
+ this.startLineIdentifier;
42
+
43
+ this.run();
44
+ }
45
+
46
+ /**
47
+ * Runs the lexer from the current position.
48
+ */
49
+ run() {
50
+ do {
51
+ const character = this.source[this.currPos];
52
+ this.processCurrentPosition(character);
53
+ } while (++this.currPos < this.source.length);
54
+ }
55
+
56
+ /**
57
+ * Processes the character at the current position.
58
+ */
59
+ processCurrentPosition(character) {
60
+ this.colno++;
61
+
62
+ if (character == " ") {
63
+ this.addAccumulatedIdentifierToken();
64
+ return;
65
+ } else if (character == "\n") {
66
+ this.lineno++;
67
+ this.colno = 0;
68
+ return;
69
+ }
70
+
71
+ const token = this.getToken(character);
72
+ if (token) {
73
+ this.addAccumulatedIdentifierToken();
74
+ this.addToken(token);
75
+ if (token === "QUOTE") {
76
+ this.extractStringFromQuotes();
77
+ }
78
+ return;
79
+ }
80
+ this.addToAccumulator(character);
81
+ }
82
+
83
+ /**
84
+ * Finds the identifier given the character.
85
+ * @param {String} character Character from scan.
86
+ * @returns {String|null} Identifier if valid token, else null.
87
+ */
88
+ getToken (character) {
89
+ for (const [identifier, value] of Object.entries(TOKENS)) {
90
+ if (value[0] !== character) {
91
+ continue;
92
+ }
93
+ return identifier;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * If a quote is encountered, scan forward until end of quote is found.
99
+ * Everything inside the quote is part of the string, this ignores any
100
+ * of the tokens like comma.
101
+ *
102
+ * TODO: Add support for escaped quotes inside quotes.
103
+ */
104
+ extractStringFromQuotes () {
105
+ this.colno++;
106
+ this.currPos++;
107
+ do {
108
+ const character = this.source[this.currPos];
109
+ const identifier = this.getToken(character);
110
+ if (character === "\n") {
111
+ this.lineno++;
112
+ this.colno = 0;
113
+ continue;
114
+ }
115
+ if (identifier !== "QUOTE") {
116
+ this.addToAccumulator(character);
117
+ this.colno++;
118
+ continue;
119
+ }
120
+ this.addAccumulatedIdentifierToken();
121
+ this.addToken(identifier);
122
+ break;
123
+ } while (this.currPos++ < this.source.length)
124
+ }
125
+
126
+ /**
127
+ * Adds to accumulator. Saves starting position of
128
+ * identifier being accumulated.
129
+ *
130
+ * @param {String} character Character to add to accumulate.
131
+ */
132
+ addToAccumulator (character) {
133
+ if (this.accumulatedIdentifier.length === 0) {
134
+ this.startColIdentifier = this.colno;
135
+ this.startLineIdentifier = this.lineno;
136
+ }
137
+ this.accumulatedIdentifier.push(character);
138
+ }
139
+
140
+ /**
141
+ * Add the accumulated identifier to the scannedTokens list.
142
+ */
143
+ addAccumulatedIdentifierToken () {
144
+ // Subtract 1 from endColno beause we have to reach token
145
+ // to identify that the accumulator is done.
146
+ if (this.accumulatedIdentifier.length > 0) {
147
+ this.scannedTokens.push({
148
+ type: "IDENTIFIER",
149
+ value: this.accumulatedIdentifier.join(""),
150
+ startLineno: this.startLineIdentifier,
151
+ startColno: this.startColIdentifier,
152
+ endLineno: this.lineno,
153
+ endColno: this.colno - 1
154
+ })
155
+ this.accumulatedIdentifier = [];
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Adds the token to the scannedTokens list.
161
+ * @param {String} type Type for the token.
162
+ * @param {Number} value Value of token (example identifier)
163
+ */
164
+ addToken (type, value) {
165
+ this.scannedTokens.push({
166
+ type: type,
167
+ value: value,
168
+ startLineno: this.lineno,
169
+ startColno: this.colno,
170
+ endLineno: this.lineno,
171
+ endColno: this.colno
172
+ })
173
+ }
174
+ }