diff --git a/frontend/src/app/annotate/annotation-menu/annotation-menu.component.html b/frontend/src/app/annotate/annotation-menu/annotation-menu.component.html
index 6e926455..dbea7eb0 100644
--- a/frontend/src/app/annotate/annotation-menu/annotation-menu.component.html
+++ b/frontend/src/app/annotate/annotation-menu/annotation-menu.component.html
@@ -1,35 +1,35 @@
-
-
Parser: {{ parseResults.parser }}
- @for (sentence of parseResults.sentences; track sentence.id) {
+ @for (parse of ccgParses; track $index) {
-
- @for (parse of sentence.parses; track $index) {
-
+
+ @for (tree of parse.ccgTrees; track $index) {
+
}
+ } @empty {
+
+ No parse results to display yet. Press the 'Parse and Prove' button to see the parse results.
+
}
diff --git a/frontend/src/app/annotate/annotation-parse-results/annotation-parse-results.component.ts b/frontend/src/app/annotate/annotation-parse-results/annotation-parse-results.component.ts
index 86b635a0..8f34ff67 100644
--- a/frontend/src/app/annotate/annotation-parse-results/annotation-parse-results.component.ts
+++ b/frontend/src/app/annotate/annotation-parse-results/annotation-parse-results.component.ts
@@ -1,31 +1,51 @@
-import { Component, DestroyRef, inject, OnInit } from "@angular/core";
+import { Component, DestroyRef, inject } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
-import { mockResult } from "./mockParseResult";
import { ParseService } from "@/services/parse.service";
import { ParseTreeTableComponent } from "./parse-tree-table/parse-tree-table.component";
import { NgbAccordionModule } from "@ng-bootstrap/ng-bootstrap";
+import { map } from "rxjs";
+import { CommonModule } from "@angular/common";
+import { CCGNode, CCGParse } from "@/types";
+
+export interface TreeWithType {
+ type: string;
+ tree: CCGNode;
+}
+
+interface UnfoldedParseResult {
+ sentence: string;
+ ccgTrees: TreeWithType[];
+}
+
+function unfoldParseResult(parse: CCGParse): UnfoldedParseResult {
+ const { ccg_tree, ccg_term, corr_term, llf } = parse.ccg_trees;
+ // TODO: Reintroduce the other trees once they are serialized properly.
+ return {
+ ...parse,
+ ccgTrees: [
+ { type: "CCG Tree", tree: ccg_tree },
+ // { type: "CCG Term", tree: ccg_term },
+ // { type: "Corrected CCG Term", tree: corr_term },
+ // { type: "Lambda Logical Form", tree: llf }
+ ]
+ };
+}
@Component({
selector: "la-annotation-parse-results",
standalone: true,
- imports: [ParseTreeTableComponent, NgbAccordionModule],
+ imports: [ParseTreeTableComponent, NgbAccordionModule, CommonModule],
templateUrl: "./annotation-parse-results.component.html",
styleUrl: "./annotation-parse-results.component.scss",
})
-export class AnnotationParseResultsComponent implements OnInit {
+export class AnnotationParseResultsComponent {
private destroyRef = inject(DestroyRef);
private parseService = inject(ParseService);
- public parseResults = mockResult;
-
- ngOnInit(): void {
- // Subscription needed to ensure a request is actually made.
- this.parseService.parse$
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((response) => {
- console.log("Parse response:", response);
- this.parseResults = response.data.ccg_trees;
- });
- }
+ public parseResults$ = this.parseService.parse$
+ .pipe(
+ map(response => response?.data?.ccg_parses.map(parse => unfoldParseResult(parse)) ?? null),
+ takeUntilDestroyed(this.destroyRef)
+ );
}
diff --git a/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.html b/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.html
index 68acf091..cdac489a 100644
--- a/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.html
+++ b/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.html
@@ -1,10 +1,10 @@
-
+
diff --git a/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.scss b/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.scss
index e69de29b..1bdf1e31 100644
--- a/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.scss
+++ b/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.scss
@@ -0,0 +1,3 @@
+:host {
+ width: fit-content;
+}
diff --git a/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.spec.ts b/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.spec.ts
index 52537594..d91fe68a 100644
--- a/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.spec.ts
+++ b/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.spec.ts
@@ -1,17 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ParseTreeTableComponent } from './parse-tree-table.component';
-import { ParseTree, ParseTreeType } from '../types';
+import { TreeWithType } from '../annotation-parse-results.component';
-const mockTree: ParseTree = {
- type: ParseTreeType.CCG_DERIVATION,
- root: {
- type: "leaf",
- lem: "dog",
- tok: "Dog",
- pos: "NN",
- ner: "O",
- cat: "N"
+const mockTree: TreeWithType = {
+ type: "CCG Tree",
+ tree: {
+ node: ["NP", "The", "the", "DT", "O", "NP"],
}
}
diff --git a/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.ts b/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.ts
index b9b8f69f..2b647d82 100644
--- a/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.ts
+++ b/frontend/src/app/annotate/annotation-parse-results/parse-tree-table/parse-tree-table.component.ts
@@ -1,87 +1,115 @@
import { Component, computed, input } from '@angular/core';
-import { BinaryNode, LeafNode, ParseTree, ParseTreeNode, ParseTreeType, UnaryNode, VariableNode } from '../types';
-import { TreeNodeComponent, TreeNodeDisplay } from './tree-node.component';
+import { TreeNodeComponent } from './tree-node.component';
+import { CCGNode, LeafNode, BinaryNode, UnaryNode } from "@/types";
+import { TreeNodeDisplay } from "./tree-node.component";
+import { TreeWithType } from '../annotation-parse-results.component';
-const TreeTypeDisplay: Record
= {
- [ParseTreeType.CCG_DERIVATION]: 'CCG Derivation',
- [ParseTreeType.CCG_TERM]: 'CCG Term',
- [ParseTreeType.CORRECTED_CCG_TERM]: 'Corrected CCG Term',
- [ParseTreeType.FIRST_LLF]: 'First LLF'
-};
-
-
-@Component({
- selector: 'la-parse-tree-table',
- imports: [TreeNodeComponent],
- templateUrl: './parse-tree-table.component.html',
- styleUrl: './parse-tree-table.component.scss'
-})
-export class ParseTreeTableComponent {
- public readonly tree = input.required();
-
- public rootNode = computed(() => this.buildDisplayTree(this.tree().root));
-
- public treeType = computed(() => TreeTypeDisplay[this.tree().type] || "Unknown Type");
-
- private buildDisplayTree(node: ParseTreeNode): TreeNodeDisplay {
- switch (node.type) {
- case 'leaf':
- return this.buildLeafNode(node);
- case 'binary':
- return this.buildBinaryNode(node);
- case 'unary':
- return this.buildUnaryNode(node);
- case 'var':
- return this.buildVariableNode(node);
- }
- }
-
- private buildLeafNode(node: LeafNode): TreeNodeDisplay {
+export function buildDisplayTree(node: CCGNode): TreeNodeDisplay {
+ if (nodeIsLeaf(node)) {
+ return buildLeafNode(node);
+ } else if (nodeIsBinary(node)) {
+ return buildBinaryNode(node);
+ } else if (nodeIsUnary(node)) {
+ return buildUnaryNode(node);
+ } else {
return {
- type: 'leaf',
- content: node.cat,
children: [],
- leaf: {
- tok: node.tok,
- lem: node.lem,
- pos: node.pos,
- ner: node.ner
- }
+ content: "Unknown Node Type",
+ type: 'node'
};
}
+}
- private buildVariableNode(node: VariableNode): TreeNodeDisplay {
- return {
- type: 'var',
- content: node.name,
- children: [],
- var: {
- typeInfo: node.typeInfo
- }
- };
- }
+function nodeIsLeaf(node: CCGNode): node is LeafNode {
+ return !('children' in node);
+}
- private buildBinaryNode(node: BinaryNode): TreeNodeDisplay {
- const left = this.buildDisplayTree(node.left);
- const right = this.buildDisplayTree(node.right);
+function nodeIsBinary(node: CCGNode): node is BinaryNode {
+ return 'children' in node && node.children.length === 2;
+}
- return {
- type: 'node',
- content: node.cat,
- rule: node.rule,
- children: [left, right]
- };
- }
+function nodeIsUnary(node: CCGNode): node is UnaryNode {
+ return 'children' in node && node.children.length === 1;
+}
- private buildUnaryNode(node: UnaryNode): TreeNodeDisplay {
- const child = this.buildDisplayTree(node.child);
+function buildLeafNode(node: LeafNode): TreeNodeDisplay {
+ const [_rule, tok, lem, pos, ner, cat] = node.node;
+ return {
+ type: 'leaf',
+ content: cat,
+ children: [],
+ leaf: { tok, lem, pos, ner }
+ };
+}
+
+function buildBinaryNode(node: BinaryNode): TreeNodeDisplay {
+ const left = buildDisplayTree(node.children[0]);
+ const right = buildDisplayTree(node.children[1]);
+
+ const { content, rule } = extractRule(node.node);
+
+ return {
+ type: 'node',
+ content: content,
+ rule: rule,
+ children: [left, right]
+ };
+}
+
+function buildUnaryNode(node: UnaryNode): TreeNodeDisplay {
+ const child = buildDisplayTree(node.children[0]);
+ const { content, rule } = extractRule(node.node);
+
+ return {
+ type: 'node',
+ content: content,
+ rule: rule,
+ children: [child]
+ };
+}
+
+/**
+ * Parses a node string to extract the rule and the content.
+ *
+ * A node string is usually of the form "A(B)", where a is the rule applied
+ * and B is the resulting category. The rule is anything everything before
+ * the first parenthesis. Everything within it is the content. For example,
+ * in "fa(s:ng-np)", "fa" is the rule and "s:ng-np" is the content.
+ *
+ * Due to a bug in the CCG parser, sometimes the node string can have
+ * multiple layers of parentheses, e.g. fa(((s:ng-np)-(s:ng-np))).
+ * function only strips off the first.
+ *
+ */
+function extractRule(nodeString: string): { rule: string, content: string; } {
+ const firstParen = nodeString.indexOf('(');
+ const lastParen = nodeString.lastIndexOf(')');
+
+ // Return a fallback value if the string is not what we expect.
+ if (firstParen === -1 || lastParen === -1 || lastParen < firstParen) {
return {
- type: 'node',
- content: node.cat,
- rule: node.rule,
- children: [child]
+ rule: "",
+ content: nodeString
};
}
+ const rule = nodeString.slice(0, firstParen);
+ // Strip off any remaining parentheses due to the CCG parser bug.
+ const content = nodeString.slice(firstParen + 1, lastParen).replaceAll('(', '').replaceAll(')', '');
+
+ return { rule, content };
+}
+
+@Component({
+ selector: 'la-parse-tree-table',
+ imports: [TreeNodeComponent],
+ templateUrl: './parse-tree-table.component.html',
+ styleUrl: './parse-tree-table.component.scss'
+})
+export class ParseTreeTableComponent {
+ public readonly tree = input.required();
+
+ public displayTree = computed(() => buildDisplayTree(this.tree().tree));
+ public treeType = computed(() => this.tree().type);
}
diff --git a/frontend/src/app/annotate/annotation-parse-results/types.ts b/frontend/src/app/annotate/annotation-parse-results/types.ts
deleted file mode 100644
index 751fc3d5..00000000
--- a/frontend/src/app/annotate/annotation-parse-results/types.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-// TODO: update these types to match the actual data structure returned
-// by LangPro's syntactic parser.
-
-export interface ParseResult {
- parser: string;
- sentences: ParsedSentence[];
-}
-
-export interface ParsedSentence {
- id: string;
- text: string;
- parses: ParseTree[];
-}
-
-export interface ParseTree {
- type: ParseTreeType;
- root: ParseTreeNode;
-}
-
-export enum ParseTreeType {
- CCG_DERIVATION,
- CCG_TERM,
- CORRECTED_CCG_TERM,
- FIRST_LLF
-}
-
-export type ParseTreeNode = LeafNode | UnaryNode | BinaryNode | VariableNode;
-
-export interface LeafNode {
- type: 'leaf';
- lem: string;
- tok: string;
- pos: string;
- ner: string;
- cat: string;
-}
-
-export interface UnaryNode {
- type: 'unary';
- child: ParseTreeNode;
- cat: string;
- rule?: string;
-}
-
-export interface BinaryNode {
- type: 'binary';
- left: ParseTreeNode;
- right: ParseTreeNode;
- cat: string; // e.g. "NP", "VP"
- rule?: string; // e.g., "fa", "ba"
-}
-
-export interface VariableNode {
- type: 'var';
- name: string;
- typeInfo: string;
-}
diff --git a/frontend/src/app/annotate/parse-tree/parse-svg.component.ts b/frontend/src/app/annotate/parse-tree/parse-svg.component.ts
index 48e25295..16bcddaa 100644
--- a/frontend/src/app/annotate/parse-tree/parse-svg.component.ts
+++ b/frontend/src/app/annotate/parse-tree/parse-svg.component.ts
@@ -1,7 +1,7 @@
import { Component, ChangeDetectorRef, ElementRef, Input, ViewChild, afterNextRender} from '@angular/core';
import { CommonModule } from "@angular/common";
import { Subject } from "rxjs";
-import { CCGTerm, Dimensions } from '@/types';
+import { Dimensions } from '@/types';
import { ParseTree } from './parse-tree.component';
import { Tree } from "@/tree";
import svgPanZoom from 'svg-pan-zoom';
@@ -35,5 +35,5 @@ export class ParseSVG {
}
@Input()
- tree: Tree = Tree.empty();
+ tree: Tree = Tree.empty();
}
diff --git a/frontend/src/app/annotate/parse-tree/parse-term.component.ts b/frontend/src/app/annotate/parse-tree/parse-term.component.ts
index 772abb3a..dfcd778c 100644
--- a/frontend/src/app/annotate/parse-tree/parse-term.component.ts
+++ b/frontend/src/app/annotate/parse-tree/parse-term.component.ts
@@ -1,5 +1,5 @@
import { Component, ElementRef, Input, Output, ViewChild, EventEmitter } from '@angular/core';
-import { CCGTerm, Dimensions } from '@/types';
+import { Dimensions } from '@/types';
@Component({
selector: "[parse-term]",
@@ -19,7 +19,7 @@ export class ParseTerm {
public bg?: string;
@Input()
- public term: CCGTerm = [];
+ public term: string[] = [];
@Input()
public end: boolean = false;
diff --git a/frontend/src/app/annotate/parse-tree/parse-tree.component.ts b/frontend/src/app/annotate/parse-tree/parse-tree.component.ts
index 9b22fad8..eef27499 100644
--- a/frontend/src/app/annotate/parse-tree/parse-tree.component.ts
+++ b/frontend/src/app/annotate/parse-tree/parse-tree.component.ts
@@ -1,6 +1,6 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { ParseTerm } from './parse-term.component';
-import { Dimensions, CCGTerm } from '@/types';
+import { Dimensions } from '@/types';
import { TreeNode } from "@/tree";
@Component({
@@ -14,7 +14,7 @@ export class ParseTree {
expanded: boolean = true;
@Input()
- treeNode: TreeNode = {value: [], children: []};
+ treeNode: TreeNode = {value: [], children: []};
levelHeight = 40;
diff --git a/frontend/src/app/services/parse.service.ts b/frontend/src/app/services/parse.service.ts
index 7b92bf49..bbf3a5da 100644
--- a/frontend/src/app/services/parse.service.ts
+++ b/frontend/src/app/services/parse.service.ts
@@ -1,26 +1,41 @@
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
-import { Subject, switchMap, catchError, of } from 'rxjs';
+import { Subject, switchMap, catchError, of, merge, map } from 'rxjs';
import { ParseInput } from '@/annotate/annotation-input/annotation-input.component';
+import { ProblemService } from './problem.service';
+import { ParseResponseData } from '@/types';
-export type ParseResponse = any;
+
+export type ParseResponse = {
+ data: ParseResponseData;
+ error: string | null;
+};
@Injectable({
providedIn: 'root'
})
export class ParseService {
private http = inject(HttpClient);
+ private problemService = inject(ProblemService);
public submit$ = new Subject();
- public parse$ = this.submit$.pipe(
+ // Clear parse results when a new problem is loaded.
+ private clearOnNewProblem$ = this.problemService.problemResponse$.pipe(map(() => null));
+
+ private parseResults$ = this.submit$.pipe(
switchMap((form) =>
this.http.post("/api/problem/parse", form).pipe(
catchError((error) => {
console.error(`Error parsing problem:`, error);
- return of(null);
- })
+ return of({ data: null, error: error.message || "An error occurred while parsing the problem." });
+ }),
)
)
);
+
+ public parse$ = merge(
+ this.parseResults$,
+ this.clearOnNewProblem$
+ );
}
diff --git a/frontend/src/app/types.ts b/frontend/src/app/types.ts
index 5e817d7c..9ffd6c00 100644
--- a/frontend/src/app/types.ts
+++ b/frontend/src/app/types.ts
@@ -132,5 +132,36 @@ export interface Dimensions {
height: number;
}
+//
+// Syntactic parse tree types
+//
+
+export type LeafNode = {
+ // Fixed order: rule, token, lemma, POS tag, NER tag, category.
+ node: [string, string, string, string, string, string];
+};
+
+export type UnaryNode = {
+ node: string;
+ children: [CCGNode];
+};
+
+export type BinaryNode = {
+ node: string;
+ children: [CCGNode, CCGNode];
+};
+
+export type CCGNode = LeafNode | UnaryNode | BinaryNode;
+
+export type ParseTreeType = 'ccg_tree' | 'ccg_term' | 'corr_term' | 'llf';
+
+export interface CCGParse {
+ sentence: string;
+ ccg_trees: Record;
+};
+
+export type ParseResponseData = {
+ ccg_parses: CCGParse[];
+ proofs: unknown[];
+};
-export type CCGTerm = string[];