drakongen 1.0.0
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/.vscode/launch.json +18 -0
- package/LICENSE +24 -0
- package/README.md +80 -0
- package/browser/drakongen.js +1303 -0
- package/browsertest.html +84 -0
- package/buildbrowser +4 -0
- package/buildexamples.js +61 -0
- package/examples/00.Empty.drakon +1 -0
- package/examples/00.Empty.txt +5 -0
- package/examples/01. /320/221/320/265/320/273/321/213/320/270/314/206.drakon" +13 -0
- package/examples/01. /320/221/320/265/320/273/321/213/320/271.json" +16 -0
- package/examples/01. /320/221/320/265/320/273/321/213/320/271.txt" +4 -0
- package/examples/02. /320/247/320/265/314/210/321/200/320/275/321/213/320/270/314/206.drakon" +28 -0
- package/examples/02. /320/247/321/221/321/200/320/275/321/213/320/271.json" +31 -0
- package/examples/02. /320/247/321/221/321/200/320/275/321/213/320/271.txt" +10 -0
- package/examples/03. /320/241/320/265/321/200/321/213/320/270/314/206.drakon" +20 -0
- package/examples/03. /320/241/320/265/321/200/321/213/320/271.json" +23 -0
- package/examples/03. /320/241/320/265/321/200/321/213/320/271.txt" +6 -0
- package/examples/04. /320/221/321/203/321/200/321/213/320/270/314/206.drakon" +25 -0
- package/examples/04. /320/221/321/203/321/200/321/213/320/271.json" +29 -0
- package/examples/04. /320/221/321/203/321/200/321/213/320/271.txt" +7 -0
- package/examples/05. /320/226/320/265/314/210/320/273/321/202/321/213/320/270/314/206.drakon" +25 -0
- package/examples/05. /320/226/321/221/320/273/321/202/321/213/320/271.json" +29 -0
- package/examples/05. /320/226/321/221/320/273/321/202/321/213/320/271.txt" +7 -0
- package/examples/06. /320/221/320/260/320/263/321/200/320/276/320/262/321/213/320/270/314/206.drakon" +30 -0
- package/examples/06. /320/221/320/260/320/263/321/200/320/276/320/262/321/213/320/271.json" +35 -0
- package/examples/06. /320/221/320/260/320/263/321/200/320/276/320/262/321/213/320/271.txt" +10 -0
- package/examples/07. /320/244/320/270/320/276/320/273/320/265/321/202/320/276/320/262/321/213/320/270/314/206.drakon" +42 -0
- package/examples/07. /320/244/320/270/320/276/320/273/320/265/321/202/320/276/320/262/321/213/320/271.json" +49 -0
- package/examples/07. /320/244/320/270/320/276/320/273/320/265/321/202/320/276/320/262/321/213/320/271.txt" +14 -0
- package/examples/08. /320/221/320/270/321/200/321/216/320/267/320/276/320/262/321/213/320/270/314/206.drakon" +27 -0
- package/examples/08. /320/221/320/270/321/200/321/216/320/267/320/276/320/262/321/213/320/271.json" +27 -0
- package/examples/08. /320/221/320/270/321/200/321/216/320/267/320/276/320/262/321/213/320/271.txt" +6 -0
- package/examples/09. /320/236/321/200/320/260/320/275/320/266/320/265/320/262/321/213/320/270/314/206.drakon" +37 -0
- package/examples/09. /320/236/321/200/320/260/320/275/320/266/320/265/320/262/321/213/320/271.json" +39 -0
- package/examples/09. /320/236/321/200/320/260/320/275/320/266/320/265/320/262/321/213/320/271.txt" +10 -0
- package/examples/10. /320/240/320/276/320/267/320/276/320/262/321/213/320/270/314/206.drakon" +42 -0
- package/examples/10. /320/240/320/276/320/267/320/276/320/262/321/213/320/271.json" +54 -0
- package/examples/10. /320/240/320/276/320/267/320/276/320/262/321/213/320/271.txt" +16 -0
- package/examples/11. /320/227/320/260/321/211/320/270/321/202/320/275/321/213/320/270/314/206.drakon" +37 -0
- package/examples/11. /320/227/320/260/321/211/320/270/321/202/320/275/321/213/320/271.json" +39 -0
- package/examples/11. /320/227/320/260/321/211/320/270/321/202/320/275/321/213/320/271.txt" +10 -0
- package/examples/12. /320/221/320/276/320/273/320/276/321/202/320/275/321/213/320/270/314/206.drakon" +44 -0
- package/examples/12. /320/221/320/276/320/273/320/276/321/202/320/275/321/213/320/271.json" +43 -0
- package/examples/12. /320/221/320/276/320/273/320/276/321/202/320/275/321/213/320/271.txt" +10 -0
- package/examples/13. /320/241/320/260/320/273/320/260/321/202/320/276/320/262/321/213/320/270/314/206.drakon" +54 -0
- package/examples/13. /320/241/320/260/320/273/320/260/321/202/320/276/320/262/321/213/320/271.json" +63 -0
- package/examples/13. /320/241/320/260/320/273/320/260/321/202/320/276/320/262/321/213/320/271.txt" +18 -0
- package/examples/14. /320/227/320/276/320/273/320/276/321/202/320/276/320/270/314/206.drakon" +64 -0
- package/examples/14. /320/227/320/276/320/273/320/276/321/202/320/276/320/271.json" +73 -0
- package/examples/14. /320/227/320/276/320/273/320/276/321/202/320/276/320/271.txt" +22 -0
- package/examples/15. /320/241/320/270/320/275/320/270/320/270/314/206.drakon" +54 -0
- package/examples/15. /320/241/320/270/320/275/320/270/320/271.json" +87 -0
- package/examples/15. /320/241/320/270/320/275/320/270/320/271.txt" +26 -0
- package/examples/16. /320/223/320/276/320/273/321/203/320/261/320/276/320/270/314/206.drakon" +37 -0
- package/examples/16. /320/223/320/276/320/273/321/203/320/261/320/276/320/271.json" +48 -0
- package/examples/16. /320/223/320/276/320/273/321/203/320/261/320/276/320/271.txt" +13 -0
- package/examples/17. /320/241/320/260/320/273/320/260/321/202/320/276/320/262/321/213/320/270/314/206.drakon" +39 -0
- package/examples/17. /320/241/320/260/320/273/320/260/321/202/320/276/320/262/321/213/320/271.json" +41 -0
- package/examples/17. /320/241/320/260/320/273/320/260/321/202/320/276/320/262/321/213/320/271.txt" +10 -0
- package/examples/18. /320/241/321/202/320/260/320/273/321/214/320/275/320/276/320/270/314/206.drakon" +49 -0
- package/examples/18. /320/241/321/202/320/260/320/273/321/214/320/275/320/276/320/271.json" +67 -0
- package/examples/18. /320/241/321/202/320/260/320/273/321/214/320/275/320/276/320/271.txt" +19 -0
- package/examples/19. Lilla.drakon +39 -0
- package/examples/19. Lilla.json +45 -0
- package/examples/19. Lilla.txt +11 -0
- package/examples/Adaptive design.drakon +116 -0
- package/examples/Adaptive design.json +153 -0
- package/examples/Adaptive design.txt +47 -0
- package/examples/And test.drakon +46 -0
- package/examples/And test.json +46 -0
- package/examples/And test.txt +10 -0
- package/examples/Arrow - double exit.drakon +41 -0
- package/examples/Arrow - double exit.json +62 -0
- package/examples/Arrow - double exit.txt +14 -0
- package/examples/Complex arrow.drakon +93 -0
- package/examples/Complex arrow.json +162 -0
- package/examples/Complex arrow.txt +43 -0
- package/examples/DoubleArrow.drakon +53 -0
- package/examples/DoubleArrow.json +86 -0
- package/examples/DoubleArrow.txt +18 -0
- package/examples/Find pointing nodes.drakon +104 -0
- package/examples/Find pointing nodes.json +137 -0
- package/examples/Find pointing nodes.txt +29 -0
- package/examples/How to tune PID on a quadcopter.drakon +488 -0
- package/examples/How to tune PID on a quadcopter.json +734 -0
- package/examples/How to tune PID on a quadcopter.txt +169 -0
- package/examples/Or test.drakon +47 -0
- package/examples/Or test.json +46 -0
- package/examples/Or test.txt +10 -0
- package/examples/Silhouette test 1.drakon +47 -0
- package/examples/Silhouette test 1.json +82 -0
- package/examples/Silhouette test 1.txt +24 -0
- package/examples/Work out action-check.drakon +30 -0
- package/examples/Work out action-check.json +45 -0
- package/examples/Work out action-check.txt +9 -0
- package/examples/Workout foreach.drakon +63 -0
- package/examples/Workout foreach.json +62 -0
- package/examples/Workout foreach.txt +14 -0
- package/examples/ar01.drakon +25 -0
- package/examples/ar01.json +40 -0
- package/examples/ar01.txt +7 -0
- package/examples/ar02.drakon +30 -0
- package/examples/ar02.json +45 -0
- package/examples/ar02.txt +10 -0
- package/examples/ar03.drakon +32 -0
- package/examples/ar03.json +44 -0
- package/examples/ar03.txt +7 -0
- package/examples/ar04.drakon +37 -0
- package/examples/ar04.json +49 -0
- package/examples/ar04.txt +10 -0
- package/examples/ar05.drakon +37 -0
- package/examples/ar05.json +59 -0
- package/examples/ar05.txt +11 -0
- package/examples/ar06.drakon +40 -0
- package/examples/ar06.json +70 -0
- package/examples/ar06.txt +13 -0
- package/examples/ar07.drakon +40 -0
- package/examples/ar07.json +70 -0
- package/examples/ar07.txt +13 -0
- package/examples/ar08.drakon +47 -0
- package/examples/ar08.json +81 -0
- package/examples/ar08.txt +16 -0
- package/examples/ar09.drakon +52 -0
- package/examples/ar09.json +87 -0
- package/examples/ar09.txt +17 -0
- package/examples/ar10.drakon +52 -0
- package/examples/ar10.json +88 -0
- package/examples/ar10.txt +18 -0
- package/examples/ar11.drakon +47 -0
- package/examples/ar11.json +82 -0
- package/examples/ar11.txt +17 -0
- package/examples/ar12.drakon +37 -0
- package/examples/ar12.json +49 -0
- package/examples/ar12.txt +10 -0
- package/examples/getToken.drakon +76 -0
- package/examples/getToken.json +88 -0
- package/examples/getToken.txt +41 -0
- package/examples/tmp.drakon +49 -0
- package/examples/tmp.json +82 -0
- package/examples/tmp.txt +37 -0
- package/package.json +21 -0
- package/prompts/drakonToPrompt.txt +139 -0
- package/prompts/index.txt +17 -0
- package/prompts/printPseudo.txt +116 -0
- package/src/browserTools.js +39 -0
- package/src/drakonToPromptStruct.js +44 -0
- package/src/drakonToStruct.js +416 -0
- package/src/drakongen.js +16 -0
- package/src/index.js +17 -0
- package/src/main.js +157 -0
- package/src/nodeTools.js +38 -0
- package/src/printPseudo.js +167 -0
- package/src/structFlow.js +382 -0
- package/src/technicalTree.js +84 -0
- package/src/tools.js +36 -0
- package/src/translate.js +108 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Write a JavaScript function printPseudo(algorithm, translations) that prints an algorithm as pseudo code
|
|
2
|
+
and returns it as a string.
|
|
3
|
+
|
|
4
|
+
"algorithm" is an object these properties:
|
|
5
|
+
- name: string, the name of the algorithm
|
|
6
|
+
- params: string, some textual description of the algorithm in HTML
|
|
7
|
+
- body: array, the steps of the algorithm
|
|
8
|
+
|
|
9
|
+
Types of steps:
|
|
10
|
+
- question
|
|
11
|
+
- non-question
|
|
12
|
+
|
|
13
|
+
A non-question step contains properties:
|
|
14
|
+
- id: string
|
|
15
|
+
- content: string with HTML
|
|
16
|
+
- secondary: string with HTML
|
|
17
|
+
- message: string with plain text
|
|
18
|
+
|
|
19
|
+
A question step contains properties:
|
|
20
|
+
- id: string
|
|
21
|
+
- content: either string with HTML or a structured object
|
|
22
|
+
- yes: an array with child nodes
|
|
23
|
+
- no: an array with child nodes
|
|
24
|
+
|
|
25
|
+
Examples of structured objects in the "content" property.
|
|
26
|
+
"operator" property is mandatory.
|
|
27
|
+
"operand", "left", "right" properties contain either an HTML string or another structured object
|
|
28
|
+
|
|
29
|
+
{operator:"not",operand:"<p>Hello</p>"}
|
|
30
|
+
{
|
|
31
|
+
"operator": "and",
|
|
32
|
+
"left": {
|
|
33
|
+
"operator": "and",
|
|
34
|
+
"left": "<p>One</p>",
|
|
35
|
+
"right": {
|
|
36
|
+
"operator": "not",
|
|
37
|
+
"operand": "<p>Two</p>"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
"right": "<p>Three</p>"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
The algorithm of printPseudo:
|
|
44
|
+
- start with an empty output list of lines
|
|
45
|
+
- run printPseudoSteps(algorithm.body, 0, output)
|
|
46
|
+
- joint the output with newline as separator and return it
|
|
47
|
+
|
|
48
|
+
printPseudoSteps(body, depth, output)
|
|
49
|
+
for each node in body
|
|
50
|
+
print node with indentation
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
Indentation contains "depth" * 4 spaces. Every line in the printed node should have the indentation.
|
|
54
|
+
|
|
55
|
+
how to print a non-question node
|
|
56
|
+
if neither node.secondary or node.content have a value, do not print the node
|
|
57
|
+
if node.type === "branch" or node.type === "comment", do not print the node
|
|
58
|
+
Else
|
|
59
|
+
if node.type === "error"
|
|
60
|
+
print a string: translated "Error" + ":"
|
|
61
|
+
print node.message
|
|
62
|
+
print node.secondary if it has a value
|
|
63
|
+
print node.content if it has a value
|
|
64
|
+
print an empty line
|
|
65
|
+
|
|
66
|
+
how to print a question node
|
|
67
|
+
if both node.yes and node.no are empty, do not print the node
|
|
68
|
+
if node.yes is empty
|
|
69
|
+
content = {operator:"not", operand:node.content}
|
|
70
|
+
print the translated string "If
|
|
71
|
+
print the structured content
|
|
72
|
+
if node.yes is empty
|
|
73
|
+
print the node.no using printPseudoSteps and incremented depth
|
|
74
|
+
Else
|
|
75
|
+
print the node.yes using printPseudoSteps and incremented depth
|
|
76
|
+
if node.no is not empty
|
|
77
|
+
print the translated string "Else"
|
|
78
|
+
print the node.no using printPseudoSteps and incremented depth
|
|
79
|
+
|
|
80
|
+
How to translate strings:
|
|
81
|
+
If a string is in "translations", take it. Otherwise do not translate the string, leave it as it is.
|
|
82
|
+
|
|
83
|
+
How to print structured content:
|
|
84
|
+
If the content is a string
|
|
85
|
+
convert the HTML to text
|
|
86
|
+
Else
|
|
87
|
+
if content.operator === "not"
|
|
88
|
+
print content.operand as operand, but prepend the first line with this string: tranlated "not" + " "
|
|
89
|
+
else if content.operator === "and"
|
|
90
|
+
print content.left as operand
|
|
91
|
+
print the translated string "and"
|
|
92
|
+
print content.right as operand
|
|
93
|
+
else if content.operator === "or"
|
|
94
|
+
print content.left as operand
|
|
95
|
+
print the translated string "or"
|
|
96
|
+
print content.right as operand
|
|
97
|
+
|
|
98
|
+
How to print content as operand:
|
|
99
|
+
if the content is a string
|
|
100
|
+
convert the HTML to text and print line by line, prepend each line with indentation
|
|
101
|
+
else
|
|
102
|
+
convert the HTML to text and print line by line, prepend each line with indentation, but prepend the first line with "(" (after indentation), and append the last line with ")"
|
|
103
|
+
|
|
104
|
+
How to convert HTML to string
|
|
105
|
+
if the string is empty, return the translated string "empty".
|
|
106
|
+
if the string is not HTML, split it by line end
|
|
107
|
+
else
|
|
108
|
+
ignore <script> tags alltogether
|
|
109
|
+
for every <p> tag create a line from the text content of the tag
|
|
110
|
+
for every <li> tag in an unordred list, create a line from the text content of the tag, prepend the line with "- "
|
|
111
|
+
for every <li> tag in an ordred list, create a line from the text content of the tag, prepend the line with the ordinal number: "1. ", "2. ", "3. ", etc.
|
|
112
|
+
skip all other tags
|
|
113
|
+
|
|
114
|
+
The code with run in Node Js.
|
|
115
|
+
Do not use the ... operator.
|
|
116
|
+
Use node-html-parser for HTML parsing.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
function htmlToString(html) {
|
|
3
|
+
if (!html) return '';
|
|
4
|
+
if (!html.startsWith('<') || !html.endsWith('>')) {
|
|
5
|
+
return html.split("\n").map(line => {return line.trim()})
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const parser = new DOMParser();
|
|
9
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
10
|
+
|
|
11
|
+
const root = doc.body
|
|
12
|
+
const output = [];
|
|
13
|
+
|
|
14
|
+
root.childNodes.forEach((node) => {
|
|
15
|
+
if (node.tagName === 'P') {
|
|
16
|
+
output.push(node.textContent.trim());
|
|
17
|
+
} else if (node.tagName === 'UL') {
|
|
18
|
+
output.push('');
|
|
19
|
+
node.childNodes.forEach((item) => {
|
|
20
|
+
if (item.tagName === 'LI') {
|
|
21
|
+
output.push(`- ${item.textContent.trim()}`);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
output.push('');
|
|
25
|
+
} else if (node.tagName === 'OL') {
|
|
26
|
+
output.push('');
|
|
27
|
+
node.childNodes.forEach((item, index) => {
|
|
28
|
+
if (item.tagName === 'LI') {
|
|
29
|
+
output.push(`${index + 1}. ${item.textContent.trim()}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
output.push('');
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {htmlToString}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const {drakonToStruct} = require("./drakonToStruct");
|
|
2
|
+
const {printPseudo} = require('./printPseudo');
|
|
3
|
+
const {addRange} = require("./tools")
|
|
4
|
+
|
|
5
|
+
function drakonToPseudocode(drakonJson, name, filename, htmlToString, translate) {
|
|
6
|
+
var diagram = drakonToStruct(drakonJson, name, filename, translate)
|
|
7
|
+
var lines = []
|
|
8
|
+
if (diagram.params) {
|
|
9
|
+
addRange(lines, htmlToString(diagram.params))
|
|
10
|
+
lines.push("")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
lines.push(translate("Procedure") + " \"" + diagram.name + "\"")
|
|
14
|
+
lines.push("")
|
|
15
|
+
lines.push(translate("Algorithm") + ":")
|
|
16
|
+
|
|
17
|
+
if (diagram.branches.length === 0) {
|
|
18
|
+
lines.push(translate("Empty"))
|
|
19
|
+
} else if (diagram.branches.length === 1) {
|
|
20
|
+
var first = diagram.branches[0]
|
|
21
|
+
printPseudo(first, translate, lines, htmlToString)
|
|
22
|
+
} else {
|
|
23
|
+
var first = diagram.branches[0]
|
|
24
|
+
lines.push(translate("Call subroutine") + ": \"" + htmlToString(first.name) + "\"")
|
|
25
|
+
diagram.branches.forEach(branch => {
|
|
26
|
+
lines.push("")
|
|
27
|
+
lines.push(translate("Subroutine") + ": \"" + htmlToString(branch.name) + "\"")
|
|
28
|
+
printPseudo(branch, translate, lines, htmlToString)
|
|
29
|
+
lines.push(translate("End of subroutine"))
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
lines.push(translate("End of procedure"))
|
|
33
|
+
if (diagram.description) {
|
|
34
|
+
lines.push("")
|
|
35
|
+
addRange(lines, htmlToString(diagram.description))
|
|
36
|
+
lines.push("")
|
|
37
|
+
}
|
|
38
|
+
var text = lines.join("\n")
|
|
39
|
+
|
|
40
|
+
var str = JSON.stringify(diagram, null, 4)
|
|
41
|
+
return {text:text,json:str}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { drakonToPseudocode };
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
const { structFlow, redirectNode } = require("./structFlow");
|
|
2
|
+
const { createError } = require("./tools");
|
|
3
|
+
|
|
4
|
+
var translate
|
|
5
|
+
|
|
6
|
+
function drakonToStruct(drakonJson, name, filename, translateFunction) {
|
|
7
|
+
translate = translateFunction
|
|
8
|
+
let drakonGraph;
|
|
9
|
+
try {
|
|
10
|
+
drakonJson = drakonJson || ""
|
|
11
|
+
drakonJson = drakonJson.trim()
|
|
12
|
+
drakonJson = drakonJson || "{}"
|
|
13
|
+
drakonGraph = JSON.parse(drakonJson);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
var message = translate("Error parsing JSON") + ": " + error.message
|
|
16
|
+
throw createError(message, filename)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const nodes = drakonGraph.items || {};
|
|
20
|
+
|
|
21
|
+
var branches = []
|
|
22
|
+
var firstNodeId = findStartNode(nodes, filename, branches)
|
|
23
|
+
|
|
24
|
+
if (!firstNodeId) {
|
|
25
|
+
return {
|
|
26
|
+
name: name,
|
|
27
|
+
params: drakonGraph.params || "",
|
|
28
|
+
description: drakonGraph.description || "",
|
|
29
|
+
branches: []
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
buildTwoWayConnections(nodes, firstNodeId)
|
|
34
|
+
|
|
35
|
+
rewireSelectsMarkLoops(nodes, filename)
|
|
36
|
+
branches.forEach(branch => checkBranchIsReferenced(branch, firstNodeId, filename))
|
|
37
|
+
rewireShortcircuit(nodes, filename)
|
|
38
|
+
branches.forEach(branch => cutOffBranch(nodes, branch))
|
|
39
|
+
|
|
40
|
+
var branchTrees = structFlow(nodes, branches, filename, translate)
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
name: name,
|
|
44
|
+
params: drakonGraph.params || "",
|
|
45
|
+
description: drakonGraph.description || "",
|
|
46
|
+
branches: branchTrees
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function drakonToGraph(drakonJson, name, filename, translateFunction) {
|
|
51
|
+
translate = translateFunction
|
|
52
|
+
let drakonGraph;
|
|
53
|
+
try {
|
|
54
|
+
drakonGraph = JSON.parse(drakonJson);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
var message = translate("Error parsing JSON") + ": " + error.message
|
|
57
|
+
throw createError(message, filename)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const nodes = drakonGraph.items || {};
|
|
61
|
+
|
|
62
|
+
var branches = []
|
|
63
|
+
var firstNodeId = findStartNode(nodes, filename, branches)
|
|
64
|
+
|
|
65
|
+
if (!firstNodeId) {
|
|
66
|
+
return undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
buildTwoWayConnections(nodes, firstNodeId)
|
|
70
|
+
|
|
71
|
+
rewireSelectsMarkLoops(nodes, filename)
|
|
72
|
+
rewireShortcircuit(nodes, filename)
|
|
73
|
+
branches.forEach(branch => checkBranchIsReferenced(branch, firstNodeId, filename))
|
|
74
|
+
branches.forEach(branch => cutOffBranch(nodes, branch))
|
|
75
|
+
|
|
76
|
+
var branchTrees = structFlow(nodes, branches, filename, translate)
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
name: name,
|
|
80
|
+
params: drakonGraph.params || "",
|
|
81
|
+
description: drakonGraph.description || "",
|
|
82
|
+
branches: branchTrees
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
function checkBranchIsReferenced(branch, firstNodeId, filename) {
|
|
88
|
+
if (branch.id === firstNodeId) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
if (branch.prev.length === 0) {
|
|
92
|
+
throw createError(translate("A silhouette branch is not referenced"), filename, branch.id)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function cutOffBranch(nodes, branch) {
|
|
97
|
+
var end = {
|
|
98
|
+
type: "end",
|
|
99
|
+
id: branch.id + "-end",
|
|
100
|
+
prev: []
|
|
101
|
+
}
|
|
102
|
+
nodes[end.id] = end
|
|
103
|
+
branch.next = branch.one
|
|
104
|
+
var addresses = []
|
|
105
|
+
traverseToHitBranch(nodes, branch.id, {}, (prev, node) => addFakeEnd(nodes, prev, node, end, addresses))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function traverseToHitBranch(nodes, nodeId, visited, action) {
|
|
109
|
+
if (!nodeId) {return}
|
|
110
|
+
if (nodeId in visited) {return}
|
|
111
|
+
visited[nodeId] = true
|
|
112
|
+
var node = nodes[nodeId]
|
|
113
|
+
if (!node) {return}
|
|
114
|
+
if (node.one) {
|
|
115
|
+
var one = nodes[node.one]
|
|
116
|
+
if (one.type === "branch") {
|
|
117
|
+
action(node, one)
|
|
118
|
+
} else {
|
|
119
|
+
traverseToHitBranch(nodes, node.one, visited, action)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (node.two) {
|
|
123
|
+
var two = nodes[node.two]
|
|
124
|
+
if (two.type === "branch") {
|
|
125
|
+
action(node, two)
|
|
126
|
+
} else {
|
|
127
|
+
traverseToHitBranch(nodes, node.two, visited, action)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
var idCounter = 1000
|
|
133
|
+
function addFakeEnd(nodes, prev, node, end, addresses) {
|
|
134
|
+
var lastAddress = undefined
|
|
135
|
+
if (addresses.length > 0) {
|
|
136
|
+
lastAddress = addresses[addresses.length - 1]
|
|
137
|
+
}
|
|
138
|
+
var address
|
|
139
|
+
if (lastAddress && lastAddress.branch === node.id) {
|
|
140
|
+
address = lastAddress
|
|
141
|
+
} else {
|
|
142
|
+
address = {
|
|
143
|
+
type: "address",
|
|
144
|
+
content: node.content,
|
|
145
|
+
id: "ad-" + idCounter,
|
|
146
|
+
branch: node.id,
|
|
147
|
+
one: end.id,
|
|
148
|
+
prev: []
|
|
149
|
+
}
|
|
150
|
+
idCounter++
|
|
151
|
+
nodes[address.id] = address
|
|
152
|
+
end.prev.push(address.id)
|
|
153
|
+
addresses.push(address)
|
|
154
|
+
}
|
|
155
|
+
redirectNode(nodes, prev, node.id, address.id)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function buildTwoWayConnections(nodes, firstNodeId) {
|
|
159
|
+
for (var id in nodes) {
|
|
160
|
+
var node = nodes[id]
|
|
161
|
+
node.id = id
|
|
162
|
+
node.prev = []
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
traverse(nodes, firstNodeId, {}, connectBack)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function findStartNode(nodes, filename, branches) {
|
|
169
|
+
var firstNodeId = undefined
|
|
170
|
+
var minBranchId = 10000
|
|
171
|
+
for (var id in nodes) {
|
|
172
|
+
var node = nodes[id]
|
|
173
|
+
if (node.type === "branch") {
|
|
174
|
+
if (node.branchId < minBranchId) {
|
|
175
|
+
firstNodeId = id
|
|
176
|
+
minBranchId = node.branchId
|
|
177
|
+
}
|
|
178
|
+
branches.push(node)
|
|
179
|
+
} else if (node.type === "select") {
|
|
180
|
+
if (!node.content) {
|
|
181
|
+
throw createError(translate("A Select icon must have content"), filename, id)
|
|
182
|
+
}
|
|
183
|
+
node.cases = [];
|
|
184
|
+
} else if (node.type === "loopbegin") {
|
|
185
|
+
if (!node.content) {
|
|
186
|
+
throw createError(translate("A Loop begin icon must have content"), filename, id)
|
|
187
|
+
}
|
|
188
|
+
} else if (node.type === "question") {
|
|
189
|
+
if (!node.content) {
|
|
190
|
+
throw createError(translate("A Question icon must have content"), filename, id)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return firstNodeId
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function rewireSelectsMarkLoops(nodes, filename) {
|
|
199
|
+
for (var id of Object.keys(nodes)) {
|
|
200
|
+
var node = nodes[id]
|
|
201
|
+
if (!node) { continue }
|
|
202
|
+
if (node.type === "select") {
|
|
203
|
+
rewireSelect(nodes, node, filename)
|
|
204
|
+
} else if (node.type === "loopbegin") {
|
|
205
|
+
markLoopBody(nodes, node, filename)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function rewireSelect(nodes, selectNode, filename) {
|
|
211
|
+
var caseNodeId = selectNode.one
|
|
212
|
+
while (caseNodeId) {
|
|
213
|
+
var caseNode = nodes[caseNodeId]
|
|
214
|
+
caseNodeId = caseNode.two
|
|
215
|
+
if (caseNode.content) {
|
|
216
|
+
caseNode.type = "question"
|
|
217
|
+
caseNode.flag1 = 1
|
|
218
|
+
caseNode.content = {operator: "equal", left:selectNode.content, right:caseNode.content}
|
|
219
|
+
if (!caseNode.two) {
|
|
220
|
+
var errorId = caseNode.id + "-unexpected"
|
|
221
|
+
var errorAction = insertIcon(nodes, "error", errorId, selectNode.content)
|
|
222
|
+
errorAction.message = "Unexpected case value"
|
|
223
|
+
|
|
224
|
+
caseNode.two = errorId
|
|
225
|
+
errorAction.prev.push(caseNode.id)
|
|
226
|
+
errorAction.one = caseNode.one
|
|
227
|
+
|
|
228
|
+
var next = nodes[caseNode.one]
|
|
229
|
+
next.prev.push(errorId)
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
if (caseNode.two) {
|
|
233
|
+
throw createError(translate("Only the rightmost Case icon can be empty"), filename, caseNode.id)
|
|
234
|
+
}
|
|
235
|
+
removeNodeOne(nodes, caseNode.id)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
removeNodeOne(nodes, selectNode.id)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function insertIcon(nodes, type, id, content) {
|
|
242
|
+
var node = {
|
|
243
|
+
type: type,
|
|
244
|
+
id: id,
|
|
245
|
+
content: content,
|
|
246
|
+
prev: []
|
|
247
|
+
}
|
|
248
|
+
nodes[id] = node
|
|
249
|
+
return node
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function removeNodeOne(nodes, nodeId) {
|
|
253
|
+
var node = nodes[nodeId]
|
|
254
|
+
redirectPrev(nodes, node, node.one)
|
|
255
|
+
redirectNext(nodes, node, node.one)
|
|
256
|
+
delete nodes[nodeId]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function removeFromNext(node, next) {
|
|
260
|
+
next.prev = next.prev.filter(prevId => prevId !== node.id)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function redirectPrev(nodes, node, newTarget) {
|
|
264
|
+
for (var prevId of node.prev) {
|
|
265
|
+
var prev = nodes[prevId]
|
|
266
|
+
if (prev.one === node.id) {
|
|
267
|
+
prev.one = newTarget
|
|
268
|
+
}
|
|
269
|
+
if (prev.two === node.id) {
|
|
270
|
+
prev.two = newTarget
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function redirectNext(nodes, node, newTarget) {
|
|
276
|
+
var target = nodes[newTarget]
|
|
277
|
+
removeFromNext(node, target)
|
|
278
|
+
for (var prevId of node.prev) {
|
|
279
|
+
target.prev.push(prevId)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function rewireShortcircuit(nodes) {
|
|
284
|
+
while (findShortcusts(nodes)) {
|
|
285
|
+
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function findShortcusts(nodes) {
|
|
290
|
+
for (var id in nodes) {
|
|
291
|
+
var node = nodes[id]
|
|
292
|
+
if (node.type === "question") {
|
|
293
|
+
var andOperand = findAndOperand(nodes, node)
|
|
294
|
+
if (andOperand) {
|
|
295
|
+
writeAndShortcut(nodes, node, andOperand)
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
var orOperand = findOrOperand(nodes, node)
|
|
299
|
+
if (orOperand) {
|
|
300
|
+
writeOrShortcut(nodes, node, orOperand)
|
|
301
|
+
return true
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function findAndOperand(nodes, node) {
|
|
309
|
+
var below = nodes[node.one]
|
|
310
|
+
if (below.type === "question") {
|
|
311
|
+
if (below.prev.length === 1 && below.two === node.two) {
|
|
312
|
+
return below
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return undefined
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function findOrOperand(nodes, node) {
|
|
319
|
+
var right = nodes[node.two]
|
|
320
|
+
if (right.type === "question") {
|
|
321
|
+
if (right.prev.length === 1 && right.one === node.one) {
|
|
322
|
+
return right
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return undefined
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function writeAndShortcut(nodes, node, andOperand) {
|
|
329
|
+
var right = nodes[node.two]
|
|
330
|
+
var down = nodes[andOperand.one]
|
|
331
|
+
removeFromNext(andOperand, right)
|
|
332
|
+
removeFromNext(andOperand, down)
|
|
333
|
+
node.content = {
|
|
334
|
+
operator: "and",
|
|
335
|
+
left: normalizeContent(node),
|
|
336
|
+
right: normalizeContent(andOperand)
|
|
337
|
+
}
|
|
338
|
+
node.one = down.id
|
|
339
|
+
node.flag1 = 1
|
|
340
|
+
down.prev.push(node.id)
|
|
341
|
+
delete nodes[andOperand.id]
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function writeOrShortcut(nodes, node, orOperand) {
|
|
345
|
+
var right = nodes[orOperand.two]
|
|
346
|
+
var down = nodes[orOperand.one]
|
|
347
|
+
removeFromNext(orOperand, right)
|
|
348
|
+
removeFromNext(orOperand, down)
|
|
349
|
+
node.content = {
|
|
350
|
+
operator: "or",
|
|
351
|
+
left: normalizeContent(node),
|
|
352
|
+
right: normalizeContent(orOperand)
|
|
353
|
+
}
|
|
354
|
+
node.two = right.id
|
|
355
|
+
node.flag1 = 1
|
|
356
|
+
right.prev.push(node.id)
|
|
357
|
+
delete nodes[orOperand.id]
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function normalizeContent(question) {
|
|
361
|
+
if (question.flag1 === 1) {
|
|
362
|
+
return question.content
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
operator: "not",
|
|
367
|
+
operand: question.content
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
function traverse(nodes, nodeId, visited, action) {
|
|
373
|
+
if (!nodeId) {
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (nodeId in visited) {
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
visited[nodeId] = true
|
|
381
|
+
var node = nodes[nodeId]
|
|
382
|
+
action(nodes, node)
|
|
383
|
+
traverse(nodes, node.one, visited, action)
|
|
384
|
+
traverse(nodes, node.two, visited, action)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function connectBack(nodes, node) {
|
|
388
|
+
if (node.one) {
|
|
389
|
+
var one = nodes[node.one]
|
|
390
|
+
one.prev.push(node.id)
|
|
391
|
+
}
|
|
392
|
+
if (node.two) {
|
|
393
|
+
var two = nodes[node.two]
|
|
394
|
+
two.prev.push(node.id)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function markLoopBody(nodes, start, filename) {
|
|
399
|
+
var nextNodeId = start.one
|
|
400
|
+
while (nextNodeId) {
|
|
401
|
+
var current = nodes[nextNodeId]
|
|
402
|
+
nextNodeId = current.one
|
|
403
|
+
current.parentLoopId = start.id
|
|
404
|
+
if (current.type === "loopbegin") {
|
|
405
|
+
nextNodeId = markLoopBody(nodes, current, filename)
|
|
406
|
+
} else if (current.type === "loopend") {
|
|
407
|
+
start.end = current.id
|
|
408
|
+
start.next = current.one
|
|
409
|
+
current.start = start.id
|
|
410
|
+
return nextNodeId
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
throw createError(translate("Loop end expected here"), filename, start.one)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
module.exports = { drakonToStruct, drakonToGraph };
|
package/src/drakongen.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const { drakonToPseudocode } = require('./drakonToPromptStruct');
|
|
2
|
+
const {htmlToString} = require("./browserTools")
|
|
3
|
+
const { setUpLanguage, translate } = require("./translate")
|
|
4
|
+
const {drakonToStruct} = require("./drakonToStruct");
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
window.toPseudocode = function(drakonJson, name, filename, language) {
|
|
8
|
+
setUpLanguage(language)
|
|
9
|
+
return drakonToPseudocode(drakonJson, name, filename, htmlToString, translate).text
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
window.toTree = function(drakonJson, name, filename, language) {
|
|
13
|
+
setUpLanguage(language)
|
|
14
|
+
var result = drakonToStruct(drakonJson, name, filename, translate)
|
|
15
|
+
return JSON.stringify(result, null, 4)
|
|
16
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { drakonToPseudocode } = require('./drakonToPromptStruct');
|
|
2
|
+
const { htmlToString } = require("./nodeTools")
|
|
3
|
+
const { setUpLanguage, translate } = require("./translate")
|
|
4
|
+
const { drakonToStruct } = require("./drakonToStruct");
|
|
5
|
+
|
|
6
|
+
function toPseudocode(drakonJson, name, filename, language) {
|
|
7
|
+
setUpLanguage(language)
|
|
8
|
+
var result = drakonToPseudocode(drakonJson, name, filename, htmlToString, translate)
|
|
9
|
+
return result.text
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function toTree(drakonJson, name, filename, language) {
|
|
13
|
+
setUpLanguage(language)
|
|
14
|
+
var result = drakonToStruct(drakonJson, name, filename, translate)
|
|
15
|
+
return JSON.stringify(result, null, 4)
|
|
16
|
+
}
|
|
17
|
+
module.exports = { toPseudocode, toTree }
|