epoxylang 0.1.2 → 0.1.4
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/.github/workflows/publish.yml +30 -0
- package/examples/demo.epx +5 -3
- package/examples/test.epx +11 -0
- package/package.json +1 -1
- package/src/generator/jsgenerator.js +33 -2
- package/src/lexer/lexer.js +8 -1
- package/src/lexer/tokens.js +7 -0
- package/src/parser/ast.js +15 -3
- package/src/parser/parser.js +93 -8
- package/src/test.js +11 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout repo
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Setup Node
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 20
|
|
24
|
+
registry-url: https://registry.npmjs.org/
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm install
|
|
28
|
+
|
|
29
|
+
- name: Publish package
|
|
30
|
+
run: npm publish --access public
|
package/examples/demo.epx
CHANGED
|
@@ -3,7 +3,9 @@ make square[n] {
|
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
assign nums as array = {2, 4, 6};
|
|
6
|
-
assign result as int = call square[nums{1}];
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
repeat[x in 0 to 2, 1]{
|
|
8
|
+
assign result as int = call square[nums{x}]; $ yaha pe comment dal rhe hai bhaii..
|
|
9
|
+
store msg = `square of [nums{x}] is [result]`;
|
|
10
|
+
show msg;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
all assign hello as array = {"satya0", "satya1", "satya2"};
|
|
2
|
+
show hello{0};
|
|
3
|
+
|
|
4
|
+
assign numbers as array = {1, 2, 3, 4, 5, 6};
|
|
5
|
+
|
|
6
|
+
repeat[i in 0 to 5, 1]{
|
|
7
|
+
assign n as int = numbers{i};
|
|
8
|
+
check [(n / 2 == 1) or (n / 2 == 2) or (n / 2 == 3)] {
|
|
9
|
+
show n;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -17,6 +17,8 @@ class JSCodeGenerator {
|
|
|
17
17
|
return node.statements.map(s => this.visit(s)).join("\n");
|
|
18
18
|
case "AssignStatement":
|
|
19
19
|
return this.visitAssignStatement(node);
|
|
20
|
+
case "UpdateStatement":
|
|
21
|
+
return this.visitUpdateStatement(node);
|
|
20
22
|
case "BinaryExpression": return this.visitBinaryExpression(node);
|
|
21
23
|
case "Literal": return this.visitLiteral(node);
|
|
22
24
|
case "Identifier": return this.visitIdentifier(node);
|
|
@@ -37,11 +39,30 @@ class JSCodeGenerator {
|
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
visitAssignStatement(node) {
|
|
40
|
-
|
|
42
|
+
// Determine the JavaScript keyword based on mutability flags
|
|
43
|
+
let keyword;
|
|
44
|
+
if (node.isFix) {
|
|
45
|
+
keyword = "const";
|
|
46
|
+
} else if (node.isGlobal) {
|
|
47
|
+
keyword = "var";
|
|
48
|
+
} else {
|
|
49
|
+
keyword = "let";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle declaration without assignment
|
|
53
|
+
if (node.value === null) {
|
|
54
|
+
return `${keyword} ${node.name};`;
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
const value = this.visit(node.value);
|
|
42
58
|
return `${keyword} ${node.name} = ${value};`;
|
|
43
59
|
}
|
|
44
60
|
|
|
61
|
+
visitUpdateStatement(node) {
|
|
62
|
+
const value = this.visit(node.value);
|
|
63
|
+
return `${node.name} = ${value};`;
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
visitBinaryExpression(node) {
|
|
46
67
|
const left = this.visit(node.left);
|
|
47
68
|
const right = this.visit(node.right);
|
|
@@ -111,8 +132,18 @@ class JSCodeGenerator {
|
|
|
111
132
|
|
|
112
133
|
|
|
113
134
|
visitStoreStatement(node) {
|
|
135
|
+
// Determine the JavaScript keyword based on mutability flags
|
|
136
|
+
let keyword;
|
|
137
|
+
if (node.isFix) {
|
|
138
|
+
keyword = "const";
|
|
139
|
+
} else if (node.isGlobal) {
|
|
140
|
+
keyword = "var";
|
|
141
|
+
} else {
|
|
142
|
+
keyword = "let";
|
|
143
|
+
}
|
|
144
|
+
|
|
114
145
|
const interpolated = this.convertInterpolation(node.value);
|
|
115
|
-
return
|
|
146
|
+
return `${keyword} ${node.name} = \`${interpolated}\`;`;
|
|
116
147
|
}
|
|
117
148
|
|
|
118
149
|
visitShowStatement(node) {
|
package/src/lexer/lexer.js
CHANGED
|
@@ -24,7 +24,12 @@ class Lexer {
|
|
|
24
24
|
|
|
25
25
|
readNumber() {
|
|
26
26
|
let num = "";
|
|
27
|
-
|
|
27
|
+
let hasDecimal = false;
|
|
28
|
+
|
|
29
|
+
while (this.current && (/[0-9]/.test(this.current) || (this.current === "." && !hasDecimal))) {
|
|
30
|
+
if (this.current === ".") {
|
|
31
|
+
hasDecimal = true;
|
|
32
|
+
}
|
|
28
33
|
num += this.current;
|
|
29
34
|
this.advance();
|
|
30
35
|
}
|
|
@@ -121,6 +126,8 @@ class Lexer {
|
|
|
121
126
|
"}": TokenType.RBRACE,
|
|
122
127
|
"[": TokenType.LBRACKET,
|
|
123
128
|
"]": TokenType.RBRACKET,
|
|
129
|
+
"(": TokenType.LPAREN,
|
|
130
|
+
")": TokenType.RPAREN,
|
|
124
131
|
";": TokenType.SEMICOLON,
|
|
125
132
|
",": TokenType.COMMA
|
|
126
133
|
};
|
package/src/lexer/tokens.js
CHANGED
|
@@ -2,6 +2,8 @@ const TokenType = {
|
|
|
2
2
|
// keywords
|
|
3
3
|
ASSIGN: "ASSIGN",
|
|
4
4
|
ALL: "ALL",
|
|
5
|
+
FIX: "FIX",
|
|
6
|
+
UPDATE: "UPDATE",
|
|
5
7
|
STORE: "STORE",
|
|
6
8
|
MAKE: "MAKE",
|
|
7
9
|
CALL: "CALL",
|
|
@@ -47,6 +49,8 @@ const TokenType = {
|
|
|
47
49
|
RBRACE: "RBRACE",
|
|
48
50
|
LBRACKET: "LBRACKET",
|
|
49
51
|
RBRACKET: "RBRACKET",
|
|
52
|
+
LPAREN: "LPAREN",
|
|
53
|
+
RPAREN: "RPAREN",
|
|
50
54
|
SEMICOLON: "SEMICOLON",
|
|
51
55
|
COMMA: "COMMA",
|
|
52
56
|
|
|
@@ -64,6 +68,8 @@ class Token {
|
|
|
64
68
|
const KEYWORDS = {
|
|
65
69
|
assign: TokenType.ASSIGN,
|
|
66
70
|
all: TokenType.ALL,
|
|
71
|
+
fix: TokenType.FIX,
|
|
72
|
+
update: TokenType.UPDATE,
|
|
67
73
|
store: TokenType.STORE,
|
|
68
74
|
make: TokenType.MAKE,
|
|
69
75
|
call: TokenType.CALL,
|
|
@@ -83,6 +89,7 @@ const KEYWORDS = {
|
|
|
83
89
|
const TYPES = [
|
|
84
90
|
"string",
|
|
85
91
|
"int",
|
|
92
|
+
"double",
|
|
86
93
|
"bool",
|
|
87
94
|
"array",
|
|
88
95
|
"null",
|
package/src/parser/ast.js
CHANGED
|
@@ -6,9 +6,10 @@ class Program {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
class AssignStatement {
|
|
9
|
-
constructor({ isGlobal, name, dataType, value }) {
|
|
9
|
+
constructor({ isGlobal, isFix, name, dataType, value }) {
|
|
10
10
|
this.type = "AssignStatement";
|
|
11
|
-
this.isGlobal = isGlobal;
|
|
11
|
+
this.isGlobal = isGlobal; // all -> var
|
|
12
|
+
this.isFix = isFix; // fix -> const
|
|
12
13
|
this.name = name;
|
|
13
14
|
this.dataType = dataType; //null allowed
|
|
14
15
|
this.value = value;
|
|
@@ -16,8 +17,18 @@ class AssignStatement {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
class StoreStatement {
|
|
19
|
-
constructor({ name, value }) {
|
|
20
|
+
constructor({ isGlobal, isFix, name, value }) {
|
|
20
21
|
this.type = "StoreStatement";
|
|
22
|
+
this.isGlobal = isGlobal; // all -> var
|
|
23
|
+
this.isFix = isFix; // fix -> const
|
|
24
|
+
this.name = name;
|
|
25
|
+
this.value = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class UpdateStatement {
|
|
30
|
+
constructor({ name, value }) {
|
|
31
|
+
this.type = "UpdateStatement";
|
|
21
32
|
this.name = name;
|
|
22
33
|
this.value = value;
|
|
23
34
|
}
|
|
@@ -124,6 +135,7 @@ export {
|
|
|
124
135
|
Program,
|
|
125
136
|
AssignStatement,
|
|
126
137
|
StoreStatement,
|
|
138
|
+
UpdateStatement,
|
|
127
139
|
BinaryExpression,
|
|
128
140
|
Identifier,
|
|
129
141
|
Literal,
|
package/src/parser/parser.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Program,
|
|
4
4
|
AssignStatement,
|
|
5
5
|
StoreStatement,
|
|
6
|
+
UpdateStatement,
|
|
6
7
|
BinaryExpression,
|
|
7
8
|
Identifier,
|
|
8
9
|
Literal,
|
|
@@ -64,29 +65,48 @@ class Parser {
|
|
|
64
65
|
return new Program(statements);
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
|
|
67
69
|
parseStatement() {
|
|
68
70
|
const t = this.current().type;
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
if (t === TokenType.
|
|
72
|
+
// Handle assign with optional all/fix prefix
|
|
73
|
+
if (t === TokenType.ALL || t === TokenType.FIX || t === TokenType.ASSIGN) {
|
|
74
|
+
// Check if it's assign (could be "assign", "all assign", or "fix assign")
|
|
75
|
+
if (t === TokenType.ASSIGN) return this.parseAssign();
|
|
76
|
+
if (this.peekType() === TokenType.ASSIGN) return this.parseAssign();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Handle store with optional all/fix prefix
|
|
80
|
+
if (t === TokenType.ALL || t === TokenType.FIX || t === TokenType.STORE) {
|
|
81
|
+
// Check if it's store (could be "store", "all store", or "fix store")
|
|
82
|
+
if (t === TokenType.STORE) return this.parseStore();
|
|
83
|
+
if (this.peekType() === TokenType.STORE) return this.parseStore();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (t === TokenType.UPDATE) return this.parseUpdate();
|
|
72
87
|
if (t === TokenType.CHECK) return this.parseCheck();
|
|
73
88
|
if (t === TokenType.MAKE) return this.parseMake();
|
|
74
89
|
if (t === TokenType.CALL) return this.parseCall();
|
|
75
90
|
if (t === TokenType.GIVE) return this.parseGive();
|
|
76
91
|
if (t === TokenType.REPEAT && this.peekType() === TokenType.LBRACKET) return this.parseRepeatFor();
|
|
77
92
|
if (t === TokenType.REPEAT) return this.parseRepeatUntil();
|
|
78
|
-
|
|
79
|
-
if (t === TokenType.SHOW) return this.parseShow(); // 🔥 ADD THIS
|
|
93
|
+
if (t === TokenType.SHOW) return this.parseShow();
|
|
80
94
|
|
|
81
95
|
throw new Error("Unknown statement: " + t);
|
|
82
96
|
}
|
|
83
97
|
|
|
98
|
+
|
|
84
99
|
parseAssign() {
|
|
85
100
|
let isGlobal = false;
|
|
101
|
+
let isFix = false;
|
|
86
102
|
|
|
103
|
+
// Check for 'all' or 'fix' prefix
|
|
87
104
|
if (this.current().type === TokenType.ALL) {
|
|
88
105
|
isGlobal = true;
|
|
89
106
|
this.eat(TokenType.ALL);
|
|
107
|
+
} else if (this.current().type === TokenType.FIX) {
|
|
108
|
+
isFix = true;
|
|
109
|
+
this.eat(TokenType.FIX);
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
this.eat(TokenType.ASSIGN);
|
|
@@ -101,9 +121,25 @@ class Parser {
|
|
|
101
121
|
this.eat(TokenType.TYPE);
|
|
102
122
|
}
|
|
103
123
|
|
|
104
|
-
this
|
|
124
|
+
// Check if this is a declaration without assignment
|
|
125
|
+
if (this.current().type === TokenType.SEMICOLON) {
|
|
126
|
+
// Declaration only: assign name as type;
|
|
127
|
+
if (!dataType) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
"assign: datatype required when declaring variable without value"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
this.eat(TokenType.SEMICOLON);
|
|
133
|
+
return new AssignStatement({
|
|
134
|
+
isGlobal,
|
|
135
|
+
isFix,
|
|
136
|
+
name,
|
|
137
|
+
dataType,
|
|
138
|
+
value: null
|
|
139
|
+
});
|
|
140
|
+
}
|
|
105
141
|
|
|
106
|
-
|
|
142
|
+
this.eat(TokenType.EQUAL);
|
|
107
143
|
const value = this.parseExpression();
|
|
108
144
|
|
|
109
145
|
// enforce rule
|
|
@@ -116,11 +152,11 @@ class Parser {
|
|
|
116
152
|
throw new Error("assign: array literal requires 'as array'");
|
|
117
153
|
}
|
|
118
154
|
|
|
119
|
-
|
|
120
155
|
this.eat(TokenType.SEMICOLON);
|
|
121
156
|
|
|
122
157
|
return new AssignStatement({
|
|
123
158
|
isGlobal,
|
|
159
|
+
isFix,
|
|
124
160
|
name,
|
|
125
161
|
dataType,
|
|
126
162
|
value
|
|
@@ -128,6 +164,18 @@ class Parser {
|
|
|
128
164
|
}
|
|
129
165
|
|
|
130
166
|
parseStore() {
|
|
167
|
+
let isGlobal = false;
|
|
168
|
+
let isFix = false;
|
|
169
|
+
|
|
170
|
+
// Check for 'all' or 'fix' prefix
|
|
171
|
+
if (this.current().type === TokenType.ALL) {
|
|
172
|
+
isGlobal = true;
|
|
173
|
+
this.eat(TokenType.ALL);
|
|
174
|
+
} else if (this.current().type === TokenType.FIX) {
|
|
175
|
+
isFix = true;
|
|
176
|
+
this.eat(TokenType.FIX);
|
|
177
|
+
}
|
|
178
|
+
|
|
131
179
|
this.eat(TokenType.STORE);
|
|
132
180
|
const name = this.current().value;
|
|
133
181
|
this.eat(TokenType.IDENTIFIER);
|
|
@@ -143,7 +191,18 @@ class Parser {
|
|
|
143
191
|
this.eat(TokenType.STRING);
|
|
144
192
|
this.eat(TokenType.SEMICOLON);
|
|
145
193
|
|
|
146
|
-
return new StoreStatement({ name, value: str.value });
|
|
194
|
+
return new StoreStatement({ isGlobal, isFix, name, value: str.value });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
parseUpdate() {
|
|
198
|
+
this.eat(TokenType.UPDATE);
|
|
199
|
+
const name = this.current().value;
|
|
200
|
+
this.eat(TokenType.IDENTIFIER);
|
|
201
|
+
this.eat(TokenType.EQUAL);
|
|
202
|
+
const value = this.parseExpression();
|
|
203
|
+
this.eat(TokenType.SEMICOLON);
|
|
204
|
+
|
|
205
|
+
return new UpdateStatement({ name, value });
|
|
147
206
|
}
|
|
148
207
|
|
|
149
208
|
parseValue() {
|
|
@@ -167,6 +226,32 @@ class Parser {
|
|
|
167
226
|
parsePrimary() {
|
|
168
227
|
const tok = this.current();
|
|
169
228
|
|
|
229
|
+
// Handle unary minus for negative numbers
|
|
230
|
+
if (tok.type === TokenType.MINUS) {
|
|
231
|
+
this.eat(TokenType.MINUS);
|
|
232
|
+
const expr = this.parsePrimary();
|
|
233
|
+
// If it's a literal number, negate it directly
|
|
234
|
+
if (expr.type === "Literal" && typeof expr.value === "number") {
|
|
235
|
+
return new Literal(-expr.value);
|
|
236
|
+
}
|
|
237
|
+
// Otherwise create a binary expression: 0 - expr
|
|
238
|
+
return new BinaryExpression(new Literal(0), TokenType.MINUS, expr);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Handle unary plus (just ignore it)
|
|
242
|
+
if (tok.type === TokenType.PLUS) {
|
|
243
|
+
this.eat(TokenType.PLUS);
|
|
244
|
+
return this.parsePrimary();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Parentheses for precedence
|
|
248
|
+
if (tok.type === TokenType.LPAREN) {
|
|
249
|
+
this.eat(TokenType.LPAREN);
|
|
250
|
+
const expr = this.parseExpression();
|
|
251
|
+
this.eat(TokenType.RPAREN);
|
|
252
|
+
return expr;
|
|
253
|
+
}
|
|
254
|
+
|
|
170
255
|
// literals
|
|
171
256
|
if (
|
|
172
257
|
tok.type === TokenType.NUMBER ||
|
package/src/test.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { compile } from './index.js';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
console.log("=== Testing test.epx ===");
|
|
4
|
+
const code1 = readFileSync('./examples/test.epx', 'utf-8');
|
|
5
|
+
console.log("Epoxy code:");
|
|
6
|
+
console.log(code1);
|
|
7
|
+
console.log("\nCompiled JavaScript:");
|
|
8
|
+
const js1 = compile(code1);
|
|
9
|
+
console.log(js1);
|
|
10
|
+
console.log("\nRunning JavaScript:");
|
|
11
|
+
eval(js1);
|