fix(parser): parse parenthetical type syntax Map(K,V), Set(T), List(T)
parseTypeExpr only handled angle-bracket generics (Map<K,V>, Set<T>, List<T>) but SpeckDL canonical syntax uses parentheses: Map(Nat, Account), Set(Nat), List(LogEntry). This caused two bugs: 1. State property types emitted as raw strings (accounts: Map(Nat, Account)) 2. Parametric types treated as enum interface names (TigerBeetleLedgerMap(Nat, Account)) Added List(T), Set(T), Map(K,V) handlers alongside existing angle-bracket handlers. All 41/41 tests passing. TigerBeetle now generates clean TS types: - accounts: Map<number, Account> (was Map(Nat, Account)) - pending: Set<number> (was Set(Nat)) - log: LogEntry[] (was List(LogEntry))
This commit is contained in:
parent
e92f430fb3
commit
c3ade18feb
1 changed files with 66 additions and 3 deletions
|
|
@ -246,7 +246,8 @@ function parseSpeck(lines: string[], startIndex: number): SpeckNode | null {
|
|||
const closeCount = (rawLine.match(/}/g) || []).length;
|
||||
|
||||
// Check for block-opening members
|
||||
const blockStarters = ['state:', 'init:', 'action ', 'event ', 'provenance ', 'bom ', 'interface '];
|
||||
const blockStarters = ['state:', 'init:', 'action ', 'event ', 'provenance ', 'bom ', 'interface ',
|
||||
'state {', 'init {'];
|
||||
const isBlockStarter = blockStarters.some(s => line.startsWith(s));
|
||||
|
||||
if (isBlockStarter && openCount > 0) {
|
||||
|
|
@ -304,9 +305,15 @@ function parseMemberBlock(lines: string[], startIndex: number): MemberNode | nul
|
|||
if (firstLine.startsWith('state:')) {
|
||||
return parseStateBlockMultiline(lines, startIndex, startBraceCount);
|
||||
}
|
||||
if (firstLine.startsWith('state {') || firstLine === 'state') {
|
||||
return parseStateBlockMultiline(lines, startIndex, startBraceCount);
|
||||
}
|
||||
if (firstLine.startsWith('init:')) {
|
||||
return parseInitBlockMultiline(lines, startIndex, startBraceCount);
|
||||
}
|
||||
if (firstLine.startsWith('init {') || firstLine === 'init') {
|
||||
return parseInitBlockMultiline(lines, startIndex, startBraceCount);
|
||||
}
|
||||
if (firstLine.startsWith('action ')) {
|
||||
return parseActionBlockMultiline(lines, startIndex, startBraceCount);
|
||||
}
|
||||
|
|
@ -390,7 +397,7 @@ function parseMember(line: string): MemberNode | null {
|
|||
return parseSource(line);
|
||||
}
|
||||
|
||||
// state:
|
||||
// state: or state {
|
||||
if (line.startsWith('state:')) {
|
||||
const varsStr = line.substring(6).trim();
|
||||
// state: { ... } - single-line form
|
||||
|
|
@ -401,8 +408,19 @@ function parseMember(line: string): MemberNode | null {
|
|||
// state: { - multi-line opening (handled by parseStateMultiline)
|
||||
return { type: 'state', variables: [] };
|
||||
}
|
||||
if (line.startsWith('state {') || line === 'state') {
|
||||
// state { ... } inline or state { on its own line (handled by parseMemberBlock)
|
||||
if (line.startsWith('state {')) {
|
||||
const inner = line.substring(6).trim();
|
||||
const content = inner.startsWith('{') ? inner.slice(1).trim() : inner;
|
||||
if (content.endsWith('}')) {
|
||||
return parseStateBlock(content.slice(0, -1).trim());
|
||||
}
|
||||
}
|
||||
return { type: 'state', variables: [] };
|
||||
}
|
||||
|
||||
// init:
|
||||
// init: or init {
|
||||
if (line.startsWith('init:')) {
|
||||
const initStr = line.substring(5).trim();
|
||||
if (initStr.startsWith('{') && initStr.endsWith('}')) {
|
||||
|
|
@ -411,6 +429,17 @@ function parseMember(line: string): MemberNode | null {
|
|||
}
|
||||
return { type: 'init', assignments: [] };
|
||||
}
|
||||
if (line.startsWith('init {') || line === 'init') {
|
||||
// init { ... } inline
|
||||
if (line.startsWith('init {')) {
|
||||
const inner = line.substring(5).trim();
|
||||
const content = inner.startsWith('{') ? inner.slice(1).trim() : inner;
|
||||
if (content.endsWith('}')) {
|
||||
return parseInitBlock(content.slice(0, -1).trim());
|
||||
}
|
||||
}
|
||||
return { type: 'init', assignments: [] };
|
||||
}
|
||||
|
||||
// action keyword
|
||||
if (line.startsWith('action ')) {
|
||||
|
|
@ -553,6 +582,40 @@ function parseTypeExpr(expr: string): TypeExpr {
|
|||
// Fallback: treat as ident if malformed
|
||||
return { type: 'ident', name: expr };
|
||||
}
|
||||
|
||||
// List(T) — parenthetical syntax (SpeckDL canonical form)
|
||||
if (expr.startsWith('List(') && expr.endsWith(')')) {
|
||||
const inner = expr.substring(5, expr.length - 1).trim();
|
||||
return { type: 'list', elementType: parseTypeExpr(inner) };
|
||||
}
|
||||
|
||||
// Set(T) — parenthetical syntax (SpeckDL canonical form)
|
||||
if (expr.startsWith('Set(') && expr.endsWith(')')) {
|
||||
const inner = expr.substring(4, expr.length - 1).trim();
|
||||
return { type: 'set', elementType: parseTypeExpr(inner) };
|
||||
}
|
||||
|
||||
// Map(K, V) — parenthetical syntax (SpeckDL canonical form)
|
||||
if (expr.startsWith('Map(') && expr.endsWith(')')) {
|
||||
const inner = expr.substring(4, expr.length - 1);
|
||||
let depth = 0;
|
||||
let commaIdx = -1;
|
||||
for (let i = 0; i < inner.length; i++) {
|
||||
if (inner[i] === '(' || inner[i] === '<') depth++;
|
||||
else if (inner[i] === ')' || inner[i] === '>') depth--;
|
||||
else if (inner[i] === ',' && depth === 0) {
|
||||
commaIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (commaIdx !== -1) {
|
||||
const keyStr = inner.substring(0, commaIdx).trim();
|
||||
const valStr = inner.substring(commaIdx + 1).trim();
|
||||
return { type: 'map', keyType: parseTypeExpr(keyStr), valueType: parseTypeExpr(valStr) };
|
||||
}
|
||||
// Fallback: treat as ident if malformed
|
||||
return { type: 'ident', name: expr };
|
||||
}
|
||||
|
||||
// List type: [T]
|
||||
if (expr.startsWith('[') && expr.endsWith(']')) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue