epoxylang 0.1.1
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 +0 -0
- package/bin/epoxy.js +20 -0
- package/examples/demo.epx +9 -0
- package/package.json +8 -0
- package/src/generator/jsgenerator.js +193 -0
- package/src/index.js +1 -0
- package/src/lexer/lexer.js +141 -0
- package/src/lexer/tokens.js +114 -0
- package/src/parser/ast.js +139 -0
- package/src/parser/parser.js +484 -0
- package/src/runtime/runner.js +29 -0
package/README.md
ADDED
|
File without changes
|
package/bin/epoxy.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { run } from "../src/index.js";
|
|
6
|
+
|
|
7
|
+
const file = process.argv[2];
|
|
8
|
+
|
|
9
|
+
if (!file) {
|
|
10
|
+
console.error("Usage: epoxy <file.epx>");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!file.endsWith(".epx")) {
|
|
15
|
+
console.error("Only .epx files allowed");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const code = fs.readFileSync(path.resolve(file), "utf8");
|
|
20
|
+
run(code);
|
package/package.json
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { OP_MAP, TokenType } from "../lexer/tokens.js";
|
|
2
|
+
import { Parser } from "../parser/parser.js";
|
|
3
|
+
import { tokenizeAll } from "../runtime/runner.js";
|
|
4
|
+
|
|
5
|
+
class JSCodeGenerator {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.output = "";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
generate(node) {
|
|
11
|
+
return this.visit(node);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
visit(node) {
|
|
15
|
+
switch (node.type) {
|
|
16
|
+
case "Program":
|
|
17
|
+
return node.statements.map(s => this.visit(s)).join("\n");
|
|
18
|
+
case "AssignStatement":
|
|
19
|
+
return this.visitAssignStatement(node);
|
|
20
|
+
case "BinaryExpression": return this.visitBinaryExpression(node);
|
|
21
|
+
case "Literal": return this.visitLiteral(node);
|
|
22
|
+
case "Identifier": return this.visitIdentifier(node);
|
|
23
|
+
case "StoreStatement": return this.visitStoreStatement(node);
|
|
24
|
+
case "ShowStatement": return this.visitShowStatement(node);
|
|
25
|
+
case "FunctionDeclaration": return this.visitFunctionDeclaration(node);
|
|
26
|
+
case "ReturnStatement": return this.visitReturnStatement(node);
|
|
27
|
+
case "CallExpression": return this.visitCallExpression(node);
|
|
28
|
+
case "IfChain": return this.visitIfChain(node);
|
|
29
|
+
case "RepeatFor": return this.visitRepeatFor(node);
|
|
30
|
+
case "RepeatUntil": return this.visitRepeatUntil(node);
|
|
31
|
+
case "ArrayLiteral": return this.visitArrayLiteral(node);
|
|
32
|
+
case "ArrayAccess": return this.visitArrayAccess(node);
|
|
33
|
+
default:
|
|
34
|
+
throw new Error("Unknown AST node: " + node.type);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
visitAssignStatement(node) {
|
|
40
|
+
const keyword = node.isGlobal ? "var" : "let";
|
|
41
|
+
const value = this.visit(node.value);
|
|
42
|
+
return `${keyword} ${node.name} = ${value};`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
visitBinaryExpression(node) {
|
|
46
|
+
const left = this.visit(node.left);
|
|
47
|
+
const right = this.visit(node.right);
|
|
48
|
+
const op = OP_MAP[node.operator];
|
|
49
|
+
return `(${left} ${op} ${right})`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
visitLiteral(node) {
|
|
54
|
+
if (typeof node.value === "string") {
|
|
55
|
+
return JSON.stringify(node.value);
|
|
56
|
+
}
|
|
57
|
+
return String(node.value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
visitIdentifier(node) {
|
|
61
|
+
return node.name;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
convertInterpolation(text) {
|
|
65
|
+
let result = "";
|
|
66
|
+
let i = 0;
|
|
67
|
+
|
|
68
|
+
while (i < text.length) {
|
|
69
|
+
if (text[i] === "[") {
|
|
70
|
+
let depth = 1;
|
|
71
|
+
let expr = "";
|
|
72
|
+
i++; // skip '['
|
|
73
|
+
|
|
74
|
+
while (i < text.length && depth > 0) {
|
|
75
|
+
if (text[i] === "[") depth++;
|
|
76
|
+
else if (text[i] === "]") depth--;
|
|
77
|
+
|
|
78
|
+
if (depth > 0) expr += text[i];
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (depth !== 0) {
|
|
83
|
+
throw new Error("Unmatched [ in store interpolation");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 🔥 parse boxed expression safely
|
|
87
|
+
const jsExpr = this.convertInlineExpression(expr.trim());
|
|
88
|
+
result += "${" + jsExpr + "}";
|
|
89
|
+
|
|
90
|
+
} else {
|
|
91
|
+
result += text[i];
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
convertInlineExpression(expr) {
|
|
101
|
+
const tokens = tokenizeAll(expr);
|
|
102
|
+
const parser = new Parser(tokens);
|
|
103
|
+
const ast = parser.parseExpression();
|
|
104
|
+
|
|
105
|
+
if (parser.current().type !== TokenType.EOF) {
|
|
106
|
+
throw new Error("Invalid expression inside [ ]");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return this.visit(ast);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
visitStoreStatement(node) {
|
|
114
|
+
const interpolated = this.convertInterpolation(node.value);
|
|
115
|
+
return `const ${node.name} = \`${interpolated}\`;`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
visitShowStatement(node) {
|
|
119
|
+
return `console.log(${this.visit(node.value)});`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
visitFunctionDeclaration(node) {
|
|
123
|
+
const params = node.params.join(", ");
|
|
124
|
+
const body = node.body.map(s => this.visit(s)).join("\n");
|
|
125
|
+
return `function ${node.name}(${params}) {\n${body}\n}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
visitReturnStatement(node) {
|
|
129
|
+
return `return ${this.visit(node.value)};`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
visitArrayLiteral(node) {
|
|
133
|
+
const items = node.elements.map(e => this.visit(e)).join(", ");
|
|
134
|
+
return `[${items}]`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
visitArrayAccess(node) {
|
|
138
|
+
const arr = this.visit(node.array);
|
|
139
|
+
const idx = this.visit(node.index);
|
|
140
|
+
return `${arr}[${idx}]`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
visitCallExpression(node) {
|
|
144
|
+
const args = node.args.map(a => this.visit(a)).join(", ");
|
|
145
|
+
return `${node.name}(${args})`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
visitIfChain(node) {
|
|
149
|
+
let code = `if (${this.visit(node.condition)}) {\n`;
|
|
150
|
+
code += node.body.map(s => this.visit(s)).join("\n");
|
|
151
|
+
code += "\n}";
|
|
152
|
+
|
|
153
|
+
for (const oc of node.orChecks) {
|
|
154
|
+
code += ` else if (${this.visit(oc.condition)}) {\n`;
|
|
155
|
+
code += oc.body.map(s => this.visit(s)).join("\n");
|
|
156
|
+
code += "\n}";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (node.altBody) {
|
|
160
|
+
code += ` else {\n`;
|
|
161
|
+
code += node.altBody.map(s => this.visit(s)).join("\n");
|
|
162
|
+
code += "\n}";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return code;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
visitRepeatFor(node) {
|
|
169
|
+
const v = node.varName;
|
|
170
|
+
const start = this.visit(node.start);
|
|
171
|
+
const end = this.visit(node.end);
|
|
172
|
+
const step = this.visit(node.step);
|
|
173
|
+
const body = node.body.map(s => this.visit(s)).join("\n");
|
|
174
|
+
|
|
175
|
+
return `
|
|
176
|
+
for (let ${v} = ${start}; ${v} <= ${end}; ${v} += ${step}) {
|
|
177
|
+
${body}
|
|
178
|
+
}`.trim();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
visitRepeatUntil(node) {
|
|
182
|
+
const condition = this.visit(node.condition);
|
|
183
|
+
const body = node.body.map(s => this.visit(s)).join("\n");
|
|
184
|
+
|
|
185
|
+
return `
|
|
186
|
+
do {
|
|
187
|
+
${body}
|
|
188
|
+
} while (!(${condition}));`.trim();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export { JSCodeGenerator };
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { compile, run } from "./runtime/runner.js";
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { TokenType, Token, KEYWORDS, TYPES } from "./tokens.js";
|
|
2
|
+
|
|
3
|
+
class Lexer {
|
|
4
|
+
constructor(input) {
|
|
5
|
+
this.input = input;
|
|
6
|
+
this.pos = 0;
|
|
7
|
+
this.current = input[0];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
advance() {
|
|
11
|
+
this.pos++;
|
|
12
|
+
this.current = this.pos < this.input.length ? this.input[this.pos] : null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
peek() {
|
|
16
|
+
return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
skipWhitespace() {
|
|
20
|
+
while (this.current && /\s/.test(this.current)) {
|
|
21
|
+
this.advance();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
readNumber() {
|
|
26
|
+
let num = "";
|
|
27
|
+
while (this.current && /[0-9]/.test(this.current)) {
|
|
28
|
+
num += this.current;
|
|
29
|
+
this.advance();
|
|
30
|
+
}
|
|
31
|
+
return new Token(TokenType.NUMBER, Number(num));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
readWord() {
|
|
35
|
+
let word = "";
|
|
36
|
+
while (this.current && /[a-zA-Z_]/.test(this.current)) {
|
|
37
|
+
word += this.current;
|
|
38
|
+
this.advance();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (KEYWORDS[word]) {
|
|
42
|
+
return new Token(KEYWORDS[word], word);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (TYPES.includes(word)) {
|
|
46
|
+
return new Token(TokenType.TYPE, word);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (word === "true" || word === "false") {
|
|
50
|
+
return new Token(TokenType.BOOLEAN, word === "true");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (word === "null") {
|
|
54
|
+
return new Token(TokenType.NULL, null);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (word === "undefined") {
|
|
58
|
+
return new Token(TokenType.UNDEFINED, undefined);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new Token(TokenType.IDENTIFIER, word);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
readString() {
|
|
65
|
+
const quote = this.current;
|
|
66
|
+
let value = "";
|
|
67
|
+
this.advance();
|
|
68
|
+
|
|
69
|
+
while (this.current && this.current !== quote) {
|
|
70
|
+
value += this.current;
|
|
71
|
+
this.advance();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.current !== quote) {
|
|
75
|
+
throw new Error("Unterminated string");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.advance();
|
|
79
|
+
|
|
80
|
+
return new Token(TokenType.STRING, value, {
|
|
81
|
+
quoteType: quote
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getNextToken() {
|
|
86
|
+
while (this.current) {
|
|
87
|
+
if (/\s/.test(this.current)) {
|
|
88
|
+
this.skipWhitespace();
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// comment
|
|
93
|
+
if (this.current === "$") {
|
|
94
|
+
while (this.current && this.current !== "\n") {
|
|
95
|
+
this.advance();
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (/[0-9]/.test(this.current)) return this.readNumber();
|
|
101
|
+
if (/[a-zA-Z_]/.test(this.current)) return this.readWord();
|
|
102
|
+
if (`'"\``.includes(this.current)) return this.readString();
|
|
103
|
+
|
|
104
|
+
// double-char operators
|
|
105
|
+
const twoChar = this.current + this.peek();
|
|
106
|
+
if (twoChar === "==") { this.advance(); this.advance(); return new Token(TokenType.EQEQ); }
|
|
107
|
+
if (twoChar === "!=") { this.advance(); this.advance(); return new Token(TokenType.NOTEQ); }
|
|
108
|
+
if (twoChar === ">=") { this.advance(); this.advance(); return new Token(TokenType.GTE); }
|
|
109
|
+
if (twoChar === "<=") { this.advance(); this.advance(); return new Token(TokenType.LTE); }
|
|
110
|
+
|
|
111
|
+
// single-char
|
|
112
|
+
const single = {
|
|
113
|
+
"=": TokenType.EQUAL,
|
|
114
|
+
"+": TokenType.PLUS,
|
|
115
|
+
"-": TokenType.MINUS,
|
|
116
|
+
"*": TokenType.STAR,
|
|
117
|
+
"/": TokenType.SLASH,
|
|
118
|
+
">": TokenType.GT,
|
|
119
|
+
"<": TokenType.LT,
|
|
120
|
+
"{": TokenType.LBRACE,
|
|
121
|
+
"}": TokenType.RBRACE,
|
|
122
|
+
"[": TokenType.LBRACKET,
|
|
123
|
+
"]": TokenType.RBRACKET,
|
|
124
|
+
";": TokenType.SEMICOLON,
|
|
125
|
+
",": TokenType.COMMA
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (single[this.current]) {
|
|
129
|
+
const t = single[this.current];
|
|
130
|
+
this.advance();
|
|
131
|
+
return new Token(t);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw new Error("Unknown character: " + this.current);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return new Token(TokenType.EOF);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { Lexer };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const TokenType = {
|
|
2
|
+
// keywords
|
|
3
|
+
ASSIGN: "ASSIGN",
|
|
4
|
+
ALL: "ALL",
|
|
5
|
+
STORE: "STORE",
|
|
6
|
+
MAKE: "MAKE",
|
|
7
|
+
CALL: "CALL",
|
|
8
|
+
GIVE: "GIVE",
|
|
9
|
+
CHECK: "CHECK",
|
|
10
|
+
ALT: "ALT",
|
|
11
|
+
REPEAT: "REPEAT",
|
|
12
|
+
UNTIL: "UNTIL",
|
|
13
|
+
IN: "IN",
|
|
14
|
+
TO: "TO",
|
|
15
|
+
OR: "OR",
|
|
16
|
+
AND: "AND",
|
|
17
|
+
AS: "AS",
|
|
18
|
+
SHOW: "SHOW",
|
|
19
|
+
|
|
20
|
+
// datatypes
|
|
21
|
+
TYPE: "TYPE",
|
|
22
|
+
|
|
23
|
+
// literals
|
|
24
|
+
NUMBER: "NUMBER",
|
|
25
|
+
STRING: "STRING",
|
|
26
|
+
BOOLEAN: "BOOLEAN",
|
|
27
|
+
NULL: "NULL",
|
|
28
|
+
UNDEFINED: "UNDEFINED",
|
|
29
|
+
|
|
30
|
+
IDENTIFIER: "IDENTIFIER",
|
|
31
|
+
|
|
32
|
+
// operators
|
|
33
|
+
EQUAL: "EQUAL",
|
|
34
|
+
PLUS: "PLUS",
|
|
35
|
+
MINUS: "MINUS",
|
|
36
|
+
STAR: "STAR",
|
|
37
|
+
SLASH: "SLASH",
|
|
38
|
+
GT: "GT",
|
|
39
|
+
LT: "LT",
|
|
40
|
+
GTE: "GTE",
|
|
41
|
+
LTE: "LTE",
|
|
42
|
+
EQEQ: "EQEQ",
|
|
43
|
+
NOTEQ: "NOTEQ",
|
|
44
|
+
|
|
45
|
+
// symbols
|
|
46
|
+
LBRACE: "LBRACE",
|
|
47
|
+
RBRACE: "RBRACE",
|
|
48
|
+
LBRACKET: "LBRACKET",
|
|
49
|
+
RBRACKET: "RBRACKET",
|
|
50
|
+
SEMICOLON: "SEMICOLON",
|
|
51
|
+
COMMA: "COMMA",
|
|
52
|
+
|
|
53
|
+
EOF: "EOF"
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
class Token {
|
|
57
|
+
constructor(type, value = null, meta = {}) {
|
|
58
|
+
this.type = type;
|
|
59
|
+
this.value = value;
|
|
60
|
+
this.meta = meta;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const KEYWORDS = {
|
|
65
|
+
assign: TokenType.ASSIGN,
|
|
66
|
+
all: TokenType.ALL,
|
|
67
|
+
store: TokenType.STORE,
|
|
68
|
+
make: TokenType.MAKE,
|
|
69
|
+
call: TokenType.CALL,
|
|
70
|
+
give: TokenType.GIVE,
|
|
71
|
+
check: TokenType.CHECK,
|
|
72
|
+
alt: TokenType.ALT,
|
|
73
|
+
repeat: TokenType.REPEAT,
|
|
74
|
+
until: TokenType.UNTIL,
|
|
75
|
+
in: TokenType.IN,
|
|
76
|
+
to: TokenType.TO,
|
|
77
|
+
or: TokenType.OR,
|
|
78
|
+
and: TokenType.AND,
|
|
79
|
+
as: TokenType.AS,
|
|
80
|
+
show: TokenType.SHOW,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const TYPES = [
|
|
84
|
+
"string",
|
|
85
|
+
"int",
|
|
86
|
+
"bool",
|
|
87
|
+
"array",
|
|
88
|
+
"null",
|
|
89
|
+
"undefined",
|
|
90
|
+
"object"
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const OP_MAP = {
|
|
94
|
+
PLUS: "+",
|
|
95
|
+
MINUS: "-",
|
|
96
|
+
STAR: "*",
|
|
97
|
+
SLASH: "/",
|
|
98
|
+
GT: ">",
|
|
99
|
+
LT: "<",
|
|
100
|
+
GTE: ">=",
|
|
101
|
+
LTE: "<=",
|
|
102
|
+
EQEQ: "===",
|
|
103
|
+
NOTEQ: "!==",
|
|
104
|
+
AND: "&&",
|
|
105
|
+
OR: "||"
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
TokenType,
|
|
110
|
+
Token,
|
|
111
|
+
KEYWORDS,
|
|
112
|
+
TYPES,
|
|
113
|
+
OP_MAP
|
|
114
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
class Program {
|
|
2
|
+
constructor(statements) {
|
|
3
|
+
this.type = "Program";
|
|
4
|
+
this.statements = statements;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class AssignStatement {
|
|
9
|
+
constructor({ isGlobal, name, dataType, value }) {
|
|
10
|
+
this.type = "AssignStatement";
|
|
11
|
+
this.isGlobal = isGlobal;
|
|
12
|
+
this.name = name;
|
|
13
|
+
this.dataType = dataType; //null allowed
|
|
14
|
+
this.value = value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class StoreStatement {
|
|
19
|
+
constructor({ name, value }) {
|
|
20
|
+
this.type = "StoreStatement";
|
|
21
|
+
this.name = name;
|
|
22
|
+
this.value = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class BinaryExpression {
|
|
27
|
+
constructor(left, operator, right) {
|
|
28
|
+
this.type = "BinaryExpression";
|
|
29
|
+
this.left = left;
|
|
30
|
+
this.operator = operator;
|
|
31
|
+
this.right = right;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class Identifier {
|
|
36
|
+
constructor(name) {
|
|
37
|
+
this.type = "Identifier";
|
|
38
|
+
this.name = name;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class Literal {
|
|
43
|
+
constructor(value) {
|
|
44
|
+
this.type = "Literal";
|
|
45
|
+
this.value = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class IfStatement {
|
|
50
|
+
constructor(condition, body, altBody) {
|
|
51
|
+
this.type = "IfStatement";
|
|
52
|
+
this.condition = condition;
|
|
53
|
+
this.body = body;
|
|
54
|
+
this.altBody = altBody;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class FunctionDeclaration {
|
|
59
|
+
constructor(name, params, body) {
|
|
60
|
+
this.type = "FunctionDeclaration";
|
|
61
|
+
this.name = name;
|
|
62
|
+
this.params = params;
|
|
63
|
+
this.body = body;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class ReturnStatement {
|
|
68
|
+
constructor(value) {
|
|
69
|
+
this.type = "ReturnStatement";
|
|
70
|
+
this.value = value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class ArrayLiteral {
|
|
75
|
+
constructor(elements) {
|
|
76
|
+
this.type = "ArrayLiteral";
|
|
77
|
+
this.elements = elements;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class ArrayAccess {
|
|
82
|
+
constructor(array, index) {
|
|
83
|
+
this.type = "ArrayAccess";
|
|
84
|
+
this.array = array;
|
|
85
|
+
this.index = index;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class CallExpression {
|
|
90
|
+
constructor(name, args) {
|
|
91
|
+
this.type = "CallExpression";
|
|
92
|
+
this.name = name;
|
|
93
|
+
this.args = args;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
class RepeatUntil {
|
|
98
|
+
constructor(condition, body) {
|
|
99
|
+
this.type = "RepeatUntil";
|
|
100
|
+
this.condition = condition;
|
|
101
|
+
this.body = body;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class ShowStatement {
|
|
106
|
+
constructor(value) {
|
|
107
|
+
this.type = "ShowStatement";
|
|
108
|
+
this.value = value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class RepeatFor {
|
|
113
|
+
constructor(varName, start, end, step, body) {
|
|
114
|
+
this.type = "RepeatFor";
|
|
115
|
+
this.varName = varName;
|
|
116
|
+
this.start = start;
|
|
117
|
+
this.end = end;
|
|
118
|
+
this.step = step;
|
|
119
|
+
this.body = body;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
Program,
|
|
125
|
+
AssignStatement,
|
|
126
|
+
StoreStatement,
|
|
127
|
+
BinaryExpression,
|
|
128
|
+
Identifier,
|
|
129
|
+
Literal,
|
|
130
|
+
IfStatement,
|
|
131
|
+
FunctionDeclaration,
|
|
132
|
+
ReturnStatement,
|
|
133
|
+
ArrayLiteral,
|
|
134
|
+
ArrayAccess,
|
|
135
|
+
CallExpression,
|
|
136
|
+
RepeatUntil,
|
|
137
|
+
ShowStatement,
|
|
138
|
+
RepeatFor
|
|
139
|
+
};
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { TokenType } from "../lexer/tokens.js";
|
|
2
|
+
import {
|
|
3
|
+
Program,
|
|
4
|
+
AssignStatement,
|
|
5
|
+
StoreStatement,
|
|
6
|
+
BinaryExpression,
|
|
7
|
+
Identifier,
|
|
8
|
+
Literal,
|
|
9
|
+
FunctionDeclaration,
|
|
10
|
+
ReturnStatement,
|
|
11
|
+
ArrayLiteral,
|
|
12
|
+
ArrayAccess,
|
|
13
|
+
CallExpression,
|
|
14
|
+
RepeatUntil,
|
|
15
|
+
ShowStatement,
|
|
16
|
+
RepeatFor
|
|
17
|
+
} from "./ast.js";
|
|
18
|
+
|
|
19
|
+
class Parser {
|
|
20
|
+
constructor(tokens) {
|
|
21
|
+
this.tokens = tokens;
|
|
22
|
+
this.pos = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
current() {
|
|
26
|
+
return this.tokens[this.pos];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
eat(type) {
|
|
30
|
+
if (this.current().type === type) {
|
|
31
|
+
this.pos++;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Expected ${type} but got ${this.current().type}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
peekType(offset = 1) {
|
|
41
|
+
const token = this.tokens[this.pos + offset];
|
|
42
|
+
return token ? token.type : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
validateStoreString(token) {
|
|
46
|
+
const text = token.value;
|
|
47
|
+
|
|
48
|
+
// if contains 'call' outside [ ]
|
|
49
|
+
const illegalCallOutsideBox = /(^|[^\[])\bcall\b/.test(text);
|
|
50
|
+
const boxedCall = /\[.*\bcall\b.*\]/.test(text);
|
|
51
|
+
|
|
52
|
+
if (illegalCallOutsideBox && !boxedCall) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"store: function calls must be inside [ ] interpolation"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
parseProgram() {
|
|
60
|
+
const statements = [];
|
|
61
|
+
while (this.current().type !== TokenType.EOF) {
|
|
62
|
+
statements.push(this.parseStatement());
|
|
63
|
+
}
|
|
64
|
+
return new Program(statements);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
parseStatement() {
|
|
68
|
+
const t = this.current().type;
|
|
69
|
+
|
|
70
|
+
if (t === TokenType.ALL || t === TokenType.ASSIGN) return this.parseAssign();
|
|
71
|
+
if (t === TokenType.STORE) return this.parseStore();
|
|
72
|
+
if (t === TokenType.CHECK) return this.parseCheck();
|
|
73
|
+
if (t === TokenType.MAKE) return this.parseMake();
|
|
74
|
+
if (t === TokenType.CALL) return this.parseCall();
|
|
75
|
+
if (t === TokenType.GIVE) return this.parseGive();
|
|
76
|
+
if (t === TokenType.REPEAT && this.peekType() === TokenType.LBRACKET) return this.parseRepeatFor();
|
|
77
|
+
if (t === TokenType.REPEAT) return this.parseRepeatUntil();
|
|
78
|
+
|
|
79
|
+
if (t === TokenType.SHOW) return this.parseShow(); // 🔥 ADD THIS
|
|
80
|
+
|
|
81
|
+
throw new Error("Unknown statement: " + t);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
parseAssign() {
|
|
85
|
+
let isGlobal = false;
|
|
86
|
+
|
|
87
|
+
if (this.current().type === TokenType.ALL) {
|
|
88
|
+
isGlobal = true;
|
|
89
|
+
this.eat(TokenType.ALL);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.eat(TokenType.ASSIGN);
|
|
93
|
+
|
|
94
|
+
const name = this.current().value;
|
|
95
|
+
this.eat(TokenType.IDENTIFIER);
|
|
96
|
+
|
|
97
|
+
let dataType = null;
|
|
98
|
+
if (this.current().type === TokenType.AS) {
|
|
99
|
+
this.eat(TokenType.AS);
|
|
100
|
+
dataType = this.current().value;
|
|
101
|
+
this.eat(TokenType.TYPE);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.eat(TokenType.EQUAL);
|
|
105
|
+
|
|
106
|
+
// 🔥 FIX IS HERE
|
|
107
|
+
const value = this.parseExpression();
|
|
108
|
+
|
|
109
|
+
// enforce rule
|
|
110
|
+
if (value.type === "CallExpression" && !dataType) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"assign: datatype required when assigning function call result"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (value.type === "ArrayLiteral" && dataType !== "array") {
|
|
116
|
+
throw new Error("assign: array literal requires 'as array'");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
this.eat(TokenType.SEMICOLON);
|
|
121
|
+
|
|
122
|
+
return new AssignStatement({
|
|
123
|
+
isGlobal,
|
|
124
|
+
name,
|
|
125
|
+
dataType,
|
|
126
|
+
value
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
parseStore() {
|
|
131
|
+
this.eat(TokenType.STORE);
|
|
132
|
+
const name = this.current().value;
|
|
133
|
+
this.eat(TokenType.IDENTIFIER);
|
|
134
|
+
this.eat(TokenType.EQUAL);
|
|
135
|
+
|
|
136
|
+
const str = this.current();
|
|
137
|
+
if (str.type !== TokenType.STRING || str.meta.quoteType !== "`") {
|
|
138
|
+
throw new Error("store requires backtick string only");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.validateStoreString(str);
|
|
142
|
+
|
|
143
|
+
this.eat(TokenType.STRING);
|
|
144
|
+
this.eat(TokenType.SEMICOLON);
|
|
145
|
+
|
|
146
|
+
return new StoreStatement({ name, value: str.value });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
parseValue() {
|
|
150
|
+
const token = this.current();
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
token.type === TokenType.NUMBER ||
|
|
154
|
+
token.type === TokenType.STRING ||
|
|
155
|
+
token.type === TokenType.BOOLEAN ||
|
|
156
|
+
token.type === TokenType.NULL ||
|
|
157
|
+
token.type === TokenType.UNDEFINED ||
|
|
158
|
+
token.type === TokenType.IDENTIFIER
|
|
159
|
+
) {
|
|
160
|
+
this.pos++;
|
|
161
|
+
return token;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
throw new Error(`Invalid value: ${token.type}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
parsePrimary() {
|
|
168
|
+
const tok = this.current();
|
|
169
|
+
|
|
170
|
+
// literals
|
|
171
|
+
if (
|
|
172
|
+
tok.type === TokenType.NUMBER ||
|
|
173
|
+
tok.type === TokenType.STRING ||
|
|
174
|
+
tok.type === TokenType.BOOLEAN ||
|
|
175
|
+
tok.type === TokenType.NULL ||
|
|
176
|
+
tok.type === TokenType.UNDEFINED
|
|
177
|
+
) {
|
|
178
|
+
this.pos++;
|
|
179
|
+
return new Literal(tok.value);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// call expression
|
|
183
|
+
if (tok.type === TokenType.CALL) {
|
|
184
|
+
this.eat(TokenType.CALL);
|
|
185
|
+
|
|
186
|
+
const name = this.current().value;
|
|
187
|
+
this.eat(TokenType.IDENTIFIER);
|
|
188
|
+
|
|
189
|
+
this.eat(TokenType.LBRACKET);
|
|
190
|
+
const args = [];
|
|
191
|
+
|
|
192
|
+
if (this.current().type !== TokenType.RBRACKET) {
|
|
193
|
+
args.push(this.parseExpression());
|
|
194
|
+
while (this.current().type === TokenType.COMMA) {
|
|
195
|
+
this.eat(TokenType.COMMA);
|
|
196
|
+
args.push(this.parseExpression());
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this.eat(TokenType.RBRACKET);
|
|
201
|
+
|
|
202
|
+
return new CallExpression(name, args);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// array literal
|
|
206
|
+
if (this.current().type === TokenType.LBRACE) {
|
|
207
|
+
this.eat(TokenType.LBRACE);
|
|
208
|
+
|
|
209
|
+
const elements = [];
|
|
210
|
+
if (this.current().type !== TokenType.RBRACE) {
|
|
211
|
+
elements.push(this.parseExpression());
|
|
212
|
+
|
|
213
|
+
while (this.current().type === TokenType.COMMA) {
|
|
214
|
+
this.eat(TokenType.COMMA);
|
|
215
|
+
elements.push(this.parseExpression());
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.eat(TokenType.RBRACE);
|
|
220
|
+
return new ArrayLiteral(elements);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
// identifier
|
|
225
|
+
if (tok.type === TokenType.IDENTIFIER) {
|
|
226
|
+
this.pos++;
|
|
227
|
+
let node = new Identifier(tok.value);
|
|
228
|
+
|
|
229
|
+
// array access: test{index}
|
|
230
|
+
if (this.current().type === TokenType.LBRACE) {
|
|
231
|
+
this.eat(TokenType.LBRACE);
|
|
232
|
+
const index = this.parseExpression();
|
|
233
|
+
this.eat(TokenType.RBRACE);
|
|
234
|
+
node = new ArrayAccess(node, index);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return node;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
throw new Error("Invalid primary expression: " + tok.type);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
parseFactor() {
|
|
245
|
+
let node = this.parsePrimary();
|
|
246
|
+
|
|
247
|
+
while (
|
|
248
|
+
this.current().type === TokenType.STAR ||
|
|
249
|
+
this.current().type === TokenType.SLASH
|
|
250
|
+
) {
|
|
251
|
+
const op = this.current().type;
|
|
252
|
+
this.pos++;
|
|
253
|
+
const right = this.parsePrimary();
|
|
254
|
+
node = new BinaryExpression(node, op, right);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return node;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
parseTerm() {
|
|
261
|
+
let node = this.parseFactor();
|
|
262
|
+
|
|
263
|
+
while (
|
|
264
|
+
this.current().type === TokenType.PLUS ||
|
|
265
|
+
this.current().type === TokenType.MINUS
|
|
266
|
+
) {
|
|
267
|
+
const op = this.current().type;
|
|
268
|
+
this.pos++;
|
|
269
|
+
const right = this.parseFactor();
|
|
270
|
+
node = new BinaryExpression(node, op, right);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return node;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
parseComparison() {
|
|
277
|
+
let node = this.parseTerm();
|
|
278
|
+
|
|
279
|
+
while (
|
|
280
|
+
[
|
|
281
|
+
TokenType.GT, TokenType.LT,
|
|
282
|
+
TokenType.GTE, TokenType.LTE,
|
|
283
|
+
TokenType.EQEQ, TokenType.NOTEQ
|
|
284
|
+
].includes(this.current().type)
|
|
285
|
+
) {
|
|
286
|
+
const op = this.current().type;
|
|
287
|
+
this.pos++;
|
|
288
|
+
const right = this.parseTerm();
|
|
289
|
+
node = new BinaryExpression(node, op, right);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return node;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
parseCheck() {
|
|
296
|
+
this.eat(TokenType.CHECK);
|
|
297
|
+
this.eat(TokenType.LBRACKET);
|
|
298
|
+
const condition = this.parseExpression();
|
|
299
|
+
this.eat(TokenType.RBRACKET);
|
|
300
|
+
|
|
301
|
+
this.eat(TokenType.LBRACE);
|
|
302
|
+
const body = [];
|
|
303
|
+
while (this.current().type !== TokenType.RBRACE) {
|
|
304
|
+
body.push(this.parseStatement());
|
|
305
|
+
}
|
|
306
|
+
this.eat(TokenType.RBRACE);
|
|
307
|
+
|
|
308
|
+
const orChecks = [];
|
|
309
|
+
while (this.current().type === TokenType.OR) {
|
|
310
|
+
this.eat(TokenType.OR);
|
|
311
|
+
this.eat(TokenType.CHECK);
|
|
312
|
+
this.eat(TokenType.LBRACKET);
|
|
313
|
+
const c = this.parseExpression();
|
|
314
|
+
this.eat(TokenType.RBRACKET);
|
|
315
|
+
|
|
316
|
+
this.eat(TokenType.LBRACE);
|
|
317
|
+
const b = [];
|
|
318
|
+
while (this.current().type !== TokenType.RBRACE) {
|
|
319
|
+
b.push(this.parseStatement());
|
|
320
|
+
}
|
|
321
|
+
this.eat(TokenType.RBRACE);
|
|
322
|
+
|
|
323
|
+
orChecks.push({ condition: c, body: b });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let altBody = null;
|
|
327
|
+
if (this.current().type === TokenType.ALT) {
|
|
328
|
+
this.eat(TokenType.ALT);
|
|
329
|
+
this.eat(TokenType.LBRACE);
|
|
330
|
+
altBody = [];
|
|
331
|
+
while (this.current().type !== TokenType.RBRACE) {
|
|
332
|
+
altBody.push(this.parseStatement());
|
|
333
|
+
}
|
|
334
|
+
this.eat(TokenType.RBRACE);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return { type: "IfChain", condition, body, orChecks, altBody };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
parseMake() {
|
|
341
|
+
this.eat(TokenType.MAKE);
|
|
342
|
+
const name = this.current().value;
|
|
343
|
+
this.eat(TokenType.IDENTIFIER);
|
|
344
|
+
|
|
345
|
+
this.eat(TokenType.LBRACKET);
|
|
346
|
+
const params = [];
|
|
347
|
+
if (this.current().type !== TokenType.RBRACKET) {
|
|
348
|
+
params.push(this.current().value);
|
|
349
|
+
this.eat(TokenType.IDENTIFIER);
|
|
350
|
+
|
|
351
|
+
while (this.current().type === TokenType.COMMA) {
|
|
352
|
+
this.eat(TokenType.COMMA);
|
|
353
|
+
params.push(this.current().value);
|
|
354
|
+
this.eat(TokenType.IDENTIFIER);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
this.eat(TokenType.RBRACKET);
|
|
358
|
+
|
|
359
|
+
this.eat(TokenType.LBRACE);
|
|
360
|
+
|
|
361
|
+
const body = [];
|
|
362
|
+
while (this.current().type !== TokenType.RBRACE) {
|
|
363
|
+
body.push(this.parseStatement());
|
|
364
|
+
}
|
|
365
|
+
this.eat(TokenType.RBRACE);
|
|
366
|
+
|
|
367
|
+
return new FunctionDeclaration(name, params, body);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
parseGive() {
|
|
371
|
+
this.eat(TokenType.GIVE);
|
|
372
|
+
const value = this.parseExpression();
|
|
373
|
+
this.eat(TokenType.SEMICOLON);
|
|
374
|
+
return new ReturnStatement(value);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
parseCall() {
|
|
378
|
+
this.eat(TokenType.CALL);
|
|
379
|
+
const name = this.current().value;
|
|
380
|
+
this.eat(TokenType.IDENTIFIER);
|
|
381
|
+
|
|
382
|
+
this.eat(TokenType.LBRACKET);
|
|
383
|
+
const args = [];
|
|
384
|
+
if (this.current().type !== TokenType.RBRACKET) {
|
|
385
|
+
args.push(this.parseExpression());
|
|
386
|
+
while (this.current().type === TokenType.COMMA) {
|
|
387
|
+
this.eat(TokenType.COMMA);
|
|
388
|
+
args.push(this.parseExpression());
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
this.eat(TokenType.RBRACKET);
|
|
392
|
+
this.eat(TokenType.SEMICOLON);
|
|
393
|
+
|
|
394
|
+
return new CallExpression(name, args);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
parseRepeatUntil() {
|
|
398
|
+
this.eat(TokenType.REPEAT);
|
|
399
|
+
this.eat(TokenType.UNTIL);
|
|
400
|
+
this.eat(TokenType.LBRACKET);
|
|
401
|
+
|
|
402
|
+
const condition = this.parseExpression();
|
|
403
|
+
|
|
404
|
+
this.eat(TokenType.RBRACKET);
|
|
405
|
+
this.eat(TokenType.LBRACE);
|
|
406
|
+
|
|
407
|
+
const body = [];
|
|
408
|
+
while (this.current().type !== TokenType.RBRACE) {
|
|
409
|
+
body.push(this.parseStatement());
|
|
410
|
+
}
|
|
411
|
+
this.eat(TokenType.RBRACE);
|
|
412
|
+
|
|
413
|
+
return new RepeatUntil(condition, body);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
parseExpression() {
|
|
417
|
+
return this.parseLogicalOr();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
parseLogicalOr() {
|
|
421
|
+
let node = this.parseLogicalAnd();
|
|
422
|
+
|
|
423
|
+
while (this.current().type === TokenType.OR) {
|
|
424
|
+
this.eat(TokenType.OR);
|
|
425
|
+
const right = this.parseLogicalAnd();
|
|
426
|
+
node = new BinaryExpression(node, "OR", right);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return node;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
parseLogicalAnd() {
|
|
433
|
+
let node = this.parseComparison();
|
|
434
|
+
|
|
435
|
+
while (this.current().type === TokenType.AND) {
|
|
436
|
+
this.eat(TokenType.AND);
|
|
437
|
+
const right = this.parseComparison();
|
|
438
|
+
node = new BinaryExpression(node, "AND", right);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return node;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
parseRepeatFor() {
|
|
445
|
+
this.eat(TokenType.REPEAT);
|
|
446
|
+
this.eat(TokenType.LBRACKET);
|
|
447
|
+
|
|
448
|
+
const varName = this.current().value;
|
|
449
|
+
this.eat(TokenType.IDENTIFIER);
|
|
450
|
+
|
|
451
|
+
this.eat(TokenType.IN);
|
|
452
|
+
const start = this.parseExpression();
|
|
453
|
+
this.eat(TokenType.TO);
|
|
454
|
+
const end = this.parseExpression();
|
|
455
|
+
|
|
456
|
+
this.eat(TokenType.COMMA);
|
|
457
|
+
const step = this.parseExpression();
|
|
458
|
+
|
|
459
|
+
this.eat(TokenType.RBRACKET);
|
|
460
|
+
this.eat(TokenType.LBRACE);
|
|
461
|
+
|
|
462
|
+
const body = [];
|
|
463
|
+
while (this.current().type !== TokenType.RBRACE) {
|
|
464
|
+
body.push(this.parseStatement());
|
|
465
|
+
}
|
|
466
|
+
this.eat(TokenType.RBRACE);
|
|
467
|
+
|
|
468
|
+
return new RepeatFor(varName, start, end, step, body);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
parseShow() {
|
|
472
|
+
this.eat(TokenType.SHOW);
|
|
473
|
+
|
|
474
|
+
// allow full expressions
|
|
475
|
+
const value = this.parseExpression();
|
|
476
|
+
|
|
477
|
+
this.eat(TokenType.SEMICOLON);
|
|
478
|
+
|
|
479
|
+
return new ShowStatement(value);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export { Parser };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Lexer } from "../lexer/lexer.js";
|
|
2
|
+
import { TokenType } from "../lexer/tokens.js";
|
|
3
|
+
import { Parser } from "../parser/parser.js";
|
|
4
|
+
import { JSCodeGenerator } from "../generator/jsGenerator.js";
|
|
5
|
+
|
|
6
|
+
function tokenizeAll(code) {
|
|
7
|
+
const lexer = new Lexer(code);
|
|
8
|
+
const tokens = [];
|
|
9
|
+
let t;
|
|
10
|
+
do {
|
|
11
|
+
t = lexer.getNextToken();
|
|
12
|
+
tokens.push(t);
|
|
13
|
+
//console.log(t);
|
|
14
|
+
} while (t.type !== TokenType.EOF);
|
|
15
|
+
return tokens;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function compile(code) {
|
|
19
|
+
const tokens = tokenizeAll(code);
|
|
20
|
+
const ast = new Parser(tokens).parseProgram();
|
|
21
|
+
return new JSCodeGenerator().generate(ast);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function run(code) {
|
|
25
|
+
const js = compile(code);
|
|
26
|
+
return eval(js);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { tokenizeAll, compile, run };
|