|
1 | 1 | import { Component, computed, input } from '@angular/core'; |
2 | | -import { BinaryNode, LeafNode, ParseTree, ParseTreeNode, ParseTreeType, UnaryNode, VariableNode } from '../types'; |
3 | | -import { TreeNodeComponent, TreeNodeDisplay } from './tree-node.component'; |
| 2 | +import { TreeNodeComponent } from './tree-node.component'; |
| 3 | +import { CCGNode, LeafNode, BinaryNode, UnaryNode } from "../types"; |
| 4 | +import { TreeNodeDisplay } from "./tree-node.component"; |
4 | 5 |
|
5 | | -const TreeTypeDisplay: Record<ParseTreeType, string> = { |
6 | | - [ParseTreeType.CCG_DERIVATION]: 'CCG Derivation', |
7 | | - [ParseTreeType.CCG_TERM]: 'CCG Term', |
8 | | - [ParseTreeType.CORRECTED_CCG_TERM]: 'Corrected CCG Term', |
9 | | - [ParseTreeType.FIRST_LLF]: 'First LLF' |
10 | | -}; |
11 | | - |
12 | | - |
13 | | -@Component({ |
14 | | - selector: 'la-parse-tree-table', |
15 | | - imports: [TreeNodeComponent], |
16 | | - templateUrl: './parse-tree-table.component.html', |
17 | | - styleUrl: './parse-tree-table.component.scss' |
18 | | -}) |
19 | | -export class ParseTreeTableComponent { |
20 | | - public readonly tree = input.required<ParseTree>(); |
21 | | - |
22 | | - public rootNode = computed(() => this.buildDisplayTree(this.tree().root)); |
23 | | - |
24 | | - public treeType = computed(() => TreeTypeDisplay[this.tree().type] || "Unknown Type"); |
25 | | - |
26 | | - private buildDisplayTree(node: ParseTreeNode): TreeNodeDisplay { |
27 | | - switch (node.type) { |
28 | | - case 'leaf': |
29 | | - return this.buildLeafNode(node); |
30 | | - case 'binary': |
31 | | - return this.buildBinaryNode(node); |
32 | | - case 'unary': |
33 | | - return this.buildUnaryNode(node); |
34 | | - case 'var': |
35 | | - return this.buildVariableNode(node); |
36 | | - } |
37 | | - } |
38 | | - |
39 | | - private buildLeafNode(node: LeafNode): TreeNodeDisplay { |
| 6 | +export function buildDisplayTree(node: CCGNode): TreeNodeDisplay { |
| 7 | + if (nodeIsLeaf(node)) { |
| 8 | + return buildLeafNode(node); |
| 9 | + } else if (nodeIsBinary(node)) { |
| 10 | + return buildBinaryNode(node); |
| 11 | + } else if (nodeIsUnary(node)) { |
| 12 | + return buildUnaryNode(node); |
| 13 | + } else { |
40 | 14 | return { |
41 | | - type: 'leaf', |
42 | | - content: node.cat, |
43 | 15 | children: [], |
44 | | - leaf: { |
45 | | - tok: node.tok, |
46 | | - lem: node.lem, |
47 | | - pos: node.pos, |
48 | | - ner: node.ner |
49 | | - } |
| 16 | + content: "Unknown Node Type", |
| 17 | + type: 'node' |
50 | 18 | }; |
51 | 19 | } |
| 20 | +} |
52 | 21 |
|
53 | | - private buildVariableNode(node: VariableNode): TreeNodeDisplay { |
54 | | - return { |
55 | | - type: 'var', |
56 | | - content: node.name, |
57 | | - children: [], |
58 | | - var: { |
59 | | - typeInfo: node.typeInfo |
60 | | - } |
61 | | - }; |
62 | | - } |
| 22 | +function nodeIsLeaf(node: CCGNode): node is LeafNode { |
| 23 | + return Array.isArray(node.node); |
| 24 | +} |
63 | 25 |
|
64 | | - private buildBinaryNode(node: BinaryNode): TreeNodeDisplay { |
65 | | - const left = this.buildDisplayTree(node.left); |
66 | | - const right = this.buildDisplayTree(node.right); |
| 26 | +function nodeIsBinary(node: CCGNode): node is BinaryNode { |
| 27 | + return 'children' in node && node.children.length === 2; |
| 28 | +} |
67 | 29 |
|
68 | | - return { |
69 | | - type: 'node', |
70 | | - content: node.cat, |
71 | | - rule: node.rule, |
72 | | - children: [left, right] |
73 | | - }; |
74 | | - } |
| 30 | +function nodeIsUnary(node: CCGNode): node is UnaryNode { |
| 31 | + return 'children' in node && node.children.length === 1; |
| 32 | +} |
75 | 33 |
|
76 | | - private buildUnaryNode(node: UnaryNode): TreeNodeDisplay { |
77 | | - const child = this.buildDisplayTree(node.child); |
| 34 | +function buildLeafNode(node: LeafNode): TreeNodeDisplay { |
| 35 | + const [_rule, tok, lem, pos, ner, cat] = node.node; |
| 36 | + return { |
| 37 | + type: 'leaf', |
| 38 | + content: cat, |
| 39 | + children: [], |
| 40 | + leaf: { tok, lem, pos, ner } |
| 41 | + }; |
| 42 | +} |
| 43 | + |
| 44 | +function buildBinaryNode(node: BinaryNode): TreeNodeDisplay { |
| 45 | + const left = buildDisplayTree(node.children[0]); |
| 46 | + const right = buildDisplayTree(node.children[1]); |
| 47 | + |
| 48 | + const { content, rule } = extractRule(node.node); |
| 49 | + |
| 50 | + return { |
| 51 | + type: 'node', |
| 52 | + content: content, |
| 53 | + rule: rule, |
| 54 | + children: [left, right] |
| 55 | + }; |
| 56 | +} |
| 57 | + |
| 58 | +function buildUnaryNode(node: UnaryNode): TreeNodeDisplay { |
| 59 | + const child = buildDisplayTree(node.children[0]); |
78 | 60 |
|
| 61 | + const { content, rule } = extractRule(node.node); |
| 62 | + |
| 63 | + return { |
| 64 | + type: 'node', |
| 65 | + content: content, |
| 66 | + rule: rule, |
| 67 | + children: [child] |
| 68 | + }; |
| 69 | +} |
| 70 | + |
| 71 | +/** |
| 72 | + * Parses a node string to extract the rule and the content. |
| 73 | + * |
| 74 | + * A node string is usually of the form "A(B)", where a is the rule applied |
| 75 | + * and B is the resulting category. The rule is anything everything before |
| 76 | + * the first parenthesis. Everything within it is the content. For example, |
| 77 | + * in "fa(s:ng-np)", "fa" is the rule and "s:ng-np" is the content. |
| 78 | + * |
| 79 | + * Due to a bug in the CCG parser, sometimes the node string can have |
| 80 | + * multiple layers of parentheses, e.g. fa(((s:ng-np)-(s:ng-np))). |
| 81 | + * function only strips off the first. |
| 82 | + * |
| 83 | + */ |
| 84 | +function extractRule(nodeString: string): { rule: string, content: string; } { |
| 85 | + const firstParen = nodeString.indexOf('('); |
| 86 | + const lastParen = nodeString.lastIndexOf(')'); |
| 87 | + |
| 88 | + // Return a fallback value if the string is not what we expect. |
| 89 | + if (firstParen === -1 || lastParen === -1 || lastParen < firstParen) { |
79 | 90 | return { |
80 | | - type: 'node', |
81 | | - content: node.cat, |
82 | | - rule: node.rule, |
83 | | - children: [child] |
| 91 | + rule: "", |
| 92 | + content: nodeString |
84 | 93 | }; |
85 | 94 | } |
86 | 95 |
|
| 96 | + const rule = nodeString.slice(0, firstParen); |
| 97 | + // Strip off any remaining parentheses due to the CCG parser bug. |
| 98 | + const content = nodeString.slice(firstParen + 1, lastParen).replaceAll('(', '').replaceAll(')', ''); |
| 99 | + |
| 100 | + return { rule, content }; |
| 101 | +} |
| 102 | + |
| 103 | +@Component({ |
| 104 | + selector: 'la-parse-tree-table', |
| 105 | + imports: [TreeNodeComponent], |
| 106 | + templateUrl: './parse-tree-table.component.html', |
| 107 | + styleUrl: './parse-tree-table.component.scss' |
| 108 | +}) |
| 109 | +export class ParseTreeTableComponent { |
| 110 | + public readonly tree = input.required<CCGNode>(); |
| 111 | + |
| 112 | + public displayTree = computed(() => buildDisplayTree(this.tree())); |
87 | 113 | } |
0 commit comments