hpp-lang 0.6.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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/ast.d.ts +89 -0
  4. package/dist/ast.d.ts.map +1 -0
  5. package/dist/ast.js +2 -0
  6. package/dist/ast.js.map +1 -0
  7. package/dist/capabilities.d.ts +75 -0
  8. package/dist/capabilities.d.ts.map +1 -0
  9. package/dist/capabilities.js +181 -0
  10. package/dist/capabilities.js.map +1 -0
  11. package/dist/errors.d.ts +21 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +27 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/examples.d.ts +9 -0
  16. package/dist/examples.d.ts.map +1 -0
  17. package/dist/examples.js +780 -0
  18. package/dist/examples.js.map +1 -0
  19. package/dist/extensions/line/index.d.ts +45 -0
  20. package/dist/extensions/line/index.d.ts.map +1 -0
  21. package/dist/extensions/line/index.js +99 -0
  22. package/dist/extensions/line/index.js.map +1 -0
  23. package/dist/extensions/maze/index.d.ts +37 -0
  24. package/dist/extensions/maze/index.d.ts.map +1 -0
  25. package/dist/extensions/maze/index.js +43 -0
  26. package/dist/extensions/maze/index.js.map +1 -0
  27. package/dist/extensions/soccer/index.d.ts +8 -0
  28. package/dist/extensions/soccer/index.d.ts.map +1 -0
  29. package/dist/extensions/soccer/index.js +93 -0
  30. package/dist/extensions/soccer/index.js.map +1 -0
  31. package/dist/hardware.d.ts +44 -0
  32. package/dist/hardware.d.ts.map +1 -0
  33. package/dist/hardware.js +2 -0
  34. package/dist/hardware.js.map +1 -0
  35. package/dist/index.d.ts +59 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +69 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/interpreter.d.ts +88 -0
  40. package/dist/interpreter.d.ts.map +1 -0
  41. package/dist/interpreter.js +795 -0
  42. package/dist/interpreter.js.map +1 -0
  43. package/dist/lexer.d.ts +21 -0
  44. package/dist/lexer.d.ts.map +1 -0
  45. package/dist/lexer.js +175 -0
  46. package/dist/lexer.js.map +1 -0
  47. package/dist/parser.d.ts +60 -0
  48. package/dist/parser.d.ts.map +1 -0
  49. package/dist/parser.js +569 -0
  50. package/dist/parser.js.map +1 -0
  51. package/dist/robotics/actions.d.ts +13 -0
  52. package/dist/robotics/actions.d.ts.map +1 -0
  53. package/dist/robotics/actions.js +38 -0
  54. package/dist/robotics/actions.js.map +1 -0
  55. package/dist/robotics/contracts.d.ts +88 -0
  56. package/dist/robotics/contracts.d.ts.map +1 -0
  57. package/dist/robotics/contracts.js +31 -0
  58. package/dist/robotics/contracts.js.map +1 -0
  59. package/dist/robotics/index.d.ts +21 -0
  60. package/dist/robotics/index.d.ts.map +1 -0
  61. package/dist/robotics/index.js +19 -0
  62. package/dist/robotics/index.js.map +1 -0
  63. package/dist/robotics/motion.d.ts +139 -0
  64. package/dist/robotics/motion.d.ts.map +1 -0
  65. package/dist/robotics/motion.js +248 -0
  66. package/dist/robotics/motion.js.map +1 -0
  67. package/dist/robotics/sensors.d.ts +15 -0
  68. package/dist/robotics/sensors.d.ts.map +1 -0
  69. package/dist/robotics/sensors.js +12 -0
  70. package/dist/robotics/sensors.js.map +1 -0
  71. package/dist/robotics/snapshot.d.ts +12 -0
  72. package/dist/robotics/snapshot.d.ts.map +1 -0
  73. package/dist/robotics/snapshot.js +9 -0
  74. package/dist/robotics/snapshot.js.map +1 -0
  75. package/dist/robotics/types.d.ts +12 -0
  76. package/dist/robotics/types.d.ts.map +1 -0
  77. package/dist/robotics/types.js +9 -0
  78. package/dist/robotics/types.js.map +1 -0
  79. package/dist/runtime.d.ts +81 -0
  80. package/dist/runtime.d.ts.map +1 -0
  81. package/dist/runtime.js +150 -0
  82. package/dist/runtime.js.map +1 -0
  83. package/dist/tokens.d.ts +33 -0
  84. package/dist/tokens.d.ts.map +1 -0
  85. package/dist/tokens.js +97 -0
  86. package/dist/tokens.js.map +1 -0
  87. package/dist/version.d.ts +14 -0
  88. package/dist/version.d.ts.map +1 -0
  89. package/dist/version.js +6 -0
  90. package/dist/version.js.map +1 -0
  91. package/package.json +49 -0
