dal-ast-js 0.0.2-dev → 0.0.4-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/README.md +31 -7
- package/dist/index.cjs +30 -2
- package/dist/index.esm.js +30 -2
- package/docs/validation_planning +12 -0
- package/package.json +1 -1
- package/src/ArgsParser.js +3 -0
- package/src/DalAstGenerator.js +1 -0
- package/src/DesignValidator.js +8 -0
- package/src/Lexer.js +18 -0
- package/src/Parser.js +6 -1
- package/src/TOKENS.js +2 -1
- package/src/docs/commands.json +56 -0
- package/tests/Lexer.test.js +2 -2
- package/tests/designs/reverse_name_persist.dal +52 -0
- package/tests/output/ast.json +71 -429
- package/tests/output/ast_direct_gen.json +71 -429
- package/tests/output/test_tokens.json +806 -2446
- package/synthesizer/Synthesizer.py +0 -88
- package/synthesizer/asts/lib_manager_ast.json +0 -842
- package/synthesizer/dal_ast_synthesizer.py +0 -31
- package/synthesizer/docs/commands.json +0 -8
- package/synthesizer/getSynthesizedNode.py +0 -362
- package/synthesizer/helper.py +0 -13
- package/synthesizer/output/LoggingHelper.py +0 -57
- package/synthesizer/output/synthesized.py +0 -76
- package/synthesizer/output_helpers/LoggingHelper.py +0 -57
- package/synthesizer/output_helpers/readme.md +0 -3
- package/synthesizer/readme.md +0 -9
- package/tests/designs/test3.dal +0 -16
package/README.md
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# DAL Abstract Syntax Tree Library
|
|
2
|
+
This tool currently takes a design defined in a Design Abstraction Language (DAL) and produces an Abstract Syntax Tree (AST).
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
To achieve this it implements the following tools:
|
|
5
|
+
- Lexer
|
|
6
|
+
- Parser
|
|
7
|
+
|
|
8
|
+
This tool will be extended to also do the following:
|
|
9
|
+
- Syntactic Validation:
|
|
10
|
+
- Provide metadata about the result of the lexing and parsing to identify syntax errors. This will be used by the workbench to visually provide feedback in realtime.
|
|
11
|
+
- Semantic Validation:
|
|
12
|
+
- Validate the design by ensuring that it is internally consistent in realtime.
|
|
13
|
+
|
|
14
|
+
This initial commit contains a working workflow but it is clearly not complete. I wanted to commit a working example to the repo to establish the workflow with the engine/workbench before I move forward.
|
|
15
|
+
|
|
16
|
+
Some relevant files:
|
|
17
|
+
- [Library Manager Design](./tests/designs/library_manager.dal)
|
|
18
|
+
- [Lexer Output](./tests/output/test_tokens.json)
|
|
19
|
+
- [Generated AST](./tests/output/ast_direct_gen.json)
|
|
20
|
+
|
|
21
|
+
# Test
|
|
22
|
+
While it does have test cases to verify functionality, currently this is more functional than complete.
|
|
6
23
|
|
|
7
24
|
Install libraries:
|
|
8
25
|
```sh
|
|
@@ -19,8 +36,15 @@ To run specific test case:
|
|
|
19
36
|
npm run test tests/Lexer.test.js
|
|
20
37
|
```
|
|
21
38
|
|
|
22
|
-
This
|
|
39
|
+
Run the lexer.test.js while providing it with a design file with a .dal extension. This will produce an AST output. Currently, I am reading the designs from the designs folder and writing the ast's to the output folder.
|
|
40
|
+
|
|
41
|
+
This will be streamlined by integrating everything into the workbench. It will automatically produce the AST and synthesize the program by invoking the python [synthesizer][synth].
|
|
42
|
+
|
|
43
|
+
# Providing feedback
|
|
44
|
+
|
|
45
|
+
You can use GitHub issues to [report a bug][bug-report] or [request a feature][feature-req].
|
|
23
46
|
|
|
24
|
-
|
|
47
|
+
[bug-report]: https://github.com/vishalpalaniappan/dal-ast-js/issues
|
|
48
|
+
[feature-req]: https://github.com/vishalpalaniappan/dal-ast-js/issues
|
|
49
|
+
[synth]: https://github.com/vishalpalaniappan/dal-synthesizer-python
|
|
25
50
|
|
|
26
|
-
This repo also contains a synthesizer that can take the generated AST and synthesize it into an implementation.
|
package/dist/index.cjs
CHANGED
|
@@ -8,7 +8,8 @@ let TOKENS = {
|
|
|
8
8
|
"LBRACE": "{",
|
|
9
9
|
"RBRACE": "}",
|
|
10
10
|
"LBRACKET": "[",
|
|
11
|
-
"RBRACKET": "]"
|
|
11
|
+
"RBRACKET": "]",
|
|
12
|
+
"HASH": "#"
|
|
12
13
|
};
|
|
13
14
|
TOKENS = Object.freeze(TOKENS);
|
|
14
15
|
|
|
@@ -84,6 +85,10 @@ class DalLexer {
|
|
|
84
85
|
|
|
85
86
|
const token = this.getToken(character);
|
|
86
87
|
if (token) {
|
|
88
|
+
if (token === "HASH") {
|
|
89
|
+
this.scanToNewLine();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
87
92
|
this.addAccumulatedIdentifierToken();
|
|
88
93
|
this.addToken(token);
|
|
89
94
|
if (token === "QUOTE") {
|
|
@@ -94,6 +99,20 @@ class DalLexer {
|
|
|
94
99
|
this.addToAccumulator(character);
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Scan to new line to ignore comment.
|
|
104
|
+
* @returns {null}
|
|
105
|
+
*/
|
|
106
|
+
scanToNewLine () {
|
|
107
|
+
do {
|
|
108
|
+
const character = this.source[this.currPos];
|
|
109
|
+
if (character == "\n") {
|
|
110
|
+
console.log("SCANNED NEW LINE");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
} while (++this.currPos < this.source.length);
|
|
114
|
+
}
|
|
115
|
+
|
|
97
116
|
/**
|
|
98
117
|
* Finds the identifier given the character.
|
|
99
118
|
* @param {String} character Character from scan.
|
|
@@ -371,6 +390,9 @@ class ArgsParser {
|
|
|
371
390
|
value = true;
|
|
372
391
|
} else if (value === "false") {
|
|
373
392
|
value = false;
|
|
393
|
+
} else if (value === "null") {
|
|
394
|
+
type = "null";
|
|
395
|
+
value = null;
|
|
374
396
|
}
|
|
375
397
|
}
|
|
376
398
|
|
|
@@ -587,11 +609,16 @@ class DalParser {
|
|
|
587
609
|
* Process design keyword.
|
|
588
610
|
*
|
|
589
611
|
* cmd(args1, arg2...argN)
|
|
612
|
+
*
|
|
613
|
+
* Type:
|
|
614
|
+
* _cmd -> "registeredCmd"
|
|
615
|
+
* cmd -> "cmd"
|
|
590
616
|
*/
|
|
591
617
|
processCmd (cmd) {
|
|
592
618
|
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
619
|
+
const type = (cmd[0] === "_")?"registeredCmd":"cmd";
|
|
593
620
|
const node = {
|
|
594
|
-
"type":
|
|
621
|
+
"type": type,
|
|
595
622
|
"command": cmd,
|
|
596
623
|
"args": parsedArgs
|
|
597
624
|
};
|
|
@@ -682,6 +709,7 @@ class DalAstGenerator {
|
|
|
682
709
|
}
|
|
683
710
|
|
|
684
711
|
run (source) {
|
|
712
|
+
this.ast = null;
|
|
685
713
|
try {
|
|
686
714
|
const lexer = new DalLexer(source);
|
|
687
715
|
const parser = new DalParser(lexer.scannedTokens);
|
package/dist/index.esm.js
CHANGED
|
@@ -6,7 +6,8 @@ let TOKENS$1 = {
|
|
|
6
6
|
"LBRACE": "{",
|
|
7
7
|
"RBRACE": "}",
|
|
8
8
|
"LBRACKET": "[",
|
|
9
|
-
"RBRACKET": "]"
|
|
9
|
+
"RBRACKET": "]",
|
|
10
|
+
"HASH": "#"
|
|
10
11
|
};
|
|
11
12
|
TOKENS$1 = Object.freeze(TOKENS$1);
|
|
12
13
|
|
|
@@ -82,6 +83,10 @@ class DalLexer {
|
|
|
82
83
|
|
|
83
84
|
const token = this.getToken(character);
|
|
84
85
|
if (token) {
|
|
86
|
+
if (token === "HASH") {
|
|
87
|
+
this.scanToNewLine();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
85
90
|
this.addAccumulatedIdentifierToken();
|
|
86
91
|
this.addToken(token);
|
|
87
92
|
if (token === "QUOTE") {
|
|
@@ -92,6 +97,20 @@ class DalLexer {
|
|
|
92
97
|
this.addToAccumulator(character);
|
|
93
98
|
}
|
|
94
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Scan to new line to ignore comment.
|
|
102
|
+
* @returns {null}
|
|
103
|
+
*/
|
|
104
|
+
scanToNewLine () {
|
|
105
|
+
do {
|
|
106
|
+
const character = this.source[this.currPos];
|
|
107
|
+
if (character == "\n") {
|
|
108
|
+
console.log("SCANNED NEW LINE");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
} while (++this.currPos < this.source.length);
|
|
112
|
+
}
|
|
113
|
+
|
|
95
114
|
/**
|
|
96
115
|
* Finds the identifier given the character.
|
|
97
116
|
* @param {String} character Character from scan.
|
|
@@ -369,6 +388,9 @@ class ArgsParser {
|
|
|
369
388
|
value = true;
|
|
370
389
|
} else if (value === "false") {
|
|
371
390
|
value = false;
|
|
391
|
+
} else if (value === "null") {
|
|
392
|
+
type = "null";
|
|
393
|
+
value = null;
|
|
372
394
|
}
|
|
373
395
|
}
|
|
374
396
|
|
|
@@ -585,11 +607,16 @@ class DalParser {
|
|
|
585
607
|
* Process design keyword.
|
|
586
608
|
*
|
|
587
609
|
* cmd(args1, arg2...argN)
|
|
610
|
+
*
|
|
611
|
+
* Type:
|
|
612
|
+
* _cmd -> "registeredCmd"
|
|
613
|
+
* cmd -> "cmd"
|
|
588
614
|
*/
|
|
589
615
|
processCmd (cmd) {
|
|
590
616
|
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
617
|
+
const type = (cmd[0] === "_")?"registeredCmd":"cmd";
|
|
591
618
|
const node = {
|
|
592
|
-
"type":
|
|
619
|
+
"type": type,
|
|
593
620
|
"command": cmd,
|
|
594
621
|
"args": parsedArgs
|
|
595
622
|
};
|
|
@@ -680,6 +707,7 @@ class DalAstGenerator {
|
|
|
680
707
|
}
|
|
681
708
|
|
|
682
709
|
run (source) {
|
|
710
|
+
this.ast = null;
|
|
683
711
|
try {
|
|
684
712
|
const lexer = new DalLexer(source);
|
|
685
713
|
const parser = new DalParser(lexer.scannedTokens);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## Syntax Validation Planning
|
|
2
|
+
- For a given keyword, it is easy to identify if the tokens match the pattern that is established in the grammar.
|
|
3
|
+
- How should I detect when blocks are not closed?
|
|
4
|
+
- I think there is actually a simple answer to this, since I am creating a stack for each nested block I encounter. I can tell at the end which stacks were not closed.
|
|
5
|
+
- I also cannot encounter an opening brace when I am inside a block without a new entry in the stack being created.
|
|
6
|
+
- Since I've already gathered the line numbers, its easy to visually identify exactly where the syntax error is.
|
|
7
|
+
|
|
8
|
+
It would be great to have a formatting class that will pretty print the script as well.
|
|
9
|
+
|
|
10
|
+
## Semantic Validation Planning
|
|
11
|
+
- I think this is very simple because it just requires setting up the rules and then seeing if they are enforced.
|
|
12
|
+
- If the rule isn't specified, that is also relevant information.
|
package/package.json
CHANGED
package/src/ArgsParser.js
CHANGED
package/src/DalAstGenerator.js
CHANGED
package/src/DesignValidator.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
// This requires knowledge about the actual transformations semantics, so I will establish that before moving forward with the validation.
|
|
5
5
|
// The synthesis development can happen in parallel because it doesn't impact this process.
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Note: This is not a working implementation, it is just the skeleton to
|
|
9
|
+
* traverse the tree. This will developed further in followup PRs.
|
|
10
|
+
*/
|
|
7
11
|
export class DesignValidator {
|
|
8
12
|
|
|
9
13
|
constructor (ast) {
|
|
@@ -23,6 +27,10 @@ export class DesignValidator {
|
|
|
23
27
|
}
|
|
24
28
|
this.processTree(child)
|
|
25
29
|
}
|
|
30
|
+
// Process else blocks in if statements.
|
|
31
|
+
if ("else" in node) {
|
|
32
|
+
this.processTree(node["else"]);
|
|
33
|
+
}
|
|
26
34
|
} else {
|
|
27
35
|
this.processNode(node);
|
|
28
36
|
}
|
package/src/Lexer.js
CHANGED
|
@@ -70,6 +70,10 @@ export class DalLexer {
|
|
|
70
70
|
|
|
71
71
|
const token = this.getToken(character);
|
|
72
72
|
if (token) {
|
|
73
|
+
if (token === "HASH") {
|
|
74
|
+
this.scanToNewLine();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
73
77
|
this.addAccumulatedIdentifierToken();
|
|
74
78
|
this.addToken(token);
|
|
75
79
|
if (token === "QUOTE") {
|
|
@@ -80,6 +84,20 @@ export class DalLexer {
|
|
|
80
84
|
this.addToAccumulator(character);
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Scan to new line to ignore comment.
|
|
89
|
+
* @returns {null}
|
|
90
|
+
*/
|
|
91
|
+
scanToNewLine () {
|
|
92
|
+
do {
|
|
93
|
+
const character = this.source[this.currPos];
|
|
94
|
+
if (character == "\n") {
|
|
95
|
+
console.log("SCANNED NEW LINE");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
} while (++this.currPos < this.source.length);
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
/**
|
|
84
102
|
* Finds the identifier given the character.
|
|
85
103
|
* @param {String} character Character from scan.
|
package/src/Parser.js
CHANGED
|
@@ -208,11 +208,16 @@ export class DalParser {
|
|
|
208
208
|
* Process design keyword.
|
|
209
209
|
*
|
|
210
210
|
* cmd(args1, arg2...argN)
|
|
211
|
+
*
|
|
212
|
+
* Type:
|
|
213
|
+
* _cmd -> "registeredCmd"
|
|
214
|
+
* cmd -> "cmd"
|
|
211
215
|
*/
|
|
212
216
|
processCmd (cmd) {
|
|
213
217
|
const parsedArgs = new ArgsParser(this.getArgs()).run();
|
|
218
|
+
const type = (cmd[0] === "_")?"registeredCmd":"cmd";
|
|
214
219
|
const node = {
|
|
215
|
-
"type":
|
|
220
|
+
"type": type,
|
|
216
221
|
"command": cmd,
|
|
217
222
|
"args": parsedArgs
|
|
218
223
|
}
|
package/src/TOKENS.js
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"set": {
|
|
3
|
+
"description": "Set the value of the target with the given keys",
|
|
4
|
+
"syntax": "set(<target>,<keys>,<value>)",
|
|
5
|
+
"synthesized": "target[<keys>] = <value>"
|
|
6
|
+
},
|
|
7
|
+
"create": {
|
|
8
|
+
"description": "Design metadata, declares participants.",
|
|
9
|
+
"syntax": "create(<name>,<type>,<role>)"
|
|
10
|
+
},
|
|
11
|
+
"log": {
|
|
12
|
+
"description": "Adds a log statement for the given participant.",
|
|
13
|
+
"syntax": "log(<behavior>, <name>, <type>, <value>)",
|
|
14
|
+
"synthesized": "semanticLogger.logParticipant(<behavior>, <name>, <type>, <value>)"
|
|
15
|
+
},
|
|
16
|
+
"insert": {
|
|
17
|
+
"description": "Inserts the value at the given index in the target with the provided keys",
|
|
18
|
+
"syntax": "insert(<target>, <keys>, <value>, <index>)",
|
|
19
|
+
"synthesized": " <target>[<keys>].insert(<index>, <value>)"
|
|
20
|
+
},
|
|
21
|
+
"get": {
|
|
22
|
+
"description": "Get the value from the source provided the keys and save in target.",
|
|
23
|
+
"syntax": "get(<target>, <source>, <keys>)",
|
|
24
|
+
"synthesized": "<target> = <source>[<keys>]"
|
|
25
|
+
},
|
|
26
|
+
"select": {
|
|
27
|
+
"description": "Selects the next behavior to exhibit.",
|
|
28
|
+
"syntax": "select(<nextBehavior>)",
|
|
29
|
+
"synthesized": "return \"<nextBehavior>\""
|
|
30
|
+
},
|
|
31
|
+
"display": {
|
|
32
|
+
"description": "Prints the promopt to screen (prompt is an f string)",
|
|
33
|
+
"example": "display(\"f'Got book named {name} and it has first letter {firstLetter}'\")",
|
|
34
|
+
"syntax": "display(<prompt>)",
|
|
35
|
+
"synthesized": "print(\"<prompt>\")"
|
|
36
|
+
},
|
|
37
|
+
"isEqual": {
|
|
38
|
+
"description": "Checks the equality of cmp1 and cmp2 and stores result.",
|
|
39
|
+
"syntax": "isEqual(<cmp1>, <cmp2>, <result>)",
|
|
40
|
+
"synthesized": "<result> = <cmp1> == <cmp2>"
|
|
41
|
+
},
|
|
42
|
+
"getFromPos": {
|
|
43
|
+
"description": "Get the value at pos from source and saves it in target.",
|
|
44
|
+
"syntax": "getFromPos(<target>, <source>, <pos>)",
|
|
45
|
+
"synthesized": "<target> = <source>[<pos>]"
|
|
46
|
+
},
|
|
47
|
+
"removeFromPos": {
|
|
48
|
+
"description": "Removes the value at pos from source and saves it in target.",
|
|
49
|
+
"syntax": "getFromPos(<target>, <source>, <pos>)",
|
|
50
|
+
"synthesized": "<target> = <source>.pop(<pos>)"
|
|
51
|
+
},
|
|
52
|
+
"run": {
|
|
53
|
+
"description": "Synthesizes the block which runs the desig.",
|
|
54
|
+
"syntax": "See geCmdRunAst in getSynthesizedNode.py"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/tests/Lexer.test.js
CHANGED
|
@@ -9,7 +9,7 @@ import { DalAstGenerator } from "../src/DalAstGenerator";
|
|
|
9
9
|
|
|
10
10
|
describe("Lexer", () => {
|
|
11
11
|
it("basic source", async () => {
|
|
12
|
-
const filePath = resolve(__dirname, "./designs/
|
|
12
|
+
const filePath = resolve(__dirname, "./designs/reverse_name_persist.dal")
|
|
13
13
|
const source = await readFile(filePath)
|
|
14
14
|
const lexer = new DalLexer(source.toString());
|
|
15
15
|
|
|
@@ -32,7 +32,7 @@ describe("Lexer", () => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it("tests direct ast generation", async () => {
|
|
35
|
-
const filePath = resolve(__dirname, "./designs/
|
|
35
|
+
const filePath = resolve(__dirname, "./designs/reverse_name_persist.dal")
|
|
36
36
|
const source = await readFile(filePath)
|
|
37
37
|
const ast = new DalAstGenerator().run(source.toString());
|
|
38
38
|
const ast_output_path = resolve(__dirname, "./output/ast_direct_gen.json")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
design ("reverse_name_persist")
|
|
2
|
+
|
|
3
|
+
# Example comment that is ignored.
|
|
4
|
+
behavior b_createDatabaseConnection {
|
|
5
|
+
create(name=connection, type="object", role="connection")
|
|
6
|
+
_connectToDatabase(connection)
|
|
7
|
+
set(worldState, ["connection"], connection)
|
|
8
|
+
select(b_createCursor)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
behavior b_createCursor {
|
|
12
|
+
create(name=cursor, type="object", role="cursor")
|
|
13
|
+
get(connection, worldState, ["connection"])
|
|
14
|
+
_createCursor(cursor, connection)
|
|
15
|
+
set(worldState, ["cursor"], cursor)
|
|
16
|
+
select(b_createTable)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
behavior b_createTable {
|
|
20
|
+
get(cursor, worldState, ["cursor"])
|
|
21
|
+
_createTable(null, cursor)
|
|
22
|
+
select(b_commitConnection)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
behavior b_commitConnection {
|
|
26
|
+
get(connection, worldState, ["connection"])
|
|
27
|
+
_commitConnection(null, connection)
|
|
28
|
+
select(b_receiveName)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
behavior b_receiveName {
|
|
32
|
+
create(name=name, type="string", role="name")
|
|
33
|
+
_receiveName(name)
|
|
34
|
+
set(worldState, ["name"], name)
|
|
35
|
+
select(b_reverse)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
behavior b_reverse {
|
|
39
|
+
get(name, worldState, ["name"])
|
|
40
|
+
_reverse(reversedName, name)
|
|
41
|
+
set(worldState, ["reversedName"], reversedName)
|
|
42
|
+
select(b_writeToDatabase)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
behavior b_writeToDatabase {
|
|
46
|
+
get(cursor, worldState, ["cursor"])
|
|
47
|
+
get(reversedName, worldState, ["reversedName"])
|
|
48
|
+
_writeToDatabase(null, cursor, reversedName)
|
|
49
|
+
select(b_commitConnection)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
run(b_createDatabaseConnection)
|