Fix: Add objects/rel ops/assignable; stable member chain

This commit is contained in:
2025-09-26 10:09:41 -07:00
parent edf64a5b55
commit 1d1e168756

View File

@@ -9,12 +9,13 @@ function buildBinaryExpression(ctx, visitor) {
// Collect all possible operator tokens from the context. // Collect all possible operator tokens from the context.
const operators = []; const operators = [];
if (ctx.EqEq) operators.push(...ctx.EqEq); if (ctx.EqEq) operators.push(...ctx.EqEq);
if (ctx.Gt) operators.push(...ctx.Gt);
if (ctx.Lt) operators.push(...ctx.Lt);
if (ctx.Plus) operators.push(...ctx.Plus); if (ctx.Plus) operators.push(...ctx.Plus);
if (ctx.Minus) operators.push(...ctx.Minus); if (ctx.Minus) operators.push(...ctx.Minus);
if (ctx.Star) operators.push(...ctx.Star); if (ctx.Star) operators.push(...ctx.Star);
if (ctx.Slash) operators.push(...ctx.Slash); if (ctx.Slash) operators.push(...ctx.Slash);
// Sort operators by their position in the source text to handle mixed operators correctly.
operators.sort((a, b) => a.startOffset - b.startOffset); operators.sort((a, b) => a.startOffset - b.startOffset);
ctx.right.forEach((rhs, i) => { ctx.right.forEach((rhs, i) => {
@@ -30,6 +31,41 @@ function buildBinaryExpression(ctx, visitor) {
return left; return left;
} }
// Build member chain using token ordering to preserve dots/brackets sequence.
function buildMemberChain(ctx, base, visitor, idStartIndex = 0) {
let obj = base;
const steps = [];
if (ctx.Dot) steps.push(...ctx.Dot.map(tok => ({ kind: 'dot', tok })));
if (ctx.LBracket) steps.push(...ctx.LBracket.map(tok => ({ kind: 'brack', tok })));
steps.sort((a, b) => a.tok.startOffset - b.tok.startOffset);
let idIdx = idStartIndex;
let exprIdx = 0;
steps.forEach(step => {
if (step.kind === 'dot') {
const propTok = ctx.Identifier[idIdx++];
obj = {
type: 'MemberExpression',
computed: false,
object: obj,
property: { type: 'Identifier', name: propTok.image }
};
} else {
const propExpr = visitor.visit(ctx.expression[exprIdx++]);
obj = {
type: 'MemberExpression',
computed: true,
object: obj,
property: propExpr
};
}
});
return obj;
}
class AstBuilder extends BaseHiVisitor { class AstBuilder extends BaseHiVisitor {
constructor() { constructor() {
super(); super();
@@ -60,14 +96,23 @@ class AstBuilder extends BaseHiVisitor {
}; };
} }
// assignment: assignable '=' expression
assignment(ctx) { assignment(ctx) {
return { return {
type: 'AssignmentExpression', type: 'AssignmentExpression',
left: { type: 'Identifier', name: ctx.Identifier[0].image }, left: this.visit(ctx.left),
right: this.visit(ctx.expression), right: this.visit(ctx.expression),
}; };
} }
// assignable: Identifier (.Identifier | [expr])*
assignable(ctx) {
const base = { type: 'Identifier', name: ctx.Identifier[0].image };
// ctx.Identifier contains base + dot-props
if (!ctx.Dot && !ctx.LBracket) return base;
return buildMemberChain(ctx, base, this, 1);
}
expression(ctx) { return this.visit(ctx.conditionalExpression); } expression(ctx) { return this.visit(ctx.conditionalExpression); }
conditionalExpression(ctx) { conditionalExpression(ctx) {
@@ -81,6 +126,7 @@ class AstBuilder extends BaseHiVisitor {
} }
equalityExpression(ctx) { return buildBinaryExpression(ctx, this); } equalityExpression(ctx) { return buildBinaryExpression(ctx, this); }
relationalExpression(ctx) { return buildBinaryExpression(ctx, this); }
additiveExpression(ctx) { return buildBinaryExpression(ctx, this); } additiveExpression(ctx) { return buildBinaryExpression(ctx, this); }
multiplicativeExpression(ctx) { return buildBinaryExpression(ctx, this); } multiplicativeExpression(ctx) { return buildBinaryExpression(ctx, this); }
@@ -101,24 +147,23 @@ class AstBuilder extends BaseHiVisitor {
argumentList(ctx) { return ctx.expression.map(expr => this.visit(expr)); } argumentList(ctx) { return ctx.expression.map(expr => this.visit(expr)); }
memberExpression(ctx) { memberExpression(ctx) {
let obj = this.visit(ctx.object); const base = this.visit(ctx.object);
ctx.Identifier?.forEach(prop => { if (!ctx.Dot && !ctx.LBracket) return base;
obj = { type: 'MemberExpression', computed: false, object: obj, property: { type: 'Identifier', name: prop.image } };
}); // ctx.Identifier are only the dot properties (no base here)
ctx.expression?.forEach(prop => { return buildMemberChain(ctx, base, this, 0);
obj = { type: 'MemberExpression', computed: true, object: obj, property: this.visit(prop) };
});
return obj;
} }
primary(ctx) { primary(ctx) {
if (ctx.literal) return this.visit(ctx.literal); if (ctx.literal) return this.visit(ctx.literal);
if (ctx.Identifier) return { type: 'Identifier', name: ctx.Identifier[0].image }; if (ctx.Identifier) return { type: 'Identifier', name: ctx.Identifier[0].image };
if (ctx.At) return { type: 'ThisExpression' }; if (ctx.At) return { type: 'ThisExpression' };
if (ctx.block) return this.visit(ctx.block);
if (ctx.arrowExpression) return this.visit(ctx.arrowExpression); if (ctx.arrowExpression) return this.visit(ctx.arrowExpression);
if (ctx.functionExpression) return this.visit(ctx.functionExpression);
if (ctx.objectLiteral) return this.visit(ctx.objectLiteral);
if (ctx.codeBlock) return this.visit(ctx.codeBlock);
if (ctx.arrayLiteral) return this.visit(ctx.arrayLiteral); if (ctx.arrayLiteral) return this.visit(ctx.arrayLiteral);
if (ctx.expression) return this.visit(ctx.expression); if (ctx.parenthesizedExpression) return this.visit(ctx.parenthesizedExpression);
} }
literal(ctx) { literal(ctx) {
@@ -127,25 +172,33 @@ class AstBuilder extends BaseHiVisitor {
if (image === '!0') return { type: 'BooleanLiteral', value: true }; if (image === '!0') return { type: 'BooleanLiteral', value: true };
return { type: 'NumericLiteral', value: Number(image) }; return { type: 'NumericLiteral', value: Number(image) };
} }
if (ctx.String) return { type: 'StringLiteral', value: ctx.String[0].image.slice(1,-1) }; if (ctx.String) return { type: 'StringLiteral', value: ctx.String[0].image.slice(1, -1) };
if (ctx.Null) return { type: 'NullLiteral' }; if (ctx.Null) return { type: 'NullLiteral' };
} }
block(ctx) { // codeBlock -> Block node (for implicit return/IIFE in conditionals)
const params = ctx.parameterList ? this.visit(ctx.parameterList) : []; codeBlock(ctx) {
const body = this.visit(ctx.statements); const body = this.visit(ctx.statements);
if (ctx.parameterList) { // If there's a param list, it's a function
return { type: 'FunctionExpression', params, body: { type: 'BlockStatement', body } };
}
return { type: 'Block', body }; return { type: 'Block', body };
} }
// functionExpression -> FunctionExpression node
functionExpression(ctx) {
const params = this.visit(ctx.parameterList);
const body = this.visit(ctx.codeBlock);
return { type: 'FunctionExpression', params, body: { type: 'BlockStatement', body: body.body } };
}
// arrowExpression(params => body)
arrowExpression(ctx) { arrowExpression(ctx) {
const params = this.visit(ctx.parameterList); const params = this.visit(ctx.parameterList);
const body = ctx.block ? this.visit(ctx.block) : this.visit(ctx.expression); const body = ctx.codeBlock ? this.visit(ctx.codeBlock) : this.visit(ctx.expression);
return { type: 'ArrowFunctionExpression', params, body }; return { type: 'ArrowFunctionExpression', params, body };
} }
// parenthesizedExpression simply returns the inner expression
parenthesizedExpression(ctx) { return this.visit(ctx.expression); }
parameterList(ctx) { parameterList(ctx) {
return ctx.Identifier?.map(id => ({ type: 'Identifier', name: id.image })) || []; return ctx.Identifier?.map(id => ({ type: 'Identifier', name: id.image })) || [];
} }
@@ -153,6 +206,21 @@ class AstBuilder extends BaseHiVisitor {
arrayLiteral(ctx) { arrayLiteral(ctx) {
return { type: 'ArrayLiteral', elements: ctx.expression?.map(e => this.visit(e)) || [] }; return { type: 'ArrayLiteral', elements: ctx.expression?.map(e => this.visit(e)) || [] };
} }
objectLiteral(ctx) {
const props = [];
const ids = ctx.Identifier || [];
const exprs = ctx.expression || [];
for (let i = 0; i < ids.length; i++) {
props.push({
type: 'Property',
key: ids[i].image,
value: this.visit(exprs[i])
});
}
return { type: 'ObjectLiteral', properties: props };
}
} }
export const astBuilder = new AstBuilder(); export const astBuilder = new AstBuilder();