starlight-cli 1.1.19 → 1.1.21

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/README.md CHANGED
@@ -1,125 +1,339 @@
1
+ # Starlight Language Guide
1
2
 
2
- # Starlight Programming Language
3
+ Welcome to **Starlight Language**, a simple and expressive server-side scripting programming language designed for clarity and flexibility.
3
4
 
4
- Starlight is a lightweight, developer-oriented programming language designed for **server-side scripting, automation, and general-purpose programming**. It combines a clean, readable syntax inspired by JavaScript and Python with powerful runtime features such as async/await, modules, and interactive I/O.
5
+ This guide introduces the core concepts and syntax to help you start writing programs quickly.
5
6
 
6
- **Official Reference:**
7
- https://starlight-programming-language.pages.dev/tutorials
8
7
  ---
9
- ## Error Handling in Starlight
10
8
 
11
- Starlight is designed with **modern, developer-friendly error handling** that clearly explains what went wrong, where it happened, and how to fix it.
9
+ ## 1. Hello World
12
10
 
13
- ### Modern Diagnostic Style
11
+ ```sl
12
+ sldeploy "Hello, world!"
13
+ ```
14
14
 
15
- Starlight errors follow the same diagnostic standards used by modern languages such as JavaScript, Rust, and Python:
15
+ ---
16
16
 
17
- - Clear error message
18
- - Exact **line and column number**
19
- - Source code preview
20
- - Visual caret (`^`) pointing to the error location
21
- - Helpful suggestions when possible
17
+ ## 2. Variables
22
18
 
23
- Example:
19
+ Variables are declared using the `define` keyword.
24
20
 
21
+ ```sl
22
+ define name = "Alice"
23
+ define age = 20
25
24
  ```
26
25
 
27
- Unterminated block
28
- Did you forget to close the block with '}'?
29
- at line 5, column 0
26
+ ### Supported Value Types
27
+
28
+ | Type | Example |
29
+ | ------- | -------------------- |
30
+ | Number | `10`, `3.14` |
31
+ | String | `"hello"` |
32
+ | Boolean | `true`, `false` |
33
+ | Array | `[1, 2, 3]` |
34
+ | Object | `{ "a": 1, "b": 2 }` |
35
+ | Null | `null` |
36
+
37
+ ---
38
+
39
+ ## 3. Output
40
+
41
+ Use `sldeploy` to print values:
30
42
 
43
+ ```sl
44
+ sldeploy name
45
+ sldeploy age
31
46
  ```
32
- ^
47
+
48
+ ---
49
+
50
+ ## 4. Input
51
+
52
+ Use `ask` to read user input:
53
+
54
+ ```sl
55
+ define name = ask("Enter your name:")
56
+ sldeploy "Hello " + name
33
57
  ```
34
58
 
35
- ````
59
+ ---
60
+
61
+ ## 5. Operators
62
+
63
+ ### Arithmetic Operators
36
64
 
37
- This format makes debugging fast and intuitive, even for beginners.
65
+ | Operator | Description |
66
+ | -------- | -------------- |
67
+ | `+` | Addition |
68
+ | `-` | Subtraction |
69
+ | `*` | Multiplication |
70
+ | `/` | Division |
71
+ | `%` | Modulus |
72
+
73
+ Example:
74
+
75
+ ```sl
76
+ define result = 10 + 5 * 2
77
+ ```
38
78
 
39
79
  ---
40
80
 
41
- ### 🧠 Syntax Errors (Parser Errors)
81
+ ### Comparison Operators
82
+
83
+ | Operator | Description |
84
+ | -------- | --------------------- |
85
+ | `==` | Equal |
86
+ | `!=` | Not equal |
87
+ | `<` | Less than |
88
+ | `<=` | Less than or equal |
89
+ | `>` | Greater than |
90
+ | `>=` | Greater than or equal |
91
+
92
+ ---
42
93
 
43
- Syntax errors are detected during parsing and reported before execution begins.
94
+ ### Logical Operators
44
95
 
45
- Features:
46
- - Precise location tracking
47
- - Friendly suggestions
48
- - No Node.js stack traces
49
- - Clean termination
96
+ | Operator | Description |
97
+ | -------- | --------------- |
98
+ | `AND` | Logical AND |
99
+ | `OR` | Logical OR |
100
+ | `??` | Null coalescing |
50
101
 
51
102
  Example:
