Skip to content

Commit

Permalink
Render effects of movement
Browse files Browse the repository at this point in the history
  • Loading branch information
lynn committed May 14, 2024
1 parent 13d5977 commit 55f2b94
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 80 deletions.
2 changes: 1 addition & 1 deletion src/bot/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class KunaBot {
tall: false,
tree,
renderer: denotationRenderText,
showArrows: false,
showMovement: false,
}),
),
);
Expand Down
7 changes: 6 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ yargs
describe: 'Annotate parse tree with semantics',
default: false,
})
.option('movement', {
type: 'boolean',
describe: 'Show effects of syntactic movement',
default: false,
})
.option('compact', {
type: 'boolean',
describe: 'Remove empty phrases with null heads',
Expand Down Expand Up @@ -143,7 +148,7 @@ yargs
tall: argv.semantics,
tree: trees[0],
renderer: denotationRenderText,
showArrows: false,
showMovement: argv.movement,
});
const png = canvas.toBuffer('image/png');
fs.writeFileSync(argv.output as string, png);
Expand Down
2 changes: 1 addition & 1 deletion src/english/clause-translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
realizeTense,
} from './conjugation';
import { serialToEnglish, treeToEnglish } from './tree';
import { leafText } from './leaf';
import { leafText } from '../tree/functions';

// @ts-ignore
import { Tagger } from 'pos';
Expand Down
12 changes: 2 additions & 10 deletions src/english/leaf.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { Impossible } from '../core/error';
import { Glosser } from '../morphology/gloss';
import { Leaf, Tree } from '../tree';
import { Leaf } from '../tree';
import { leafText } from '../tree/functions';
import { Constituent } from './clause-translator';
import { verbFormFor } from './conjugation';

export function leafText(tree: Tree): string {
if (!('word' in tree)) {
throw new Impossible('Unexpected non-leaf ' + tree.label);
}
if (tree.word.covert) return '';
return tree.word.text;
}

