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 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);
@@ -0,0 +1,9 @@
1
+ make square[n] {
2
+ give n * n;
3
+ }
4
+
5
+ assign nums as array = {2, 4, 6};
6
+ assign result as int = call square[nums{1}];
7
+
8
+ store msg = `square of [nums{1}] is [result]`;
9
+ show msg;
package/package.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "epoxylang",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "bin": {
6
+ "epoxy": "./bin/epoxy.js"
7
+ }
8
+ }
@@ -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 };