Skip to content

Commit cb3dd31

Browse files
CrispStrobeclaude
andcommitted
Sync: pluggable runtime-driver convention (gamepad + LEGO Boost)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 81df55f commit cb3dd31

3 files changed

Lines changed: 203 additions & 41 deletions

File tree

src/lib/sb3-creator-javascript.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Translator } from './sb3-creator-python.js';
1515
const JS_KEYWORDS = new Set(['function', 'if', 'else', 'while', 'for', 'return', 'let',
1616
'const', 'var', 'true', 'false', 'null', 'undefined', 'new', 'typeof', 'break', 'continue']);
1717

18-
const JS_OPS = ['===', '!==', '**', '==', '!=', '<=', '>=', '&&', '||', '++', '--',
18+
const JS_OPS = ['===', '!==', '=>', '**', '==', '!=', '<=', '>=', '&&', '||', '++', '--',
1919
'+=', '-=', '*=', '/=', '%=', '(', ')', '{', '}', '[', ']', ';', ',', '.',
2020
'+', '-', '*', '/', '%', '<', '>', '=', '!', '?', ':'];
2121

@@ -297,13 +297,36 @@ class JsParser {
297297
if (t.type === 'false') { this.next(); return { type: 'Const', value: false }; }
298298
if (t.type === 'null' || t.type === 'undefined') { this.next(); return { type: 'Const', value: null }; }
299299
if (t.type === 'NAME') { this.next(); return { type: 'Name', id: t.value }; }
300-
if (this.at('OP', '(')) { this.next(); const e = this.parseExpr(); this.eat('OP', ')'); return e; }
300+
if (this.at('OP', '(')) {
301+
// Distinguish a grouped expression from arrow-function params `(a, b) => …`.
302+
let j = this.p, depth = 0;
303+
for (; j < this.toks.length; j++) { const tk = this.toks[j]; if (tk.type === 'OP' && tk.value === '(') depth++; else if (tk.type === 'OP' && tk.value === ')') { depth--; if (depth === 0) break; } }
304+
const after = this.toks[j + 1];
305+
if (after && after.type === 'OP' && after.value === '=>') {
306+
this.p = j + 2; // consume `(...) =>`
307+
if (this.at('OP', '{')) this.skipBraces(); else this.parseExpr(); // arrow body (discarded)
308+
return { type: 'Const', value: null };
309+
}
310+
this.next(); const e = this.parseExpr(); this.eat('OP', ')'); return e;
311+
}
301312
if (this.at('OP', '[')) {
302313
this.next(); const elts = [];
303314
while (!this.at('OP', ']')) { elts.push(this.parseExpr()); if (this.at('OP', ',')) this.next(); }
304315
this.eat('OP', ']');
305316
return { type: 'List', elts };
306317
}
318+
// object literal (e.g. the generated `_arrays = {}` registry / driver shims) — not in
319+
// the subset; consume the balanced braces and return a placeholder so parsing survives.
320+
if (this.at('OP', '{')) { this.skipBraces(); return { type: 'Const', value: null }; }
321+
// inline function expression (e.g. `.reduce(function(s,x){...})`) — out of subset;
322+
// consume it raw so parsing survives (the enclosing op won't reverse-map anyway).
323+
if (t.type === 'function') {
324+
this.next();
325+
if (this.at('NAME')) this.next();
326+
if (this.at('OP', '(')) { let d = 0; do { if (this.at('OP', '(')) d++; else if (this.at('OP', ')')) d--; this.next(); } while (d > 0 && !this.at('EOF')); }
327+
if (this.at('OP', '{')) this.skipBraces();
328+
return { type: 'Const', value: null };
329+
}
307330
this.err(`unexpected ${t.type} ${t.value || ''}`);
308331
return null;
309332
}

src/lib/sb3-creator-python.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@ class Parser {
205205
if (t.type === 'def') return this.parseDef();
206206
if (t.type === 'import' || t.type === 'from') { this.skipToNewline(); return null; }
207207
if (t.type === 'global') { this.skipToNewline(); return null; }
208+
// Generated driver classes (`class _BoostDriver:`) are re-emitted — skip them.
209+
if (t.type === 'NAME' && t.value === 'class') {
210+
while (!this.at('OP', ':') && !this.at('EOF') && !this.at('NEWLINE')) this.next();
211+
this.skipSuite();
212+
return null;
213+
}
208214
return this.parseSimpleStatement();
209215
}
210216

@@ -357,9 +363,22 @@ class Parser {
357363
this.eat('OP', ']');
358364
return { type: 'List', elts };
359365
}
366+
// dict/set literal (e.g. the generated `_arrays = {}` registry) — not in the
367+
// subset; consume the balanced braces and return a placeholder so parsing survives.
368+
if (this.at('OP', '{')) { this.skipBalanced('{', '}'); return { type: 'Const', value: null }; }
360369
this.err(`unexpected ${t.type} ${t.value || ''}`);
361370
return null;
362371
}
372+
373+
skipBalanced (open, close) {
374+
this.eat('OP', open);
375+
let depth = 1;
376+
while (depth > 0 && !this.at('EOF')) {
377+
if (this.at('OP', open)) depth++;
378+
else if (this.at('OP', close)) depth--;
379+
this.next();
380+
}
381+
}
363382
}
364383

365384
// ---- Translator (AST -> pseudocode) ----------------------------------------------

0 commit comments

Comments
 (0)