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.
@@ -68,7 +68,7 @@
68
68
  },
69
69
  {
70
70
  "name": "keyword.other.epoxy",
71
- "match": "\\b(call|show|as)\\b"
71
+ "match": "\\b(call|show|as|error|snafu|skip|halt)\\b"
72
72
  },
73
73
  {
74
74
  "name": "keyword.operator.logical.epoxy",
@@ -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]`;
@@ -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}]; $ yaha pe comment dal rhe hai bhaii..
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epoxylang",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "epoxy": "./bin/epoxy.js"
@@ -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
- return `
170
-
171
- console.error("\x1b[31m" + ${this.visit(node.value)} + "\x1b[0m");`;
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)}) {\n`;
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)}) {\n`;
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 {\n`;
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}));`.trim();
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
 
@@ -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); }
@@ -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
- NOTEQ: "!==",
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,
@@ -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);