class Environment {
constructor(outer = null) {
this.variables = {};
this.outer = outer;
}
get(name) {
if (name in this.variables) {
return this.variables[name];
} else if (this.outer !== null) {
return this.outer.get(name);
} else {
throw new ReferenceError(`Variable "${name}" is not defined.`);
}
}
set(name, value) {
this.variables[name] = value;
}
define(name, value) {
this.variables[name] = value;
}
}
class Interpreter {
constructor() {
this.globalEnv = new Environment();
this.currentEnv = this.globalEnv;
}
interpret(node) {
switch (node.type) {
case 'Program':
return this.interpretProgram(node);
case 'VariableDeclaration':
return this.interpretVariableDeclaration(node);
case 'FunctionDeclaration':
return this.interpretFunctionDeclaration(node);
case 'ExpressionStatement':
return this.interpret(node.expression);
case 'BlockStatement':
return this.interpretBlockStatement(node);
case 'AssignmentExpression':
return this.interpretAssignmentExpression(node);
case 'BinaryExpression':
return this.interpretBinaryExpression(node);
case 'Literal':
return node.value;
case 'Identifier':
return this.currentEnv.get(node.name);
case 'CallExpression':
return this.interpretCallExpression(node);
case 'ReturnStatement':
return this.interpretReturnStatement(node);
default:
throw new Error(`Unknown node type: ${node.type}`);
}
}
interpretProgram(node) {
for (const statement of node.body) {
this.interpret(statement);
}
}
interpretVariableDeclaration(node) {
for (const declaration of node.declarations) {
const name = declaration.id.name;
const value = declaration.init ? this.interpret(declaration.init) : undefined;
this.currentEnv.define(name, value);
}
}
interpretFunctionDeclaration(node) {
const name = node.id.name;
this.currentEnv.define(name, node);
}
interpretBlockStatement(node) {
for (const statement of node.body) {
const result = this.interpret(statement);
if (statement.type === 'ReturnStatement') {
return result;
}
}
}
interpretAssignmentExpression(node) {
const name = node.left.name;
const value = this.interpret(node.right);
this.currentEnv.set(name, value);
}
interpretBinaryExpression(node) {
const left = this.interpret(node.left);
const right = this.interpret(node.right);
switch (node.operator) {
case '+':
return left + right;
case '-':
return left - right;
case '*':
return left * right;
case '/':
return left / right;
default:
throw new Error(`Unknown binary operator: ${node.operator}`);
}
}
interpretCallExpression(node) {
const func = this.currentEnv.get(node.callee.name);
const args = node.arguments.map(arg => this.interpret(arg));
const funcEnv = new Environment(this.currentEnv);
for (let i = 0; i < func.params.length; i++) {
funcEnv.define(func.params[i].name, args[i]);
}
const interpreter = new Interpreter();
interpreter.currentEnv = funcEnv;
return interpreter.interpret(func.body);
}
interpretReturnStatement(node) {
return this.interpret(node.argument);
}
}
const ast = {
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "x"
},
"init": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Literal",
"value": 10,
"raw": "10"
},
"right": {
"type": "Literal",
"value": 20,
"raw": "20"
}
}
}
],
"kind": "var"
},
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "add"
},
"params": [
{
"type": "Identifier",
"name": "a"
},
{
"type": "Identifier",
"name": "b"
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Identifier",
"name": "a"
},
"right": {
"type": "Identifier",
"name": "b"
}
}
}
]
}
},
{
"type": "ExpressionStatement",
"expression": {
"type": "AssignmentExpression",
"operator": "=",
"left": {
"type": "Identifier",
"name": "x"
},
"right": {
"type": "CallExpression",
"callee": {
"type": "Identifier",
"name": "add"
},
"arguments": [
{
"type": "Identifier",
"name": "x"
},
{
"type": "Literal",
"value": 5,
"raw": "5"
}
]
}
}
}
]
};
const interpreter = new Interpreter();
interpreter.interpret(ast);
console.log(interpreter.currentEnv);