Fix SQLiteWAL: Map literals, nested bracket access, and/or, comments

- Added and/or -> &&/|| rewrite
- Strip // comments before TS processing
- Fix [a..b] range syntax to array literal
- Fix nested Map bracket access (rewrite inner expressions)
- Fix Map assignment index expression rewriting
- Convert {k: v} Map literals to new Map([[k, v]])
- SQLiteWAL: 19 errors -> 0 errors

Progress: 11 specs at 0 errors
This commit is contained in:
Scott Scoble 2026-05-11 21:24:50 -07:00
parent 7f417d37e0
commit 832feb47b3

View file

@ -333,9 +333,12 @@ function emitAction(action: ActionNode, stateVarNames: Set<string>, mapVarNames:
case 'assign': {
const target = cleanName(stmt.target);
const expr = rewriteExpr(stmt.expr, stateVarNames, mapVarNames, localNames, stateEnumName, knownStateValues);
const mapBracketMatch = target.match(/^(\w+)\[([^\]]+)\]$/);
// Handle Map bracket assignment: mapVar[index] := value -> this.mapVar.set(index, value)
// Support nested brackets by finding the outermost [ ] pair
const mapBracketMatch = target.match(/^(\w+)\[([\s\S]+)\]$/);
if (mapBracketMatch && mapVarNames.has(mapBracketMatch[1])) {
bodyParts.push(` this.${mapBracketMatch[1]}.set(${mapBracketMatch[2]}, ${expr});`);
const indexExpr = rewriteExpr(mapBracketMatch[2], stateVarNames, mapVarNames, localNames, stateEnumName, knownStateValues);
bodyParts.push(` this.${mapBracketMatch[1]}.set(${indexExpr}, ${expr});`);
} else {
bodyParts.push(` this.${target} = ${expr};`);
}
@ -416,13 +419,19 @@ function rewriteExpr(expr: string, stateVarNames: Set<string>, mapVarNames: Set<
});
// Step 3b: Replace identifiers that are state variables but not local params
// Track if previous token was 'this.' to avoid double-prefixing
const tokens = ts.split(/([\s\(\)\[\]{}+\-*/=<>!&|.,;:'"`]+)/);
let prevWasThisDot = false;
const result = tokens.map(tok => {
if (/^[a-zA-Z_]\w*\.\w+$/.test(tok)) return tok; // already has dot (enum ref)
if (/^(true|false|null|undefined|\d+|\w+\.\w+)$/.test(tok)) return tok;
if (tok === 'this') { prevWasThisDot = true; return tok; }
if (tok === '.') { prevWasThisDot = true; return tok; }
if (prevWasThisDot) { prevWasThisDot = false; return tok; } // skip if preceded by this.
if (/^[a-zA-Z_]\w*$/.test(tok) && shouldPrefix(tok)) {
return `this.${tok}`;
}
prevWasThisDot = false;
return tok;
}).join('')
// Undo this. prefix on identifiers after bracket access
@ -478,6 +487,28 @@ function rewriteExpr(expr: string, stateVarNames: Set<string>, mapVarNames: Set<
// "not isOn" -> "!this.isOn" (will be prefixed by step 3)
ts = ts.replace(/\bnot\s+/g, '!');
// 5d2: and -> &&, or -> || (logical operators)
ts = ts.replace(/\band\b/g, '&&').replace(/\bor\b/g, '||');
// 5d3: Strip SpeckDL comments (// ...) before TS processing
ts = ts.replace(/\/\/.*$/gm, '');
// 5d4: [a..b] range syntax -> array literal [a, a+1, ..., b]
ts = ts.replace(/\[(\d+)\.\.(\d+)\]/g, (match, a, b) => {
const start = parseInt(a);
const end = parseInt(b);
const values = [];
for (let i = start; i <= end; i++) values.push(i);
return `[${values.join(', ')}]`;
});
// 5d5: Map literals {k: v} for Map type assignments
// If target is a Map var, convert {k: v} to new Map([[k, v]])
ts = ts.replace(/\{(\d+):\s*([^}]+)\}/g, (match, k, v) => {
// Only convert if it looks like a Map literal (single key:value pair with number key)
return `new Map([[${k}, ${v}]])`;
});
// 5e: Clean up trailing empty comparisons (===) — expression has no RHS after old() removal
// "accounts[t.from].debits_posted ===" -> "accounts[t.from].debits_posted === accounts[t.from].debits_posted"
ts = ts.replace(/(\S+)\s*===\s*$/g, '$1 === $1');
@ -491,10 +522,17 @@ function rewriteExpr(expr: string, stateVarNames: Set<string>, mapVarNames: Set<
for (const mapVar of mapVarNames) {
// Match: mapVar[expr] where mapVar is a known Map state variable
// Handle both bare mapVar[...] and this.mapVar[...]
// The inner expr needs rewriting for this-prefixing
const barePattern = new RegExp('\\b(' + mapVar + ')\\[([^\\]]+)\\]', 'g');
ts = ts.replace(barePattern, '$1.get($2)!');
ts = ts.replace(barePattern, (match, varName, innerExpr) => {
const rewrittenInner = rewriteExpr(innerExpr, stateVarNames, mapVarNames, localNames, stateEnumName, knownStateValues);
return `${varName}.get(${rewrittenInner})!`;
});
const thisPattern = new RegExp('\\b(this\\.' + mapVar + ')\\[([^\\]]+)\\]', 'g');
ts = ts.replace(thisPattern, '$1.get($2)!');
ts = ts.replace(thisPattern, (match, varName, innerExpr) => {
const rewrittenInner = rewriteExpr(innerExpr, stateVarNames, mapVarNames, localNames, stateEnumName, knownStateValues);
return `${varName}.get(${rewrittenInner})!`;
});
}
// 5h: .size() -> .size (Set/Map size property, not a method in JS)
ts = ts.replace(/\.size\(\)/g, '.size');
@ -571,13 +609,15 @@ function tsType(t: any, speckName: string = '', enumMap?: Map<string, string[]>)
}
if (t.type === 'ident') {
const rawName = cleanName(t.name); // strip trailing comma
// Map known identifier types to concrete TS types BEFORE checking enum
if (rawName === 'Integer') return 'number';
if (rawName === 'Boolean') return 'boolean';
if (rawName === 'Nat') return 'number';
// Check if this ident maps to a known enum interface
if (enumMap && enumMap.has(rawName)) {
return `${speckName}${rawName}`;
}
// Map known identifier types to concrete TS types
if (rawName === 'IssueState') return 'IssueWorkflowState';
if (rawName === 'Nat') return 'number';
// Opaque interfaces become 'any' for now
if (rawName === 'AST' || rawName === 'ArtifactMap') return 'any';
return rawName;