epoxylang 0.1.2 → 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.
@@ -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
- store msg = `square of [nums{1}] is [result]`;
9
- show msg;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epoxylang",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "epoxy": "./bin/epoxy.js"
@@ -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
- const keyword = node.isGlobal ? "var" : "let";
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 `const ${node.name} = \`${interpolated}\`;`;
146
+ return `${keyword} ${node.name} = \`${interpolated}\`;`;
116
147
  }
117
148
 
118
149
  visitShowStatement(node) {
@@ -24,7 +24,12 @@ class Lexer {
24
24
 
25
25
  readNumber() {
26
26
  let num = "";
27
- while (this.current && /[0-9]/.test(this.current)) {
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
  };
@@ -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,
@@ -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
- if (t === TokenType.ALL || t === TokenType.ASSIGN) return this.parseAssign();
71
- if (t === TokenType.STORE) return this.parseStore();
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.eat(TokenType.EQUAL);
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
- // 🔥 FIX IS HERE
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);