export function leafTextToEnglish(text: string): string {
if (text === '◌́') {
return 'the';
Expand Down
3 changes: 2 additions & 1 deletion src/english/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { bare, clean } from '../morphology/tokenize';
import { Branch, Leaf, Tree, assertBranch, isQuestion } from '../tree';
import { ClauseTranslator, Constituent } from './clause-translator';
import { VerbForm } from './conjugation';
import { leafText, leafTextToEnglish, leafToEnglish } from './leaf';
import { leafTextToEnglish, leafToEnglish } from './leaf';
import { leafText } from '../tree/functions';

/**
* Translate one verb (part of a *Serial) to English.
Expand Down
2 changes: 1 addition & 1 deletion src/modes/kdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function formatTreeAsKdl(tree: Tree): KDL.Node {
properties: {
binding: tree.binding,
coindex: tree.coindex,
...('word' in tree ? { id: tree.id, movedTo: tree.movedTo } : {}),
...('word' in tree ? { movement: tree.movement } : {}),
},
});
}
7 changes: 3 additions & 4 deletions src/syntax/recover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
} from '../tree';
import { Impossible } from '../core/error';
import { reverse } from '../core/misc';
import { inTone } from '../morphology/tokenize';
import { inTone, repairTones } from '../morphology/tokenize';
import { Tone } from '../morphology/tone';
import { moveUp } from '../tree/movement';

interface Quantification {
type: 'quantification';
Expand Down Expand Up @@ -191,10 +192,8 @@ class Recoverer {

private move(source: Tree, target: Tree) {
assertLeaf(source);
source.id ??= 'r' + this.nextMovementId++;
assertLeaf(target);
target.id ??= 'r' + this.nextMovementId++;
source.movedTo = target.id;
moveUp(source, target);
}

recover(tree: Tree, scope: Scope | undefined): StrictTree {
Expand Down
53 changes: 22 additions & 31 deletions src/syntax/serial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
effectiveLabel,
makeNull,
} from '../tree';
import { moveUp } from '../tree/movement';

/**
* Toaq serials are too complicated to parse directly in the context-free
Expand Down Expand Up @@ -92,27 +93,31 @@ export function getFrame(verb: Tree): string {
}
}

let _i = 0;
function makeId(): string {
return 's' + _i++;
}

function makevP(verb: Tree, args: Tree[]): Tree {
const agent =
'word' in verb &&
!verb.word.covert &&
verb.word.entry?.type === 'predicate' &&
verb.word.entry.subject === 'agent';

const v = makeId();
const v: Leaf = {
label: '𝘷',
word: { covert: true, value: agent ? 'CAUSE' : 'BE' },
};

if ('word' in verb) {
moveUp(verb, v);
} else {
// TODO: non-leaf movement? (object incorporation)
}

switch (args.length) {
case 0: {
if (!('word' in verb)) throw new Impossible('Weird nullary verb');
return {
label: '𝘷P',
left: { label: '𝘷', word: { covert: true, value: 'BE' }, id: v },
right: { id: makeId(), label: 'VP', word: verb.word, movedTo: v },
left: v,
right: { ...verb, label: 'VP' },
};
}
case 1: {
Expand All @@ -125,19 +130,15 @@ function makevP(verb: Tree, args: Tree[]): Tree {
left: subject,
right: {
label: "𝘷'",
left: { label: '𝘷', word: { covert: true, value: 'CAUSE' }, id: v },
right: { id: makeId(), label: 'VP', word: verb.word, movedTo: v },
left: v,
right: { ...verb, label: 'VP' },
},
};
} else {
return {
label: '𝘷P',
left: { label: '𝘷', word: { covert: true, value: 'BE' }, id: v },
right: {
label: 'VP',
left: { id: makeId(), ...verb, movedTo: v },
right: subject,
},
left: v,
right: { label: 'VP', left: verb, right: subject },
};
}
}
Expand All @@ -148,16 +149,8 @@ function makevP(verb: Tree, args: Tree[]): Tree {
left: subject,
right: {
label: "𝘷'",
left: {
label: '𝘷',
word: { covert: true, value: agent ? 'CAUSE' : 'BE' },
id: v,
},
right: {
label: 'VP',
left: { id: makeId(), ...verb, movedTo: v },
right: directObject,
},
left: v,
right: { label: 'VP', left: verb, right: directObject },
},
};
}
Expand All @@ -168,13 +161,13 @@ function makevP(verb: Tree, args: Tree[]): Tree {
left: subject,
right: {
label: "𝘷'",
left: { label: '𝘷', word: { covert: true, value: 'CAUSE' }, id: v },
left: v,
right: {
label: 'VP',
left: indirectObject,
right: {
label: "V'",
left: { id: makeId(), ...verb, movedTo: v },
left: verb,
right: directObject,
},
},
Expand Down Expand Up @@ -246,9 +239,7 @@ function serialTovP(
assertBranch(vP);
const v = vP.left.label === '𝘷' ? vP.left : (vP.right as Branch<Tree>).left;
assertLeaf(v);
v.id ??= makeId();
v0.id ??= makeId();
v.movedTo = v0.id;
moveUp(v, v0);
const outerArgs: Tree[] = [...args.slice(0, cCount), vP];
return makevP(v0, outerArgs);
}
Expand Down
54 changes: 37 additions & 17 deletions src/tree/draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TreePlacer,
} from './place';
import { Theme, ThemeName, themes } from './theme';
import { MovementID } from './movement';

interface Location {
x: number;
Expand All @@ -26,14 +27,14 @@ class TreeDrawer {
private rootX: number;
private rootY: number;
private extent: { minX: number; maxX: number; minY: number; maxY: number };
private locations: Map<string, Location> = new Map();
private arrows: Array<[string, string]> = [];
private locations: Map<MovementID, Location> = new Map();
private arrows: Array<[MovementID, MovementID]> = [];
private promises: Array<Promise<void>> = [];

constructor(
private theme: Theme,
private layerHeight: number,
private showArrows: boolean,
private showMovement: boolean,
) {
const width = 8400;
const height = 4400;
Expand Down Expand Up @@ -111,22 +112,33 @@ class TreeDrawer {
if (tree.denotation) {
this.drawText(tree.denotation, x, y + 30, this.theme.denotationColor);
}
if (tree.word !== undefined) {

const [wordColor, word] =
this.showMovement && tree.movement && tree.movement.text
? [this.theme.movedWordColor, tree.movement.text]
: [
this.showMovement && tree.movement?.movedTo
? this.theme.traceColor
: this.theme.wordColor,
tree.word,
];

if (word !== undefined) {
const dy = 35 + (tree.denotation?.height(this.ctx) ?? 0);
this.drawLine(x, y + dy, x, y + this.layerHeight - 15);
this.drawText(tree.word, x, y + this.layerHeight, this.theme.wordColor);
if (tree.gloss) {
this.drawText(word, x, y + this.layerHeight, wordColor);
if (tree.word && tree.gloss) {
const yg = y + this.layerHeight + 30;
this.drawText(tree.gloss, x, yg, this.theme.textColor);
}
}

if (tree.id) {
if (tree.movement) {
const width = this.ctx.measureText(tree.word ?? '').width;
const location = { x, y: y + 120, width };
this.locations.set(tree.id, location);
if (tree.movedTo) {
this.arrows.push([tree.id, tree.movedTo]);
this.locations.set(tree.movement.id, location);
if (tree.movement.movedTo) {
this.arrows.push([tree.movement.id, tree.movement.movedTo]);
}
}
}
Expand Down Expand Up @@ -162,23 +174,31 @@ class TreeDrawer {
}

private drawArrows() {
this.ctx.strokeStyle = this.theme.textColor;
this.ctx.lineWidth = 1;
for (const [i, j] of this.arrows) {
this.ctx.strokeStyle = this.theme.textColor;
this.ctx.lineWidth = 1;
this.ctx.beginPath();
const start = this.locations.get(i);
const end = this.locations.get(j);
if (!start || !end) continue;
const x0 = start.x - start.width / 2;
const y0 = Math.max(end.y + 50, start.y + 20);
const x1 = end.x;
const y1 = end.y + 20;
const y1 = end.y + 40;
this.ctx.moveTo(x0, y0);
this.ctx.bezierCurveTo((x0 + x1) / 2, y0 + 40, x1, y0, x1, y1);
this.ctx.stroke();
for (const dx of [-8, 8]) {
this.drawLine(x1, y1, x1 + dx, y1 + 8);
}

// Cross out the text
this.ctx.strokeStyle = this.theme.traceColor;
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(start.x - start.width / 2, start.y - 10);
this.ctx.lineTo(start.x + start.width / 2, start.y - 10);
this.ctx.stroke();
}
}

Expand All @@ -204,7 +224,7 @@ class TreeDrawer {
const placed = placer.placeTree(tree);
this.drawTree(this.rootX, this.rootY, placed);
await Promise.all(this.promises);
if (this.showArrows) this.drawArrows();
if (this.showMovement) this.drawArrows();
this.fitCanvasToContents();
return this.canvas;
}
Expand All @@ -218,10 +238,10 @@ export function drawTreeToCanvas(options: {
denotation: CompactExpr,
theme: Theme,
) => RenderedDenotation<CanvasRenderingContext2D>;
showArrows: boolean;
showMovement: boolean;
}): Promise<Canvas> {
const { theme, tall, tree, renderer, showArrows } = options;
const { theme, tall, tree, renderer, showMovement } = options;
const layerHeight = tall ? 150 : 100;
const drawer = new TreeDrawer(themes[theme], layerHeight, showArrows);
const drawer = new TreeDrawer(themes[theme], layerHeight, showMovement);
return drawer.drawToCanvas(tree, renderer);
}
15 changes: 9 additions & 6 deletions src/tree/functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Impossible } from '../core/error';
import { some } from '../core/misc';
import { bare, clean, repairTones } from '../morphology/tokenize';
import { Label, Tree, assertBranch } from './types';

Expand Down Expand Up @@ -82,13 +81,17 @@ function circled(i: number): string {
return i < 10 ? '⓪①②③④⑤⑥⑦⑧⑨'[i] : `(${i})`;
}

export function leafText(tree: Tree): string {
if (!('word' in tree)) {
throw new Impossible('Unexpected non-leaf ' + tree.label);
}
if (tree.word.covert) return '';
return tree.word.text;
}

export function treeText(tree: Tree, cpIndices?: Map<Tree, number>): string {
if ('word' in tree) {
if (tree.word.covert) {
return '';
} else {
return tree.word.text;
}
return leafText(tree);
} else {
if (cpIndices) {
const cpIndex = cpIndices.get(tree);
Expand Down
Loading

0 comments on commit 55f2b94

Please sign in to comment.