const AST = require('./ast') const Env = require('./env') const OsloError = require('./osloError') module.exports = class Evaluator { eval(tree, env) { this.env = env this.error = false let evaluatedTree = [] tree.forEach(node => { let evaluatedNode = this.evalNode(node) if (evaluatedNode) { if (evaluatedNode.constructor === OsloError) { this.error = evaluatedNode } else { evaluatedTree.push(evaluatedNode) } } }) if (this.error) { return this.error } return evaluatedTree } evalNode(node, env) { if (!env) env = this.env switch (node.constructor) { case AST.Boolean: case AST.Number: case AST.String: return node case AST.Identifier: return env.get(node) case AST.Definition: this.env.set(node.symbol, node.value) return false case AST.LetBinding: let innerEnv = new Env(env) node.bindings.forEach(binding => { innerEnv.set(binding.key, binding.value) }) return this.evalNode(node.body, innerEnv) case AST.Conditional: let result = this.evalNode(node.condition, env) if (result.constructor == AST.Boolean && result.value === true) { return this.evalNode(node.ifCase, env) } return this.evalNode(node.elseCase, env) case AST.Application: switch (node.function.constructor) { case AST.Identifier: return this.evalNode( new AST.Application({ function: env.get(node.function), args: node.args, }), env, ) case AST.Lambda: let innerEnv = new Env(env) node.function.parameters.forEach((param, index) => { innerEnv.set(param, node.args[index]) }) return this.evalNode(node.function.body, innerEnv) case Function: let args = node.args.map(arg => { return this.evalNode(arg, env) }) args.unshift(this) return node.function.call(...args) } } return node } }