lny-interpreter 0.99.0

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.

Potentially problematic release.


This version of lny-interpreter might be problematic. Click here for more details.

Files changed (130) hide show
  1. package/.vscode/launch.json +19 -0
  2. package/README.md +96 -0
  3. package/book_ALT/LennySmartIcon1.png +0 -0
  4. package/book_ALT/chapter01.md +141 -0
  5. package/book_ALT/chapter02.md +48 -0
  6. package/book_ALT/chapter03.md +28 -0
  7. package/book_ALT/chapter04.md +19 -0
  8. package/book_ALT/chapter05.md +21 -0
  9. package/book_ALT/chapter06.md +167 -0
  10. package/book_ALT/chapter07.md +232 -0
  11. package/book_ALT/chapter08.md +200 -0
  12. package/book_ALT/chapter09.md +175 -0
  13. package/book_ALT/chapter10.md +196 -0
  14. package/book_ALT/deckblatt.md +60 -0
  15. package/book_ALT/images/Lenny01.png +0 -0
  16. package/book_ALT/images/Lenny02.png +0 -0
  17. package/book_ALT/images/Lenny03.png +0 -0
  18. package/book_ALT/images/Lenny04.png +0 -0
  19. package/book_ALT/images/Lenny05.png +0 -0
  20. package/book_ALT/images/Lenny06.png +0 -0
  21. package/book_ALT/images/Lenny07.png +0 -0
  22. package/book_ALT/images/Lenny08.png +0 -0
  23. package/book_ALT/images/Lenny09.png +0 -0
  24. package/book_ALT/images/Lenny10.png +0 -0
  25. package/book_ALT/images/Lenny11.png +0 -0
  26. package/book_ALT/images/Lenny12.png +0 -0
  27. package/book_ALT/images/Lenny13.png +0 -0
  28. package/book_ALT/images/Lenny14.png +0 -0
  29. package/book_ALT/images/Lenny15.png +0 -0
  30. package/book_ALT/lehrerheft.md +255 -0
  31. package/dist/ast/ast.d.ts +126 -0
  32. package/dist/ast/ast.d.ts.map +1 -0
  33. package/dist/ast/ast.js +3 -0
  34. package/dist/ast/ast.js.map +1 -0
  35. package/dist/browser.d.ts +2 -0
  36. package/dist/browser.d.ts.map +1 -0
  37. package/dist/browser.js +1067 -0
  38. package/dist/browser.js.map +1 -0
  39. package/dist/examples.d.ts +11 -0
  40. package/dist/examples.d.ts.map +1 -0
  41. package/dist/examples.js +65 -0
  42. package/dist/examples.js.map +1 -0
  43. package/dist/i18n/i18n.d.ts +18 -0
  44. package/dist/i18n/i18n.d.ts.map +1 -0
  45. package/dist/i18n/i18n.js +505 -0
  46. package/dist/i18n/i18n.js.map +1 -0
  47. package/dist/index.d.ts +7 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +31 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/interpreter/interpreter.d.ts +40 -0
  52. package/dist/interpreter/interpreter.d.ts.map +1 -0
  53. package/dist/interpreter/interpreter.js +415 -0
  54. package/dist/interpreter/interpreter.js.map +1 -0
  55. package/dist/lexer/lexer.d.ts +87 -0
  56. package/dist/lexer/lexer.d.ts.map +1 -0
  57. package/dist/lexer/lexer.js +425 -0
  58. package/dist/lexer/lexer.js.map +1 -0
  59. package/dist/lny-config.yml +6 -0
  60. package/dist/parser/parser.d.ts +50 -0
  61. package/dist/parser/parser.d.ts.map +1 -0
  62. package/dist/parser/parser.js +605 -0
  63. package/dist/parser/parser.js.map +1 -0
  64. package/dist/repl.d.ts +3 -0
  65. package/dist/repl.d.ts.map +1 -0
  66. package/dist/repl.js +472 -0
  67. package/dist/repl.js.map +1 -0
  68. package/dist/run-file.d.ts +2 -0
  69. package/dist/run-file.d.ts.map +1 -0
  70. package/dist/run-file.js +140 -0
  71. package/dist/run-file.js.map +1 -0
  72. package/dist/test.d.ts +2 -0
  73. package/dist/test.d.ts.map +1 -0
  74. package/dist/test.js +72 -0
  75. package/dist/test.js.map +1 -0
  76. package/dist/world/world.d.ts +103 -0
  77. package/dist/world/world.d.ts.map +1 -0
  78. package/dist/world/world.js +416 -0
  79. package/dist/world/world.js.map +1 -0
  80. package/docs/About.md +19 -0
  81. package/docs/INTERPRETER.md +240 -0
  82. package/docs/README.md +68 -0
  83. package/docs/i18n/README.md +20 -0
  84. package/docs/lny-referenz.md +161 -0
  85. package/docs/lny_ueberblick.md +33 -0
  86. package/examples/00-demo.lny +3 -0
  87. package/examples/01-variable-assignment.lny +4 -0
  88. package/examples/02-conditional.lny +7 -0
  89. package/examples/03-while-loop.lny +6 -0
  90. package/examples/04-for-loop.lny +5 -0
  91. package/examples/05-lenny-movement.lny +10 -0
  92. package/examples/06-procedure.lny +6 -0
  93. package/examples/fuer-range-1.lny +4 -0
  94. package/examples/fuer-range-2.lny +6 -0
  95. package/examples/fuer-range-3.lny +6 -0
  96. package/examples/fuer-range-4.lny +4 -0
  97. package/examples/fuer-range-5.lny +6 -0
  98. package/examples/solange-verschachtelt.lny +12 -0
  99. package/favicon.ico +0 -0
  100. package/index.html +108 -0
  101. package/jest.config.js +7 -0
  102. package/lny-config.yml +6 -0
  103. package/package.json +37 -0
  104. package/settings.json +4 -0
  105. package/src/ast/ast.ts +182 -0
  106. package/src/browser.ts +1274 -0
  107. package/src/examples.ts +68 -0
  108. package/src/i18n/i18n.ts +537 -0
  109. package/src/index.ts +6 -0
  110. package/src/interpreter/interpreter.ts +453 -0
  111. package/src/lexer/lexer.ts +493 -0
  112. package/src/parser/parser.ts +711 -0
  113. package/src/repl.ts +496 -0
  114. package/src/test.ts +71 -0
  115. package/src/world/world.ts +512 -0
  116. package/style.css +315 -0
  117. package/test1.lny +2 -0
  118. package/tests/interpreter.test.ts +42 -0
  119. package/tests/parser.test.ts +18 -0
  120. package/tsconfig.json +19 -0
  121. package/vercel.json +7 -0
  122. package/vscode-lny/.vscode/launch.json +13 -0
  123. package/vscode-lny/.vscodeignore +3 -0
  124. package/vscode-lny/README.md +83 -0
  125. package/vscode-lny/language-configuration.json +19 -0
  126. package/vscode-lny/license.txt +21 -0
  127. package/vscode-lny/lny-language-0.1.0.vsix +0 -0
  128. package/vscode-lny/package.json +41 -0
  129. package/vscode-lny/snippets/lny.json +136 -0
  130. package/vscode-lny/syntaxes/lny.tmLanguage.json +85 -0
