epoxylang 0.1.13 → 0.1.15
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/epoxy-vs-code/epoxy-language-0.1.0.vsix +0 -0
- package/epoxy-vs-code/syntaxes/epoxy.tmLanguage.json +1 -1
- package/examples/guess.epx +25 -0
- package/examples/interpolation_test.epx +22 -0
- package/examples/loop_control_test.epx +47 -0
- package/examples/square.epx +1 -2
- package/package.json +1 -1
- package/src/generator/jsgenerator.js +55 -23
- package/src/lexer/lexer.js +7 -0
- package/src/lexer/tokens.js +10 -2
- package/src/parser/ast.js +14 -0
- package/src/parser/parser.js +18 -1
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
all assign guessed as string;
|
|
2
|
+
@js :~
|
|
3
|
+
let randnum = Math.floor(Math.random() * 99) + 1;
|
|
4
|
+
~:
|
|
5
|
+
show randnum;
|
|
6
|
+
all assign tries = 0;
|
|
7
|
+
all assign numberoftries = 5;
|
|
8
|
+
repeat until[tries > numberoftries]{
|
|
9
|
+
update guessed = :input;
|
|
10
|
+
check[guessed != randnum]{
|
|
11
|
+
update tries = tries + 1;
|
|
12
|
+
check[guessed > randnum]{
|
|
13
|
+
snafu "you guessed too high";
|
|
14
|
+
show "tries left: " + (numberoftries - tries + 1);
|
|
15
|
+
}
|
|
16
|
+
alt{
|
|
17
|
+
snafu "you guessed too low";
|
|
18
|
+
show "tries left: " + (numberoftries - tries + 1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
alt{
|
|
22
|
+
show `you guessed it right in [tries]`;
|
|
23
|
+
halt;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
$ Test string interpolation in show and error
|
|
2
|
+
|
|
3
|
+
assign tries = 78;
|
|
4
|
+
|
|
5
|
+
$ Test 1: store with interpolation (already working)
|
|
6
|
+
store finalmsg = `you guessed it right in [tries]`;
|
|
7
|
+
show finalmsg;
|
|
8
|
+
|
|
9
|
+
$ Test 2: show with backtick interpolation (should now work)
|
|
10
|
+
show `you guessed it right in [tries]`;
|
|
11
|
+
|
|
12
|
+
$ Test 3: error with backtick interpolation
|
|
13
|
+
error `error occurred at try [tries]`;
|
|
14
|
+
|
|
15
|
+
$ Test 4: snafu with backtick interpolation
|
|
16
|
+
snafu `snafu at try number [tries]`;
|
|
17
|
+
|
|
18
|
+
$ Test 5: Complex expression in interpolation
|
|
19
|
+
assign x = 10;
|
|
20
|
+
assign y = 20;
|
|
21
|
+
show `sum of x and y is [x + y]`;
|
|
22
|
+
error `product is [x * y]`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
$ Test skip (continue) and halt (break) keywords
|
|
2
|
+
|
|
3
|
+
show "Test 1: skip (continue) in loop";
|
|
4
|
+
assign count = 0;
|
|
5
|
+
repeat[i in 0 to 10, 1]{
|
|
6
|
+
check[i == 5]{
|
|
7
|
+
show "skipping 5";
|
|
8
|
+
skip;
|
|
9
|
+
}
|
|
10
|
+
update count = count + 1;
|
|
11
|
+
show i;
|
|
12
|
+
}
|
|
13
|
+
show `total iterations: [count]`;
|
|
14
|
+
|
|
15
|
+
show "---";
|
|
16
|
+
|
|
17
|
+
show "Test 2: halt (break) in loop";
|
|
18
|
+
repeat[i in 0 to 100, 1]{
|
|
19
|
+
check[i == 7]{
|
|
20
|
+
show "halting at 7";
|
|
21
|
+
halt;
|
|
22
|
+
}
|
|
23
|
+
show i;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
show "---";
|
|
27
|
+
|
|
28
|
+
show "Test 3: skip even numbers";
|
|
29
|
+
repeat[i in 0 to 10, 1]{
|
|
30
|
+
check[i % 2 == 0]{
|
|
31
|
+
skip;
|
|
32
|
+
}
|
|
33
|
+
show `odd number: [i]`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
show "---";
|
|
37
|
+
|
|
38
|
+
show "Test 4: halt when condition met";
|
|
39
|
+
assign found = false;
|
|
40
|
+
repeat[i in 0 to 20, 1]{
|
|
41
|
+
check[i == 15]{
|
|
42
|
+
update found = true;
|
|
43
|
+
show "found target at 15";
|
|
44
|
+
halt;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
show `found: [found]`;
|
package/examples/square.epx
CHANGED
|
@@ -4,8 +4,7 @@ make square[n] {
|
|
|
4
4
|
assign nums as array = {2, 4, 6};
|
|
5
5
|
assign ok as string;
|
|
6
6
|
repeat[x in nums]{
|
|
7
|
-
assign result as int = call square[nums{x}];
|
|
8
|
-
$ assign add as int = call [x,y] -> give x +y
|
|
7
|
+
assign result as int = call square[nums{x}];
|
|
9
8
|
store msg = `square of [nums{x}] is [result]`;
|
|
10
9
|
show msg;
|
|
11
10
|
}
|
package/package.json
CHANGED
|
@@ -28,6 +28,8 @@ class JSCodeGenerator {
|
|
|
28
28
|
case "StoreStatement": return this.visitStoreStatement(node);
|
|
29
29
|
case "ShowStatement": return this.visitShowStatement(node);
|
|
30
30
|
case "ErrorStatement": return this.visitErrorStatement(node);
|
|
31
|
+
case "SkipStatement": return this.visitSkipStatement(node);
|
|
32
|
+
case "HaltStatement": return this.visitHaltStatement(node);
|
|
31
33
|
case "FunctionDeclaration": return this.visitFunctionDeclaration(node);
|
|
32
34
|
case "ReturnStatement": return this.visitReturnStatement(node);
|
|
33
35
|
case "CallExpression": return this.visitCallExpression(node);
|
|
@@ -162,23 +164,50 @@ class JSCodeGenerator {
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
visitShowStatement(node) {
|
|
167
|
+
// Check if the value is a backtick string literal that needs interpolation
|
|
168
|
+
if (node.value.type === "Literal" && typeof node.value.value === "string") {
|
|
169
|
+
// We need to check the original token to see if it was a backtick string
|
|
170
|
+
// Since we don't have access to the token here, we'll handle it differently
|
|
171
|
+
// by creating a template literal if the string contains []
|
|
172
|
+
const str = node.value.value;
|
|
173
|
+
if (str.includes("[") && str.includes("]")) {
|
|
174
|
+
// This might be an interpolated string, convert it
|
|
175
|
+
const interpolated = this.convertInterpolation(str);
|
|
176
|
+
return `console.log(\`${interpolated}\`);`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
165
179
|
return `console.log(${this.visit(node.value)});`;
|
|
166
180
|
}
|
|
167
181
|
|
|
168
182
|
visitErrorStatement(node) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
// Check if the value is a backtick string literal that needs interpolation
|
|
184
|
+
if (node.value.type === "Literal" && typeof node.value.value === "string") {
|
|
185
|
+
const str = node.value.value;
|
|
186
|
+
if (str.includes("[") && str.includes("]")) {
|
|
187
|
+
// This might be an interpolated string, convert it
|
|
188
|
+
const interpolated = this.convertInterpolation(str);
|
|
189
|
+
return `console.error(\`${interpolated}\`);`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return `console.error(${this.visit(node.value)});`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
visitSkipStatement(node) {
|
|
196
|
+
return "continue;";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
visitHaltStatement(node) {
|
|
200
|
+
return "break;";
|
|
172
201
|
}
|
|
173
202
|
|
|
174
203
|
visitFunctionDeclaration(node) {
|
|
175
204
|
const params = node.params.join(", ");
|
|
176
205
|
const body = node.body.map(s => this.visit(s)).join("\n");
|
|
177
|
-
return `function ${node.name}(${params}) {\n${body}\n}`;
|
|
206
|
+
return `function ${node.name} (${params}) { \n${body} \n } `;
|
|
178
207
|
}
|
|
179
208
|
|
|
180
209
|
visitReturnStatement(node) {
|
|
181
|
-
return `return ${this.visit(node.value)}
|
|
210
|
+
return `return ${this.visit(node.value)}; `;
|
|
182
211
|
}
|
|
183
212
|
|
|
184
213
|
visitArrayLiteral(node) {
|
|
@@ -189,27 +218,30 @@ class JSCodeGenerator {
|
|
|
189
218
|
visitArrayAccess(node) {
|
|
190
219
|
const arr = this.visit(node.array);
|
|
191
220
|
const idx = this.visit(node.index);
|
|
192
|
-
return `${arr}[${idx}]`;
|
|
221
|
+
return `${arr} [${idx}]`;
|
|
193
222
|
}
|
|
194
223
|
|
|
195
224
|
visitCallExpression(node) {
|
|
196
225
|
const args = node.args.map(a => this.visit(a)).join(", ");
|
|
197
|
-
return `${node.name}(${args})`;
|
|
226
|
+
return `${node.name} (${args})`;
|
|
198
227
|
}
|
|
199
228
|
|
|
200
229
|
visitIfChain(node) {
|
|
201
|
-
let code = `if (${this.visit(node.condition)}) {
|
|
230
|
+
let code = `if (${this.visit(node.condition)}) {
|
|
231
|
+
\n`;
|
|
202
232
|
code += node.body.map(s => this.visit(s)).join("\n");
|
|
203
233
|
code += "\n}";
|
|
204
234
|
|
|
205
235
|
for (const oc of node.orChecks) {
|
|
206
|
-
code += ` else if (${this.visit(oc.condition)}) {
|
|
236
|
+
code += ` else if (${this.visit(oc.condition)}) {
|
|
237
|
+
\n`;
|
|
207
238
|
code += oc.body.map(s => this.visit(s)).join("\n");
|
|
208
239
|
code += "\n}";
|
|
209
240
|
}
|
|
210
241
|
|
|
211
242
|
if (node.altBody) {
|
|
212
|
-
code += ` else {
|
|
243
|
+
code += ` else {
|
|
244
|
+
\n`;
|
|
213
245
|
code += node.altBody.map(s => this.visit(s)).join("\n");
|
|
214
246
|
code += "\n}";
|
|
215
247
|
}
|
|
@@ -224,9 +256,9 @@ class JSCodeGenerator {
|
|
|
224
256
|
// Handle array iteration: repeat[x in arrayName]
|
|
225
257
|
if (node.arrayName) {
|
|
226
258
|
return `
|
|
227
|
-
for (let ${v} = 0; ${v} < ${node.arrayName}.length; ${v}++) {
|
|
259
|
+
for (let ${v} = 0; ${v} < ${node.arrayName}.length; ${v} ++) {
|
|
228
260
|
${body}
|
|
229
|
-
}`.trim();
|
|
261
|
+
} `.trim();
|
|
230
262
|
}
|
|
231
263
|
|
|
232
264
|
// Handle numeric range with bidirectional support
|
|
@@ -237,9 +269,9 @@ ${body}
|
|
|
237
269
|
// Generate bidirectional loop: compare start and end at runtime
|
|
238
270
|
// If start <= end: increment, else: decrement
|
|
239
271
|
return `
|
|
240
|
-
for (let ${v} = ${start}; ${start} <= ${end} ? ${v} <= ${end} : ${v} >= ${end}; ${start} <= ${end} ? ${v} += ${step} : ${v} -= ${step}) {
|
|
272
|
+
for (let ${v} = ${start}; ${start} <= ${end} ? ${v} <= ${end} : ${v} >= ${end}; ${start} <= ${end} ? ${v} += ${step} : ${v} -= ${step}) {
|
|
241
273
|
${body}
|
|
242
|
-
}`.trim();
|
|
274
|
+
} `.trim();
|
|
243
275
|
}
|
|
244
276
|
|
|
245
277
|
visitRepeatUntil(node) {
|
|
@@ -247,9 +279,9 @@ ${body}
|
|
|
247
279
|
const body = node.body.map(s => this.visit(s)).join("\n");
|
|
248
280
|
|
|
249
281
|
return `
|
|
250
|
-
do {
|
|
282
|
+
do {
|
|
251
283
|
${body}
|
|
252
|
-
} while (!(${condition}))
|
|
284
|
+
} while (!(${condition})); `.trim();
|
|
253
285
|
}
|
|
254
286
|
|
|
255
287
|
visitMethodCall(node) {
|
|
@@ -310,7 +342,7 @@ ${body}
|
|
|
310
342
|
return this.convertPythonSlice(target, args[0]);
|
|
311
343
|
|
|
312
344
|
default:
|
|
313
|
-
throw new Error(`Unknown array method: ${methodName}`);
|
|
345
|
+
throw new Error(`Unknown array method: ${methodName} `);
|
|
314
346
|
}
|
|
315
347
|
}
|
|
316
348
|
|
|
@@ -344,18 +376,18 @@ ${body}
|
|
|
344
376
|
return `${target}.replace(${this.visit(args[0])}, ${this.visit(args[1])})`;
|
|
345
377
|
|
|
346
378
|
default:
|
|
347
|
-
throw new Error(`Unknown string method: ${methodName}`);
|
|
379
|
+
throw new Error(`Unknown string method: ${methodName} `);
|
|
348
380
|
}
|
|
349
381
|
}
|
|
350
382
|
|
|
351
|
-
throw new Error(`Unknown target type: ${node.targetType}`);
|
|
383
|
+
throw new Error(`Unknown target type: ${node.targetType} `);
|
|
352
384
|
}
|
|
353
385
|
|
|
354
386
|
visitLambdaExpression(node) {
|
|
355
387
|
// Convert lambda to arrow function: call [x] -> x % 2 == 0 => (x) => x % 2 === 0
|
|
356
388
|
const params = node.params.join(", ");
|
|
357
389
|
const body = this.visit(node.body);
|
|
358
|
-
return `(${params}) => ${body}`;
|
|
390
|
+
return `(${params}) => ${body} `;
|
|
359
391
|
}
|
|
360
392
|
|
|
361
393
|
convertPythonSlice(target, sliceExpr) {
|
|
@@ -369,17 +401,17 @@ ${body}
|
|
|
369
401
|
// Parse slice notation
|
|
370
402
|
if (slice === "::-1") {
|
|
371
403
|
// Reverse array
|
|
372
|
-
return `${target}.slice().reverse()
|
|
404
|
+
return `${target}.slice().reverse(); `;
|
|
373
405
|
} else if (slice.startsWith("::")) {
|
|
374
406
|
// Every nth element
|
|
375
407
|
const step = parseInt(slice.substring(2));
|
|
376
|
-
return `${target}.filter((_, i) => i % ${step} === 0)
|
|
408
|
+
return `${target}.filter((_, i) => i % ${step} === 0); `;
|
|
377
409
|
} else if (slice.includes(":")) {
|
|
378
410
|
// Range slice
|
|
379
411
|
const parts = slice.split(":");
|
|
380
412
|
const start = parts[0] || "0";
|
|
381
413
|
const end = parts[1] || `${target}.length`;
|
|
382
|
-
return `${target}.slice(${start}, ${end})
|
|
414
|
+
return `${target}.slice(${start}, ${end}); `;
|
|
383
415
|
}
|
|
384
416
|
}
|
|
385
417
|
|
package/src/lexer/lexer.js
CHANGED
|
@@ -173,6 +173,13 @@ class Lexer {
|
|
|
173
173
|
// If not :input, fall through to handle : as COLON token
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
// three-char operators (check BEFORE two-char!)
|
|
177
|
+
const peek1 = this.peek();
|
|
178
|
+
const peek2 = this.pos + 2 < this.input.length ? this.input[this.pos + 2] : null;
|
|
179
|
+
const threeChar = this.current + peek1 + peek2;
|
|
180
|
+
if (threeChar === "===") { this.advance(); this.advance(); this.advance(); return new Token(TokenType.EQEQEQ); }
|
|
181
|
+
if (threeChar === "!==") { this.advance(); this.advance(); this.advance(); return new Token(TokenType.NOTEQEQ); }
|
|
182
|
+
|
|
176
183
|
// double-char operators
|
|
177
184
|
const twoChar = this.current + this.peek();
|
|
178
185
|
if (twoChar === "==") { this.advance(); this.advance(); return new Token(TokenType.EQEQ); }
|
package/src/lexer/tokens.js
CHANGED
|
@@ -20,6 +20,8 @@ const TokenType = {
|
|
|
20
20
|
SHOW: "SHOW",
|
|
21
21
|
ERROR: "ERROR",
|
|
22
22
|
SNAFU: "SNAFU",
|
|
23
|
+
SKIP: "SKIP",
|
|
24
|
+
HALT: "HALT",
|
|
23
25
|
METHOD: "METHOD",
|
|
24
26
|
|
|
25
27
|
// datatypes
|
|
@@ -50,7 +52,9 @@ const TokenType = {
|
|
|
50
52
|
GTE: "GTE",
|
|
51
53
|
LTE: "LTE",
|
|
52
54
|
EQEQ: "EQEQ",
|
|
55
|
+
EQEQEQ: "EQEQEQ",
|
|
53
56
|
NOTEQ: "NOTEQ",
|
|
57
|
+
NOTEQEQ: "NOTEQEQ",
|
|
54
58
|
ARROW: "ARROW",
|
|
55
59
|
|
|
56
60
|
// symbols
|
|
@@ -97,6 +101,8 @@ const KEYWORDS = {
|
|
|
97
101
|
show: TokenType.SHOW,
|
|
98
102
|
error: TokenType.ERROR,
|
|
99
103
|
snafu: TokenType.SNAFU,
|
|
104
|
+
skip: TokenType.SKIP,
|
|
105
|
+
halt: TokenType.HALT,
|
|
100
106
|
method: TokenType.METHOD,
|
|
101
107
|
};
|
|
102
108
|
|
|
@@ -121,8 +127,10 @@ const OP_MAP = {
|
|
|
121
127
|
LT: "<",
|
|
122
128
|
GTE: ">=",
|
|
123
129
|
LTE: "<=",
|
|
124
|
-
EQEQ: "
|
|
125
|
-
|
|
130
|
+
EQEQ: "==",
|
|
131
|
+
EQEQEQ: "===",
|
|
132
|
+
NOTEQ: "!=",
|
|
133
|
+
NOTEQEQ: "!==",
|
|
126
134
|
AND: "&&",
|
|
127
135
|
OR: "||"
|
|
128
136
|
};
|
package/src/parser/ast.js
CHANGED
|
@@ -127,6 +127,18 @@ class ErrorStatement {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
class SkipStatement {
|
|
131
|
+
constructor() {
|
|
132
|
+
this.type = "SkipStatement";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class HaltStatement {
|
|
137
|
+
constructor() {
|
|
138
|
+
this.type = "HaltStatement";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
130
142
|
class RepeatFor {
|
|
131
143
|
constructor(varName, start, end, step, body, arrayName = null) {
|
|
132
144
|
this.type = "RepeatFor";
|
|
@@ -187,6 +199,8 @@ export {
|
|
|
187
199
|
RepeatUntil,
|
|
188
200
|
ShowStatement,
|
|
189
201
|
ErrorStatement,
|
|
202
|
+
SkipStatement,
|
|
203
|
+
HaltStatement,
|
|
190
204
|
RepeatFor,
|
|
191
205
|
RawJSBlock,
|
|
192
206
|
InputExpression,
|
package/src/parser/parser.js
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
RepeatUntil,
|
|
16
16
|
ShowStatement,
|
|
17
17
|
ErrorStatement,
|
|
18
|
+
SkipStatement,
|
|
19
|
+
HaltStatement,
|
|
18
20
|
RepeatFor,
|
|
19
21
|
RawJSBlock,
|
|
20
22
|
InputExpression,
|
|
@@ -98,6 +100,8 @@ class Parser {
|
|
|
98
100
|
if (t === TokenType.REPEAT) return this.parseRepeatUntil();
|
|
99
101
|
if (t === TokenType.SHOW) return this.parseShow();
|
|
100
102
|
if (t === TokenType.ERROR || t === TokenType.SNAFU) return this.parseError();
|
|
103
|
+
if (t === TokenType.SKIP) return this.parseSkip();
|
|
104
|
+
if (t === TokenType.HALT) return this.parseHalt();
|
|
101
105
|
if (t === TokenType.METHOD) return this.parseMethodCall();
|
|
102
106
|
|
|
103
107
|
throw new Error("Unknown statement: " + t);
|
|
@@ -449,7 +453,8 @@ class Parser {
|
|
|
449
453
|
[
|
|
450
454
|
TokenType.GT, TokenType.LT,
|
|
451
455
|
TokenType.GTE, TokenType.LTE,
|
|
452
|
-
TokenType.EQEQ, TokenType.NOTEQ
|
|
456
|
+
TokenType.EQEQ, TokenType.NOTEQ,
|
|
457
|
+
TokenType.EQEQEQ, TokenType.NOTEQEQ
|
|
453
458
|
].includes(this.current().type)
|
|
454
459
|
) {
|
|
455
460
|
const op = this.current().type;
|
|
@@ -686,6 +691,18 @@ class Parser {
|
|
|
686
691
|
return new ErrorStatement(value);
|
|
687
692
|
}
|
|
688
693
|
|
|
694
|
+
parseSkip() {
|
|
695
|
+
this.eat(TokenType.SKIP);
|
|
696
|
+
this.eat(TokenType.SEMICOLON);
|
|
697
|
+
return new SkipStatement();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
parseHalt() {
|
|
701
|
+
this.eat(TokenType.HALT);
|
|
702
|
+
this.eat(TokenType.SEMICOLON);
|
|
703
|
+
return new HaltStatement();
|
|
704
|
+
}
|
|
705
|
+
|
|
689
706
|
parseMethodCall() {
|
|
690
707
|
this.eat(TokenType.METHOD);
|
|
691
708
|
this.eat(TokenType.COLON);
|