@dhiraj2105/dlang
Version:
A basic programming language built for learning and experimenting
277 lines (248 loc) • 8.04 kB
JavaScript
// =====================================
// evaluator.js
// This file executes the AST
// from the parser and runs the actual logic of the program
// ✅ Input: AST from parser
// ✅ Output: Console output, variable storage, etc.
//
// 🧠 Example:
// AST: [{ type: 'VariableDeclaration', name: 'x', value: { type: 'NumberLiteral', value: 5 } }]
// Result: Stores variable 'x' with value 5
// ==========================================
// ------------------------------
// 📦 Environment Object
// This acts like memory to store variables
// ------------------------------
const env = {};
// store functions definations
const functions = {};
// Control flow signals for break and continue
class BreakSignal {}
class ContinueSignal {}
// return signal
class ReturnSignal {
constructor(value) {
this.value = value;
}
}
// ------------------------------
// Main Evaluate function
// Accepts the full AST and runs each node
// ------------------------------
function evaluate(ast) {
for (const node of ast) {
evaluateNode(node); // evaluate each top- level AST node in sequence
}
}
// ------------------------------
// Dispatcher: Handle different node types
// Delegates to the correct handler based on node.type
// ------------------------------
function evaluateNode(node) {
switch (node.type) {
case "VariableDeclaration":
return handleVariableDeclaration(node);
case "PrintStatement":
return handlePrintStatement(node);
case "IfStatement":
return handleIfStatement(node);
case "WhileStatement":
return handleWhileStatement(node);
case "BlockStatement":
return handleBlockStatement(node);
case "BinaryExpression":
return evaluateBinaryExpression(node);
case "AssignmentExpression":
return handleAssignment(node);
case "ContinueStatement": // Handling ContinueStatement
return handleContinueStatement(); // Trigger continue logic
case "BreakStatement": // Handling BreakStatement
return handleBreakStatement(); // Trigger break logic
case "NumberLiteral":
case "StringLiteral":
case "Identifier":
return evaluateExpression(node);
case "FunctionDeclaration":
return handleFunctionDeclaration(node);
case "ReturnStatement":
return handleReturnStatement(node);
case "ExpressionStatement":
return evaluateExpression(node.expression);
case "CallExpression":
return handleCallExpression(node);
default:
throw new Error("Unknown AST Node Type: " + node.type);
}
}
// ------------------------------
// Handle Variable Declaration
// stores variable values in the environment
// ------------------------------
function handleVariableDeclaration(node) {
const { name, value } = node; // destructure variable name and assigned value
const evaluatedValue = evaluateExpression(value); // evaluate expression on right hand side
env[name] = evaluatedValue; // store in env
return evaluatedValue;
}
// ------------------------------
// 🖨️ Handle Print Statement
// Example: print x or print 5 + 2
// ------------------------------
function handlePrintStatement(node) {
const value = evaluateExpression(node.value);
console.log(value); // print to output
return value;
}
// ------------------------------
// If Statement
// ------------------------------
function handleIfStatement(node) {
const testResult = evaluateExpression(node.condition);
if (testResult) {
return evaluateNode(node.thenBranch);
} else if (node.elseBranch) {
return evaluateNode(node.elseBranch);
}
}
// ------------------------------
// 🔁 While Statement
// ------------------------------
function handleWhileStatement(node) {
let result;
while (evaluateExpression(node.condition)) {
try {
result = evaluateNode(node.body);
} catch (e) {
if (e instanceof BreakSignal) break;
if (e instanceof ContinueSignal) continue;
throw e; // rethrow other unexpected errors
}
}
return result;
}
// ------------------------------
// Handle Continue Signal
// This will signal the loop to continue to the next iteration
// ------------------------------
function handleContinueStatement() {
throw new ContinueSignal(); // Trigger a ContinueSignal to skip the current loop iteration
}
// ------------------------------
// Handle Break Signal
// This will signal the loop to break and exit
// ------------------------------
function handleBreakStatement() {
throw new BreakSignal(); // Trigger a BreakSignal to exit the loop
}
function handleAssignment(node) {
if (!(node.name in env)) {
throw new Error(`Cannot assign to undeclared variable: ${node.name}`);
}
const newValue = evaluateExpression(node.value);
env[node.name] = newValue;
return newValue;
}
// ------------------------------
// Block Statement
// Executes multiple statements inside a block
// ------------------------------
function handleBlockStatement(node) {
let result;
for (const stmt of node.body) {
result = evaluateNode(stmt);
}
return result;
}
// ------------------------------
// 🧮 Handle Arithmetic and Binary Expressions
// Example: 5 + 3, 10 - x
// ------------------------------
function evaluateBinaryExpression(node) {
const left = evaluateExpression(node.left); // recursively evaluate left side
const right = evaluateExpression(node.right); // recusively evaluate right side
switch (node.operator) {
case "+":
return left + right;
case "-":
return left - right;
case "*":
return left * right;
case "/":
return left / right;
case "=":
return left === right;
case "<":
return left < right;
case ">":
return left > right;
case "==": // ✅ ADDED
return left === right;
case "!=": // ✅ ADDED
return left !== right;
default:
throw new Error("Unsupported operator: " + node.operator);
}
}
// ------------------------------
// 🧠 Evaluate Expressions
// Handles literals, variables, binary expressions
// ------------------------------
function evaluateExpression(expr) {
switch (expr.type) {
case "NumberLiteral":
return expr.value; // return numeric value directly
case "StringLiteral":
return expr.value; // return string value directly
case "Identifier":
// lookup variable name in environment
if (expr.name in env) {
return env[expr.name]; // return stored value
} else {
throw new Error(`Undefined variable: ${expr.name}`);
}
case "BinaryExpression":
return evaluateBinaryExpression(expr); // recurse for nested expression
case "CallExpression":
return handleCallExpression(expr);
default:
throw new Error("Unknown expression type: " + expr.type);
}
}
function handleFunctionDeclaration(node) {
const { name, params, body } = node;
functions[name] = { params, body };
}
function handleCallExpression(node) {
const func = functions[node.name];
if (!func) {
throw new Error(`Undefined function: ${node.name}`);
}
const { params, body } = func;
const args = node.arguments.map(evaluateExpression);
// Create a local environment for the function
const oldEnv = { ...env };
// Assign arguments to parameters
for (let i = 0; i < params.length; i++) {
env[params[i]] = args[i];
}
try {
const result = evaluateNode(body); // Evaluate function body
return result; // If function ends without return, return undefined
} catch (e) {
if (e instanceof ReturnSignal) {
return e.value; // if a return is encountered, return its value
}
throw e; // rethrow unexpected errors
} finally {
// Restore previous environment after function call
for (const key of Object.keys(env)) {
delete env[key];
}
Object.assign(env, oldEnv);
}
}
function handleReturnStatement(node) {
const value = evaluateExpression(node.value);
throw new ReturnSignal(value); // Immediately return the value
}
export default evaluate;