@@ -0,0 +1,711 @@
1
+ import { Token, TokenType, Lexer } from '../lexer/lexer';
2
+ import * as AST from '../ast/ast';
3
+ import { Language, I18n } from '../i18n/i18n';
4
+
5
+ export class Parser {
6
+ private tokens: Token[];
7
+ private current: number = 0;
8
+ private i18n: I18n;
9
+
10
+ constructor(source: string, language: Language = 'de') {
11
+ this.i18n = new I18n(language);
12
+ const lexer = new Lexer(source, language);
13
+ this.tokens = lexer.tokenize();
14
+ }
15
+
16
+ private syntax(message: string): Error {
17
+ const token = this.getErrorToken();
18
+ if (token) {
19
+ return new Error(
20
+ `${this.i18n.t('error_syntax')}: ${message} (line ${token.line}, column ${token.column})`
21
+ );
22
+ }
23
+ return new Error(`${this.i18n.t('error_syntax')}: ${message}`);
24
+ }
25
+
26
+ private getErrorToken(): Token | undefined {
27
+ if (this.current >= 0 && this.current < this.tokens.length) {
28
+ return this.tokens[this.current];
29
+ }
30
+ if (this.current > 0 && this.current - 1 < this.tokens.length) {
31
+ return this.tokens[this.current - 1];
32
+ }
33
+ return undefined;
34
+ }
35
+
36
+ parse(): AST.Program {
37
+ const statements: AST.Statement[] = [];
38
+ while (!this.isAtEnd()) {
39
+ const stmt = this.parseStatement();
40
+ if (stmt) {
41
+ statements.push(stmt);
42
+ }
43
+ }
44
+ return {
45
+ type: 'Program',
46
+ body: statements,
47
+ };
48
+ }
49
+
50
+ private parseStatement(): AST.Statement | null {
51
+ // Comments
52
+ if (this.match(TokenType.REM)) {
53
+ return this.parseComment();
54
+ }
55
+
56
+ // Variable declaration
57
+ if (this.match(TokenType.VAR)) {
58
+ return this.parseVariableDeclaration();
59
+ }
60
+
61
+ // Explicit variable assignment with SET keyword
62
+ if (this.match(TokenType.SET)) {
63
+ return this.parseSetAssignment();
64
+ }
65
+
66
+
67
+
68
+ // Variable assignment
69
+ if (this.check(TokenType.IDENTIFIER)) {
70
+ const checkpoint = this.current;
71
+ const identifier = this.advance();
72
+
73
+ if (this.isAssignmentOperator(this.peek().type)) {
74
+ return this.parseAssignmentFromIdentifier(identifier.value);
75
+ }
76
+
77
+ // If not assignment, reset and parse as expression statement
78
+ this.current = checkpoint;
79
+ }
80
+
81
+ // Control Flow
82
+ if (this.match(TokenType.IF)) {
83
+ return this.parseIfStatement();
84
+ }
85
+
86
+ if (this.match(TokenType.WHILE)) {
87
+ return this.parseWhileStatement();
88
+ }
89
+
90
+ if (this.match(TokenType.FOR)) {
91
+ return this.parseForStatement();
92
+ }
93
+
94
+ // Procedure declaration
95
+ if (this.match(TokenType.PROC)) {
96
+ return this.parseProcedureDeclaration();
97
+ }
98
+
99
+ // Log statement
100
+ if (this.match(TokenType.LOG)) {
101
+ return this.parseLogStatement();
102
+ }
103
+
104
+ // Return statement
105
+ if (this.match(TokenType.RETURN)) {
106
+ return this.parseReturnStatement();
107
+ }
108
+
109
+ // Expression statement
110
+ if (!this.isAtEnd()) {
111
+ return {
112
+ type: 'ExpressionStatement',
113
+ expression: this.parseExpression(),
114
+ };
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ private parseComment(): AST.CommentStatement {
121
+ // Das aktuelle Token ist das Kommentar-Token (REM), also einfach übernehmen
122
+ const token = this.peek();
123
+ const text = token.value;
124
+ this.advance(); // Zum nächsten Token weitergehen
125
+ return {
126
+ type: 'CommentStatement',
127
+ text: text.trim(),
128
+ };
129
+ }
130
+
131
+ private parseVariableDeclaration(): AST.VariableDeclaration {
132
+ const varToken = this.previous();
133
+ const name = this.expectIdentifier('Expected variable name after VAR');
134
+ let initialValue: AST.Expression | undefined;
135
+
136
+ if (this.match(TokenType.ASSIGN)) {
137
+ initialValue = this.parseExpression();
138
+ }
139
+
140
+ return {
141
+ type: 'VariableDeclaration',
142
+ name,
143
+ initialValue,
144
+ line: varToken.line,
145
+ column: varToken.column
146
+ };
147
+ }
148
+
149
+ private parseSetAssignment(): AST.VariableAssignment {
150
+ const setToken = this.previous();
151
+ const name = this.expectIdentifier('Expected variable name after SETZE');
152
+ if (!this.match(TokenType.AUF)) {
153
+ throw this.syntax('Expected AUF after variable name in SETZE statement');
154
+ }
155
+ const value = this.parseExpression();
156
+ return {
157
+ type: 'VariableAssignment',
158
+ name,
159
+ operator: 'AUF',
160
+ value,
161
+ line: setToken.line,
162
+ column: setToken.column
163
+ };
164
+ }
165
+
166
+ private parseAssignmentFromIdentifier(name: string): AST.VariableAssignment {
167
+ const nameToken = this.previous();
168
+ if (!this.isAssignmentOperator(this.peek().type)) {
169
+ throw this.syntax('Expected assignment operator (<<, +=, -=, *=, /=)');
170
+ }
171
+
172
+ const operator = this.advance().value;
173
+ const value = this.parseExpression();
174
+
175
+ return {
176
+ type: 'VariableAssignment',
177
+ name,
178
+ operator,
179
+ value,
180
+ line: nameToken.line,
181
+ column: nameToken.column
182
+ };
183
+ }
184
+
185
+ private parseIfStatement(): AST.IfStatement {
186
+ const condition = this.parseExpression();
187
+
188
+ if (!this.match(TokenType.THEN)) {
189
+ throw this.syntax('Expected THEN after IF condition');
190
+ }
191
+
192
+ const consequent: AST.Statement[] = [];
193
+ while (!this.check(TokenType.ELSE) && !this.check(TokenType.END) && !this.isAtEnd()) {
194
+ const stmt = this.parseStatement();
195
+ if (stmt) consequent.push(stmt);
196
+ }
197
+
198
+ let alternate: AST.Statement[] | undefined;
199
+
200
+ if (this.match(TokenType.ELSE)) {
201
+ alternate = [];
202
+ while (!this.check(TokenType.END) && !this.isAtEnd()) {
203
+ const stmt = this.parseStatement();
204
+ if (stmt) alternate.push(stmt);
205
+ }
206
+ }
207
+
208
+ // Allow EOF as implicit END (for REPL)
209
+ if (!this.match(TokenType.END)) {
210
+ if (!this.isAtEnd()) {
211
+ throw this.syntax('Expected END after IF statement');
212
+ }
213
+ }
214
+
215
+ return {
216
+ type: 'IfStatement',
217
+ condition,
218
+ consequent,
219
+ alternate,
220
+ };
221
+ }
222
+
223
+ private parseWhileStatement(): AST.WhileStatement {
224
+ const condition = this.parseExpression();
225
+
226
+ const body: AST.Statement[] = [];
227
+ while (!this.check(TokenType.END) && !this.isAtEnd()) {
228
+ const stmt = this.parseStatement();
229
+ if (stmt) body.push(stmt);
230
+ }
231
+
232
+ // Allow EOF as implicit END (for REPL)
233
+ if (!this.match(TokenType.END)) {
234
+ if (!this.isAtEnd()) {
235
+ throw this.syntax('Expected END after WHILE body');
236
+ }
237
+ }
238
+
239
+ return {
240
+ type: 'WhileStatement',
241
+ condition,
242
+ body,
243
+ };
244
+ }
245
+
246
+ private parseForStatement(): AST.ForStatement {
247
+ const variable = this.expectIdentifier('Expected variable name after FOR');
248
+
249
+ if (!this.match(TokenType.IN)) {
250
+ throw this.syntax('Expected IN after FOR variable');
251
+ }
252
+
253
+ const iterable = this.parseExpression();
254
+
255
+ const body: AST.Statement[] = [];
256
+ while (!this.check(TokenType.END) && !this.isAtEnd()) {
257
+ const stmt = this.parseStatement();
258
+ if (stmt) body.push(stmt);
259
+ }
260
+
261
+ // Allow EOF as implicit END (for REPL)
262
+ if (!this.match(TokenType.END)) {
263
+ if (!this.isAtEnd()) {
264
+ throw this.syntax('Expected END after FOR body');
265
+ }
266
+ }
267
+
268
+ return {
269
+ type: 'ForStatement',
270
+ variable,
271
+ iterable,
272
+ body,
273
+ };
274
+ }
275
+
276
+ private parseProcedureDeclaration(): AST.ProcedureDeclaration {
277
+ const name = this.expectIdentifier('Expected procedure name');
278
+ const parameters: string[] = [];
279
+
280
+ // Parse parameters if they exist
281
+ if (this.match(TokenType.LPAREN)) {
282
+ if (!this.check(TokenType.RPAREN)) {
283
+ do {
284
+ parameters.push(this.expectIdentifier('Expected parameter name'));
285
+ } while (this.match(TokenType.COMMA));
286
+ }
287
+
288
+ if (!this.match(TokenType.RPAREN)) {
289
+ throw this.syntax('Expected ) after parameters');
290
+ }
291
+ }
292
+
293
+ const body: AST.Statement[] = [];
294
+ while (!this.check(TokenType.END) && !this.isAtEnd()) {
295
+ const stmt = this.parseStatement();
296
+ if (stmt) body.push(stmt);
297
+ }
298
+
299
+ if (!this.match(TokenType.END)) {
300
+ throw this.syntax('Expected END after PROC body');
301
+ }
302
+
303
+ return {
304
+ type: 'ProcedureDeclaration',
305
+ name,
306
+ parameters,
307
+ body,
308
+ };
309
+ }
310
+
311
+ private parseLogStatement(): AST.LogStatement {
312
+ const expression = this.parseExpression();
313
+ return {
314
+ type: 'LogStatement',
315
+ expression,
316
+ };
317
+ }
318
+
319
+ private parseReturnStatement(): AST.ReturnStatement {
320
+ let argument: AST.Expression | undefined;
321
+
322
+ if (!this.isAtEnd() && !this.check(TokenType.END) && !this.check(TokenType.ELSE)) {
323
+ argument = this.parseExpression();
324
+ }
325
+
326
+ return {
327
+ type: 'ReturnStatement',
328
+ argument,
329
+ };
330
+ }
331
+
332
+ private parseExpression(): AST.Expression {
333
+ return this.parseOrExpression();
334
+ }
335
+
336
+ // --- List and Range Parsing ---
337
+ private parseListLiteral(): AST.Expression {
338
+ this.expect(TokenType.LBRACKET, 'Expected [');
339
+ // Check for range: [start..end]
340
+ const first = this.parseExpression();
341
+ if (this.match(TokenType.DOT) && this.match(TokenType.DOT)) {
342
+ // [start..end]
343
+ const end = this.parseExpression();
344
+ this.expect(TokenType.RBRACKET, 'Expected ] after range');
345
+ return {
346
+ type: 'RangeLiteral',
347
+ start: first,
348
+ end: end,
349
+ };
350
+ }
351
+ // Otherwise, normal list
352
+ const elements = [first];
353
+ while (this.match(TokenType.COMMA)) {
354
+ elements.push(this.parseExpression());
355
+ }
356
+ this.expect(TokenType.RBRACKET, 'Expected ] after list');
357
+ return {
358
+ type: 'ListLiteral',
359
+ elements,
360
+ };
361
+ }
362
+
363
+ private parseOrExpression(): AST.Expression {
364
+ let expr = this.parseAndExpression();
365
+
366
+ while (this.match(TokenType.OR)) {
367
+ const operator = this.previous().value;
368
+ const right = this.parseAndExpression();
369
+ expr = {
370
+ type: 'BinaryExpression',
371
+ left: expr,
372
+ operator,
373
+ right,
374
+ };
375
+ }
376
+
377
+ return expr;
378
+ }
379
+
380
+ private parseAndExpression(): AST.Expression {
381
+ let expr = this.parseEqualityExpression();
382
+
383
+ while (this.match(TokenType.AND)) {
384
+ const operator = this.previous().value;
385
+ const right = this.parseEqualityExpression();
386
+ expr = {
387
+ type: 'BinaryExpression',
388
+ left: expr,
389
+ operator,
390
+ right,
391
+ };
392
+ }
393
+
394
+ return expr;
395
+ }
396
+
397
+ private parseEqualityExpression(): AST.Expression {
398
+ let expr = this.parseRelationalExpression();
399
+
400
+ while (this.match(TokenType.EQUAL, TokenType.NOT_EQUAL)) {
401
+ const operator = this.previous().value;
402
+ const right = this.parseRelationalExpression();
403
+ expr = {
404
+ type: 'BinaryExpression',
405
+ left: expr,
406
+ operator,
407
+ right,
408
+ };
409
+ }
410
+
411
+ return expr;
412
+ }
413
+
414
+ private parseRelationalExpression(): AST.Expression {
415
+ let expr = this.parseAdditiveExpression();
416
+
417
+ while (this.match(TokenType.LESS, TokenType.GREATER, TokenType.LESS_EQUAL, TokenType.GREATER_EQUAL)) {
418
+ const operator = this.previous().value;
419
+ const right = this.parseAdditiveExpression();
420
+ expr = {
421
+ type: 'BinaryExpression',
422
+ left: expr,
423
+ operator,
424
+ right,
425
+ };
426
+ }
427
+
428
+ return expr;
429
+ }
430
+
431
+ private parseAdditiveExpression(): AST.Expression {
432
+ let expr = this.parseMultiplicativeExpression();
433
+
434
+ while (this.match(TokenType.PLUS, TokenType.MINUS)) {
435
+ const operator = this.previous().value;
436
+ const right = this.parseMultiplicativeExpression();
437
+ expr = {
438
+ type: 'BinaryExpression',
439
+ left: expr,
440
+ operator,
441
+ right,
442
+ };
443
+ }
444
+
445
+ return expr;
446
+ }
447
+
448
+ private parseMultiplicativeExpression(): AST.Expression {
449
+ let expr = this.parseExponentiationExpression();
450
+
451
+ while (this.match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO)) {
452
+ const operator = this.previous().value;
453
+ const right = this.parseExponentiationExpression();
454
+ expr = {
455
+ type: 'BinaryExpression',
456
+ left: expr,
457
+ operator,
458
+ right,
459
+ };
460
+ }
461
+
462
+ return expr;
463
+ }
464
+
465
+ private parseExponentiationExpression(): AST.Expression {
466
+ let expr = this.parseUnaryExpression();
467
+
468
+ if (this.match(TokenType.POWER)) {
469
+ const operator = this.previous().value;
470
+ const right = this.parseExponentiationExpression(); // Right associative
471
+ expr = {
472
+ type: 'BinaryExpression',
473
+ left: expr,
474
+ operator,
475
+ right,
476
+ };
477
+ }
478
+
479
+ return expr;
480
+ }
481
+
482
+ private parseUnaryExpression(): AST.Expression {
483
+ if (this.match(TokenType.NOT, TokenType.MINUS)) {
484
+ const operator = this.previous().value;
485
+ const argument = this.parseUnaryExpression();
486
+ return {
487
+ type: 'UnaryExpression',
488
+ operator,
489
+ argument,
490
+ };
491
+ }
492
+
493
+ return this.parsePostfixExpression();
494
+ }
495
+
496
+ private parsePostfixExpression(): AST.Expression {
497
+ let expr = this.parsePrimaryExpression();
498
+
499
+ while (true) {
500
+ if (this.match(TokenType.LPAREN)) {
501
+ // Function call
502
+ if (expr.type !== 'Identifier') {
503
+ throw this.syntax('Only identifiers can be called');
504
+ }
505
+ const args: AST.Expression[] = [];
506
+
507
+ if (!this.check(TokenType.RPAREN)) {
508
+ do {
509
+ args.push(this.parseExpression());
510
+ } while (this.match(TokenType.COMMA));
511
+ }
512
+
513
+ if (!this.match(TokenType.RPAREN)) {
514
+ throw this.syntax('Expected ) after function arguments');
515
+ }
516
+
517
+ expr = {
518
+ type: 'CallExpression',
519
+ callee: expr as AST.Identifier,
520
+ arguments: args,
521
+ };
522
+ } else {
523
+ break;
524
+ }
525
+ }
526
+
527
+ return expr;
528
+ }
529
+
530
+ private parsePrimaryExpression(): AST.Expression {
531
+
532
+
533
+ // Numbers
534
+ if (this.check(TokenType.NUMBER)) {
535
+ const value = parseFloat(this.advance().value);
536
+ return {
537
+ type: 'NumberLiteral',
538
+ value,
539
+ };
540
+ }
541
+
542
+ // Strings
543
+ if (this.check(TokenType.STRING)) {
544
+ const value = this.advance().value;
545
+ return {
546
+ type: 'StringLiteral',
547
+ value,
548
+ };
549
+ }
550
+
551
+ // Booleans
552
+ if (this.match(TokenType.TRUE)) {
553
+ return {
554
+ type: 'BooleanLiteral',
555
+ value: true,
556
+ };
557
+ }
558
+
559
+ if (this.match(TokenType.FALSE)) {
560
+ return {
561
+ type: 'BooleanLiteral',
562
+ value: false,
563
+ };
564
+ }
565
+
566
+ // Nothing
567
+ if (this.match(TokenType.NOTHING)) {
568
+ return {
569
+ type: 'NothingLiteral',
570
+ };
571
+ }
572
+
573
+ // Lists (inkl. Bereichsoperator)
574
+ if (this.check(TokenType.LBRACKET)) {
575
+ return this.parseListLiteral();
576
+ }
577
+
578
+ // Parenthesized expressions
579
+ if (this.match(TokenType.LPAREN)) {
580
+ const expression = this.parseExpression();
581
+
582
+ if (!this.match(TokenType.RPAREN)) {
583
+ throw this.syntax('Expected ) after expression');
584
+ }
585
+
586
+ return {
587
+ type: 'ParenthesizedExpression',
588
+ expression,
589
+ };
590
+ }
591
+
592
+ // Private attribute access
593
+ if (this.match(TokenType.PRIVATE)) {
594
+ const name = this.expectIdentifier('Expected attribute name after PRIVATE');
595
+ return {
596
+ type: 'PrivateAttribute',
597
+ name,
598
+ };
599
+ }
600
+
601
+ // Global attribute access
602
+ if (this.match(TokenType.GLOBAL)) {
603
+ const name = this.expectIdentifier('Expected attribute name after GLOBAL');
604
+ return {
605
+ type: 'GlobalAttribute',
606
+ name,
607
+ };
608
+ }
609
+
610
+ // Identifiers
611
+ if (this.match(TokenType.IDENTIFIER)) {
612
+ const token = this.previous();
613
+ return {
614
+ type: 'Identifier',
615
+ name: token.value,
616
+ line: token.line,
617
+ column: token.column
618
+ };
619
+ }
620
+
621
+ throw this.syntax(`Unexpected token: ${this.peek().value}`);
622
+ }
623
+
624
+ // Helper methods
625
+ private expectIdentifier(expectedMessage: string): string {
626
+ const token = this.peek();
627
+
628
+ if (token.type === TokenType.IDENTIFIER) {
629
+ return this.advance().value;
630
+ }
631
+
632
+ if (this.isReservedWordToken(token.type)) {
633
+ throw this.syntax(this.i18n.t('error_reserved_word', { name: token.value }));
634
+ }
635
+
636
+ throw this.syntax(expectedMessage);
637
+ }
638
+
639
+ private isReservedWordToken(type: TokenType): boolean {
640
+ // Prüfe reservierte Wörter nur für die aktuelle Sprache
641
+ // y ist z.B. nur in Spanisch reserviert, nicht in Deutsch/Englisch
642
+ const reserved: TokenType[] = [
643
+ TokenType.IF, TokenType.THEN, TokenType.ELSE, TokenType.END, TokenType.WHILE, TokenType.FOR, TokenType.IN,
644
+ TokenType.VAR, TokenType.SET, TokenType.AUF, TokenType.LIST, TokenType.PROC, TokenType.RETURN, TokenType.LOG, TokenType.REM,
645
+ TokenType.NOT, TokenType.TRUE, TokenType.FALSE, TokenType.NOTHING, TokenType.PRIVATE, TokenType.GLOBAL
646
+ ];
647
+ // AND und OR nur reserviert, wenn sie in der aktuellen Sprache wirklich so heißen
648
+ if (this.i18n.t('kw_and').toLowerCase() === 'and' && type === TokenType.AND) return true;
649
+ if (this.i18n.t('kw_or').toLowerCase() === 'or' && type === TokenType.OR) return true;
650
+ // In anderen Sprachen (z.B. Spanisch: 'y' für AND) ist AND nur reserviert, wenn das aktuelle Keyword so heißt
651
+ if (type === TokenType.AND && this.i18n.t('kw_and').toLowerCase() !== 'and') return false;
652
+ if (type === TokenType.OR && this.i18n.t('kw_or').toLowerCase() !== 'or') return false;
653
+ return reserved.includes(type);
654
+ }
655
+
656
+ private isAssignmentOperator(type: TokenType): boolean {
657
+ return (
658
+ type === TokenType.ASSIGN ||
659
+ type === TokenType.PLUS_ASSIGN ||
660
+ type === TokenType.MINUS_ASSIGN ||
661
+ type === TokenType.MULT_ASSIGN ||
662
+ type === TokenType.DIV_ASSIGN
663
+ );
664
+ }
665
+
666
+ private match(...types: TokenType[]): boolean {
667
+ for (const type of types) {
668
+ if (this.check(type)) {
669
+ this.advance();
670
+ return true;
671
+ }
672
+ }
673
+ return false;
674
+ }
675
+
676
+ private check(type: TokenType): boolean {
677
+ if (this.isAtEnd()) return false;
678
+ return this.peek().type === type;
679
+ }
680
+
681
+ private advance(): Token {
682
+ if (!this.isAtEnd()) {
683
+ this.current++;
684
+ }
685
+ return this.previous();
686
+ }
687
+
688
+ private isAtEnd(): boolean {
689
+ return this.peek().type === TokenType.EOF;
690
+ }
691
+
692
+ private peek(): Token {
693
+ return this.tokens[this.current];
694
+ }
695
+
696
+ private previous(): Token {
697
+ return this.tokens[this.current - 1];
698
+ }
699
+
700
+ /**
701
+ * Consumes the next token if it matches the expected type, otherwise throws a syntax error.
702
+ * @param type The expected token type
703
+ * @param message The error message if the token does not match
704
+ */
705
+ private expect(type: TokenType, message: string): Token {
706
+ if (this.check(type)) {
707
+ return this.advance();
708
+ }
709
+ throw this.syntax(message);
710
+ }
711
+ }