@the-little-books/little
Version:
101 lines (94 loc) • 2.96 kB
text/typescript
import * as Env from "../env"
import * as Node from "../node"
import * as Pattern from "../pattern"
function put_back_entry(env: Env.Env, entry: Env.ReturnEntry): Env.Env {
const { nodes, index } = entry
// NOTE Handle proper tail call.
// - put back entry unless at the tail of its nodes.
if (index + 1 < nodes.length) {
env.return_stack.push({ ...entry, index: index + 1 })
}
return env
}
export async function next(env: Env.Env): Promise<Node.Node> {
const entry = env.return_stack.pop()
if (entry === undefined) {
throw new Error("The return_stack is empty.")
}
const { nodes, index } = entry
const node = nodes[index]
if (node.kind === "Node.Element") {
if (node.tag === "jump") {
return await next_jump(env, entry, node)
} else if (node.tag === "call") {
return await next_call(env, entry, node)
} else if (node.tag === "match") {
return await next_match(env, entry, node)
} else {
put_back_entry(env, entry)
return node
}
} else {
put_back_entry(env, entry)
return node
}
}
function find_label_index(nodes: Array<Node.Node>, label: string): number {
return nodes.findIndex(
(node) => node.kind === "Node.Element" && node.attributes.label === label
)
}
async function next_jump(
env: Env.Env,
entry: Env.ReturnEntry,
node: Node.Element
): Promise<Node.Node> {
const module = node.attributes.module || entry.module
const nodes = await env.loader(env.book, module)
const index = node.attributes.label
? find_label_index(nodes, node.attributes.label)
: 0
env.return_stack.push({ module, nodes, index })
return await next(env)
}
async function next_call(
env: Env.Env,
entry: Env.ReturnEntry,
node: Node.Element
): Promise<Node.Node> {
put_back_entry(env, entry)
return await next_jump(env, entry, node)
}
async function next_match(
env: Env.Env,
entry: Env.ReturnEntry,
node: Node.Element
): Promise<Node.Node> {
const top_node = env.node_stack.pop()
// NOTE better log.
// console.log("top_node:", top_node)
if (top_node === undefined) {
throw new Error("Empty env.node_stack.")
}
for (const case_node of node.contents) {
if (case_node.kind === "Node.Element") {
if (case_node.tag === "case") {
const pattern = Pattern.from_node(case_node.contents[0])
const result = Pattern.match(pattern, top_node)
if (result !== null) {
// TODO use `result` to subst pattern variables in body.
const body = case_node.contents.slice(1)
put_back_entry(env, entry)
env.return_stack.push({ ...entry, nodes: body, index: 0 })
return await next(env)
}
} else if (case_node.tag === "default") {
const body = case_node.contents
put_back_entry(env, entry)
env.return_stack.push({ ...entry, nodes: body, index: 0 })
return await next(env)
}
}
}
throw new Error("Mismatch.")
}