const AST = require("./ast"); module.exports = class Resolver { constructor() { this.tree = []; this.context = {}; this.stdlib = { if: (predicate, left, right) => { const resolvedPredicate = this.resolveNode(predicate); this.typecheck(resolvedPredicate, AST.Boolean); if (resolvedPredicate && resolvedPredicate.value === true) { return this.resolveNode(left); } else if (resolvedPredicate && resolvedPredicate.value === false) { return this.resolveNode(right); } }, list: args => { let elements = []; args.forEach(arg => { elements.push(this.resolveNode(arg)); }); return new AST.List({ elements: elements }); }, quote: value => { return new AST.Symbol({ value: value }); }, htmlElement: node => { let resolvedArgs = []; node.args.forEach(arg => { resolvedArgs.push(this.resolveNode(arg)); }); return new AST.Application({ functionName: node.functionName, args: resolvedArgs }); }, "=": (left, right) => { this.typecheck(right, left.constructor); if (left.constructor.name === "List") { if (left.elements.length !== right.elements.length) { return new AST.Boolean({ value: false }); } let equal = true; left.elements.forEach((el, i) => { this.typecheck(el.value, right.elements[i].constructor); if (el.value !== right.elements[i].value) { equal = false; } }); return new AST.Boolean({ value: equal }); } return new AST.Boolean({ value: left.value === right.value }); }, ">": (left, right) => { this.typecheck(left, AST.Number) && this.typecheck(right, AST.Number); return new AST.Boolean({ value: left.value > right.value }); }, "<": (left, right) => { this.typecheck(left, AST.Number) && this.typecheck(right, AST.Number); return new AST.Boolean({ value: left.value < right.value }); }, ">=": (left, right) => { this.typecheck(left, AST.Number) && this.typecheck(right, AST.Number); return new AST.Boolean({ value: left.value >= right.value }); }, "<=": (left, right) => { this.typecheck(left, AST.Number) && this.typecheck(right, AST.Number); return new AST.Boolean({ value: left.value <= right.value }); }, and: args => { let and = true; args.forEach(arg => { this.typecheck(arg, AST.Boolean); if (arg.value === false) { and = false; } }); return new AST.Boolean({ value: and }); }, or: args => { let or = false; args.forEach(arg => { this.typecheck(arg, AST.Boolean); if (arg.value === true) { or = true; } }); return new AST.Boolean({ value: or }); }, lambda: function(parameter, body) { console.log(parameter, body); } }; } resolve(tree, context) { this.context = context; tree.forEach(node => { this.tree.push(this.resolveNode(node)); }); return this.tree; } resolveNode(node, context) { if (!context) { context = this.context; } switch (node.constructor.name) { case "Boolean": case "Number": case "String": return node; case "Attribute": return new AST.Attribute({ name: node.name, value: this.resolveNode(node.value) }); case "Identifier": return this.lookup(node.name); case "Application": if (this.stdlib.hasOwnProperty(node.functionName.name)) { const f = this.stdlib[node.functionName.name]; if (["list", "and", "or"].includes(node.functionName.name)) { return f(node.args); } let resolvedArgs = []; if (node.functionName.name === "quote") { resolvedArgs = node.args; } else { node.args.forEach(arg => { resolvedArgs.push(this.resolveNode(arg)); }); } return f(...resolvedArgs); } return this.stdlib.htmlElement(node); } } lookup(name) { console.log(name); const result = this.context[name]; return this.wrap(result); } wrap(value) { switch (value.constructor.name) { case "String": return new AST.String({ value: value }); case "Number": return new AST.Number({ value: value }); case "Array": return new AST.List({ elements: value.map(this.wrap) }); } } typecheck(value, type) { console.log(value); console.log(type); if (value.constructor.name !== type.name) { throw `Type error: expected a ${type.name} but got a ${ value.constructor.name }`; } } };