@@ -0,0 +1,795 @@
1
+ import { ACTION_PT } from './tokens.js';
2
+ import { MotionState, motionCapabilityError, ENCODER_SENSORS, HEADING_SENSORS } from './robotics/motion.js';
3
+ import { RuntimeError } from './errors.js';
4
+ export { RuntimeError } from './errors.js';
5
+ class ReturnSignal {
6
+ constructor(value) {
7
+ Object.defineProperty(this, "value", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: value
12
+ });
13
+ }
14
+ }
15
+ function isNum(v) {
16
+ return typeof v === 'number';
17
+ }
18
+ function truthy(v) {
19
+ if (typeof v === 'boolean')
20
+ return v;
21
+ if (typeof v === 'number')
22
+ return v !== 0;
23
+ if (typeof v === 'string')
24
+ return v.length > 0;
25
+ return true;
26
+ }
27
+ const BUILTINS = {
28
+ abs: { __builtin: true, name: 'abs', arity: 1, impl: ([a]) => Math.abs(expectNum(a, 'ABS')) },
29
+ min: { __builtin: true, name: 'min', arity: 2, impl: ([a, b]) => Math.min(expectNum(a, 'MIN'), expectNum(b, 'MIN')) },
30
+ max: { __builtin: true, name: 'max', arity: 2, impl: ([a, b]) => Math.max(expectNum(a, 'MAX'), expectNum(b, 'MAX')) }
31
+ };
32
+ function expectNum(v, ctx) {
33
+ if (!isNum(v))
34
+ throw new RuntimeError(0, `"${ctx}" só funciona com números.`);
35
+ return v;
36
+ }
37
+ function clamp1(v) {
38
+ return Math.min(1, Math.max(-1, v));
39
+ }
40
+ /** Diferença angular em graus, normalizada para -180..180. */
41
+ function normDeg(d) {
42
+ let x = d;
43
+ while (x > 180)
44
+ x -= 360;
45
+ while (x < -180)
46
+ x += 360;
47
+ return x;
48
+ }
49
+ class Env {
50
+ constructor(vars = new Map(), parent = null) {
51
+ Object.defineProperty(this, "vars", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: vars
56
+ });
57
+ Object.defineProperty(this, "parent", {
58
+ enumerable: true,
59
+ configurable: true,
60
+ writable: true,
61
+ value: parent
62
+ });
63
+ }
64
+ get(name, line) {
65
+ if (this.vars.has(name))
66
+ return this.vars.get(name);
67
+ if (this.parent)
68
+ return this.parent.get(name, line);
69
+ throw new RuntimeError(line, `Não conheço "${name}" — crie antes com ${name} = valor, ou confira o nome do sensor.`);
70
+ }
71
+ /** Atribui onde a variável já existe (escopo), senão cria no global. */
72
+ set(name, value) {
73
+ let env = this;
74
+ while (env) {
75
+ if (env.vars.has(name)) {
76
+ env.vars.set(name, value);
77
+ return;
78
+ }
79
+ env = env.parent;
80
+ }
81
+ // nova: cria no global (persiste entre ciclos — memória do robô!)
82
+ let root = this;
83
+ while (root.parent)
84
+ root = root.parent;
85
+ root.vars.set(name, value);
86
+ }
87
+ define(name, value) {
88
+ this.vars.set(name, value);
89
+ }
90
+ }
91
+ /**
92
+ * Intérprete por árvore (tree-walk). Cada `run()` = 1 ciclo:
93
+ * foto dos sensores → (avalia o relógio do movimento) → executa o programa
94
+ * com combustível → ações saem na hora pelo hardware. Variáveis do aluno
95
+ * sobrevivem entre ciclos.
96
+ *
97
+ * **Movimentos temporais (§6)** tornam o programa um gerador: ao encontrar
98
+ * `ANDAR_POR`/`ESPERAR` o interpretador SUSPENDE a execução e guarda o
99
+ * ponto exato (dentro de `SE`, `REPETIR`, função…). Nos ciclos seguintes ele
100
+ * só soma `dt` no relógio — quando o tempo vence, conclui o movimento e
101
+ * RETOMA de onde parou. Nada bloqueia: `sleep`/`setTimeout`/`await` não
102
+ * existem aqui.
103
+ */
104
+ export class Interpreter {
105
+ constructor() {
106
+ Object.defineProperty(this, "globals", {
107
+ enumerable: true,
108
+ configurable: true,
109
+ writable: true,
110
+ value: new Env()
111
+ });
112
+ Object.defineProperty(this, "fuel", {
113
+ enumerable: true,
114
+ configurable: true,
115
+ writable: true,
116
+ value: 0
117
+ });
118
+ /** Execução suspensa entre ciclos (null = programa parou no topo). */
119
+ Object.defineProperty(this, "gen", {
120
+ enumerable: true,
121
+ configurable: true,
122
+ writable: true,
123
+ value: null
124
+ });
125
+ Object.defineProperty(this, "motion", {
126
+ enumerable: true,
127
+ configurable: true,
128
+ writable: true,
129
+ value: new MotionState()
130
+ });
131
+ Object.defineProperty(this, "caps", {
132
+ enumerable: true,
133
+ configurable: true,
134
+ writable: true,
135
+ value: null
136
+ });
137
+ Object.defineProperty(this, "dt", {
138
+ enumerable: true,
139
+ configurable: true,
140
+ writable: true,
141
+ value: 1 / 120
142
+ });
143
+ /** Linha do movimento em curso (p/ erros de sensor ausente). */
144
+ Object.defineProperty(this, "pendingLine", {
145
+ enumerable: true,
146
+ configurable: true,
147
+ writable: true,
148
+ value: 1
149
+ });
150
+ Object.defineProperty(this, "steps", {
151
+ enumerable: true,
152
+ configurable: true,
153
+ writable: true,
154
+ value: 0
155
+ });
156
+ Object.defineProperty(this, "snapshot", {
157
+ enumerable: true,
158
+ configurable: true,
159
+ writable: true,
160
+ value: {}
161
+ });
162
+ }
163
+ /** Limpa a memória do aluno e a execução em curso (recomeçar). */
164
+ reset() {
165
+ this.globals = new Env();
166
+ this.gen = null;
167
+ this.motion.reset();
168
+ }
169
+ run(program, hw, fuel = 5000, opts) {
170
+ this.fuel = fuel;
171
+ this.caps = opts?.capabilities ?? null;
172
+ this.dt = typeof opts?.dt === 'number' && opts.dt > 0 ? opts.dt : 1 / 120;
173
+ this.steps = 0;
174
+ try {
175
+ // Foto dos sensores (somente leitura neste ciclo). `for-in` em vez de
176
+ // `Object.entries`: evita 1 array + N pares por ciclo (GC); o snapshot
177
+ // é objeto literal novo, então a enumeração é equivalente.
178
+ const snap = hw.sensors();
179
+ this.snapshot = snap;
180
+ for (const k in snap) {
181
+ this.globals.define(k.toLowerCase(), snap[k]);
182
+ }
183
+ this.motion.justCompleted = false;
184
+ // Relógio do movimento temporal/espacial (§6/§8) — feito ANTES de
185
+ // retomar o programa, para `movimento_concluido` valer neste ciclo.
186
+ if (this.motion.pending) {
187
+ const p = this.motion.pending;
188
+ if (p.mode === 'timed') {
189
+ const finished = this.motion.tickTimed(this.dt);
190
+ if (this.motion.pending) {
191
+ this.injectMotionState();
192
+ return { ok: true, steps: this.steps, suspended: true };
193
+ }
194
+ if (finished)
195
+ this.finishTimed(finished, hw);
196
+ }
197
+ else {
198
+ const done = this.advanceSpatial(p, hw);
199
+ if (this.motion.pending) {
200
+ this.injectMotionState();
201
+ return { ok: true, steps: this.steps, suspended: true };
202
+ }
203
+ if (done) {
204
+ hw.stop();
205
+ this.motion.noteStopped();
206
+ }
207
+ }
208
+ }
209
+ this.injectMotionState();
210
+ if (!this.gen)
211
+ this.gen = this.execList(program, this.globals, hw);
212
+ const r = this.gen.next();
213
+ if (r.done)
214
+ this.gen = null;
215
+ return { ok: true, steps: this.steps, suspended: !r.done };
216
+ }
217
+ catch (e) {
218
+ // Erro fatal do ciclo: solta a execução; o próximo ciclo recomeça
219
+ // do topo (e o chamador trava a mensagem até novo código).
220
+ this.gen = null;
221
+ this.motion.reset();
222
+ if (e instanceof RuntimeError) {
223
+ return { ok: false, error: { line: e.line, col: 1, message: e.message }, steps: this.steps };
224
+ }
225
+ if (e instanceof ReturnSignal) {
226
+ return {
227
+ ok: false,
228
+ error: { line: 1, col: 1, message: 'RETORNAR só pode ser usado dentro de FUNCAO.' },
229
+ steps: this.steps
230
+ };
231
+ }
232
+ throw e;
233
+ }
234
+ }
235
+ /** Sensores genéricos de estado de movimento (§14). */
236
+ injectMotionState() {
237
+ const m = this.motion;
238
+ const g = this.globals;
239
+ const driving = m.isDriving();
240
+ const stopped = m.isStopped();
241
+ const turning = m.isTurning();
242
+ const active = m.pending !== null || !m.isStopped();
243
+ g.define('esta_andando', driving);
244
+ g.define('is_moving', driving);
245
+ g.define('esta_parado', stopped);
246
+ g.define('is_stopped', stopped);
247
+ g.define('esta_girando', turning);
248
+ g.define('is_turning', turning);
249
+ g.define('movimento_ativo', active);
250
+ g.define('motion_active', active);
251
+ g.define('movimento_concluido', m.justCompleted);
252
+ g.define('motion_done', m.justCompleted);
253
+ }
254
+ /** Conclusão de um temporal: para o robô (§6 — "executar PARAR"). */
255
+ finishTimed(p, hw) {
256
+ if (p.kind === 'drive' || p.kind === 'reverse' || p.kind === 'turn') {
257
+ hw.stop();
258
+ this.motion.noteStopped();
259
+ }
260
+ }
261
+ /** Lê um sensor numérico do snapshot atual, na ordem de preferência. */
262
+ readSensorNum(names) {
263
+ for (const n of names) {
264
+ const v = this.snapshot[n];
265
+ if (typeof v === 'number')
266
+ return v;
267
+ }
268
+ return null;
269
+ }
270
+ /**
271
+ * Avalia a meta de um comando espacial. `true` = meta atingida.
272
+ * Sem leitura real o movimento é ABORTADO com erro — o H++ nunca finge
273
+ * que uma distância/ângulo foi alcançado (§8).
274
+ */
275
+ advanceSpatial(p, _hw) {
276
+ const reading = this.readSensorNum(p.sensors);
277
+ if (reading === null) {
278
+ this.motion.abortSpatial();
279
+ const what = p.kind === 'distance' ? 'distância' : 'ângulo';
280
+ throw new RuntimeError(this.pendingLine, `"${p.label}" precisa do sensor ${p.sensors.map((s) => `"${s}"`).join(' ou ')} ` +
281
+ `para medir a ${what}. Sem odometria real o H++ não finge que chegou.`);
282
+ }
283
+ if (p.kind === 'distance') {
284
+ p.accum = Math.abs(reading - p.start);
285
+ }
286
+ else {
287
+ p.accum += Math.abs(normDeg(reading - p.prev));
288
+ p.prev = reading;
289
+ }
290
+ if (p.accum + 1e-9 >= p.target) {
291
+ this.motion.finishSpatial();
292
+ return true;
293
+ }
294
+ return false;
295
+ }
296
+ burn(line) {
297
+ if (--this.fuel <= 0) {
298
+ throw new RuntimeError(line, 'Seu programa cansou (passou do limite do ciclo)! Dica: troque ENQUANTO infinito por SEMPRE … FIM, que roda um pouco a cada ciclo.');
299
+ }
300
+ }
301
+ // ---- comandos (geradores: podem suspender o programa) ----
302
+ *execList(list, env, hw) {
303
+ for (const s of list)
304
+ yield* this.execStmt(s, env, hw);
305
+ }
306
+ *execStmt(s, env, hw) {
307
+ this.burn(s.line);
308
+ this.steps++;
309
+ switch (s.kind) {
310
+ case 'assign': {
311
+ const v = yield* this.evalExpr(s.expr, env, hw);
312
+ if (v !== undefined && (typeof v === 'object' && v !== null && '__fn' in v)) {
313
+ throw new RuntimeError(s.line, 'Não dá para guardar função em variável (ainda). Chame direto: nome().');
314
+ }
315
+ env.set(s.name, v);
316
+ return;
317
+ }
318
+ case 'expr':
319
+ yield* this.evalExpr(s.expr, env, hw);
320
+ return;
321
+ case 'action': {
322
+ const args = [];
323
+ for (const a of s.args)
324
+ args.push(yield* this.evalExpr(a, env, hw));
325
+ yield* this.execAction(s.name, args, s.line, hw);
326
+ return;
327
+ }
328
+ case 'if': {
329
+ const c = yield* this.evalExpr(s.cond, env, hw);
330
+ const branch = truthy(c) ? s.then : s.otherwise;
331
+ yield* this.execList(branch, env, hw);
332
+ return;
333
+ }
334
+ case 'while': {
335
+ let guard = 0;
336
+ while (truthy(yield* this.evalExpr(s.cond, env, hw))) {
337
+ if (++guard > 100000)
338
+ throw new RuntimeError(s.line, 'ENQUANTO rodou demais neste ciclo.');
339
+ yield* this.execList(s.body, env, hw);
340
+ }
341
+ return;
342
+ }
343
+ case 'repeat': {
344
+ const n = yield* this.evalExpr(s.count, env, hw);
345
+ if (!isNum(n))
346
+ throw new RuntimeError(s.line, 'REPETIR precisa de número. Ex.: REPETIR 3 VEZES … FIM');
347
+ const times = Math.max(0, Math.floor(n));
348
+ for (let i = 0; i < times; i++) {
349
+ yield* this.execList(s.body, env, hw);
350
+ }
351
+ return;
352
+ }
353
+ case 'for': {
354
+ const from = yield* this.evalExpr(s.from, env, hw);
355
+ const to = yield* this.evalExpr(s.to, env, hw);
356
+ if (!isNum(from) || !isNum(to)) {
357
+ throw new RuntimeError(s.line, 'PARA precisa de números. Ex.: PARA i DE 1 ATE 5 … FIM');
358
+ }
359
+ let step = 1;
360
+ if (s.step) {
361
+ const st = yield* this.evalExpr(s.step, env, hw);
362
+ if (!isNum(st) || st === 0)
363
+ throw new RuntimeError(s.line, 'PASSO precisa ser número diferente de 0.');
364
+ step = st;
365
+ }
366
+ const frame = new Env(new Map(), env);
367
+ if (step > 0) {
368
+ for (let i = from; i <= to; i += step) {
369
+ frame.define(s.name, i);
370
+ yield* this.execList(s.body, frame, hw);
371
+ }
372
+ }
373
+ else {
374
+ for (let i = from; i >= to; i += step) {
375
+ frame.define(s.name, i);
376
+ yield* this.execList(s.body, frame, hw);
377
+ }
378
+ }
379
+ return;
380
+ }
381
+ case 'func':
382
+ env.define(s.name, { __fn: true, name: s.name, params: s.params, body: s.body });
383
+ return;
384
+ case 'return': {
385
+ const v = s.expr ? (yield* this.evalExpr(s.expr, env, hw)) : 0;
386
+ throw new ReturnSignal(v);
387
+ }
388
+ case 'always':
389
+ yield* this.execList(s.body, env, hw);
390
+ return;
391
+ }
392
+ }
393
+ *execAction(name, args, line, hw) {
394
+ // Checagem de capability em runtime (§21): só quando o chamador declarou
395
+ // capabilities (HppRuntime sempre declara; núcleo puro não checa).
396
+ if (this.caps) {
397
+ const capErr = motionCapabilityError(name, this.caps);
398
+ if (capErr)
399
+ throw new RuntimeError(line, capErr);
400
+ }
401
+ const num = (v, cmd) => {
402
+ if (!isNum(v))
403
+ throw new RuntimeError(line, `"${cmd}" precisa de número entre -1 e 1. Ex.: ${cmd} 0.5`);
404
+ return clamp1(v);
405
+ };
406
+ const raw = (v, cmd) => {
407
+ if (!isNum(v))
408
+ throw new RuntimeError(line, `"${cmd}" precisa de número. Ex.: ${cmd} 1`);
409
+ return v;
410
+ };
411
+ const seconds = (v, cmd) => {
412
+ const n = raw(v, cmd);
413
+ if (n < 0)
414
+ throw new RuntimeError(line, `"${cmd}" precisa de tempo maior ou igual a 0 (segundos).`);
415
+ return n;
416
+ };
417
+ switch (name) {
418
+ // ---- movimento básico ----
419
+ case 'drive': {
420
+ const v = num(args[0], 'ANDAR');
421
+ hw.drive(v);
422
+ this.motion.noteDrive(v);
423
+ return;
424
+ }
425
+ case 'reverse': {
426
+ const v = num(args[0], 'VOLTAR');
427
+ hw.drive(-v); // semanticamente drive(-v): sem física duplicada (§5.2)
428
+ this.motion.noteDrive(-v);
429
+ return;
430
+ }
431
+ case 'stop':
432
+ hw.stop();
433
+ this.motion.noteStopped();
434
+ return;
435
+ case 'turn': {
436
+ const v = num(args[0], 'GIRAR');
437
+ hw.turn(v);
438
+ this.motion.noteTurn(v);
439
+ return;
440
+ }
441
+ case 'turnLeft': {
442
+ const v = num(args[0], 'VIRAR_ESQUERDA');
443
+ hw.turn(v); // convenção do projeto: GIRAR positivo = esquerda
444
+ this.motion.noteTurn(v);
445
+ return;
446
+ }
447
+ case 'turnRight': {
448
+ const v = num(args[0], 'VIRAR_DIREITA');
449
+ hw.turn(-v);
450
+ this.motion.noteTurn(-v);
451
+ return;
452
+ }
453
+ // ---- movimento temporal (não bloqueante) ----
454
+ case 'driveFor': {
455
+ const v = num(args[0], 'ANDAR_POR');
456
+ const t = seconds(args[1], 'ANDAR_POR');
457
+ if (t <= 0)
458
+ return;
459
+ hw.drive(v);
460
+ this.motion.beginTimed('drive', v, t);
461
+ this.pendingLine = line;
462
+ yield;
463
+ return;
464
+ }
465
+ case 'reverseFor': {
466
+ const v = num(args[0], 'VOLTAR_POR');
467
+ const t = seconds(args[1], 'VOLTAR_POR');
468
+ if (t <= 0)
469
+ return;
470
+ hw.drive(-v);
471
+ this.motion.beginTimed('reverse', -v, t);
472
+ this.pendingLine = line;
473
+ yield;
474
+ return;
475
+ }
476
+ case 'turnFor': {
477
+ const v = num(args[0], 'GIRAR_POR');
478
+ const t = seconds(args[1], 'GIRAR_POR');
479
+ if (t <= 0)
480
+ return;
481
+ hw.turn(v);
482
+ this.motion.beginTimed('turn', v, t);
483
+ this.pendingLine = line;
484
+ yield;
485
+ return;
486
+ }
487
+ case 'stopFor': {
488
+ const t = seconds(args[0], 'PARAR_POR');
489
+ hw.stop();
490
+ this.motion.noteStopped();
491
+ if (t <= 0)
492
+ return;
493
+ this.motion.beginTimed('stop', 0, t);
494
+ this.pendingLine = line;
495
+ yield;
496
+ return;
497
+ }
498
+ case 'wait': {
499
+ const t = seconds(args[0], 'ESPERAR');
500
+ if (t <= 0)
501
+ return;
502
+ this.motion.beginTimed('wait', 0, t);
503
+ this.pendingLine = line;
504
+ yield;
505
+ return;
506
+ }
507
+ // ---- movimento espacial (capability + leitura real) ----
508
+ case 'driveMeters':
509
+ case 'reverseMeters': {
510
+ const label = ACTION_PT[name];
511
+ const meters = raw(args[0], label);
512
+ if (meters < 0) {
513
+ throw new RuntimeError(line, `"${label}" precisa de distância maior ou igual a 0 (metros).`);
514
+ }
515
+ const encoder = this.readSensorNum(ENCODER_SENSORS);
516
+ if (encoder === null) {
517
+ throw new RuntimeError(line, `"${label}" precisa do sensor ${ENCODER_SENSORS.map((s) => `"${s}"`).join(' ou ')} ` +
518
+ 'para medir a distância. Sem odometria real o H++ não finge que chegou.');
519
+ }
520
+ if (meters === 0)
521
+ return;
522
+ const forward = name === 'driveMeters';
523
+ const drive = forward ? 1 : -1;
524
+ hw.drive(drive);
525
+ this.motion.noteDrive(drive);
526
+ this.motion.beginSpatial({
527
+ action: name,
528
+ label,
529
+ sensors: [...ENCODER_SENSORS],
530
+ kind: 'distance',
531
+ start: encoder,
532
+ prev: encoder,
533
+ accum: 0,
534
+ target: meters,
535
+ drive,
536
+ turn: 0
537
+ });
538
+ this.pendingLine = line;
539
+ yield;
540
+ return;
541
+ }
542
+ case 'turnDegrees': {
543
+ const label = ACTION_PT.turnDegrees;
544
+ const deg = raw(args[0], label);
545
+ const heading = this.readSensorNum(HEADING_SENSORS);
546
+ if (heading === null) {
547
+ throw new RuntimeError(line, `"${label}" precisa do sensor ${HEADING_SENSORS.map((s) => `"${s}"`).join(' ou ')} ` +
548
+ 'para saber quando parou de girar. Sem bússola real o H++ não finge que chegou.');
549
+ }
550
+ const target = Math.abs(deg);
551
+ if (target === 0)
552
+ return;
553
+ const steer = deg > 0 ? 1 : -1;
554
+ hw.turn(steer);
555
+ this.motion.noteTurn(steer);
556
+ this.motion.beginSpatial({
557
+ action: name,
558
+ label,
559
+ sensors: [...HEADING_SENSORS],
560
+ kind: 'degrees',
561
+ start: heading,
562
+ prev: heading,
563
+ accum: 0,
564
+ target,
565
+ drive: 0,
566
+ turn: steer
567
+ });
568
+ this.pendingLine = line;
569
+ yield;
570
+ return;
571
+ }
572
+ // ---- controle de motores (capability obrigatória) ----
573
+ case 'motor': {
574
+ const id = Math.max(0, Math.floor(raw(args[0], 'MOTOR')));
575
+ const v = num(args[1], 'MOTOR');
576
+ if (!hw.motor)
577
+ throw new RuntimeError(line, '"MOTOR" não está implementado neste hardware.');
578
+ hw.motor(id, v);
579
+ return;
580
+ }
581
+ case 'motorLeft': {
582
+ const v = num(args[0], 'MOTOR_ESQUERDO');
583
+ if (!hw.motorLeft) {
584
+ throw new RuntimeError(line, '"MOTOR_ESQUERDO" não está implementado neste hardware.');
585
+ }
586
+ hw.motorLeft(v);
587
+ this.motion.noteMotorLeft(v);
588
+ return;
589
+ }
590
+ case 'motorRight': {
591
+ const v = num(args[0], 'MOTOR_DIREITO');
592
+ if (!hw.motorRight) {
593
+ throw new RuntimeError(line, '"MOTOR_DIREITO" não está implementado neste hardware.');
594
+ }
595
+ hw.motorRight(v);
596
+ this.motion.noteMotorRight(v);
597
+ return;
598
+ }
599
+ case 'motors': {
600
+ const l = num(args[0], 'MOTORES');
601
+ const r = num(args[1], 'MOTORES');
602
+ if (!hw.motors)
603
+ throw new RuntimeError(line, '"MOTORES" não está implementado neste hardware.');
604
+ hw.motors(l, r);
605
+ this.motion.noteMotors(l, r);
606
+ return;
607
+ }
608
+ // ---- movimento avançado ----
609
+ case 'curve': {
610
+ const v = num(args[0], 'CURVA');
611
+ const d = num(args[1], 'CURVA');
612
+ // throttle + steer: a mesma física de ANDAR/GIRAR, sem duplicar (§13)
613
+ hw.drive(v);
614
+ hw.turn(d);
615
+ this.motion.noteDrive(v);
616
+ this.motion.noteTurn(d);
617
+ return;
618
+ }
619
+ case 'moveLateral': {
620
+ const v = num(args[0], 'MOVER_LATERAL');
621
+ if (!hw.moveLateral) {
622
+ throw new RuntimeError(line, '"MOVER_LATERAL" não está implementado neste hardware.');
623
+ }
624
+ hw.moveLateral(v);
625
+ this.motion.noteLateral(v);
626
+ return;
627
+ }
628
+ case 'moveXY': {
629
+ const vx = num(args[0], 'MOVER_XY');
630
+ const vy = num(args[1], 'MOVER_XY');
631
+ if (!hw.moveXY)
632
+ throw new RuntimeError(line, '"MOVER_XY" não está implementado neste hardware.');
633
+ hw.moveXY(vx, vy);
634
+ this.motion.noteDrive(vx);
635
+ this.motion.noteLateral(vy);
636
+ return;
637
+ }
638
+ // ---- domínio de extensão (§16 — continuam na extensão) ----
639
+ case 'kick':
640
+ hw.fire();
641
+ return;
642
+ case 'aimBall':
643
+ hw.aimMain();
644
+ return;
645
+ case 'aimGoal':
646
+ hw.aimSecondary();
647
+ return;
648
+ case 'radioSend':
649
+ if (!isNum(args[0]))
650
+ throw new RuntimeError(line, `"ENVIAR_RADIO" precisa de um número.`);
651
+ hw.radioSend(args[0]);
652
+ return;
653
+ case 'dribble':
654
+ if (!isNum(args[0]))
655
+ throw new RuntimeError(line, `"DRIBLAR" precisa de número: 0 desliga, outro valor liga.`);
656
+ hw.dribble(args[0] !== 0);
657
+ return;
658
+ }
659
+ }
660
+ // ---- contas ----
661
+ *evalExpr(e, env, hw) {
662
+ this.burn(e.line);
663
+ switch (e.kind) {
664
+ case 'num':
665
+ case 'str':
666
+ case 'bool':
667
+ return e.value;
668
+ case 'var':
669
+ return env.get(e.name, e.line);
670
+ case 'unary': {
671
+ const v = yield* this.evalExpr(e.expr, env, hw);
672
+ if (e.op === '-') {
673
+ if (!isNum(v))
674
+ throw new RuntimeError(e.line, 'Sinal de menos só funciona com número.');
675
+ return -v;
676
+ }
677
+ return !truthy(v);
678
+ }
679
+ case 'binary':
680
+ return yield* this.evalBinary(e.op, e.left, e.right, env, hw, e.line);
681
+ case 'call': {
682
+ const args = [];
683
+ for (const a of e.args)
684
+ args.push(yield* this.evalExpr(a, env, hw));
685
+ const builtin = BUILTINS[e.name];
686
+ if (builtin) {
687
+ if (args.length !== builtin.arity) {
688
+ throw new RuntimeError(e.line, `"${e.name.toUpperCase()}" precisa de ${builtin.arity} número(s).`);
689
+ }
690
+ try {
691
+ return builtin.impl(args);
692
+ }
693
+ catch (err) {
694
+ if (err instanceof RuntimeError && err.line === 0)
695
+ throw new RuntimeError(e.line, err.message);
696
+ throw err;
697
+ }
698
+ }
699
+ let fn;
700
+ try {
701
+ fn = env.get(e.name, e.line);
702
+ }
703
+ catch {
704
+ throw new RuntimeError(e.line, `Não conheço "${e.name}" — é FUNCAO, sensor ou variável?`);
705
+ }
706
+ if (typeof fn !== 'object' || fn === null || !('__fn' in fn)) {
707
+ throw new RuntimeError(e.line, `"${e.name}" não é uma FUNCAO que dá para chamar.`);
708
+ }
709
+ const f = fn;
710
+ if (args.length !== f.params.length) {
711
+ throw new RuntimeError(e.line, `"${e.name}" espera ${f.params.length} valor(es), mas recebeu ${args.length}.`);
712
+ }
713
+ const frame = new Env(new Map(), this.globals);
714
+ f.params.forEach((p, i) => frame.define(p, args[i]));
715
+ try {
716
+ yield* this.execList(f.body, frame, hw);
717
+ }
718
+ catch (err) {
719
+ if (err instanceof ReturnSignal)
720
+ return err.value;
721
+ throw err;
722
+ }
723
+ return 0;
724
+ }
725
+ }
726
+ }
727
+ *evalBinary(op, l, r, env, hw, line) {
728
+ // E/OU com curto-circuito
729
+ if (op === 'AND') {
730
+ const lv = yield* this.evalExpr(l, env, hw);
731
+ if (!truthy(lv))
732
+ return false;
733
+ return truthy(yield* this.evalExpr(r, env, hw));
734
+ }
735
+ if (op === 'OR') {
736
+ const lv = yield* this.evalExpr(l, env, hw);
737
+ if (truthy(lv))
738
+ return true;
739
+ return truthy(yield* this.evalExpr(r, env, hw));
740
+ }
741
+ const lv = yield* this.evalExpr(l, env, hw);
742
+ const rv = yield* this.evalExpr(r, env, hw);
743
+ switch (op) {
744
+ case '+':
745
+ if (typeof lv === 'string' || typeof rv === 'string')
746
+ return String(lv) + String(rv);
747
+ if (isNum(lv) && isNum(rv))
748
+ return lv + rv;
749
+ throw new RuntimeError(line, 'Só dá para somar número com número (ou juntar textos).');
750
+ case '-':
751
+ case '*':
752
+ case '/':
753
+ case '%':
754
+ case '^': {
755
+ if (!isNum(lv) || !isNum(rv)) {
756
+ throw new RuntimeError(line, 'Conta só funciona com números.');
757
+ }
758
+ if (op === '-')
759
+ return lv - rv;
760
+ if (op === '*')
761
+ return lv * rv;
762
+ if (op === '/') {
763
+ if (rv === 0)
764
+ throw new RuntimeError(line, 'Divisão por zero não vale!');
765
+ return lv / rv;
766
+ }
767
+ if (op === '%')
768
+ return lv % rv;
769
+ return Math.pow(lv, rv);
770
+ }
771
+ case '==':
772
+ return lv === rv;
773
+ case '!=':
774
+ return lv !== rv;
775
+ case '<':
776
+ case '<=':
777
+ case '>':
778
+ case '>=': {
779
+ if ((typeof lv === 'string' || typeof rv === 'string') && !(typeof lv === 'string' && typeof rv === 'string')) {
780
+ throw new RuntimeError(line, 'Comparação de ordem só com números (ou texto com texto).');
781
+ }
782
+ if (op === '<')
783
+ return lv < rv;
784
+ if (op === '<=')
785
+ return lv <= rv;
786
+ if (op === '>')
787
+ return lv > rv;
788
+ return lv >= rv;
789
+ }
790
+ default:
791
+ throw new RuntimeError(line, `Operação "${op}" desconhecida.`);
792
+ }
793
+ }
794
+ }
795
+ //# sourceMappingURL=interpreter.js.map