compile.js (10263B)
1 import { List as ImmutableList } from "immutable"; 2 import { infixFunctions, unaryFunctions } from "../ast/peggyHelpers.js"; 3 import { ICompileError } from "../errors/IError.js"; 4 import { ImmutableMap } from "../utility/immutableMap.js"; 5 import * as Result from "../utility/result.js"; 6 import { vBool, vNumber, vString } from "../value/index.js"; 7 import { INDEX_LOOKUP_FUNCTION } from "./constants.js"; 8 import * as expression from "./index.js"; 9 function createInitialCompileContext(externals) { 10 return { 11 externals, 12 nameToPos: ImmutableMap(), 13 locals: ImmutableList(), 14 size: 0, 15 }; 16 } 17 function resolveName(context, ast, name) { 18 const offset = context.nameToPos.get(name); 19 if (offset !== undefined) { 20 return expression.eResolvedSymbol(name, context.size - 1 - offset); 21 } 22 const value = context.externals.get(name); 23 if (value !== undefined) { 24 return expression.eValue(value); 25 } 26 throw new ICompileError(`${name} is not defined`, ast.location); 27 } 28 function compileToContent(ast, context) { 29 switch (ast.type) { 30 case "Block": { 31 let currentContext = { 32 externals: context.externals, 33 nameToPos: context.nameToPos, 34 locals: ImmutableList(), 35 size: context.size, 36 }; 37 const statements = []; 38 for (const astStatement of ast.statements) { 39 if ((astStatement.type === "LetStatement" || 40 astStatement.type === "DefunStatement") && 41 astStatement.exported) { 42 throw new ICompileError("Exports aren't allowed in blocks", astStatement.location); 43 } 44 const [statement, newContext] = innerCompileAst(astStatement, currentContext); 45 statements.push(statement); 46 currentContext = newContext; 47 } 48 return [expression.eBlock(statements), context]; 49 } 50 case "Program": { 51 let currentContext = { 52 externals: context.externals, 53 nameToPos: context.nameToPos, 54 locals: ImmutableList(), 55 size: context.size, 56 }; 57 const statements = []; 58 const exports = []; 59 for (const astStatement of ast.statements) { 60 const [statement, newContext] = innerCompileAst(astStatement, currentContext); 61 statements.push(statement); 62 if ((astStatement.type === "LetStatement" || 63 astStatement.type === "DefunStatement") && 64 astStatement.exported) { 65 exports.push(astStatement.variable.value); 66 } 67 currentContext = newContext; 68 } 69 return [expression.eProgram(statements, exports), currentContext]; 70 } 71 case "DefunStatement": 72 case "LetStatement": { 73 const newContext = { 74 externals: context.externals, 75 nameToPos: context.nameToPos.set(ast.variable.value, context.size), 76 locals: context.locals.push(ast.variable.value), 77 size: context.size + 1, 78 }; 79 return [ 80 expression.eLetStatement(ast.variable.value, innerCompileAst(ast.value, context)[0]), 81 newContext, 82 ]; 83 } 84 case "Call": { 85 return [ 86 expression.eCall(innerCompileAst(ast.fn, context)[0], ast.args.map((arg) => innerCompileAst(arg, context)[0])), 87 context, 88 ]; 89 } 90 case "InfixCall": { 91 return [ 92 expression.eCall({ ast, ...resolveName(context, ast, infixFunctions[ast.op]) }, ast.args.map((arg) => innerCompileAst(arg, context)[0])), 93 context, 94 ]; 95 } 96 case "UnaryCall": 97 return [ 98 expression.eCall({ ast, ...resolveName(context, ast, unaryFunctions[ast.op]) }, [innerCompileAst(ast.arg, context)[0]]), 99 context, 100 ]; 101 case "Pipe": 102 return [ 103 expression.eCall(innerCompileAst(ast.fn, context)[0], [ 104 innerCompileAst(ast.leftArg, context)[0], 105 ...ast.rightArgs.map((arg) => innerCompileAst(arg, context)[0]), 106 ]), 107 context, 108 ]; 109 case "DotLookup": 110 return [ 111 expression.eCall({ ast, ...resolveName(context, ast, INDEX_LOOKUP_FUNCTION) }, [ 112 innerCompileAst(ast.arg, context)[0], 113 { ast, ...expression.eValue(vString(ast.key)) }, 114 ]), 115 context, 116 ]; 117 case "BracketLookup": 118 return [ 119 expression.eCall({ ast, ...resolveName(context, ast, INDEX_LOOKUP_FUNCTION) }, [ 120 innerCompileAst(ast.arg, context)[0], 121 innerCompileAst(ast.key, context)[0], 122 ]), 123 context, 124 ]; 125 case "Lambda": { 126 let newNameToPos = context.nameToPos; 127 const args = []; 128 for (let i = 0; i < ast.args.length; i++) { 129 const astArg = ast.args[i]; 130 let arg; 131 if (astArg.type === "Identifier") { 132 arg = { name: astArg.value, annotation: undefined }; 133 } 134 else if (astArg.type === "IdentifierWithAnnotation") { 135 arg = { 136 name: astArg.variable, 137 annotation: innerCompileAst(astArg.annotation, context)[0], 138 }; 139 } 140 else { 141 throw new ICompileError(`Internal error: argument ${astArg.type} is not an identifier`, ast.location); 142 } 143 args.push(arg); 144 newNameToPos = newNameToPos.set(arg.name, context.size + i); 145 } 146 const innerContext = { 147 externals: context.externals, 148 nameToPos: newNameToPos, 149 locals: ImmutableList(), 150 size: context.size + ast.args.length, 151 }; 152 return [ 153 expression.eLambda(ast.name, args, innerCompileAst(ast.body, innerContext)[0]), 154 context, 155 ]; 156 } 157 case "KeyValue": 158 return [ 159 expression.eArray([ 160 innerCompileAst(ast.key, context)[0], 161 innerCompileAst(ast.value, context)[0], 162 ]), 163 context, 164 ]; 165 case "Ternary": 166 return [ 167 expression.eTernary(innerCompileAst(ast.condition, context)[0], innerCompileAst(ast.trueExpression, context)[0], innerCompileAst(ast.falseExpression, context)[0]), 168 context, 169 ]; 170 case "Array": 171 return [ 172 expression.eArray(ast.elements.map((statement) => innerCompileAst(statement, context)[0])), 173 context, 174 ]; 175 case "Dict": 176 return [ 177 expression.eDict(ast.elements.map((kv) => { 178 if (kv.type === "KeyValue") { 179 return [ 180 innerCompileAst(kv.key, context)[0], 181 innerCompileAst(kv.value, context)[0], 182 ]; 183 } 184 else if (kv.type === "Identifier") { 185 const key = { ast: kv, ...expression.eValue(vString(kv.value)) }; 186 const value = { 187 ast: kv, 188 ...resolveName(context, kv, kv.value), 189 }; 190 return [key, value]; 191 } 192 else { 193 throw new Error(`Internal AST error: unexpected kv ${kv}`); 194 } 195 })), 196 context, 197 ]; 198 case "Boolean": 199 return [expression.eValue(vBool(ast.value)), context]; 200 case "Float": { 201 const value = parseFloat(`${ast.integer}${ast.fractional === null ? "" : `.${ast.fractional}`}${ast.exponent === null ? "" : `e${ast.exponent}`}`); 202 if (Number.isNaN(value)) { 203 throw new ICompileError("Failed to compile a number", ast.location); 204 } 205 return [expression.eValue(vNumber(value)), context]; 206 } 207 case "String": 208 return [expression.eValue(vString(ast.value)), context]; 209 case "Void": 210 return [expression.eVoid(), context]; 211 case "Identifier": { 212 const offset = context.nameToPos.get(ast.value); 213 if (offset === undefined) { 214 return [resolveName(context, ast, ast.value), context]; 215 } 216 else { 217 const result = expression.eResolvedSymbol(ast.value, context.size - 1 - offset); 218 return [result, context]; 219 } 220 } 221 case "UnitValue": { 222 const fromUnitFn = resolveName(context, ast, `fromUnit_${ast.unit}`); 223 return [ 224 expression.eCall({ ast, ...fromUnitFn }, [ 225 innerCompileAst(ast.value, context)[0], 226 ]), 227 context, 228 ]; 229 } 230 case "IdentifierWithAnnotation": 231 throw new ICompileError("Can't compile IdentifierWithAnnotation outside of lambda declaration", ast.location); 232 default: 233 throw new Error(`Unsupported AST value ${ast}`); 234 } 235 } 236 function innerCompileAst(ast, context) { 237 const [content, newContext] = compileToContent(ast, context); 238 return [ 239 { 240 ast, 241 ...content, 242 }, 243 newContext, 244 ]; 245 } 246 export function compileAst(ast, externals) { 247 try { 248 const [expression] = innerCompileAst(ast, createInitialCompileContext(externals)); 249 return Result.Ok(expression); 250 } 251 catch (err) { 252 if (err instanceof ICompileError) { 253 return Result.Err(err); 254 } 255 throw err; 256 } 257 } 258 //# sourceMappingURL=compile.js.map