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,453 @@
1
+ import * as AST from '../ast/ast';
2
+ import { World, LnyValue, LnyEnvironment, LnyProcedure, BuiltInFunction } from '../world/world';
3
+ import { I18n, Language } from '../i18n/i18n';
4
+
5
+ export interface InterpreterOptions {
6
+ worldWidth?: number;
7
+ worldHeight?: number;
8
+ }
9
+
10
+ export class ReturnValue extends Error {
11
+ constructor(public value: LnyValue) {
12
+ super();
13
+ }
14
+ }
15
+
16
+ export class BreakException extends Error {
17
+ constructor() {
18
+ super();
19
+ }
20
+ }
21
+
22
+ export class Interpreter {
23
+ private world: World;
24
+ private i18n: I18n;
25
+ private globalEnv: LnyEnvironment;
26
+ private currentEnv: LnyEnvironment;
27
+ private output: string[] = [];
28
+
29
+ constructor(language: Language = 'de', options: InterpreterOptions = {}) {
30
+ this.i18n = new I18n(language);
31
+ this.world = new World(language, options.worldWidth, options.worldHeight);
32
+ this.globalEnv = this.world.getEnvironment();
33
+ this.currentEnv = this.globalEnv;
34
+ }
35
+
36
+ setLanguage(language: Language): void {
37
+ this.i18n.setLanguage(language);
38
+ this.world.setLanguage(language);
39
+ }
40
+
41
+ interpret(program: AST.Program): LnyValue {
42
+ try {
43
+ // Top-Level: KEIN neues Environment erzeugen, sondern globalEnv verwenden
44
+ for (const statement of program.body) {
45
+ this.executeStatement(statement);
46
+ }
47
+ return null;
48
+ } catch (e) {
49
+ if (e instanceof ReturnValue) {
50
+ return e.value;
51
+ }
52
+ throw e;
53
+ }
54
+ }
55
+
56
+ private executeBlock(statements: AST.Statement[], env: LnyEnvironment): void {
57
+ const previousEnv = this.currentEnv;
58
+ this.currentEnv = env;
59
+
60
+ try {
61
+ for (const statement of statements) {
62
+ this.executeStatement(statement);
63
+ }
64
+ } finally {
65
+ this.currentEnv = previousEnv;
66
+ }
67
+ }
68
+
69
+ private executeStatement(stmt: AST.Statement): void {
70
+ switch (stmt.type) {
71
+ case 'ExpressionStatement':
72
+ this.evaluate((stmt as AST.ExpressionStatement).expression);
73
+ break;
74
+
75
+ case 'VariableDeclaration': {
76
+ const varDecl = stmt as AST.VariableDeclaration;
77
+ const value = varDecl.initialValue ? this.evaluate(varDecl.initialValue) : null;
78
+ this.currentEnv.define(varDecl.name, value);
79
+ break;
80
+ }
81
+
82
+ case 'VariableAssignment': {
83
+ const assignment = stmt as AST.VariableAssignment;
84
+ const currentValue = this.currentEnv.get(assignment.name);
85
+ let newValue: LnyValue;
86
+
87
+ if (assignment.operator === '<<') {
88
+ newValue = this.evaluate(assignment.value);
89
+ } else if (assignment.operator === 'AUF') {
90
+ newValue = this.evaluate(assignment.value);
91
+ } else {
92
+ const rightValue = this.evaluate(assignment.value);
93
+ newValue = this.applyCompoundOperator(
94
+ assignment.operator,
95
+ currentValue as number,
96
+ rightValue as number
97
+ );
98
+ }
99
+
100
+ this.currentEnv.set(assignment.name, newValue);
101
+ break;
102
+ }
103
+
104
+ case 'IfStatement':
105
+ this.executeIfStatement(stmt as AST.IfStatement);
106
+ break;
107
+
108
+ case 'WhileStatement':
109
+ this.executeWhileStatement(stmt as AST.WhileStatement);
110
+ break;
111
+
112
+ case 'ForStatement':
113
+ this.executeForStatement(stmt as AST.ForStatement);
114
+ break;
115
+
116
+ case 'ProcedureDeclaration': {
117
+ const procDecl = stmt as AST.ProcedureDeclaration;
118
+ const procedure: LnyProcedure = {
119
+ parameters: procDecl.parameters,
120
+ body: procDecl.body,
121
+ closure: this.currentEnv,
122
+ };
123
+ this.currentEnv.define(procDecl.name, procedure);
124
+ break;
125
+ }
126
+
127
+ case 'ReturnStatement': {
128
+ const returnStmt = stmt as AST.ReturnStatement;
129
+ const value = returnStmt.argument ? this.evaluate(returnStmt.argument) : null;
130
+ throw new ReturnValue(value);
131
+ }
132
+
133
+ case 'LogStatement': {
134
+ const logStmt = stmt as AST.LogStatement;
135
+ const value = this.evaluate(logStmt.expression);
136
+ this.output.push(this.lnyValueToString(value));
137
+ break;
138
+ }
139
+
140
+ case 'CommentStatement':
141
+ // Comments are ignored
142
+ break;
143
+
144
+ case 'Block':
145
+ this.executeBlock((stmt as AST.Block).statements, new LnyEnvironment(this.currentEnv));
146
+ break;
147
+ }
148
+ }
149
+
150
+ private executeIfStatement(stmt: AST.IfStatement): void {
151
+ const condition = this.isTruthy(this.evaluate(stmt.condition));
152
+
153
+ if (condition) {
154
+ this.executeBlock(stmt.consequent, new LnyEnvironment(this.currentEnv));
155
+ } else if (stmt.alternate) {
156
+ this.executeBlock(stmt.alternate, new LnyEnvironment(this.currentEnv));
157
+ }
158
+ }
159
+
160
+ private executeWhileStatement(stmt: AST.WhileStatement): void {
161
+ while (this.isTruthy(this.evaluate(stmt.condition))) {
162
+ try {
163
+ this.executeBlock(stmt.body, new LnyEnvironment(this.currentEnv));
164
+ } catch (e) {
165
+ if (e instanceof BreakException) {
166
+ break;
167
+ }
168
+ throw e;
169
+ }
170
+ }
171
+ }
172
+
173
+ private executeForStatement(stmt: AST.ForStatement): void {
174
+ const iterable = this.evaluate(stmt.iterable);
175
+ const env = new LnyEnvironment(this.currentEnv);
176
+
177
+ if (!Array.isArray(iterable)) {
178
+ throw new Error(this.i18n.t('error_for_iterable_must_list'));
179
+ }
180
+
181
+ for (const item of iterable) {
182
+ env.define(stmt.variable, item);
183
+
184
+ try {
185
+ this.executeBlock(stmt.body, env);
186
+ } catch (e) {
187
+ if (e instanceof BreakException) {
188
+ break;
189
+ }
190
+ throw e;
191
+ }
192
+ }
193
+ }
194
+
195
+ private evaluate(expr: AST.Expression): LnyValue {
196
+ switch (expr.type) {
197
+ case 'RangeLiteral': {
198
+ const rangeExpr = expr as AST.RangeLiteral;
199
+ const start = this.evaluate(rangeExpr.start);
200
+ const end = this.evaluate(rangeExpr.end);
201
+ if (typeof start !== 'number' || typeof end !== 'number') {
202
+ throw new Error(this.i18n.t('error_type_mismatch'));
203
+ }
204
+ const result = [];
205
+ if (start <= end) {
206
+ for (let i = start; i <= end; i++) result.push(i);
207
+ } else {
208
+ for (let i = start; i >= end; i--) result.push(i);
209
+ }
210
+ return result;
211
+ }
212
+ case 'NumberLiteral':
213
+ return (expr as AST.NumberLiteral).value;
214
+
215
+ case 'StringLiteral':
216
+ return (expr as AST.StringLiteral).value;
217
+
218
+ case 'BooleanLiteral':
219
+ return (expr as AST.BooleanLiteral).value;
220
+
221
+ case 'NothingLiteral':
222
+ return null;
223
+
224
+ case 'ListLiteral': {
225
+ const listExpr = expr as AST.ListLiteral;
226
+ return listExpr.elements.map((el) => this.evaluate(el));
227
+ }
228
+
229
+ case 'Identifier': {
230
+ const idExpr = expr as AST.Identifier;
231
+ // 1. Variable?
232
+ if (this.currentEnv.has(idExpr.name)) {
233
+ const value = this.currentEnv.get(idExpr.name);
234
+ // 2. Prozedur? Dann direkt ausführen (ohne Argumente)
235
+ if (value && typeof value === 'object' && 'parameters' in value && 'body' in value) {
236
+ const procedure = value as LnyProcedure;
237
+ if (procedure.parameters.length === 0) {
238
+ // Keine Argumente nötig
239
+ const procEnv = new LnyEnvironment(procedure.closure);
240
+ try {
241
+ this.executeBlock(procedure.body, procEnv);
242
+ return null;
243
+ } catch (e) {
244
+ if (e instanceof ReturnValue) {
245
+ return e.value;
246
+ }
247
+ throw e;
248
+ }
249
+ } else {
250
+ throw new Error(this.i18n.t('error_invalid_arguments') + ': Prozedur benötigt Argumente');
251
+ }
252
+ }
253
+ // Normale Variable
254
+ return value;
255
+ }
256
+ // 3. Built-In?
257
+ const builtIn = this.world.getBuiltIn(idExpr.name);
258
+ if (builtIn) {
259
+ return builtIn([]);
260
+ }
261
+ // 4. Nicht gefunden
262
+ let posInfo = '';
263
+ if (typeof idExpr.line === 'number' && typeof idExpr.column === 'number') {
264
+ posInfo = ` (Zeile ${idExpr.line}, Spalte ${idExpr.column})`;
265
+ }
266
+ throw new Error(this.i18n.t('error_undefined_variable', { name: idExpr.name }) + posInfo);
267
+ }
268
+
269
+ case 'BinaryExpression':
270
+ return this.evaluateBinaryExpression(expr as AST.BinaryExpression);
271
+
272
+ case 'UnaryExpression':
273
+ return this.evaluateUnaryExpression(expr as AST.UnaryExpression);
274
+
275
+ case 'CallExpression':
276
+ return this.evaluateCallExpression(expr as AST.CallExpression);
277
+
278
+ case 'ParenthesizedExpression': {
279
+ const parenExpr = expr as AST.ParenthesizedExpression;
280
+ return this.evaluate(parenExpr.expression);
281
+ }
282
+
283
+ case 'PrivateAttribute': {
284
+ const privExpr = expr as AST.PrivateAttribute;
285
+ const state = this.world.getLennyState();
286
+ return (state as any)[privExpr.name] ?? null;
287
+ }
288
+
289
+ case 'GlobalAttribute': {
290
+ const globExpr = expr as AST.GlobalAttribute;
291
+ try {
292
+ return this.world.getGlobal(globExpr.name);
293
+ } catch {
294
+ return null;
295
+ }
296
+ }
297
+
298
+ default:
299
+ throw new Error(
300
+ this.i18n.t('error_unknown_expression_type', {
301
+ type: (expr as any).type,
302
+ })
303
+ );
304
+ }
305
+ }
306
+
307
+ private evaluateBinaryExpression(expr: AST.BinaryExpression): LnyValue {
308
+ const left = this.evaluate(expr.left);
309
+ const right = this.evaluate(expr.right);
310
+
311
+ const operators: { [key: string]: (l: any, r: any) => any } = {
312
+ '+': (l, r) => l + r,
313
+ '-': (l, r) => l - r,
314
+ '*': (l, r) => l * r,
315
+ '/': (l, r) => {
316
+ if (r === 0) throw new Error(this.i18n.t('error_division_by_zero'));
317
+ return l / r;
318
+ },
319
+ '%': (l, r) => l % r,
320
+ '**': (l, r) => Math.pow(l, r),
321
+ '<': (l, r) => l < r,
322
+ '>': (l, r) => l > r,
323
+ '<=': (l, r) => l <= r,
324
+ '>=': (l, r) => l >= r,
325
+ '==': (l, r) => l === r,
326
+ '<>': (l, r) => l !== r,
327
+ 'and': (l, r) => this.isTruthy(l) && this.isTruthy(r),
328
+ 'und': (l, r) => this.isTruthy(l) && this.isTruthy(r),
329
+ 'et': (l, r) => this.isTruthy(l) && this.isTruthy(r),
330
+ 'y': (l, r) => this.isTruthy(l) && this.isTruthy(r),
331
+ 'e': (l, r) => this.isTruthy(l) && this.isTruthy(r),
332
+ 'or': (l, r) => this.isTruthy(l) || this.isTruthy(r),
333
+ 'oder': (l, r) => this.isTruthy(l) || this.isTruthy(r),
334
+ 'ou': (l, r) => this.isTruthy(l) || this.isTruthy(r),
335
+ 'o': (l, r) => this.isTruthy(l) || this.isTruthy(r),
336
+ };
337
+
338
+ const operator = operators[expr.operator];
339
+ if (!operator) {
340
+ throw new Error(this.i18n.t('error_unknown_operator', { op: expr.operator }));
341
+ }
342
+
343
+ return operator(left, right);
344
+ }
345
+
346
+ private evaluateUnaryExpression(expr: AST.UnaryExpression): LnyValue {
347
+ const operand = this.evaluate(expr.argument);
348
+ switch (expr.operator.toLowerCase()) {
349
+ case 'not':
350
+ case 'nicht':
351
+ case 'no':
352
+ return !this.isTruthy(operand);
353
+ case '+':
354
+ if (operand === null || operand === undefined) return 0;
355
+ return +operand;
356
+ case '-':
357
+ if (operand === null || operand === undefined) return 0;
358
+ return -operand;
359
+ default:
360
+ throw new Error(this.i18n.t('error_unknown_unary_operator', { op: expr.operator }));
361
+ }
362
+ }
363
+
364
+ private evaluateCallExpression(expr: AST.CallExpression): LnyValue {
365
+ // Funktionsaufruf: callee kann Identifier sein
366
+ const callee = expr.callee;
367
+ if (callee.type !== 'Identifier') {
368
+ throw new Error(this.i18n.t('error_undefined_function', { name: '?' }));
369
+ }
370
+ const fnName = callee.name;
371
+ // 1. Prozedur?
372
+ if (this.currentEnv.has(fnName)) {
373
+ const value = this.currentEnv.get(fnName);
374
+ if (value && typeof value === 'object' && 'parameters' in value && 'body' in value) {
375
+ const procedure = value as LnyProcedure;
376
+ if (procedure.parameters.length !== expr.arguments.length) {
377
+ throw new Error(this.i18n.t('error_invalid_arguments'));
378
+ }
379
+ const procEnv = new LnyEnvironment(procedure.closure);
380
+ for (let i = 0; i < procedure.parameters.length; i++) {
381
+ procEnv.define(procedure.parameters[i], this.evaluate(expr.arguments[i]));
382
+ }
383
+ try {
384
+ this.executeBlock(procedure.body, procEnv);
385
+ return null;
386
+ } catch (e) {
387
+ if (e instanceof ReturnValue) {
388
+ return e.value;
389
+ }
390
+ throw e;
391
+ }
392
+ }
393
+ }
394
+ // 2. Built-In?
395
+ const builtIn = this.world.getBuiltIn(fnName);
396
+ if (builtIn) {
397
+ return builtIn(expr.arguments.map(arg => this.evaluate(arg)));
398
+ }
399
+ // 3. Nicht gefunden
400
+ const pos = (callee.line && callee.column) ? ` (line ${callee.line}, column ${callee.column})` : '';
401
+ throw new Error(this.i18n.t('error_undefined_function', { name: fnName }) + pos);
402
+ }
403
+
404
+ private applyCompoundOperator(operator: string, left: number, right: number): number {
405
+ switch (operator) {
406
+ case '+=':
407
+ return left + right;
408
+ case '-=':
409
+ return left - right;
410
+ case '*=':
411
+ return left * right;
412
+ case '/=':
413
+ if (right === 0) throw new Error(this.i18n.t('error_division_by_zero'));
414
+ return left / right;
415
+ default:
416
+ throw new Error(this.i18n.t('error_unknown_compound_operator', { op: operator }));
417
+ }
418
+ }
419
+
420
+ private isTruthy(value: LnyValue): boolean {
421
+ if (value === null || value === undefined) return false;
422
+ if (typeof value === 'boolean') return value;
423
+ if (typeof value === 'number') return value !== 0;
424
+ if (typeof value === 'string') return value.length > 0;
425
+ if (Array.isArray(value)) return value.length > 0;
426
+ return true;
427
+ }
428
+
429
+ private lnyValueToString(value: LnyValue): string {
430
+ if (value === null || value === undefined) {
431
+ return this.i18n.t('kw_nothing');
432
+ }
433
+ if (typeof value === 'boolean') {
434
+ return value ? this.i18n.t('kw_true') : this.i18n.t('kw_false');
435
+ }
436
+ if (Array.isArray(value)) {
437
+ return '[' + value.map((v) => this.lnyValueToString(v)).join(', ') + ']';
438
+ }
439
+ return String(value);
440
+ }
441
+
442
+ getOutput(): string {
443
+ return this.output.join('\n');
444
+ }
445
+
446
+ clearOutput(): void {
447
+ this.output = [];
448
+ }
449
+
450
+ getWorld(): World {
451
+ return this.world;
452
+ }
453
+ }