Skip to content

Commit 5899df2

Browse files
committed
Apply proof types in frontend logic (#79)
1 parent c214457 commit 5899df2

6 files changed

Lines changed: 52 additions & 32 deletions

File tree

frontend/src/app/annotate/tableau-svg/tableau-svg.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Component, ChangeDetectorRef, ElementRef, input } from '@angular/core';
22
import { CommonModule } from "@angular/common";
33
import { Subject } from "rxjs";
4-
import { Dimensions } from '@/types';
4+
import { Dimensions, ProofTree } from '@/types';
55
import { TableauTree } from './tableau-tree.component';
66

77
@Component({
@@ -34,5 +34,5 @@ export class TableauSVG {
3434
});
3535
}
3636

37-
public readonly tree = input.required<any>();
37+
public readonly tree = input.required<ProofTree>();
3838
}

frontend/src/app/annotate/tableau-svg/tableau-tree.component.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, Input, Output, EventEmitter } from '@angular/core';
22
import { TableauNode } from './tableau-node.component';
3-
import { Dimensions } from '@/types';
3+
import { Dimensions, ProofTree, ProofNode } from '@/types';
44
import { sum } from "@/util";
55

66
@Component({
@@ -13,13 +13,13 @@ import { sum } from "@/util";
1313
export class TableauTree {
1414
expanded: boolean[] = [];
1515

16-
_tree: any;
16+
_tree: ProofTree = {nodes: []};
1717
@Input()
18-
get tree(): any {
18+
get tree(): ProofTree {
1919
return this._tree;
2020
}
2121

22-
set tree(value: any) {
22+
set tree(value: ProofTree) {
2323
this._tree = value;
2424
this.expanded = value.nodes.map(() => true);
2525
}
@@ -61,7 +61,8 @@ export class TableauTree {
6161
/* Determines the X coordinate of where subtree of index `idx` should be drawn */
6262
subtreePosition(idx: number) {
6363
let widthWithPadding = 1.15 * this.subWidth;
64-
return widthWithPadding * idx - (widthWithPadding / 2) * (this.tree.subtrees.length - 1);
64+
const subtreeCount = this.tree.subtrees!.length;
65+
return widthWithPadding * idx - (widthWithPadding / 2) * (subtreeCount - 1);
6566
}
6667

6768
/* Generates a path definition for linking the end of the current node to subtree index `idx` */
@@ -76,9 +77,9 @@ export class TableauTree {
7677
].join(' ');
7778
}
7879

79-
nodeHeight(node: any) {
80+
nodeHeight(node: ProofNode) {
8081
// note that in this case height includes bottom padding for the node
81-
return node.rule ? 60 : 40;
82+
return 'rule' in node && node.rule ? 60 : 40;
8283
}
8384

8485
totalNodeHeight() {

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TestBed } from '@angular/core/testing';
22

33
import { provideHttpClient } from '@angular/common/http';
44

5+
import { NLTKTree, ProofTree } from '@/types';
56
import { ParseService, nltk2tableau } from './parse.service';
67
import realNltk from '@/shared/mockNltkProof';
78
import realTableau from '@/shared/mockTableauTree';
@@ -23,22 +24,22 @@ describe('ParseService', () => {
2324

2425
describe('nltk2tableau', () => {
2526
it('handles a trivial dead end', () => {
26-
const nltk = {node: 'Model'};
27-
const tableau = {nodes: [{end: true}]};
27+
const nltk: NLTKTree = {node: 'Model'};
28+
const tableau: ProofTree = {nodes: [{end: true}]};
2829
expect(nltk2tableau(nltk)).toEqual(tableau);
2930
});
3031

3132
it('handles a closing end', () => {
32-
const nltk = {node: 'Closed\ncl_subsumption([1, 2])'};
33-
const tableau = {nodes: [{
33+
const nltk: NLTKTree = {node: 'Closed\ncl_subsumption([1, 2])'};
34+
const tableau: ProofTree = {nodes: [{
3435
end: true,
3536
rule: 'cl_subsumption([1, 2])',
3637
}]};
3738
expect(nltk2tableau(nltk)).toEqual(tableau);
3839
});
3940

4041
it('handles nested nodes', () => {
41-
const nltk = {
42+
const nltk: NLTKTree = {
4243
node: '1\nterm\nTrue',
4344
children: [{
4445
node: '2:some_rule([1])\nanotherterm\n[c1]\nFalse',
@@ -53,7 +54,7 @@ describe('nltk2tableau', () => {
5354
}],
5455
}],
5556
};
56-
const tableau = {
57+
const tableau: ProofTree = {
5758
nodes: [{
5859
id: 1,
5960
head: 'term',
@@ -85,15 +86,15 @@ describe('nltk2tableau', () => {
8586
});
8687

8788
it('can handle branching', () => {
88-
const nltk = {
89+
const nltk: NLTKTree = {
8990
node: '1:rule([1, 2])\nterm\n[arg]\nFalse',
9091
children: [{
9192
node: 'Model',
9293
}, {
9394
node: 'Closed\nclosing_rule([1])',
9495
}],
9596
};
96-
const tableau = {
97+
const tableau: ProofTree = {
9798
nodes: [{
9899
id: 1,
99100
rule: 'rule([1, 2])',
@@ -111,8 +112,8 @@ describe('nltk2tableau', () => {
111112
});
112113

113114
it('does not cause a stack overflow', () => {
114-
let nltk: any = {node: 'Model'};
115-
const tableau: any = {nodes: []};
115+
let nltk: NLTKTree = {node: 'Model'};
116+
const tableau: ProofTree = {nodes: []};
116117
for (let i = 0; i < 100000; ++i) {
117118
nltk = {
118119
node: '1\nterm\nFalse',

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { inject, Injectable } from '@angular/core';
33
import { Subject, switchMap, catchError, of, merge, map, share } from 'rxjs';
44
import { ParseInput } from '@/annotate/annotation-input/annotation-input.component';
55
import { ProblemService } from './problem.service';
6-
import { ParseResponse } from '@/types';
6+
import {
7+
ParseResponse,
8+
NLTKTree,
9+
ProofNode, InternalProofNode, TerminalProofNode,
10+
ProofTree,
11+
} from '@/types';
712

813

914
@Injectable({
@@ -66,12 +71,14 @@ const closedLeafPattern = /^Closed\n(?<rule>.+)$/;
6671
const internalNodePattern = /^(?<id>\d+):?(?<rule>.+)?(\n\[(?<mod>.+)\])?\n(?<head>.+)(\n\[(?<args>.+)\])?\n(?<sign>True|False)$/;
6772

6873
// Helpers for the functions below.
69-
const emptyTableau = (): any => ({nodes: []});
74+
const emptyTableau = (): ProofTree => ({nodes: []});
7075
const pairNodes = (other: any[]) =>
7176
(node: any, index: number) => [node, other[index]];
7277
const quotes = /'/g;
7378
const unquote = (text: string) => text.replace(quotes, '');
7479

80+
type ProofConversionThunk = [NLTKTree, ProofTree];
81+
7582
/**
7683
* Given a single proof in serialized NLTK.Tree format, return the same proof in
7784
* a more convenient format where each node has explicitly labeled `id`, `rule`,
@@ -80,7 +87,7 @@ const unquote = (text: string) => text.replace(quotes, '');
8087
* This function can handle proof trees of arbitrary depth without causing a
8188
* stack overflow.
8289
*/
83-
export function nltk2tableau(nltk: any) {
90+
export function nltk2tableau(nltk: NLTKTree): ProofTree {
8491
const tree = emptyTableau();
8592
// We will be using trampolining instead of recursion. A general but
8693
// admittedly not very helpful description can be found in the first bullet
@@ -95,7 +102,7 @@ export function nltk2tableau(nltk: any) {
95102
// `nltkNode2tableauNode`, we leave this implicit in the thunk
96103
// representation. It consists only of the arguments that we are passing to
97104
// the function.
98-
const todo = [[nltk, tree]];
105+
const todo: ProofConversionThunk[] = [[nltk, tree]];
99106
// The core of the trampoline. Process one thunk at a time. We know we are
100107
// done when the list is empty.
101108
while (todo.length) {
@@ -105,7 +112,7 @@ export function nltk2tableau(nltk: any) {
105112
// guaranteed to be nonempty on the next line, hence the silencing
106113
// comment.
107114
// @ts-ignore
108-
const task: [any, any] = todo.pop();
115+
const task = todo.pop() as ProofConversionThunk;
109116
// Processing a thunk might produce zero or more new thunks, depending
110117
// on the number of children of the processed node.
111118
const newTasks = nltkNode2tableauNode(...task);
@@ -120,20 +127,21 @@ export function nltk2tableau(nltk: any) {
120127
* trampoline (in `nltk2tableau`) and returns thunks instead of recursing into
121128
* child nodes.
122129
*/
123-
function nltkNode2tableauNode(nltk: any, tree: any) {
130+
function nltkNode2tableauNode(nltk: NLTKTree, tree: ProofTree):
131+
ProofConversionThunk[] {
124132
// `nltk` is in a strictly nested format: each node contains its children.
125133
// Our own format does not follow this rule: chains of nodes with single
126134
// children are stored as adjacent elements in an array. `tree` is the
127135
// object that contains this array (`tree.nodes`). When there is a
128136
// bifurcation, the subtrees are stored inside `tree` rather than in the
129137
// bifurcating node.
130-
const node: any = parseNltkNode(nltk.node);
138+
const node = parseNltkNode(nltk.node);
131139
tree.nodes.push(node);
132140
// With the node itself having been decoded, now comes the part where a
133141
// "normal" function would recurse and where we return follow-up thunks
134142
// instead.
135143
if (!nltk.children || !nltk.children.length) {
136-
node.end = true;
144+
(node as TerminalProofNode).end = true;
137145
// Leaf node. No recursion, no thunks.
138146
return [];
139147
}
@@ -151,17 +159,19 @@ function nltkNode2tableauNode(nltk: any, tree: any) {
151159
// We essentially do a `_.zip(nltk.children, tree.subtrees)` in order to
152160
// create the thunks. I could have fumbled with `Iterator.zip` instead, but
153161
// that feels like more hassle and the polyfill would be huge.
154-
return nltk.children.map(pairNodes(tree.subtrees));
162+
return nltk.children.map(pairNodes(tree.subtrees)) as ProofConversionThunk[];
155163
}
156164

157165
/**
158166
* Decode the payload of a serialized nltk.Tree node, where all information is
159167
* combined in a single string, back to an object with explicitly labeled parts.
160168
*/
161-
function parseNltkNode(text: string) {
169+
function parseNltkNode(text: string): ProofNode {
162170
if (openLeafPattern.test(text)) return {};
163171
let match;
164-
if (match = text.match(closedLeafPattern)) return match.groups;
172+
if (match = text.match(closedLeafPattern)) {
173+
return match.groups as unknown as ProofNode;
174+
}
165175
match = text.match(internalNodePattern);
166176
if (!match || !match.groups) return {head: text};
167177
const result: any = {

frontend/src/app/shared/mockNltkProof.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export default {
1+
import { NLTKTree } from '@/types';
2+
3+
const tree: NLTKTree = {
24
"children": [
35
{
46
"children": [
@@ -127,3 +129,5 @@ export default {
127129
],
128130
"node": "1:\n(every man)(be work)\nTrue"
129131
};
132+
133+
export default tree;

frontend/src/app/shared/mockTableauTree.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export default {
1+
import { ProofTree } from '@/types';
2+
3+
const tree: ProofTree = {
24
nodes: [
35
{id: 1, head: '(every man)(be work)', sign: true},
46
{id: 2, head: '(every ((who (be work)) person))(λF.((a (expensive car))(λE.((have E) F))))', sign: true},
@@ -49,3 +51,5 @@ export default {
4951
}
5052
]
5153
};
54+
55+
export default tree;

0 commit comments

Comments
 (0)