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/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
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -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
|
+
}
|
package/src/KEYWORDS.js
ADDED
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
|
+
}
|