diff --git a/src/boxes.ts b/src/boxes.ts index 6dbe217..6706a59 100644 --- a/src/boxes.ts +++ b/src/boxes.ts @@ -3,6 +3,7 @@ import { Glosser } from './gloss'; import { inTone } from './tokenize'; import { Branch, Tree, assertBranch, isQuestion } from './tree'; import { Tone } from './types'; +import { Impossible, Ungrammatical, Unimplemented } from './error'; export interface PostField { earlyAdjuncts: string[]; @@ -88,15 +89,15 @@ function boxifyClause(tree: Tree): BoxClause { let postField: PostField | undefined = undefined; let conjunction: AndClause | undefined = undefined; const cp = skipFree(tree); - if (!('left' in cp)) throw new Error('bad CP?'); + if (!('left' in cp)) throw new Impossible('bad CP?'); const c = cp.left; - if (!('word' in c)) throw new Error('C without word?'); + if (!('word' in c)) throw new Impossible('C without word?'); if (!c.word.covert) { complementizer = c.word.text; } for (let node = cp.right; ; ) { if ('children' in node) { - if (node.label !== '*𝘷P') throw new Error('non-*𝘷P Rose'); + if (node.label !== '*𝘷P') throw new Impossible('non-*𝘷P Rose'); const serial = node.children[0]; verbalComplexWords.push(words(serial)); postField = boxifyPostField(node.children.slice(1)); @@ -131,10 +132,10 @@ function boxifyClause(tree: Tree): BoxClause { break; default: console.log(node); - throw new Error('unimplemented in boxifyClause: ' + node.label); + throw new Unimplemented('in boxifyClause: ' + node.label); } } else { - throw new Error('unexpected leaf in clause'); + throw new Impossible('hit leaf in boxifyClause'); } } const verbalComplex = verbalComplexWords.join(' ').trim(); @@ -144,11 +145,12 @@ function boxifyClause(tree: Tree): BoxClause { export function boxify(tree: Tree): BoxSentence { let speechAct: string = ''; tree = skipFree(tree); - if (tree.label !== 'SAP') throw new Error('Cannot boxify non-sentence'); - if (!('left' in tree)) throw new Error('bad SAP?'); + if (tree.label !== 'SAP') + throw new Ungrammatical('Cannot boxify non-sentence'); + if (!('left' in tree)) throw new Impossible('bad SAP?'); const sa = tree.right; - if (sa.label !== 'SA') throw new Error('SAP without SA?'); - if (!('word' in sa)) throw new Error('SA without word?'); + if (sa.label !== 'SA') throw new Impossible('SAP without SA?'); + if (!('word' in sa)) throw new Impossible('SA without word?'); if (!sa.word.covert) { speechAct = sa.word.text; } diff --git a/src/english.ts b/src/english.ts index 8bf886f..ee2eb86 100644 --- a/src/english.ts +++ b/src/english.ts @@ -3,6 +3,7 @@ import { parse } from './parse'; import { bare, clean } from './tokenize'; import { Branch, Label, Leaf, Tree, assertBranch, isQuestion } from './tree'; import { VerbForm, conjugate } from './english-conjugation'; +import { Impossible, Unimplemented } from './error'; interface Constituent { text: string; @@ -11,7 +12,7 @@ interface Constituent { function leafText(tree: Tree): string { if (!('word' in tree)) { - throw new Error('Unexpected non-leaf ' + tree.label); + throw new Impossible('Unexpected non-leaf ' + tree.label); } if (tree.word.covert) return ''; return tree.word.text; @@ -42,14 +43,14 @@ function verbToEnglish(tree: Tree): string { const sep = left.endsWith('-') ? '' : ' '; return left + sep + right; } else { - throw new Error('weird verb ' + tree.label); + throw new Impossible('weird verb ' + tree.label); } } function serialToEnglish(serial: Tree): string { if ('word' in serial && serial.word.covert) return ''; - if (serial.label !== '*Serial') throw new Error('non-*Serial serial'); - if (!('children' in serial)) throw new Error('non-Rose serial'); + if (serial.label !== '*Serial') throw new Impossible('non-*Serial serial'); + if (!('children' in serial)) throw new Impossible('non-Rose serial'); return serial.children.map(x => verbToEnglish(x)).join('-'); } @@ -81,7 +82,7 @@ class ClauseTranslator { public processClause(tree: Tree): void { for (let node = tree; ; ) { if ('children' in node) { - if (node.label !== '*𝘷P') throw new Error('non-*𝘷P Rose'); + if (node.label !== '*𝘷P') throw new Impossible('non-*𝘷P Rose'); this.verb = serialToEnglish(node.children[0]); let late = false; for (let i = 1; i < node.children.length; i++) { @@ -141,10 +142,10 @@ class ClauseTranslator { break; default: console.log(node); - throw new Error('unimplemented in processClause: ' + node.label); + throw new Unimplemented('in processClause: ' + node.label); } } else { - throw new Error('unexpected leaf in clause'); + throw new Impossible('hit leaf in clause'); } } } @@ -395,7 +396,7 @@ function branchToEnglish(tree: Branch): Constituent { return { text: left + ' ' + right, person }; } } - throw new Error('unimplemented in branchToEnglish: ' + tree.label); + throw new Unimplemented('in branchToEnglish: ' + tree.label); } function treeToEnglish(tree: Tree): Constituent { @@ -419,7 +420,7 @@ function treeToEnglish(tree: Tree): Constituent { } else if ('left' in tree) { return branchToEnglish(tree); } else { - throw new Error('unexpected Rose in treeToEnglish: ' + tree.label); + throw new Impossible('unexpected Rose in treeToEnglish: ' + tree.label); } } diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 0000000..bb7890a --- /dev/null +++ b/src/error.ts @@ -0,0 +1,46 @@ +/** + * Thrown when the input is detected to be ungrammatical during the fix or + * denote step. + */ +export class Ungrammatical extends Error { + constructor(message?: string) { + super(message); + this.name = 'Ungrammatical'; + Object.setPrototypeOf(this, new.target.prototype); + } +} + +/** + * Thrown when Kuna doesn't know what a word means, and can't continue denoting + * the sentence. + */ +export class Unrecognized extends Error { + constructor(message?: string) { + super(message); + this.name = 'Unrecognized'; + Object.setPrototypeOf(this, new.target.prototype); + } +} + +/** + * Thrown in code paths that aren't expected to be reached by any input sentence + * (so if this gets thrown, there's a bug in Kuna). + */ +export class Impossible extends Error { + constructor(message?: string) { + super(message); + this.name = 'Impossible'; + Object.setPrototypeOf(this, new.target.prototype); + } +} + +/** + * Thrown in code paths that aren't handled yet. + */ +export class Unimplemented extends Error { + constructor(message?: string) { + super(message); + this.name = 'Unimplemented'; + Object.setPrototypeOf(this, new.target.prototype); + } +} diff --git a/src/fix.ts b/src/fix.ts index 7a867a4..79a6fb2 100644 --- a/src/fix.ts +++ b/src/fix.ts @@ -1,5 +1,6 @@ import { analyzeSerial } from './serial'; import { CovertValue, StrictTree, Tree, assertLeaf } from './tree'; +import { Impossible } from './error'; interface Quantification { quantifier: CovertValue; @@ -55,13 +56,14 @@ export function fix(tree: Tree, scope?: Scope): StrictTree { if ('children' in tree) { if (tree.label === '*𝘷P') { const serial = tree.children[0]; - if (!serial) throw new Error('*𝘷P without children'); - if (serial.label !== '*Serial') throw new Error('*𝘷P without *Serial'); - if (!('children' in serial)) throw new Error('strange *Serial'); + if (!serial) throw new Impossible('*𝘷P without children'); + if (serial.label !== '*Serial') + throw new Impossible('*𝘷P without *Serial'); + if (!('children' in serial)) throw new Impossible('strange *Serial'); const vP = analyzeSerial(serial, tree.children.slice(1)); return fix(vP, scope); } else { - throw new Error('unexpected non-binary tree'); + throw new Impossible('unexpected non-binary tree'); } } else if ('left' in tree) { if (tree.label === 'CP') { @@ -78,7 +80,7 @@ export function fix(tree: Tree, scope?: Scope): StrictTree { if (scope && tree.label === 'DP') { const d = tree.left; assertLeaf(d); - if (d.word.covert) throw new Error('covert D'); + if (d.word.covert) throw new Impossible('covert D'); const q = quantifiers[d.word.entry?.toaq ?? '']; if (q) { scope.quantify(q, right); diff --git a/src/semantics/denote.ts b/src/semantics/denote.ts index 12ad080..bc7e4b2 100644 --- a/src/semantics/denote.ts +++ b/src/semantics/denote.ts @@ -1,4 +1,5 @@ import { VerbEntry } from '../dictionary'; +import { Impossible, Unimplemented, Unrecognized } from '../error'; import { Branch, CovertValue, Leaf, StrictTree, Word } from '../tree'; import { after, @@ -230,7 +231,7 @@ function denoteAspect(toaq: string): Expr { case 'fı': return fi; default: - throw new Error(`Unrecognized aspect: ${toaq}`); + throw new Unrecognized(`aspect: ${toaq}`); } } @@ -287,7 +288,7 @@ function denoteTense(toaq: string): Expr { case 'jela': return jela; default: - throw new Error(`Unrecognized tense: ${toaq}`); + throw new Unrecognized(`tense: ${toaq}`); } } @@ -306,7 +307,7 @@ function denoteSpeechAct(toaq: string): string { case 'móq': return 'teqga'; default: - throw new Error(`Unrecognized speech act: ${toaq}`); + throw new Unrecognized(`speech act: ${toaq}`); } } @@ -324,7 +325,7 @@ function denoteVerb(toaq: string, arity: number): Expr { ), ); default: - throw new Error(`Unhandled verb arity: ${toaq} (${arity})`); + throw new Impossible(`Invalid verb arity: ${toaq} (${arity})`); } } @@ -358,7 +359,7 @@ function denoteCovertLittleV(value: CovertValue): Expr | null { case 'BE': return null; default: - throw new Error(`Unrecognized 𝘷: ${value}`); + throw new Unrecognized(`𝘷: ${value}`); } } @@ -367,7 +368,7 @@ function denoteOvertLittleV(toaq: string): Expr { case 'nä': return na; default: - throw new Error(`Unrecognized 𝘷: ${toaq}`); + throw new Unrecognized(`𝘷: ${toaq}`); } } @@ -440,7 +441,7 @@ function denoteQuantifier(value: CovertValue): Expr { case '[∀]': return qEvery; default: - throw new Error(`Unrecognized quantifier: ${value}`); + throw new Unrecognized(`quantifier: ${value}`); } } @@ -452,10 +453,10 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { let bindings = noBindings; if (leaf.label === 'V') { - if (leaf.word.covert) throw new Error('covert V'); + if (leaf.word.covert) throw new Impossible('covert V'); const entry = leaf.word.entry; - if (!entry) throw new Error(); - if (entry.type !== 'predicate') throw new Error(); + if (!entry) throw new Unrecognized('verb: ' + leaf.word.text); + if (entry.type !== 'predicate') throw new Impossible('non-predicate V'); denotation = denoteVerb(entry.toaq, entry.frame.split(' ').length); } else if (leaf.label === 'DP') { @@ -463,7 +464,7 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { denotation = hoa; bindings = covertHoaBindings; } else if (leaf.word.entry === undefined) { - throw new Error(`Unrecognized DP: ${leaf.word.text}`); + throw new Unrecognized(`DP: ${leaf.word.text}`); } else { const toaq = leaf.word.entry.toaq; @@ -516,16 +517,17 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { bindings = taBindings; break; default: - throw new Error(`Unrecognized DP: ${toaq}`); + throw new Unrecognized(`DP: ${toaq}`); } } } else if (leaf.label === 'D') { denotation = boundThe; bindings = boundTheBindings; } else if (leaf.label === 'n') { - if (cCommand === null) throw new Error("Can't denote an n in isolation"); + if (cCommand === null) + throw new Impossible("Can't denote an n in isolation"); const vp = findVp(cCommand); - if (vp === null) throw new Error("Can't find the VP for this n"); + if (vp === null) throw new Impossible("Can't find the VP for this n"); let word: Word; if ('word' in vp) { @@ -533,7 +535,7 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { } else { const v = vp.left; if (v.label !== 'V' || !('word' in v)) - throw new Error('Unrecognized VP shape'); + throw new Impossible('Unrecognized VP shape'); word = v.word as Word; } @@ -562,7 +564,7 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { if (leaf.word.covert) { denotation = denoteCovertLittleV(leaf.word.value); } else if (leaf.word.entry === undefined) { - throw new Error(`Unrecognized 𝘷: ${leaf.word.text}`); + throw new Unrecognized(`𝘷: ${leaf.word.text}`); } else { denotation = denoteOvertLittleV(leaf.word.entry.toaq); } @@ -571,7 +573,7 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { if (leaf.word.covert) { toaq = 'tam'; } else if (leaf.word.entry === undefined) { - throw new Error(`Unrecognized Asp: ${leaf.word.text}`); + throw new Unrecognized(`Asp: ${leaf.word.text}`); } else { toaq = leaf.word.entry.toaq; } @@ -581,7 +583,7 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { if (leaf.word.covert) { denotation = defaultTense; } else if (leaf.word.entry === undefined) { - throw new Error(`Unrecognized T: ${leaf.word.text}`); + throw new Unrecognized(`T: ${leaf.word.text}`); } else { denotation = denoteTense(leaf.word.entry.toaq); } @@ -592,7 +594,7 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { if (leaf.word.covert) { toaq = 'da'; // TODO: covert móq } else if (leaf.word.entry === undefined) { - throw new Error(`Unrecognized SA: ${leaf.word.text}`); + throw new Unrecognized(`SA: ${leaf.word.text}`); } else { toaq = leaf.word.entry.toaq; } @@ -610,11 +612,11 @@ function denoteLeaf(leaf: Leaf, cCommand: StrictTree | null): DTree { ); } else if (leaf.label === 'Q') { if (!leaf.word.covert) { - throw new Error(`Overt Q: ${leaf.word.text}`); + throw new Impossible(`Overt Q: ${leaf.word.text}`); } denotation = denoteQuantifier(leaf.word.value); } else { - throw new Error(`TODO: ${leaf.label}`); + throw new Unimplemented(`TODO: ${leaf.label}`); } return { ...leaf, denotation, bindings }; @@ -710,11 +712,13 @@ const eventIdentification: CompositionRule = (branch, left, right) => { const cComposition: CompositionRule = (branch, left, right) => { if (right.denotation === null) { - throw new Error(`C composition on a null ${right.label}`); + throw new Impossible(`C composition on a null ${right.label}`); } else { const worldIndex = right.denotation.context.findIndex(t => t === 's'); if (worldIndex === -1) - throw new Error(`C composition on something without a world variable`); + throw new Impossible( + `C composition on something without a world variable`, + ); const newContext = [...right.denotation.context]; newContext.splice(worldIndex, 1); @@ -740,7 +744,7 @@ const cComposition: CompositionRule = (branch, left, right) => { const cRelComposition: CompositionRule = (branch, left, right) => { if (right.denotation === null) { - throw new Error(`Crel composition on a null ${right.label}`); + throw new Impossible(`Crel composition on a null ${right.label}`); } else { const hoa = right.bindings.resumptive ?? right.bindings.covertResumptive; if (hoa === undefined) { @@ -788,9 +792,9 @@ const cRelComposition: CompositionRule = (branch, left, right) => { const dComposition: CompositionRule = (branch, left, right) => { if (left.denotation === null) { - throw new Error(`D composition on a null ${left.label}`); + throw new Impossible(`D composition on a null ${left.label}`); } else if (right.denotation === null) { - throw new Error(`D composition on a null ${right.label}`); + throw new Impossible(`D composition on a null ${right.label}`); } else { // Because unifyDenotations is heuristic and asymmetric, and nP will have more // binding information than D, we need to pretend that nP is on the left here @@ -804,7 +808,7 @@ const dComposition: CompositionRule = (branch, left, right) => { }; const predicateAbstraction: CompositionRule = (branch, left, right) => { - throw new Error(`TODO: implement predicate abstraction`); + throw new Unimplemented(`TODO: implement predicate abstraction`); }; function getCompositionRule(left: DTree, right: DTree): CompositionRule { @@ -841,7 +845,9 @@ function getCompositionRule(left: DTree, right: DTree): CompositionRule { return reverseFunctionalApplication; } - throw new Error(`TODO: composition of ${left.label} and ${right.label}`); + throw new Unimplemented( + `TODO: composition of ${left.label} and ${right.label}`, + ); } function denoteBranch( diff --git a/src/semantics/model.ts b/src/semantics/model.ts index b42390b..b11c606 100644 --- a/src/semantics/model.ts +++ b/src/semantics/model.ts @@ -1,4 +1,5 @@ import { NonVerbEntry, VerbEntry } from '../dictionary'; +import { Impossible } from '../error'; import { Branch, Leaf } from '../tree'; /** @@ -204,7 +205,7 @@ export function typesEqual(t1: ExprType, t2: ExprType): boolean { export function assertTypesEqual(t1: ExprType, t2: ExprType): void { if (!typesEqual(t1, t2)) - throw new Error(`Types ${t1} and ${t2} are not equal`); + throw new Impossible(`Types ${t1} and ${t2} are not equal`); } export function contextsEqual(c1: ExprType[], c2: ExprType[]): boolean { @@ -216,7 +217,7 @@ export function contextsEqual(c1: ExprType[], c2: ExprType[]): boolean { export function assertContextsEqual(c1: ExprType[], c2: ExprType[]): void { if (!contextsEqual(c1, c2)) - throw new Error(`Contexts ${c1} and ${c2} are not equal`); + throw new Impossible(`Contexts ${c1} and ${c2} are not equal`); } /** @@ -224,7 +225,7 @@ export function assertContextsEqual(c1: ExprType[], c2: ExprType[]): void { */ export function v(index: number, context: ExprType[]): Expr { if (index < 0 || index >= context.length) - throw new Error(`Index ${index} out of bounds for context ${context}`); + throw new Impossible(`Index ${index} out of bounds for context ${context}`); return { head: 'variable', type: context[index], context, index }; } @@ -262,7 +263,7 @@ export function λ( * Constructor for function application expressions. */ export function app(fn: Expr, argument: Expr): Expr { - if (!Array.isArray(fn.type)) throw new Error(`${fn} is not a function`); + if (!Array.isArray(fn.type)) throw new Impossible(`${fn} is not a function`); const [inputType, outputType] = fn.type; assertTypesEqual(inputType, argument.type); assertContextsEqual(fn.context, argument.context); diff --git a/src/semantics/operations.ts b/src/semantics/operations.ts index 0f241d7..27af97e 100644 --- a/src/semantics/operations.ts +++ b/src/semantics/operations.ts @@ -1,3 +1,4 @@ +import { Impossible } from '../error'; import { and, app, @@ -318,11 +319,11 @@ export function unifyDenotations( right: DTree, ): [Expr, Expr, Bindings] { if (left.denotation === null) - throw new Error( + throw new Impossible( `Can't unify a semantically empty ${left.label} with a ${right.label}`, ); if (right.denotation === null) - throw new Error( + throw new Impossible( `Can't unify a ${left.label} with a semantically empty ${right.label}`, ); @@ -413,10 +414,11 @@ export function unifyDenotations( export function makeWorldExplicit(tree: DTree): DTree { const e = tree.denotation; if (e === null) - throw new Error("Can't make world explicit in a null denotation"); + throw new Impossible("Can't make world explicit in a null denotation"); const worldIndex = e.context.findIndex(t => t === 's'); - if (worldIndex === -1) throw new Error('No world variable to make explicit'); + if (worldIndex === -1) + throw new Impossible('No world variable to make explicit'); // Given an input expression 𝘗, build the expression // λ𝘢. λ𝘣. … λ𝘸'. 𝘗[𝘸/𝘸'](𝘢)(𝘣)… diff --git a/src/semantics/render.ts b/src/semantics/render.ts index 71b7164..d3bee74 100644 --- a/src/semantics/render.ts +++ b/src/semantics/render.ts @@ -1,3 +1,4 @@ +import { Impossible } from '../error'; import { Expr, ExprType } from './model'; type NameType = 'e' | 'v' | 'i' | 's' | 'fn'; @@ -39,7 +40,7 @@ const noNames: Names = { }; function getNameType(type: ExprType): NameType { - if (type === 't') throw new Error('There can be no variables of type t'); + if (type === 't') throw new Impossible('There can be no variables of type t'); return typeof type === 'string' ? type : 'fn'; } diff --git a/src/serial.ts b/src/serial.ts index 4f656c3..1d83d26 100644 --- a/src/serial.ts +++ b/src/serial.ts @@ -1,3 +1,4 @@ +import { Impossible, Ungrammatical } from './error'; import { Branch, Label, Tree, makeNull } from './tree'; const arityPreservingVerbPrefixes: Label[] = ['buP', 'muP', 'buqP', 'geP']; @@ -6,11 +7,11 @@ const pro: Tree = { label: 'DP', word: { covert: true, value: 'PRO' } }; export function getFrame(verb: Tree): string { if ('word' in verb) { - if (verb.word.covert) throw new Error('covert verb?'); + if (verb.word.covert) throw new Impossible('covert verb in a serial?'); if (verb.word.entry?.type === 'predicate') { return verb.word.entry.frame; } else { - throw new Error('weird verb'); + throw new Impossible('weird verb'); } } else if (verb.label === '&P' && 'left' in verb) { return getFrame(verb.left); @@ -26,7 +27,7 @@ export function getFrame(verb: Tree): string { } else if (arityPreservingVerbPrefixes.includes(verb.label)) { return getFrame((verb as Branch).right); } else { - throw new Error('weird nonverb: ' + verb.label); + throw new Impossible('weird nonverb: ' + verb.label); } } @@ -35,7 +36,7 @@ function serialTovP(verbs: Tree[], args: Tree[]): Tree { if (verbs.length === 1) { const arity = firstFrame.split(' ').length; if (args.length < arity) { - throw new Error('not enough arguments'); + throw new Ungrammatical('not enough arguments'); } if (arity === 1) { @@ -69,24 +70,24 @@ function serialTovP(verbs: Tree[], args: Tree[]): Tree { }, }; } else { - throw new Error('bad arity'); + throw new Impossible('bad arity'); } } else { const frame = firstFrame.replace(/a/g, 'c').split(' '); for (let i = 0; i < frame.length - 1; i++) { if (frame[i] !== 'c') { - throw new Error('too many numbers to serialize: ' + firstFrame); + throw new Ungrammatical("frame can't serialize: " + firstFrame); } } if (frame[frame.length - 1] === 'c') { - throw new Error('no slot to serialize: ' + firstFrame); + throw new Ungrammatical("frame can't serialize: " + firstFrame); } const cCount = frame.length - 1; const jaCount = Number(frame[frame.length - 1][0]); // TODO pro coindexation const pros: Tree[] = new Array(jaCount).fill(pro); if (args.length < cCount) { - throw new Error('not enough arguments'); + throw new Ungrammatical('not enough arguments'); } const innerArgs: Tree[] = [...pros, ...args.slice(cCount)]; const inner = serialTovP(verbs.slice(1), innerArgs); @@ -122,7 +123,7 @@ function serialTovP(verbs: Tree[], args: Tree[]): Tree { }, }; } else { - throw new Error('bad arity ' + arity); + throw new Impossible('bad arity ' + arity); } } } @@ -155,14 +156,14 @@ function attachAdjective(VP: Tree, vP: Tree): Tree { export function analyzeSerial(tree: Tree, args: Tree[]): Tree { if (tree.label !== '*Serial') { - throw new Error('not a serial'); + throw new Impossible('not a serial'); } if (!('children' in tree)) { - throw new Error('no children'); + throw new Impossible('no children'); } const children = tree.children; if (children.length === 0) { - throw new Error('zero children'); + throw new Impossible('zero children'); } const frames = children.map(getFrame); diff --git a/src/tokenize.ts b/src/tokenize.ts index a6e52e6..4baa262 100644 --- a/src/tokenize.ts +++ b/src/tokenize.ts @@ -1,4 +1,5 @@ import { dictionary, underscoredWordTypes, WordType } from './dictionary'; +import { Impossible } from './error'; import { Tone } from './types'; // Vyái → ꝡáı @@ -107,7 +108,7 @@ export class ToaqTokenizer { for (const tokenText of [...prefixes.map(p => p + '-'), root]) { const lemmaForm = clean(tokenText); if (!lemmaForm) { - throw new Error('empty token at ' + m.index); + throw new Impossible('empty token at ' + m.index); } const exactEntry = dictionary.get(lemmaForm); diff --git a/src/tree.ts b/src/tree.ts index 0e117f3..328e910 100644 --- a/src/tree.ts +++ b/src/tree.ts @@ -1,4 +1,5 @@ import { dictionary, Entry, VerbEntry } from './dictionary'; +import { Impossible } from './error'; import { getFrame } from './serial'; import { bare, clean, ToaqToken, tone } from './tokenize'; import { Tone } from './types'; @@ -152,12 +153,12 @@ export type StrictTree = Leaf | Branch; export function assertLeaf(tree: Tree): asserts tree is Leaf { if ('word' in tree) return; - throw new Error('Unexpected non-leaf ' + tree.label); + throw new Impossible('Unexpected non-leaf ' + tree.label); } export function assertBranch(tree: Tree): asserts tree is Branch { if ('left' in tree) return; - throw new Error('Unexpected non-branch ' + tree.label); + throw new Impossible('Unexpected non-branch ' + tree.label); } export function makeWord([token]: [ToaqToken]): Word {