import { Syntax, SyntaxKind, Expr, isNode, BoltQualName } from "./ast" import { TypeChecker, Type, RecordType, PrimType, boolType } from "./checker" import { FastStringMap } from "./util" export interface Value { type: Type; } export class PrimValue implements Value { constructor( public type: PrimType, public value: any ) { } } export const TRUE = new PrimValue(boolType, true); export const FALSE = new PrimValue(boolType, false); export abstract class RecordValue implements Value { abstract type: RecordType; abstract getValueOfField(name: string): Value; } export class NativeRecord implements Value { constructor( public type: RecordType, protected fields: FastStringMap, ) { } getValueOfField(name: string): Value { if (!this.type.hasField(name)) { throw new Error(`Field '${name}' does not exist on this record.`) } return this.fields[name] } } export class RecordWrapper extends RecordValue { constructor( public type: RecordType, protected data: any, ) { super(); } getValueOfField(name: string): Value { if (!this.type.hasField(name)) { throw new Error(`Field '${name}' does not exist on this record.`) } return this.data[name] } } function getDeclarationPath(node: BoltQualName) { return [...node.modulePath.map(id => id.text), node.name.text]; } class Environment { private symbols = FastStringMap(); constructor(public parentEnv: Environment | null = null) { } public setValue(name: string, value: Value) { if (name in this.symbols) { throw new Error(`A variable with the name '${name}' already exists.`); } this.symbols[name] = value; } public updateValue(name: string, newValue: Value) { if (!(name in this.symbols)) { throw new Error(`Trying to update a variable '${name}' that has not been declared.`); } } public lookup(name: string) { let curr = this as Environment; while (true) { if (name in curr.symbols) { return curr.symbols[name]; } if (curr.parentEnv === null) { break; } curr = curr.parentEnv; } throw new Error(`A variable named '${name}' was not found.`); } } export class Evaluator { constructor(public checker: TypeChecker) { } match(value: Value, node: Syntax) { switch (node.kind) { case SyntaxKind.RecordPatt: for (const field of node.fields) { if (!this.match((value as RecordValue).getValueOfField(field.name.text), field.pattern)) { return false; } } return true; case SyntaxKind.TypePatt: return value.type === this.checker.getTypeOfNode(node) default: throw new Error(`I did not know how to match on pattern ${SyntaxKind[node.kind]}`) } } createValue(data: any) { if (isNode(data)) { return new RecordWrapper(this.checker.getTypeNamed(`Bolt.AST.${SyntaxKind[data.kind]}`)! as RecordType, data) } } public eval(node: Syntax, env: Environment = new Environment()): Value { switch (node.kind) { case SyntaxKind.BoltSourceFile: case SyntaxKind.BoltModule: for (const element of node.elements) { this.eval(element, env); } break; case SyntaxKind.BoltReferenceTypeExpression: // FIXME return env.lookup(node.name.name.text); case SyntaxKind.BoltRecordDeclaration: case SyntaxKind.BoltFunctionDeclaration: break; case SyntaxKind.BoltMatchExpression: const value = this.eval(node.value, env); for (const [pattern, result] of node.arms) { if (this.match(value, pattern)) { return this.eval(result as Expr, env) } } return new PrimValue(this.checker.getTypeNamed('Void')!, null); case SyntaxKind.BoltConstantExpression: return new PrimValue(this.checker.getTypeOfNode(node), node.value) default: throw new Error(`Could not evaluate node ${SyntaxKind[node.kind]}`) } } }