UNPKG

@the-little-books/little

Version:

112 lines (107 loc) 3.28 kB
import * as Pattern from "../pattern" import * as Node from "../node" import * as ut from "../ut" export type MatchResult = { [key: string]: Node.Node | Array<Node.Node> } export function match( pattern: Pattern.Pattern, node: Node.Node, result: MatchResult = {} ): null | MatchResult { const matched = match_nodes(pattern, [node], result) if (matched === null) { return null } else { const [_remain_nodes, result] = matched return result } } function match_tag(pattern_tag: Pattern.Tag, tag: string): boolean { if (typeof pattern_tag === "string") { return pattern_tag === tag } else if (pattern_tag instanceof RegExp) { return pattern_tag.test(tag) } else if (pattern_tag instanceof Array) { return pattern_tag.some((p_tag: Pattern.Tag) => match_tag(p_tag, tag)) } else { throw new Error(`unknown pattern_tag: ${pattern_tag} for tag: ${tag}.`) } } // NOTE No side effect on the argument `result`. // - `result` is used as env for name lookup. export function match_nodes( pattern: Pattern.Pattern, nodes: Array<Node.Node>, result: MatchResult = {} ): null | [Array<Node.Node>, MatchResult] { if (pattern.kind === "Pattern.Var") { if (nodes.length === 0) { return null } const [node] = nodes const previous = result[pattern.name] if (previous === undefined) { return [nodes.slice(1), { ...result, [pattern.name]: node }] } else if (ut.equal(previous, node)) { return [nodes.slice(1), result] } else { return null } } else if (pattern.kind === "Pattern.End") { if (nodes.length === 0) { return [[], result] } else { return null } } else if (pattern.kind === "Pattern.ListVar") { const previous = result[pattern.name] if (previous === undefined) { return [[], { ...result, [pattern.name]: nodes }] } else if (ut.equal(previous, nodes)) { return [[], result] } else { return null } } else if (pattern.kind === "Pattern.Element") { if (nodes.length === 0) { return null } const [node] = nodes if (node.kind === "Node.Element") { if (!match_tag(pattern.tag, node.tag)) { return null } else { let new_result: null | MatchResult = { ...result } let remain_nodes = node.contents for (let i = 0; i < pattern.contents.length; i++) { const sub_pattern = pattern.contents[i] const matched = match_nodes(sub_pattern, remain_nodes, new_result) if (matched === null) { return null } const [next_remain_nodes, next_new_result] = matched new_result = next_new_result remain_nodes = next_remain_nodes } return [nodes.slice(1), new_result] } } else { return null } } else if (pattern.kind === "Pattern.Text") { if (nodes.length === 0) { return null } const [node] = nodes if (node.kind === "Node.Text") { if (typeof pattern.value === "string") { return pattern.value === node.value ? [nodes.slice(1), result] : null } else { return pattern.value.test(node.value) ? [nodes.slice(1), result] : null } } else { return null } } else { return null } }