forked from github/quartz
165 lines
5.7 KiB
TypeScript
165 lines
5.7 KiB
TypeScript
import { BinaryExpr, Expr, Literal, Span, UnaryExpr } from "./ast"
|
|
|
|
export type JumpInstruction = {
|
|
op: "jump" | "jump_if_false" | "jump_if_true"
|
|
target: number
|
|
span: Span
|
|
}
|
|
|
|
export type Instruction =
|
|
| { op: "const"; literal: Literal; span: Span }
|
|
| { op: "ident"; name: string; span: Span }
|
|
| { op: "load_formula"; name: string; span: Span }
|
|
| { op: "load_formula_index"; span: Span }
|
|
| { op: "member"; property: string; span: Span }
|
|
| { op: "index"; span: Span }
|
|
| { op: "list"; count: number; span: Span }
|
|
| { op: "unary"; operator: UnaryExpr["operator"]; span: Span }
|
|
| { op: "binary"; operator: BinaryExpr["operator"]; span: Span }
|
|
| { op: "to_bool"; span: Span }
|
|
| { op: "call_global"; name: string; argc: number; span: Span }
|
|
| { op: "call_method"; name: string; argc: number; span: Span }
|
|
| { op: "call_dynamic"; span: Span }
|
|
| { op: "filter"; program: ProgramIR | null; span: Span }
|
|
| { op: "map"; program: ProgramIR | null; span: Span }
|
|
| { op: "reduce"; program: ProgramIR | null; initial: ProgramIR | null; span: Span }
|
|
| JumpInstruction
|
|
|
|
export type ProgramIR = { instructions: Instruction[]; span: Span }
|
|
|
|
const compileExpr = (expr: Expr, out: Instruction[]) => {
|
|
switch (expr.type) {
|
|
case "Literal":
|
|
out.push({ op: "const", literal: expr, span: expr.span })
|
|
return
|
|
case "Identifier":
|
|
out.push({ op: "ident", name: expr.name, span: expr.span })
|
|
return
|
|
case "UnaryExpr":
|
|
compileExpr(expr.argument, out)
|
|
out.push({ op: "unary", operator: expr.operator, span: expr.span })
|
|
return
|
|
case "BinaryExpr":
|
|
compileExpr(expr.left, out)
|
|
compileExpr(expr.right, out)
|
|
out.push({ op: "binary", operator: expr.operator, span: expr.span })
|
|
return
|
|
case "LogicalExpr": {
|
|
if (expr.operator === "&&") {
|
|
compileExpr(expr.left, out)
|
|
const jumpFalse: JumpInstruction = { op: "jump_if_false", target: -1, span: expr.span }
|
|
out.push(jumpFalse)
|
|
compileExpr(expr.right, out)
|
|
out.push({ op: "to_bool", span: expr.span })
|
|
const jumpEnd: JumpInstruction = { op: "jump", target: -1, span: expr.span }
|
|
out.push(jumpEnd)
|
|
const falseTarget = out.length
|
|
jumpFalse.target = falseTarget
|
|
out.push({
|
|
op: "const",
|
|
literal: { type: "Literal", kind: "boolean", value: false, span: expr.span },
|
|
span: expr.span,
|
|
})
|
|
jumpEnd.target = out.length
|
|
return
|
|
}
|
|
compileExpr(expr.left, out)
|
|
const jumpTrue: JumpInstruction = { op: "jump_if_true", target: -1, span: expr.span }
|
|
out.push(jumpTrue)
|
|
compileExpr(expr.right, out)
|
|
out.push({ op: "to_bool", span: expr.span })
|
|
const jumpEnd: JumpInstruction = { op: "jump", target: -1, span: expr.span }
|
|
out.push(jumpEnd)
|
|
const trueTarget = out.length
|
|
jumpTrue.target = trueTarget
|
|
out.push({
|
|
op: "const",
|
|
literal: { type: "Literal", kind: "boolean", value: true, span: expr.span },
|
|
span: expr.span,
|
|
})
|
|
jumpEnd.target = out.length
|
|
return
|
|
}
|
|
case "MemberExpr":
|
|
if (expr.object.type === "Identifier" && expr.object.name === "formula") {
|
|
out.push({ op: "load_formula", name: expr.property, span: expr.span })
|
|
return
|
|
}
|
|
compileExpr(expr.object, out)
|
|
out.push({ op: "member", property: expr.property, span: expr.span })
|
|
return
|
|
case "IndexExpr":
|
|
if (expr.object.type === "Identifier" && expr.object.name === "formula") {
|
|
compileExpr(expr.index, out)
|
|
out.push({ op: "load_formula_index", span: expr.span })
|
|
return
|
|
}
|
|
compileExpr(expr.object, out)
|
|
compileExpr(expr.index, out)
|
|
out.push({ op: "index", span: expr.span })
|
|
return
|
|
case "ListExpr":
|
|
for (const element of expr.elements) {
|
|
compileExpr(element, out)
|
|
}
|
|
out.push({ op: "list", count: expr.elements.length, span: expr.span })
|
|
return
|
|
case "CallExpr": {
|
|
if (expr.callee.type === "Identifier") {
|
|
for (const arg of expr.args) {
|
|
compileExpr(arg, out)
|
|
}
|
|
out.push({
|
|
op: "call_global",
|
|
name: expr.callee.name,
|
|
argc: expr.args.length,
|
|
span: expr.span,
|
|
})
|
|
return
|
|
}
|
|
if (expr.callee.type === "MemberExpr") {
|
|
const method = expr.callee.property
|
|
if (method === "filter" || method === "map" || method === "reduce") {
|
|
compileExpr(expr.callee.object, out)
|
|
const exprArg = expr.args[0]
|
|
const program = exprArg ? compileExpression(exprArg) : null
|
|
if (method === "filter") {
|
|
out.push({ op: "filter", program, span: expr.span })
|
|
return
|
|
}
|
|
if (method === "map") {
|
|
out.push({ op: "map", program, span: expr.span })
|
|
return
|
|
}
|
|
const initialArg = expr.args[1]
|
|
const initial = initialArg ? compileExpression(initialArg) : null
|
|
out.push({ op: "reduce", program, initial, span: expr.span })
|
|
return
|
|
}
|
|
compileExpr(expr.callee.object, out)
|
|
for (const arg of expr.args) {
|
|
compileExpr(arg, out)
|
|
}
|
|
out.push({ op: "call_method", name: method, argc: expr.args.length, span: expr.span })
|
|
return
|
|
}
|
|
compileExpr(expr.callee, out)
|
|
out.push({ op: "call_dynamic", span: expr.span })
|
|
return
|
|
}
|
|
case "ErrorExpr":
|
|
out.push({
|
|
op: "const",
|
|
literal: { type: "Literal", kind: "null", value: null, span: expr.span },
|
|
span: expr.span,
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
export const compileExpression = (expr: Expr): ProgramIR => {
|
|
const instructions: Instruction[] = []
|
|
compileExpr(expr, instructions)
|
|
return { instructions, span: expr.span }
|
|
}
|