103
+
104
+ ```sl
105
+ define x = null ?? 10
106
+ ```
107
+
108
+ ---
109
+
110
+ ## 6. Conditional Statements
111
+
112
+ ```sl
113
+ if (age > 18) {
114
+ sldeploy "Adult"
115
+ } else {
116
+ sldeploy "Minor"
117
+ }
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 7. Loops
123
+
124
+ ### While Loop
125
+
126
+ ```sl
127
+ define i = 0
128
+
129
+ while (i < 5) {
130
+ sldeploy i
131
+ i = i + 1
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ### For Loop
138
+
139
+ ```sl
140
+ for (define i = 0; i < 5; i = i + 1) {
141
+ sldeploy i
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ### For-In Loop
148
+
149
+ ```sl
150
+ define arr = [10, 20, 30]
151
+
152
+ for x in arr {
153
+ sldeploy x
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## 8. Functions
160
+
161
+ ### Function Declaration
162
+
52
163
  ```sl
53
- if true {
54
- sldeploy("This block is never closed")
55
- ````
164
+ func add(a, b) {
165
+ return a + b
166
+ }
167
+ ```
56
168
 
57
- Output:
169
+ ### Function Usage
58
170
 
171
+ ```sl
172
+ define result = add(2, 3)
173
+ sldeploy result
59
174
  ```
60
- Unterminated block
61
- Did you forget to close the block with '}'?
62
- at line 2, column 0
63
175
 
64
- ^
176
+ ---
177
+
178
+ ### Arrow Functions
179
+
180
+ ```sl
181
+ define add = (a, b) => a + b
65
182
  ```
66
183
 
67
184
  ---
68
185
 
69
- ### ⚙️ Runtime Errors (Evaluator Errors)
186
+ ## 9. Arrays
70
187
 
71
- Runtime errors occur during execution and include:
188
+ ```sl
189
+ define arr = [1, 2, 3]
190
+
191
+ sldeploy arr[0]
192
+ ```
72
193
 
73
- * Undefined variables
74
- * Invalid operations
75
- * Logical mistakes
194
+ ### Common Array Operations
76
195
 
77
- Starlight also provides **intelligent name suggestions**:
196
+ | Function | Description |
197
+ | -------- | ------------------- |
198
+ | `push` | Add element |
199
+ | `pop` | Remove last element |
78
200
 
201
+ ```sl
202
+ push(arr, 4)
203
+ pop(arr)
79
204
  ```
80
- Undefined variable: "scroe"
81
- Did you mean "score"?
82
- at line 10, column 5
83
- ^
205
+
206
+ ---
207
+
208
+ ## 10. Objects
209
+
210
+ ```sl
211
+ define user = {
212
+ "name": "Alice",
213
+ "age": 20
214
+ }
215
+
216
+ sldeploy user.name
84
217
  ```
85
218
 
86
219
  ---
87
220
 
88
- ### 🎨 Color-Coded Errors
221
+ ## 11. Slicing
222
+
223
+ ```sl
224
+ define arr = [1, 2, 3, 4, 5]
89
225
 
90
- Errors are color-coded for clarity:
226
+ sldeploy arr[1:4]
227
+ sldeploy arr[0:5:2]
228
+ ```
91
229
 
92
- * **Red** – Fatal syntax or runtime errors
93
- * **Yellow** – Warnings or suggestions
94
- * **White** – Neutral information (source preview, pointers)
230
+ ### Slice Syntax
95
231
 
96
- This ensures errors are readable in all terminals.
232
+ | Format | Description |
233
+ | ------------------ | ------------------ |
234
+ | `[start:end]` | Basic slicing |
235
+ | `[start:end:step]` | Step-based slicing |
97
236
 
98
237
  ---
99
238
 
100
- ### Safe Execution Model
239
+ ## 12. Error Handling
101
240
 
102
- * Execution **stops immediately** on error
103
- * No uncaught exceptions
104
- * No JavaScript stack traces leaked
105
- * Clean exit behavior
241
+ ```sl
242
+ do {
243
+ define x = y
244
+ } track {
245
+ sldeploy "Error occurred"
246
+ }
247
+ ```
106
248
 
107
- This keeps Starlight predictable and safe for learning and production use.
249
+ ---
250
+
251
+ ## 13. Imports
252
+
253
+ ```sl
254
+ import math from "math"
255
+ ```
256
+
257
+ ### Supported Imports
258
+
259
+ | Type | Description |
260
+ | --------------- | --------------------- |
261
+ | `.sl` files | Local modules |
262
+ | Node.js modules | External dependencies |
108
263
 
109
264
  ---
110
265
 
111
- ### Summary
266
+ ## 14. Built-in Functions
112
267
 
113
- Starlight’s error handling is:
268
+ Examples:
114
269
 
115
- * Modern
116
- * Precise
117
- * Beginner-friendly
118
- * Professional-grade
270
+ ```sl
271
+ len([1,2,3])
272
+ upper("hello")
273
+ random(1, 10)
274
+ ```
275
+
276
+ ### Categories
277
+
278
+ | Category | Examples |
279
+ | -------- | ---------------- |
280
+ | String | `upper`, `lower` |
281
+ | Array | `push`, `pop` |
282
+ | Math | `random` |
283
+ | Utility | `len` |
284
+
285
+ ---
286
+
287
+ ## 15. Asynchronous Code
288
+
289
+ ```sl
290
+ define data = await get("https://api.example.com")
291
+ sldeploy data
292
+ ```
293
+
294
+ ---
295
+
296
+ ## 16. Object Construction
297
+
298
+ ```sl
299
+ func Person(name) {
300
+ this.name = name
301
+ }
302
+
303
+ define p = new Person("Alice")
304
+ sldeploy p.name
305
+ ```
306
+
307
+ ---
308
+
309
+ ## 17. Comments
119
310
 
120
- By using caret-based diagnostics and intelligent suggestions, Starlight provides a first-class developer experience from day one.
311
+ ```sl
312
+ # This is a comment
313
+ ```
314
+
315
+ ---
121
316
 
317
+ ## 18. Language Notes
318
+
319
+ | Behavior | Description |
320
+ | ---------------- | --------------------------------- |
321
+ | Undefined values | Treated as `null` |
322
+ | Function return | Defaults to `null` if unspecified |
323
+ | Data structures | Dynamic (arrays and objects) |
324
+ | Error reporting | Includes line and column details |
325
+
326
+ ---
327
+
328
+ ## 19. Next Steps
329
+
330
+ * Explore built-in functions
331
+ * Write small programs
332
+ * Review full syntax reference
333
+ * Experiment with custom scripts
334
+
335
+ ---
122
336
 
337
+ ## Keywords
123
338
 
124
- ## Author and Developer
125
- Dominex Macedon
339
+ starlight language, scripting language, programming language tutorial, interpreter language, CLI scripting, custom language design
package/dist/index.js CHANGED
@@ -10356,9 +10356,42 @@ async callFunction(fn, args, env, node = null) {
10356
10356
 
10357
10357
  throw new RuntimeError('Value is not callable', node, this.source);
10358
10358
  }
10359
+ createBaseProxy(instance, parentEntity) {
10360
+ const evaluator = this;
10361
+
10362
+ return new Proxy({}, {
10363
+ get(_, prop) {
10364
+ const method = parentEntity.methods[prop];
10365
+
10366
+ if (!method) {
10367
+ throw new RuntimeError(
10368
+ `Base method '${prop}' not found`,
10369
+ null,
10370
+ evaluator.source
10371
+ );
10372
+ }
10373
+
10374
+ return async (...args) => {
10375
+ const methodEnv = new Environment(parentEntity.env);
10359
10376
 
10377
+ methodEnv.define('self', instance);
10360
10378
 
10379
+ if (parentEntity.parent) {
10380
+ methodEnv.define(
10381
+ 'base',
10382
+ evaluator.createBaseProxy(instance, parentEntity.parent)
10383
+ );
10384
+ }
10361
10385
 
10386
+ for (let i = 0; i < method.params.length; i++) {
10387
+ methodEnv.define(method.params[i], args[i]);
10388
+ }
10389
+
10390
+ return await evaluator.evaluate(method.body, methodEnv);
10391
+ };
10392
+ }
10393
+ });
10394
+ }
10362
10395
  formatValue(value, seen = new Set()) {
10363
10396
  const color = __nccwpck_require__(55);
10364
10397
 
@@ -11028,6 +11061,19 @@ case 'SliceExpression':
11028
11061
  case 'Identifier': return env.get(node.name, node, this.source);
11029
11062
  case 'IfStatement': return await this.evalIf(node, env);
11030
11063
  case 'WhileStatement': return await this.evalWhile(node, env);
11064
+ case 'EntityDeclaration':
11065
+ return await this.evalEntity(node, env);
11066
+
11067
+ case 'EntityInstantiation':
11068
+ return await this.evalEntityNew(node, env);
11069
+
11070
+ case 'SelfExpression':
11071
+ return env.get('self', node, this.source);
11072
+
11073
+ case 'InitMethod':
11074
+ return node;
11075
+ case 'InheritsClause':
11076
+ return await this.evalInherits(node, env);
11031
11077
  case 'ForStatement':
11032
11078
  case 'ForInStatement':
11033
11079
  return await this.evalFor(node, env);
@@ -11065,6 +11111,70 @@ case 'RaceClause':
11065
11111
  case 'NewExpression': {
11066
11112
  const callee = await this.evaluate(node.callee, env);
11067
11113
 
11114
+ const args = [];
11115
+ for (const a of node.arguments) {
11116
+ args.push(await this.evaluate(a, env));
11117
+ }
11118
+
11119
+ if (callee && callee.methods) {
11120
+ const instance = {};
11121
+
11122
+
11123
+
11124
+ const evaluator = this;
11125
+
11126
+ const entityEnv = new Environment(callee.env);
11127
+ entityEnv.define('self', instance);
11128
+
11129
+ if (callee.parent) {
11130
+ entityEnv.define(
11131
+ 'base',
11132
+ this.createBaseProxy(instance, callee.parent)
11133
+ );
11134
+ }
11135
+
11136
+ if (callee.methods.init) {
11137
+ const init = callee.methods.init;
11138
+
11139
+ for (let i = 0; i < init.params.length; i++) {
11140
+ entityEnv.define(init.params[i], args[i]);
11141
+ }
11142
+
11143
+ await evaluator.evaluate(init.body, entityEnv);
11144
+ }
11145
+
11146
+ for (const key in callee.methods) {
11147
+ if (key === 'init') continue;
11148
+
11149
+ instance[key] = async (...callArgs) => {
11150
+ const methodEnv = new Environment(callee.env);
11151
+
11152
+ methodEnv.define('self', instance);
11153
+
11154
+ if (callee.parent) {
11155
+ methodEnv.define(
11156
+ 'base',
11157
+ evaluator.createBaseProxy(instance, callee.parent)
11158
+ );
11159
+ }
11160
+
11161
+ for (let i = 0; i < callee.methods[key].params.length; i++) {
11162
+ methodEnv.define(
11163
+ callee.methods[key].params[i],
11164
+ callArgs[i]
11165
+ );
11166
+ }
11167
+
11168
+ return await evaluator.evaluate(
11169
+ callee.methods[key].body,
11170
+ methodEnv
11171
+ );
11172
+ };
11173
+ }
11174
+
11175
+ return instance;
11176
+ }
11177
+
11068
11178
  if (typeof callee === 'object' && callee.body) {
11069
11179
  const evaluator = this;
11070
11180
 
@@ -11074,7 +11184,9 @@ case 'RaceClause':
11074
11184
 
11075
11185
  for (let i = 0; i < callee.params.length; i++) {
11076
11186
  const param = callee.params[i];
11077
- const paramName = typeof param === 'string' ? param : param.name;
11187
+ const paramName =
11188
+ typeof param === 'string' ? param : param.name;
11189
+
11078
11190
  newEnv.define(paramName, args[i]);
11079
11191
  }
11080
11192
 
@@ -11084,18 +11196,17 @@ case 'RaceClause':
11084
11196
  })();
11085
11197
  };
11086
11198
 
11087
- const args = [];
11088
- for (const a of node.arguments) args.push(await this.evaluate(a, env));
11089
11199
  return await new Constructor(...args);
11090
11200
  }
11091
-
11092
- // native JS constructor fallback
11201
+
11093
11202
  if (typeof callee !== 'function') {
11094
- throw new RuntimeError('NewExpression callee is not a function', node, this.source);
11203
+ throw new RuntimeError(
11204
+ 'NewExpression callee is not a function',
11205
+ node,
11206
+ this.source
11207
+ );
11095
11208
  }
11096
11209
 
11097
- const args = [];
11098
- for (const a of node.arguments) args.push(await this.evaluate(a, env));
11099
11210
  return new callee(...args);
11100
11211
  }
