Skip to content

Commit 3775368

Browse files
Handle single CCG parse tree
1 parent 5383b45 commit 3775368

4 files changed

Lines changed: 124 additions & 128 deletions

File tree

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<div class="border border-secondary rounded overflow-hidden">
22
<div class="card-header text-bg-secondary p-3">
3-
<h3 class="h5 fw-bold">{{ treeType() }}</h3>
3+
<h3 class="h5 fw-bold">--tree type--</h3>
44
</div>
55
<div class="p-4">
66
<div class="d-flex border border-secondary">
7-
<la-tree-node [node]="rootNode()" />
7+
<la-tree-node [node]="displayTree()" />
88
</div>
99
</div>
1010
</div>
Lines changed: 97 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,113 @@
11
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";
45

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 {
4014
return {
41-
type: 'leaf',
42-
content: node.cat,
4315
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'
5018
};
5119
}
20+
}
5221

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+
}
6325

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+
}
6729

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+
}
7533

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]);
7860

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) {
7990
return {
80-
type: 'node',
81-
content: node.cat,
82-
rule: node.rule,
83-
children: [child]
91+
rule: "",
92+
content: nodeString
8493
};
8594
}
8695

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()));
87113
}
Lines changed: 17 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,22 @@
1-
// TODO: update these types to match the actual data structure returned
2-
// by LangPro's syntactic parser.
1+
export type LeafNode = {
2+
// Fixed order: rule, token, lemma, POS tag, NER tag, category.
3+
node: [string, string, string, string, string, string];
4+
};
35

4-
export interface ParseResult {
5-
parser: string;
6-
sentences: ParsedSentence[];
7-
}
6+
export type UnaryNode = {
7+
node: string;
8+
children: [CCGNode];
9+
};
810

9-
export interface ParsedSentence {
10-
id: string;
11-
text: string;
12-
parses: ParseTree[];
13-
}
11+
export type BinaryNode = {
12+
node: string;
13+
children: [CCGNode, CCGNode];
14+
};
1415

15-
export interface ParseTree {
16-
type: ParseTreeType;
17-
root: ParseTreeNode;
18-
}
16+
export type CCGNode = LeafNode | UnaryNode | BinaryNode;
1917

20-
export enum ParseTreeType {
21-
CCG_DERIVATION,
22-
CCG_TERM,
23-
CORRECTED_CCG_TERM,
24-
FIRST_LLF
25-
}
2618

27-
export type ParseTreeNode = LeafNode | UnaryNode | BinaryNode | VariableNode;
28-
29-
export interface LeafNode {
30-
type: 'leaf';
31-
lem: string;
32-
tok: string;
33-
pos: string;
34-
ner: string;
35-
cat: string;
36-
}
37-
38-
export interface UnaryNode {
39-
type: 'unary';
40-
child: ParseTreeNode;
41-
cat: string;
42-
rule?: string;
43-
}
44-
45-
export interface BinaryNode {
46-
type: 'binary';
47-
left: ParseTreeNode;
48-
right: ParseTreeNode;
49-
cat: string; // e.g. "NP", "VP<dcl>"
50-
rule?: string; // e.g., "fa", "ba"
51-
}
52-
53-
export interface VariableNode {
54-
type: 'var';
55-
name: string;
56-
typeInfo: string;
57-
}
19+
export type ParseResponseData = {
20+
ccg_trees: CCGNode[];
21+
proofs: unknown[];
22+
};

frontend/src/app/services/parse.service.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ import { HttpClient } from '@angular/common/http';
22
import { inject, Injectable } from '@angular/core';
33
import { Subject, switchMap, catchError, of } from 'rxjs';
44
import { ParseInput } from '@/annotate/annotation-input/annotation-input.component';
5+
import { ParseResponseData } from '@/annotate/annotation-parse-results/types';
56

6-
export type ParseResponse = any;
7+
8+
export type ParseResponse = {
9+
data: ParseResponseData;
10+
error: string | null;
11+
};
712

813
@Injectable({
914
providedIn: 'root'
@@ -18,8 +23,8 @@ export class ParseService {
1823
this.http.post<ParseResponse>("/api/problem/parse", form).pipe(
1924
catchError((error) => {
2025
console.error(`Error parsing problem:`, error);
21-
return of(null);
22-
})
26+
return of({ data: null, error: error.message || "An error occurred while parsing the problem." });
27+
}),
2328
)
2429
)
2530
);

0 commit comments

Comments
 (0)