Feat: Build AST nodes for new language features

This commit is contained in:
2025-09-26 08:29:51 -07:00
parent 5c39bae274
commit 1589f8affe

View File

@@ -2,30 +2,45 @@ import { parser } from './parser.js';
const BaseHiVisitor = parser.getBaseCstVisitorConstructor(); const BaseHiVisitor = parser.getBaseCstVisitorConstructor();
// Helper to build a left-associative binary expression AST from a CST
function buildBinaryExpression(ctx, visitor) {
let left = visitor.visit(ctx.left);
if (ctx.right) {
ctx.right.forEach((rhs, i) => {
const operator = ctx.Or[i].image;
left = {
type: 'BinaryExpression',
operator,
left,
right: visitor.visit(rhs),
};
});
}
return left;
}
class AstBuilder extends BaseHiVisitor { class AstBuilder extends BaseHiVisitor {
constructor() { constructor() {
super(); super();
this.validateVisitor(); this.validateVisitor();
} }
program(ctx) { program(ctx) { return { type: 'Program', body: this.visit(ctx.statements) }; }
return { type: 'Program', body: this.visit(ctx.statements) }; statements(ctx) { return ctx.statement?.map(stmt => this.visit(stmt)) || []; }
}
statements(ctx) {
return ctx.statement?.map(stmt => this.visit(stmt)) || [];
}
statement(ctx) { statement(ctx) {
if (ctx.declaration) return this.visit(ctx.declaration); if (ctx.declaration) return this.visit(ctx.declaration);
if (ctx.assignment) return this.visit(ctx.assignment); if (ctx.assignment) return this.visit(ctx.assignment);
if (ctx.returnStatement) return this.visit(ctx.returnStatement);
if (ctx.expressionStatement) return this.visit(ctx.expressionStatement); if (ctx.expressionStatement) return this.visit(ctx.expressionStatement);
} }
expressionStatement(ctx) { returnStatement(ctx) {
return { type: 'ExpressionStatement', expression: this.visit(ctx.expression) }; return { type: 'ReturnStatement', argument: ctx.expression ? this.visit(ctx.expression) : null };
} }
expressionStatement(ctx) { return { type: 'ExpressionStatement', expression: this.visit(ctx.expression) }; }
declaration(ctx) { declaration(ctx) {
return { return {
type: 'VariableDeclaration', type: 'VariableDeclaration',
@@ -36,90 +51,96 @@ class AstBuilder extends BaseHiVisitor {
assignment(ctx) { assignment(ctx) {
return { return {
type: 'Assignment', type: 'AssignmentExpression',
identifier: ctx.Identifier[0].image, left: { type: 'Identifier', name: ctx.Identifier[0].image },
value: this.visit(ctx.expression), right: this.visit(ctx.expression),
}; };
} }
expression(ctx) { expression(ctx) { return this.visit(ctx.conditionalExpression); }
return this.visit(ctx.additiveExpression);
conditionalExpression(ctx) {
if (!ctx.Question) return this.visit(ctx.condition);
return {
type: 'ConditionalExpression',
test: this.visit(ctx.condition),
consequent: this.visit(ctx.consequent),
alternate: ctx.alternate ? this.visit(ctx.alternate) : { type: 'NullLiteral' },
};
} }
additiveExpression(ctx) { equalityExpression(ctx) { return buildBinaryExpression(ctx, this); }
let left = this.visit(ctx.left); additiveExpression(ctx) { return buildBinaryExpression(ctx, this); }
if (ctx.right) { multiplicativeExpression(ctx) { return buildBinaryExpression(ctx, this); }
ctx.right.forEach((rhs) => {
left = {
type: 'BinaryExpression',
operator: '+',
left: left,
right: this.visit(rhs),
};
});
}
return left;
}
callExpression(ctx) { callExpression(ctx) {
let callee = this.visit(ctx.memberExpression); let expr = this.visit(ctx.callee);
if (ctx.LParen) { if (ctx.LParen) {
return { ctx.LParen.forEach((_, i) => {
type: 'CallExpression', expr = {
callee: callee, type: 'CallExpression',
arguments: ctx.argumentList ? this.visit(ctx.argumentList) : [], callee: expr,
}; arguments: ctx.argumentList?.[i] ? this.visit(ctx.argumentList[i]) : [],
}
return callee;
}
argumentList(ctx) {
return ctx.expression.map(expr => this.visit(expr));
}
memberExpression(ctx) {
let obj = this.visit(ctx.primary);
if (ctx.Identifier) {
ctx.Identifier.forEach(propIdent => {
obj = {
type: 'MemberExpression',
object: obj,
property: { type: 'Identifier', name: propIdent.image },
}; };
}); });
} }
return expr;
}
argumentList(ctx) { return ctx.expression.map(expr => this.visit(expr)); }
memberExpression(ctx) {
let obj = this.visit(ctx.object);
ctx.Identifier?.forEach(prop => {
obj = { type: 'MemberExpression', computed: false, object: obj, property: { type: 'Identifier', name: prop.image } };
});
ctx.expression?.forEach(prop => {
obj = { type: 'MemberExpression', computed: true, object: obj, property: this.visit(prop) };
});
return obj; 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.block) return this.visit(ctx.block); if (ctx.block) return this.visit(ctx.block);
if (ctx.arrowExpression) return this.visit(ctx.arrowExpression);
if (ctx.arrayLiteral) return this.visit(ctx.arrayLiteral);
if (ctx.expression) return this.visit(ctx.expression); if (ctx.expression) return this.visit(ctx.expression);
} }
literal(ctx) { literal(ctx) {
if (ctx.Number) return { type: 'NumericLiteral', value: Number(ctx.Number[0].image) }; if (ctx.Number) {
if (ctx.String) return { type: 'StringLiteral', value: ctx.String[0].image }; const image = ctx.Number[0].image;
if (image === '!0') return { type: 'BooleanLiteral', value: true };
return { type: 'NumericLiteral', value: Number(image) };
}
if (ctx.String) return { type: 'StringLiteral', value: ctx.String[0].image.slice(1,-1) };
if (ctx.Null) return { type: 'NullLiteral' };
} }
block(ctx) { block(ctx) {
return { const params = ctx.parameterList ? this.visit(ctx.parameterList) : [];
type: 'Block', const body = this.visit(ctx.statements);
properties: ctx.keyValuePairs ? this.visit(ctx.keyValuePairs) : [] if (params.length > 0) {
}; return { type: 'FunctionExpression', params, body: { type: 'BlockStatement', body } };
}
return { type: 'Block', body };
} }
keyValuePairs(ctx) { arrowExpression(ctx) {
return ctx.keyValuePair.map(kv => this.visit(kv)); const params = this.visit(ctx.parameterList);
const body = ctx.block ? this.visit(ctx.block) : this.visit(ctx.expression);
return { type: 'ArrowFunctionExpression', params, body };
} }
keyValuePair(ctx) { parameterList(ctx) {
return { return ctx.Identifier?.map(id => ({ type: 'Identifier', name: id.image })) || [];
type: 'Property', }
key: ctx.Identifier[0].image,
value: this.visit(ctx.expression) arrayLiteral(ctx) {
}; return { type: 'ArrayLiteral', elements: ctx.expression?.map(e => this.visit(e)) || [] };
} }
} }