11101
11212
 
@@ -11104,7 +11215,20 @@ case 'RaceClause':
11104
11215
 
11105
11216
  }
11106
11217
  }
11218
+ async evalInherits(node, env) {
11219
+ const parent = await this.evaluate(node.parent, env);
11107
11220
 
11221
+ if (!parent || !parent.methods) {
11222
+ throw new RuntimeError(
11223
+ 'Invalid parent entity',
11224
+ node,
11225
+ this.source,
11226
+ env
11227
+ );
11228
+ }
11229
+
11230
+ return parent;
11231
+ }
11108
11232
  async evalProgram(node, env) {
11109
11233
  let result = null;
11110
11234
  for (const stmt of node.body) {
@@ -11126,6 +11250,29 @@ async evalProgram(node, env) {
11126
11250
  }
11127
11251
  return result;
11128
11252
  }
11253
+ async evalEntity(node, env) {
11254
+ const entity = {
11255
+ name: node.name,
11256
+ methods: {},
11257
+ parent: null,
11258
+ env
11259
+ };
11260
+
11261
+ if (node.inherits) {
11262
+ entity.parent = await this.evaluate(node.inherits, env);
11263
+ }
11264
+
11265
+ for (const method of node.body) {
11266
+ entity.methods[method.name] = {
11267
+ params: method.params,
11268
+ body: method.body,
11269
+ env
11270
+ };
11271
+ }
11272
+
11273
+ env.define(node.name, entity);
11274
+ return entity;
11275
+ }
11129
11276
  async evalSlice(node, env) {
11130
11277
  try {
11131
11278
  const arr = await this.evaluate(node.object, env);
@@ -12242,7 +12389,7 @@ class Lexer {
12242
12389
  'break', 'continue', 'func', 'return',
12243
12390
  'true', 'false', 'null',
12244
12391
  'ask', 'define', 'import', 'from', 'as',
12245
- 'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race', 'not', 'and', 'or'
12392
+ 'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race', 'not', 'and', 'or', 'entity', 'self', 'inherits', 'init'
12246
12393
  ];
12247
12394
  }
12248
12395
 
@@ -12536,6 +12683,7 @@ class Parser {
12536
12683
  case 'IF': return this.ifStatement();
12537
12684
  case 'WHILE': return this.whileStatement();
12538
12685
  case 'FOR': return this.forStatement();
12686
+ case 'ENTITY': return this.entityDeclaration();
12539
12687
  case 'DO': return this.doTrackStatement();
12540
12688
  case 'START': return this.startStatement();
12541
12689
  case 'BREAK': return this.breakStatement();
@@ -12603,7 +12751,103 @@ varDeclaration() {
12603
12751
  column: t.column
12604
12752
  };
12605
12753
  }
12754
+ entityDeclaration() {
12755
+ const t = this.current;
12756
+ this.eat('ENTITY');
12757
+
12758
+ if (this.current.type !== 'IDENTIFIER') {
12759
+ throw new ParseError(
12760
+ "Expected entity name",
12761
+ this.current,
12762
+ this.source,
12763
+ "Use: entity Car { ... }"
12764
+ );
12765
+ }
12766
+
12767
+ const name = this.current.value;
12768
+ this.eat('IDENTIFIER');
12769
+
12770
+ let parent = null;
12771
+
12772
+ if (this.current.type === 'INHERITS') {
12773
+ this.eat('INHERITS');
12774
+
12775
+ if (this.current.type !== 'IDENTIFIER') {
12776
+ throw new ParseError(
12777
+ "Expected parent entity name",
12778
+ this.current,
12779
+ this.source
12780
+ );
12781
+ }
12782
+
12783
+ parent = {
12784
+ type: 'Identifier',
12785
+ name: this.current.value
12786
+ };
12787
+
12788
+ this.eat('IDENTIFIER');
12789
+ }
12790
+
12791
+ this.eat('LBRACE');
12792
+
12793
+ const body = [];
12794
+
12795
+ while (this.current.type !== 'RBRACE') {
12796
+ body.push(this.methodDefinition());
12797
+ }
12798
+
12799
+ this.eat('RBRACE');
12800
+
12801
+ return {
12802
+ type: 'EntityDeclaration',
12803
+ name,
12804
+ parent,
12805
+ body,
12806
+ line: t.line,
12807
+ column: t.column
12808
+ };
12809
+ }
12810
+ methodDefinition() {
12811
+ if (this.current.type !== 'IDENTIFIER' && this.current.type !== 'INIT') {
12812
+ throw new ParseError(
12813
+ "Expected method name",
12814
+ this.current,
12815
+ this.source
12816
+ );
12817
+ }
12818
+
12819
+ const isInit = this.current.type === 'INIT';
12820
+ const name = isInit ? 'init' : this.current.value;
12821
+
12822
+ this.eat(this.current.type);
12606
12823
 
12824
+ this.eat('LPAREN');
12825
+
12826
+ const params = [];
12827
+
12828
+ if (this.current.type !== 'RPAREN') {
12829
+ params.push(this.current.value);
12830
+ this.eat('IDENTIFIER');
12831
+
12832
+ while (this.current.type === 'COMMA') {
12833
+ this.eat('COMMA');
12834
+ params.push(this.current.value);
12835
+ this.eat('IDENTIFIER');
12836
+ }
12837
+ }
12838
+
12839
+ this.eat('RPAREN');
12840
+
12841
+ const body = this.block();
12842
+
12843
+ return {
12844
+ type: 'MethodDefinition',
12845
+ name,
12846
+ params,
12847
+ body,
12848
+ isInit
12849
+ };
12850
+ }
12607
12851
  startStatement() {
12608
12852
  const t = this.current;
12609
12853
  this.eat('START');
@@ -13886,7 +14130,15 @@ arrowFunction(params) {
13886
14130
 
13887
14131
  primary() {
13888
14132
  const t = this.current;
14133
+ if (t.type === 'BASE') {
14134
+ this.eat('BASE');
14135
+ return { type: 'Identifier', name: 'base', line: t.line, column: t.column };
14136
+ }
13889
14137
 
14138
+ if (t.type === 'SELF') {
14139
+ this.eat('SELF');
14140
+ return { type: 'Identifier', name: 'self', line: t.line, column: t.column };
14141
+ }
13890
14142
  if (t.type === 'NUMBER') {
13891
14143
  this.eat('NUMBER');
13892
14144
  return { type: 'Literal', value: t.value, line: t.line, column: t.column };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/evaluator.js CHANGED
@@ -146,9 +146,42 @@ async callFunction(fn, args, env, node = null) {
146
146
 
147
147
  throw new RuntimeError('Value is not callable', node, this.source);
148
148
  }
149
+ createBaseProxy(instance, parentEntity) {
150
+ const evaluator = this;
149
151
 
152
+ return new Proxy({}, {
153
+ get(_, prop) {
154
+ const method = parentEntity.methods[prop];
150
155
 
156
+ if (!method) {
157
+ throw new RuntimeError(
158
+ `Base method '${prop}' not found`,
159
+ null,
160
+ evaluator.source
161
+ );
162
+ }
163
+
164
+ return async (...args) => {
165
+ const methodEnv = new Environment(parentEntity.env);
151
166
 
167
+ methodEnv.define('self', instance);
168
+
169
+ if (parentEntity.parent) {
170
+ methodEnv.define(
171
+ 'base',
172
+ evaluator.createBaseProxy(instance, parentEntity.parent)
173
+ );
174
+ }
175
+
176
+ for (let i = 0; i < method.params.length; i++) {
177
+ methodEnv.define(method.params[i], args[i]);
178
+ }
179
+
180
+ return await evaluator.evaluate(method.body, methodEnv);
181
+ };
182
+ }
183
+ });
184
+ }
152
185
  formatValue(value, seen = new Set()) {
153
186
  const color = require('starlight-color');
154
187
 
@@ -818,6 +851,19 @@ case 'SliceExpression':
818
851
  case 'Identifier': return env.get(node.name, node, this.source);
819
852
  case 'IfStatement': return await this.evalIf(node, env);
820
853
  case 'WhileStatement': return await this.evalWhile(node, env);
854
+ case 'EntityDeclaration':
855
+ return await this.evalEntity(node, env);
856
+
857
+ case 'EntityInstantiation':
858
+ return await this.evalEntityNew(node, env);
859
+
860
+ case 'SelfExpression':
861
+ return env.get('self', node, this.source);
862
+
863
+ case 'InitMethod':
864
+ return node;
865
+ case 'InheritsClause':
866
+ return await this.evalInherits(node, env);
821
867
  case 'ForStatement':
822
868
  case 'ForInStatement':
823
869
  return await this.evalFor(node, env);
@@ -855,6 +901,70 @@ case 'RaceClause':
855
901
  case 'NewExpression': {
856
902
  const callee = await this.evaluate(node.callee, env);
857
903
 
904
+ const args = [];
905
+ for (const a of node.arguments) {
906
+ args.push(await this.evaluate(a, env));
907
+ }
908
+
909
+ if (callee && callee.methods) {
910
+ const instance = {};
911
+
912
+
913
+
914
+ const evaluator = this;
915
+
916
+ const entityEnv = new Environment(callee.env);
917
+ entityEnv.define('self', instance);
918
+
919
+ if (callee.parent) {
920
+ entityEnv.define(
921
+ 'base',
922
+ this.createBaseProxy(instance, callee.parent)
923
+ );
924
+ }
925
+
926
+ if (callee.methods.init) {
927
+ const init = callee.methods.init;
928
+
929
+ for (let i = 0; i < init.params.length; i++) {
930
+ entityEnv.define(init.params[i], args[i]);
931
+ }
932
+
933
+ await evaluator.evaluate(init.body, entityEnv);
934
+ }
935
+
936
+ for (const key in callee.methods) {
937
+ if (key === 'init') continue;
938
+
939
+ instance[key] = async (...callArgs) => {
940
+ const methodEnv = new Environment(callee.env);
941
+
942
+ methodEnv.define('self', instance);
943
+
944
+ if (callee.parent) {
945
+ methodEnv.define(
946
+ 'base',
947
+ evaluator.createBaseProxy(instance, callee.parent)
948
+ );
949
+ }
950
+
951
+ for (let i = 0; i < callee.methods[key].params.length; i++) {
952
+ methodEnv.define(
953
+ callee.methods[key].params[i],
954
+ callArgs[i]
955
+ );
956
+ }
957
+
958
+ return await evaluator.evaluate(
959
+ callee.methods[key].body,
960
+ methodEnv
961
+ );
962
+ };
963
+ }
964
+
965
+ return instance;
966
+ }
967
+
858
968
  if (typeof callee === 'object' && callee.body) {
859
969
  const evaluator = this;
860
970
 
@@ -864,7 +974,9 @@ case 'RaceClause':
864
974
 
865
975
  for (let i = 0; i < callee.params.length; i++) {
866
976
  const param = callee.params[i];
867
- const paramName = typeof param === 'string' ? param : param.name;
977
+ const paramName =
978
+ typeof param === 'string' ? param : param.name;
979
+
868
980
  newEnv.define(paramName, args[i]);
869
981
  }
870
982
 
@@ -874,18 +986,17 @@ case 'RaceClause':
874
986
  })();
875
987
  };
876
988
 
877
- const args = [];
878
- for (const a of node.arguments) args.push(await this.evaluate(a, env));
879
989
  return await new Constructor(...args);
880
990
  }
881
-
882
- // native JS constructor fallback
991
+
883
992
  if (typeof callee !== 'function') {
884
- throw new RuntimeError('NewExpression callee is not a function', node, this.source);
993
+ throw new RuntimeError(
994
+ 'NewExpression callee is not a function',
995
+ node,
996
+ this.source
997
+ );
885
998
  }
886
999
 
887
- const args = [];
888
- for (const a of node.arguments) args.push(await this.evaluate(a, env));
889
1000
  return new callee(...args);
890
1001
  }
891
1002
 
@@ -894,7 +1005,20 @@ case 'RaceClause':
894
1005
 
895
1006
  }
896
1007
  }
1008
+ async evalInherits(node, env) {
1009
+ const parent = await this.evaluate(node.parent, env);
897
1010
 
1011
+ if (!parent || !parent.methods) {
1012
+ throw new RuntimeError(
1013
+ 'Invalid parent entity',
1014
+ node,
1015
+ this.source,
1016
+ env
1017
+ );
1018
+ }
1019
+
1020
+ return parent;
1021
+ }
898
1022
  async evalProgram(node, env) {
899
1023
  let result = null;
900
1024
  for (const stmt of node.body) {
@@ -916,6 +1040,29 @@ async evalProgram(node, env) {
916
1040
  }
917
1041
  return result;
918
1042
  }
1043
+ async evalEntity(node, env) {
1044
+ const entity = {
1045
+ name: node.name,
1046
+ methods: {},
1047
+ parent: null,
1048
+ env
1049
+ };
1050
+
1051
+ if (node.inherits) {
1052
+ entity.parent = await this.evaluate(node.inherits, env);
1053
+ }
1054
+
1055
+ for (const method of node.body) {
1056
+ entity.methods[method.name] = {
1057
+ params: method.params,
1058
+ body: method.body,
1059
+ env
1060
+ };
1061
+ }
1062
+
1063
+ env.define(node.name, entity);
1064
+ return entity;
1065
+ }
919
1066
  async evalSlice(node, env) {
920
1067
  try {
921
1068
  const arr = await this.evaluate(node.object, env);
package/src/lexer.js CHANGED
@@ -28,7 +28,7 @@ class Lexer {
28
28
  'break', 'continue', 'func', 'return',
29
29
  'true', 'false', 'null',
30
30
  'ask', 'define', 'import', 'from', 'as',
31
- 'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race', 'not', 'and', 'or'
31
+ 'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race', 'not', 'and', 'or', 'entity', 'self', 'inherits', 'init'
32
32
  ];
33
33
  }
34
34
 
package/src/parser.js CHANGED
@@ -90,6 +90,7 @@ class Parser {
90
90
  case 'IF': return this.ifStatement();
91
91
  case 'WHILE': return this.whileStatement();
92
92
  case 'FOR': return this.forStatement();
93
+ case 'ENTITY': return this.entityDeclaration();
93
94
  case 'DO': return this.doTrackStatement();
94
95
  case 'START': return this.startStatement();
95
96
  case 'BREAK': return this.breakStatement();
@@ -157,7 +158,103 @@ varDeclaration() {
157
158
  column: t.column
158
159
  };
159
160
  }
161
+ entityDeclaration() {
162
+ const t = this.current;
163
+ this.eat('ENTITY');
164
+
165
+ if (this.current.type !== 'IDENTIFIER') {
166
+ throw new ParseError(
167
+ "Expected entity name",
168
+ this.current,
169
+ this.source,
170
+ "Use: entity Car { ... }"
171
+ );
172
+ }
173
+
174
+ const name = this.current.value;
175
+ this.eat('IDENTIFIER');
176
+
177
+ let parent = null;
178
+
179
+ if (this.current.type === 'INHERITS') {
180
+ this.eat('INHERITS');
181
+
182
+ if (this.current.type !== 'IDENTIFIER') {
183
+ throw new ParseError(
184
+ "Expected parent entity name",
185
+ this.current,
186
+ this.source
187
+ );
188
+ }
189
+
190
+ parent = {
191
+ type: 'Identifier',
192
+ name: this.current.value
193
+ };
194
+
195
+ this.eat('IDENTIFIER');
196
+ }
197
+
198
+ this.eat('LBRACE');
199
+
200
+ const body = [];
201
+
202
+ while (this.current.type !== 'RBRACE') {
203
+ body.push(this.methodDefinition());
204
+ }
205
+
206
+ this.eat('RBRACE');
207
+
208
+ return {
209
+ type: 'EntityDeclaration',
210
+ name,
211
+ parent,
212
+ body,
213
+ line: t.line,
214
+ column: t.column
215
+ };
216
+ }
217
+ methodDefinition() {
218
+ if (this.current.type !== 'IDENTIFIER' && this.current.type !== 'INIT') {
219
+ throw new ParseError(
220
+ "Expected method name",
221
+ this.current,
222
+ this.source
223
+ );
224
+ }
225
+
226
+ const isInit = this.current.type === 'INIT';
227
+ const name = isInit ? 'init' : this.current.value;
228
+
229
+ this.eat(this.current.type);
160
230
 
231
+ this.eat('LPAREN');
232
+
233
+ const params = [];
234
+
235
+ if (this.current.type !== 'RPAREN') {
236
+ params.push(this.current.value);
237
+ this.eat('IDENTIFIER');
238
+
239
+ while (this.current.type === 'COMMA') {
240
+ this.eat('COMMA');
241
+ params.push(this.current.value);
242
+ this.eat('IDENTIFIER');
243
+ }
244
+ }
245
+
246
+ this.eat('RPAREN');
247
+
248
+ const body = this.block();
249
+
250
+ return {
251
+ type: 'MethodDefinition',
252
+ name,
253
+ params,
254
+ body,
255
+ isInit
256
+ };
257
+ }
161
258
  startStatement() {
162
259
  const t = this.current;
163
260
  this.eat('START');
@@ -1440,7 +1537,15 @@ arrowFunction(params) {
1440
1537
 
1441
1538
  primary() {
1442
1539
  const t = this.current;
1540
+ if (t.type === 'BASE') {
1541
+ this.eat('BASE');
1542
+ return { type: 'Identifier', name: 'base', line: t.line, column: t.column };
1543
+ }
1443
1544
 
1545
+ if (t.type === 'SELF') {
1546
+ this.eat('SELF');
1547
+ return { type: 'Identifier', name: 'self', line: t.line, column: t.column };
1548
+ }
1444
1549
  if (t.type === 'NUMBER') {
1445
1550
  this.eat('NUMBER');
1446
1551
  return { type: 'Literal', value: t.value, line: t.line, column: t.column };