aboutsummaryrefslogtreecommitdiff
path: root/api/word.ts
blob: e92bc195700fcb9afaf4f1e1e60a66de2c46117b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import Glossary from "./glossary.ts";
import APIBase from "./base.ts";
import Japanese, { JapaneseFormatter } from "./japanese.ts";
import "../util/string.ts";
import "../util/object.ts";
import { Tag, TagGroup, TokenTags } from "../search/tags.ts";
import { SearchWord } from "../search/types.ts";
import { recursiveValues } from "../util/object.ts";
import Sentence from "./sentence.ts";

// TODO: better irregular reading handling (should also work for counter words / 入る)

// irregular stems taken from <https://en.wikipedia.org/wiki/Japanese_irregular_verbs#suru_and_kuru>
function irregularSuru(tags: TokenTags, conjugation: string): string {
	for (let i = 0, tag = tags[i]; i < tags.length; i++, tag = tags[i]) {
		if (!recursiveValues(Tag.Inflection).includes(tag)) continue;
		if (recursiveValues(Tag.Inflection.Reason).includes(tag)) continue;
		if ([
			Tag.Inflection.Polite.Masu,
			Tag.Inflection.Suffix.Te,
			Tag.Inflection.Desirable.Itai, // part of Wikipedia's -ta form
			Tag.Inflection.Command,
		].includes(tag as any)) return "し" + conjugation;
		if ([
			Tag.Inflection.Passive,
			Tag.Inflection.Causative,
		].includes(tag as any)) return "さ" + conjugation;
		// wikipedia has できる as the potential form for する, but できる here
		// means it's already foobar'd
		break;
	}
	return conjugation;
}

function irregularKuru(tags: TokenTags, conjugation: string): string {
	for (let i = 0, tag = tags[i]; i < tags.length; i++, tag = tags[i]) {
		if (!recursiveValues(Tag.Inflection).includes(tag)) continue;
		if (recursiveValues(Tag.Inflection.Reason).includes(tag)) continue;
		if ([
			Tag.Inflection.Polite.Masu,
			Tag.Inflection.Suffix.Te,
			Tag.Inflection.Tense.Past,
			Tag.Inflection.Desirable.Itai, // part of Wikipedia's -ta form
		].includes(tag as any)) return "き" + conjugation;
		if ([
			Tag.Inflection.Negative,
			Tag.Inflection.Desirable.Volitional,
			Tag.Inflection.Passive,
			Tag.Inflection.Causative,
			Tag.Inflection.Potential,
			Tag.Inflection.Command,
		].includes(tag as any)) return "こ" + conjugation;
		break;
	}
	return "く" + conjugation;
}

export default class Word extends APIBase {
	/** @prop dictionary form of verb if this word is a verb */
	protected base: Japanese;
	/** @prop word as written in parent sentence */
	protected text: Japanese;
	/** @prop this word represents an unrecognized sentence part between recognized terms */
	protected filler: boolean;

  private _resolveParent: (sentence: Sentence) => void = _ => {};
	/** @prop parent sentence */
	protected parent = new Promise<Sentence>(res => this._resolveParent = res);

	/** @prop length of word in sentence */
	public length: number;
	/** @prop (conjugated) writing of term (*may* contain kanji) */
	public writing: string;
	/** @prop (conjugated) reading of term (kana-only) */
	public reading: string;
	/** @prop dictionary id for term */
	public id: number = -1;

  constructor(input: string | SearchWord) {
		super();
		if (typeof input === "string") {
			this.filler = true;
			input = input as string;
			this.base = new Japanese(input, input);
			this.text = this.base;
		} else {
			this.filler = false;
			input = input as SearchWord;
			this.base = new Japanese(input.writing, input.reading);
			if (input.tags.anyOf(TagGroup.Conjugable as string[])) {
				// transfer conjugation from input.source to both dictionary reading and writing for furigana
				var writingCommon = input.writing.cmpLen(input.source);
				var readingCommon = input.reading.cmpLen(input.source);
				var stemLength = Math.max(writingCommon, readingCommon);
				var base = input[writingCommon > readingCommon ? "writing" : "reading"].substring(stemLength);
				var conjugation = input.source.substring(stemLength);
	
				// special reading for irregular verbs
				var reading = input.reading;
				if (input.writing == '来る') reading = irregularKuru(input.tags, conjugation);
				else if (input.writing == '為る') reading = irregularSuru(input.tags, conjugation);
				else reading = reading.replaceLast(base, conjugation);

				// generate conjugated version of verb with kanji
				this.text = new Japanese(input.source, reading);
			} else {
				// add dictionary reading to this.source as writing (could contain kanji)
				this.text = new Japanese(input.source, this.base.reading);
			}
			this.id = input.id;
		}
		this.writing = this.text.writing;
		this.length = this.text.writing.length;
		this.reading = this.text.reading;
  }

	furigana(format: JapaneseFormatter) {
		return this.text.furigana(format);
	}

  async glossary() {
		// TODO: output nothing if this.filler == true
    return new Glossary().withAPI(await this.api);
  }

	/** @summary check if this word is written as ~ */
	public written(as: string) {
		return this.text.writing == as || this.base.writing == as;
	}

	/** @summary check if this word is read as ~ */
	public read(as: string) {
		return this.text.reading == as || this.base.reading == as;
	}

	/** @summary ignore this word for currently logged in user and refresh the sentence */
	async ignore() {
		await (await this.api)["setTermPriority"](this.base.writing, this.base.reading, -1);
		await (await this.parent).update();
	}

	/** @summary set parent sentence for this word */
	public withParent(parent: Sentence) {
		this._resolveParent(parent);
		return this;
	}
}