epoxylang 0.1.1 → 0.1.3
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/README.md +319 -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/README.md
CHANGED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Epoxy Language
|
|
2
|
+
|
|
3
|
+
Epoxy is a small, expressive programming language that compiles to JavaScript.
|
|
4
|
+
It is designed to be readable, explicit and strict while remaining close to core programming concepts.
|
|
5
|
+
|
|
6
|
+
Epoxy focuses on:
|
|
7
|
+
- Clear English-like syntax
|
|
8
|
+
- Explicit intent (assign vs store)
|
|
9
|
+
- Predictable execution
|
|
10
|
+
- JavaScript as a compilation target
|
|
11
|
+
|
|
12
|
+
The project includes:
|
|
13
|
+
- A lexer
|
|
14
|
+
- A parser
|
|
15
|
+
- An AST
|
|
16
|
+
- A JavaScript code generator
|
|
17
|
+
- A CLI runner
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### Global installation (recommended)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g epoxylang
|
|
27
|
+
````
|
|
28
|
+
|
|
29
|
+
Verify installation:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
epoxy --help
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If you are using Git Bash on Windows and the command is not found, ensure your npm global bin directory is in `PATH`:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
C:\Users\<your-username>\AppData\Roaming\npm
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Running an Epoxy Program
|
|
44
|
+
|
|
45
|
+
Epoxy source files use the `.epx` extension.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
epoxy file.epx
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
epoxy examples/demo.epx
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Your First Program
|
|
60
|
+
|
|
61
|
+
Create a file called `hello.epx`:
|
|
62
|
+
|
|
63
|
+
```epx
|
|
64
|
+
assign mymsg = "Hello, Epoxy";
|
|
65
|
+
show mymsg;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Run it:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
epoxy hello.epx
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Output:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Hello, Epoxy
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Language Overview
|
|
83
|
+
|
|
84
|
+
### Variables
|
|
85
|
+
|
|
86
|
+
Epoxy distinguishes between **computed values** and **stored strings**.
|
|
87
|
+
|
|
88
|
+
#### assign — for values and expressions
|
|
89
|
+
|
|
90
|
+
```epx
|
|
91
|
+
assign x = 5;
|
|
92
|
+
assign y as int = 10;
|
|
93
|
+
assign flag as bool = true;
|
|
94
|
+
assign mymsg as string = "Hello, Epoxy"
|
|
95
|
+
assign myarray as array = {1,2,3,4};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Global variables:
|
|
99
|
+
|
|
100
|
+
```epx
|
|
101
|
+
all assign version = 1;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Type annotation is optional, but required when assigning a function call result.
|
|
105
|
+
|
|
106
|
+
Supported types:
|
|
107
|
+
|
|
108
|
+
* `int`
|
|
109
|
+
* `string`
|
|
110
|
+
* `bool`
|
|
111
|
+
* `array`
|
|
112
|
+
* `null`
|
|
113
|
+
* `undefined`
|
|
114
|
+
* `object`
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### store — for interpolated strings
|
|
119
|
+
|
|
120
|
+
`store` is used only for backtick strings and supports interpolation.
|
|
121
|
+
|
|
122
|
+
```epx
|
|
123
|
+
store msg = `Value of ex is 69`;
|
|
124
|
+
show msg;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Function calls or variable inside `store` **must** be wrapped in `[ ]`.
|
|
128
|
+
|
|
129
|
+
```epx
|
|
130
|
+
store result = `Sum is [call add[2, 3]]`;
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Functions
|
|
136
|
+
|
|
137
|
+
### Defining a function
|
|
138
|
+
|
|
139
|
+
```epx
|
|
140
|
+
make add[a, b] {
|
|
141
|
+
give a + b;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Calling a function
|
|
146
|
+
|
|
147
|
+
```epx
|
|
148
|
+
call add[2,3];
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
or
|
|
152
|
+
|
|
153
|
+
```epx
|
|
154
|
+
assign result as int = call add[2, 3];
|
|
155
|
+
show result;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Function calls assigned to variables **require a datatype**.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Control Flow
|
|
163
|
+
|
|
164
|
+
### Conditional statements
|
|
165
|
+
|
|
166
|
+
```epx
|
|
167
|
+
assign x = 10;
|
|
168
|
+
check [x > 5] {
|
|
169
|
+
store ok = `[x] is greater than 5`;
|
|
170
|
+
show ok;
|
|
171
|
+
}
|
|
172
|
+
or check [x == 5] {
|
|
173
|
+
store ok = `[x] is equal to 5`;
|
|
174
|
+
show ok;
|
|
175
|
+
}
|
|
176
|
+
alt {
|
|
177
|
+
store ok = `[x] is smaller than 5`;
|
|
178
|
+
show ok;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Supported logical operators:
|
|
183
|
+
|
|
184
|
+
* `and`
|
|
185
|
+
* `or`
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Loops
|
|
190
|
+
|
|
191
|
+
### Repeat (for-style loop)
|
|
192
|
+
|
|
193
|
+
```epx
|
|
194
|
+
repeat [i in 0 to 5, 1] {
|
|
195
|
+
show i;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Syntax:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
repeat [variable in start to end, step] { ... }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### Repeat until (do-until loop)
|
|
208
|
+
|
|
209
|
+
```epx
|
|
210
|
+
assign x = 0;
|
|
211
|
+
|
|
212
|
+
repeat until [x > 3] {
|
|
213
|
+
show x;
|
|
214
|
+
assign x = x + 1;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Arrays
|
|
221
|
+
|
|
222
|
+
### Array declaration
|
|
223
|
+
|
|
224
|
+
```epx
|
|
225
|
+
assign nums as array = {1, 2, 3, 4};
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Array access
|
|
229
|
+
|
|
230
|
+
```epx
|
|
231
|
+
show nums{2};
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Expressions
|
|
237
|
+
|
|
238
|
+
Epoxy supports standard arithmetic and comparison expressions:
|
|
239
|
+
|
|
240
|
+
```epx
|
|
241
|
+
assign total = 10 + 10 * 2;
|
|
242
|
+
assign valid = 10 > 3 and 10 < 10;
|
|
243
|
+
show total;
|
|
244
|
+
show valid;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Compilation Model
|
|
250
|
+
|
|
251
|
+
Epoxy programs are compiled to JavaScript and executed by Node.js.
|
|
252
|
+
|
|
253
|
+
Example Epoxy code:
|
|
254
|
+
|
|
255
|
+
```epx
|
|
256
|
+
assign x = 5;
|
|
257
|
+
show x;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Generated JavaScript:
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
let x = 5;
|
|
264
|
+
console.log(x);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## CLI Behavior
|
|
270
|
+
|
|
271
|
+
* Epoxy compiles and runs code in one step
|
|
272
|
+
* Errors are reported during parsing or execution
|
|
273
|
+
* The runtime uses Node.js
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Project Structure
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
src/
|
|
281
|
+
├── lexer/
|
|
282
|
+
│ ├── lexer.js
|
|
283
|
+
│ └── tokens.js
|
|
284
|
+
├── parser/
|
|
285
|
+
│ ├── parser.js
|
|
286
|
+
│ └── ast.js
|
|
287
|
+
├── generator/
|
|
288
|
+
│ └── jsgenerator.js
|
|
289
|
+
├── runtime/
|
|
290
|
+
│ └── runner.js
|
|
291
|
+
bin/
|
|
292
|
+
└── epoxy.js
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Versioning
|
|
298
|
+
|
|
299
|
+
Epoxy follows semantic versioning:
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
MAJOR.MINOR.PATCH
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Until `1.0.0`, breaking changes may occur between minor versions.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
MIT License
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Status
|
|
316
|
+
|
|
317
|
+
Epoxy is an experimental language project intended for learning, exploration, and language design experimentation.
|
|
318
|
+
|
|
319
|
+
Contributions, ideas, and discussions are welcome.
|
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);
|