// morphology-ja.el - Japanese morphology: verb conjugation and noun particles. // // Japanese is SOV, agglutinative, pro-drop. Morphology is suffix-chain based. // // Key facts: // - Nouns do not inflect; grammatical relations are marked by postpositional // particles attached after the noun. // - Verbs inflect for tense, polarity, and politeness. // - Two main verb groups: Ichidan (vowel-stem, Group 2) and Godan (consonant- // stem, Group 1), plus a small set of irregular verbs. // - Politeness levels: plain (dictionary/casual) and polite (masu-form). // - Questions are formed by appending the sentence-final particle か (ka). // // Depends on: (no dependencies - standalone morphology module) // ── Verb group classification ───────────────────────────────────────────────── // // Returns "ichidan" | "godan" | "irregular" // // Irregular verbs are checked first. Ichidan verbs end in -る and have a // vowel immediately before the る. Everything else is Godan. // // Note: this is a heuristic classifier for romanized input. For production use // with native kana/kanji forms, the dictionary form (辞書形) must be consulted. import "morphology.el" fn ja_verb_group(dict_form: String) -> String { // Irregular verbs (exact match on dictionary form) if str_eq(dict_form, "する") { return "irregular" } if str_eq(dict_form, "くる") { return "irregular" } if str_eq(dict_form, "くる") { return "irregular" } if str_eq(dict_form, "いる") { return "irregular" } if str_eq(dict_form, "ある") { return "irregular" } if str_eq(dict_form, "だ") { return "irregular" } // Romanized irregulars if str_eq(dict_form, "suru") { return "irregular" } if str_eq(dict_form, "kuru") { return "irregular" } if str_eq(dict_form, "iru") { return "irregular" } if str_eq(dict_form, "aru") { return "irregular" } if str_eq(dict_form, "da") { return "irregular" } // Ichidan: ends in -る (-ru) and has a vowel before it. // We test using native kana ending and the romanized -eru / -iru pattern. if str_ends_with(dict_form, "る") { // Any kana-form ending in る that is not in the irregular list is // heuristically classified as Ichidan when the preceding mora ends in // a vowel sound. For the romanized path we check -eru and -iru endings. return "ichidan" } if str_ends_with(dict_form, "eru") { return "ichidan" } if str_ends_with(dict_form, "iru") { return "ichidan" } return "godan" } // ── Ichidan stem extraction ─────────────────────────────────────────────────── // // Strip the final -る (-ru) from an Ichidan dictionary form. // 食べる → 食べ taberu → tabe fn ja_ichidan_stem(dict_form: String) -> String { if str_ends_with(dict_form, "る") { let n: Int = str_len(dict_form) // str_slice operates on byte offsets; る is 3 UTF-8 bytes. // We trust the runtime to handle Unicode correctly via str_drop_last. return str_drop_last(dict_form, 1) } if str_ends_with(dict_form, "ru") { let n: Int = str_len(dict_form) return str_slice(dict_form, 0, n - 2) } return dict_form } // ── Godan stem changes ──────────────────────────────────────────────────────── // // Godan verbs mutate their final kana to a different row before attaching a // suffix. The "row" parameter selects which stem form is needed: // // "i" - the i-row (連用形 ren'yōkei): for polite forms, te-form base // "a" - the a-row (未然形 mizenkei): for negative // "te" - te/ta-form (special sound changes apply) // // Final kana → i-row: く→き ぐ→ぎ す→し つ→ち ぬ→に ぶ→び む→み る→り う→い // Final kana → a-row: く→か ぐ→が す→さ つ→た ぬ→な ぶ→ば む→ま る→ら う→わ // Te/ta-form: く→い ぐ→い す→し つ→っ ぬ→ん ぶ→ん む→ん る→っ う→っ fn ja_godan_stem_change(dict_form: String, row: String) -> String { let n: Int = str_len(dict_form) if n == 0 { return dict_form } // i-row (polite stem / ren'yōkei) if str_eq(row, "i") { if str_ends_with(dict_form, "く") { return str_drop_last(dict_form, 1) + "き" } if str_ends_with(dict_form, "ぐ") { return str_drop_last(dict_form, 1) + "ぎ" } if str_ends_with(dict_form, "す") { return str_drop_last(dict_form, 1) + "し" } if str_ends_with(dict_form, "つ") { return str_drop_last(dict_form, 1) + "ち" } if str_ends_with(dict_form, "ぬ") { return str_drop_last(dict_form, 1) + "に" } if str_ends_with(dict_form, "ぶ") { return str_drop_last(dict_form, 1) + "び" } if str_ends_with(dict_form, "む") { return str_drop_last(dict_form, 1) + "み" } if str_ends_with(dict_form, "る") { return str_drop_last(dict_form, 1) + "り" } if str_ends_with(dict_form, "う") { return str_drop_last(dict_form, 1) + "い" } // Romanized fallbacks if str_ends_with(dict_form, "ku") { return str_drop_last(dict_form, 2) + "ki" } if str_ends_with(dict_form, "gu") { return str_drop_last(dict_form, 2) + "gi" } if str_ends_with(dict_form, "su") { return str_drop_last(dict_form, 2) + "shi" } if str_ends_with(dict_form, "tsu") { return str_drop_last(dict_form, 3) + "chi" } if str_ends_with(dict_form, "nu") { return str_drop_last(dict_form, 2) + "ni" } if str_ends_with(dict_form, "bu") { return str_drop_last(dict_form, 2) + "bi" } if str_ends_with(dict_form, "mu") { return str_drop_last(dict_form, 2) + "mi" } if str_ends_with(dict_form, "ru") { return str_drop_last(dict_form, 2) + "ri" } if str_ends_with(dict_form, "u") { return str_drop_last(dict_form, 1) + "i" } return dict_form } // a-row (negative stem / mizenkei) if str_eq(row, "a") { if str_ends_with(dict_form, "く") { return str_drop_last(dict_form, 1) + "か" } if str_ends_with(dict_form, "ぐ") { return str_drop_last(dict_form, 1) + "が" } if str_ends_with(dict_form, "す") { return str_drop_last(dict_form, 1) + "さ" } if str_ends_with(dict_form, "つ") { return str_drop_last(dict_form, 1) + "た" } if str_ends_with(dict_form, "ぬ") { return str_drop_last(dict_form, 1) + "な" } if str_ends_with(dict_form, "ぶ") { return str_drop_last(dict_form, 1) + "ば" } if str_ends_with(dict_form, "む") { return str_drop_last(dict_form, 1) + "ま" } if str_ends_with(dict_form, "る") { return str_drop_last(dict_form, 1) + "ら" } if str_ends_with(dict_form, "う") { return str_drop_last(dict_form, 1) + "わ" } // Romanized fallbacks if str_ends_with(dict_form, "ku") { return str_drop_last(dict_form, 2) + "ka" } if str_ends_with(dict_form, "gu") { return str_drop_last(dict_form, 2) + "ga" } if str_ends_with(dict_form, "su") { return str_drop_last(dict_form, 2) + "sa" } if str_ends_with(dict_form, "tsu") { return str_drop_last(dict_form, 3) + "ta" } if str_ends_with(dict_form, "nu") { return str_drop_last(dict_form, 2) + "na" } if str_ends_with(dict_form, "bu") { return str_drop_last(dict_form, 2) + "ba" } if str_ends_with(dict_form, "mu") { return str_drop_last(dict_form, 2) + "ma" } if str_ends_with(dict_form, "ru") { return str_drop_last(dict_form, 2) + "ra" } if str_ends_with(dict_form, "u") { return str_drop_last(dict_form, 1) + "wa" } return dict_form } // te/ta-row (te-form and plain past; special euphonic changes) // Sound changes: く→い, ぐ→い (voiced), す→し, つ/る/う→っ, ぬ/ぶ/む→ん if str_eq(row, "te") { if str_ends_with(dict_form, "く") { return str_drop_last(dict_form, 1) + "い" } if str_ends_with(dict_form, "ぐ") { return str_drop_last(dict_form, 1) + "い" } if str_ends_with(dict_form, "す") { return str_drop_last(dict_form, 1) + "し" } if str_ends_with(dict_form, "つ") { return str_drop_last(dict_form, 1) + "っ" } if str_ends_with(dict_form, "ぬ") { return str_drop_last(dict_form, 1) + "ん" } if str_ends_with(dict_form, "ぶ") { return str_drop_last(dict_form, 1) + "ん" } if str_ends_with(dict_form, "む") { return str_drop_last(dict_form, 1) + "ん" } if str_ends_with(dict_form, "る") { return str_drop_last(dict_form, 1) + "っ" } if str_ends_with(dict_form, "う") { return str_drop_last(dict_form, 1) + "っ" } // Romanized fallbacks if str_ends_with(dict_form, "ku") { return str_drop_last(dict_form, 2) + "i" } if str_ends_with(dict_form, "gu") { return str_drop_last(dict_form, 2) + "i" } if str_ends_with(dict_form, "su") { return str_drop_last(dict_form, 2) + "shi" } if str_ends_with(dict_form, "tsu") { return str_drop_last(dict_form, 3) + "tt" } if str_ends_with(dict_form, "nu") { return str_drop_last(dict_form, 2) + "n" } if str_ends_with(dict_form, "bu") { return str_drop_last(dict_form, 2) + "n" } if str_ends_with(dict_form, "mu") { return str_drop_last(dict_form, 2) + "n" } if str_ends_with(dict_form, "ru") { return str_drop_last(dict_form, 2) + "tt" } if str_ends_with(dict_form, "u") { return str_drop_last(dict_form, 1) + "tt" } return dict_form } return dict_form } // ── Verb conjugation ────────────────────────────────────────────────────────── // // ja_conjugate(dict_form, form) -> String // // form values: // "present" - plain non-past (dictionary form) // "past" - plain past // "negative" - plain negative // "volitional" - plain volitional (let's …) // "polite" - polite non-past (masu-form) // "polite-past" - polite past (mashita-form) // "polite-neg" - polite negative (masen-form) // "te" - te-form (connective / gerund) fn ja_conjugate(dict_form: String, form: String) -> String { let group: String = ja_verb_group(dict_form) // ── Irregular verbs ─────────────────────────────────────────────────────── if str_eq(group, "irregular") { // する / suru (to do) if str_eq(dict_form, "する") { if str_eq(form, "present") { return "する" } if str_eq(form, "past") { return "した" } if str_eq(form, "negative") { return "しない" } if str_eq(form, "volitional") { return "しよう" } if str_eq(form, "polite") { return "します" } if str_eq(form, "polite-past") { return "しました" } if str_eq(form, "polite-neg") { return "しません" } if str_eq(form, "te") { return "して" } return dict_form } if str_eq(dict_form, "suru") { if str_eq(form, "present") { return "suru" } if str_eq(form, "past") { return "shita" } if str_eq(form, "negative") { return "shinai" } if str_eq(form, "volitional") { return "shiyou" } if str_eq(form, "polite") { return "shimasu" } if str_eq(form, "polite-past") { return "shimashita" } if str_eq(form, "polite-neg") { return "shimasen" } if str_eq(form, "te") { return "shite" } return dict_form } // くる / kuru (to come) if str_eq(dict_form, "くる") { if str_eq(form, "present") { return "くる" } if str_eq(form, "past") { return "きた" } if str_eq(form, "negative") { return "こない" } if str_eq(form, "volitional") { return "こよう" } if str_eq(form, "polite") { return "きます" } if str_eq(form, "polite-past") { return "きました" } if str_eq(form, "polite-neg") { return "きません" } if str_eq(form, "te") { return "きて" } return dict_form } if str_eq(dict_form, "kuru") { if str_eq(form, "present") { return "kuru" } if str_eq(form, "past") { return "kita" } if str_eq(form, "negative") { return "konai" } if str_eq(form, "volitional") { return "koyou" } if str_eq(form, "polite") { return "kimasu" } if str_eq(form, "polite-past") { return "kimashita" } if str_eq(form, "polite-neg") { return "kimasen" } if str_eq(form, "te") { return "kite" } return dict_form } // いる / iru (to be / exist, animate) if str_eq(dict_form, "いる") { if str_eq(form, "present") { return "いる" } if str_eq(form, "past") { return "いた" } if str_eq(form, "negative") { return "いない" } if str_eq(form, "volitional") { return "いよう" } if str_eq(form, "polite") { return "います" } if str_eq(form, "polite-past") { return "いました" } if str_eq(form, "polite-neg") { return "いません" } if str_eq(form, "te") { return "いて" } return dict_form } if str_eq(dict_form, "iru") { if str_eq(form, "present") { return "iru" } if str_eq(form, "past") { return "ita" } if str_eq(form, "negative") { return "inai" } if str_eq(form, "volitional") { return "iyou" } if str_eq(form, "polite") { return "imasu" } if str_eq(form, "polite-past") { return "imashita" } if str_eq(form, "polite-neg") { return "imasen" } if str_eq(form, "te") { return "ite" } return dict_form } // ある / aru (to be / exist, inanimate) if str_eq(dict_form, "ある") { if str_eq(form, "present") { return "ある" } if str_eq(form, "past") { return "あった" } if str_eq(form, "negative") { return "ない" } if str_eq(form, "volitional") { return "あろう" } if str_eq(form, "polite") { return "あります" } if str_eq(form, "polite-past") { return "ありました" } if str_eq(form, "polite-neg") { return "ありません" } if str_eq(form, "te") { return "あって" } return dict_form } if str_eq(dict_form, "aru") { if str_eq(form, "present") { return "aru" } if str_eq(form, "past") { return "atta" } if str_eq(form, "negative") { return "nai" } if str_eq(form, "volitional") { return "arou" } if str_eq(form, "polite") { return "arimasu" } if str_eq(form, "polite-past") { return "arimashita" } if str_eq(form, "polite-neg") { return "arimasen" } if str_eq(form, "te") { return "atte" } return dict_form } // だ / da (copula) if str_eq(dict_form, "だ") { if str_eq(form, "present") { return "だ" } if str_eq(form, "past") { return "だった" } if str_eq(form, "negative") { return "ではない" } if str_eq(form, "volitional") { return "だろう" } if str_eq(form, "polite") { return "です" } if str_eq(form, "polite-past") { return "でした" } if str_eq(form, "polite-neg") { return "ではありません" } if str_eq(form, "te") { return "で" } return dict_form } if str_eq(dict_form, "da") { if str_eq(form, "present") { return "da" } if str_eq(form, "past") { return "datta" } if str_eq(form, "negative") { return "dewanai" } if str_eq(form, "volitional") { return "darou" } if str_eq(form, "polite") { return "desu" } if str_eq(form, "polite-past") { return "deshita" } if str_eq(form, "polite-neg") { return "dewaarimarsen" } if str_eq(form, "te") { return "de" } return dict_form } // Unknown irregular — fall through to base form return dict_form } // ── Ichidan verbs ───────────────────────────────────────────────────────── if str_eq(group, "ichidan") { let stem: String = ja_ichidan_stem(dict_form) if str_eq(form, "present") { return dict_form } if str_eq(form, "past") { return stem + "た" } if str_eq(form, "negative") { return stem + "ない" } if str_eq(form, "volitional") { return stem + "よう" } if str_eq(form, "polite") { return stem + "ます" } if str_eq(form, "polite-past") { return stem + "ました" } if str_eq(form, "polite-neg") { return stem + "ません" } if str_eq(form, "te") { return stem + "て" } return dict_form } // ── Godan verbs ─────────────────────────────────────────────────────────── // Godan plain present: dictionary form unchanged if str_eq(form, "present") { return dict_form } // Godan polite forms use the i-row stem + masu endings if str_eq(form, "polite") { let istem: String = ja_godan_stem_change(dict_form, "i") return istem + "ます" } if str_eq(form, "polite-past") { let istem: String = ja_godan_stem_change(dict_form, "i") return istem + "ました" } if str_eq(form, "polite-neg") { let istem: String = ja_godan_stem_change(dict_form, "i") return istem + "ません" } // Godan plain negative uses the a-row stem + nai if str_eq(form, "negative") { let astem: String = ja_godan_stem_change(dict_form, "a") return astem + "ない" } // Godan volitional: i-row + ou (う ending → おう, others → ろう via i-stem) if str_eq(form, "volitional") { // う-verbs: drop final う and add おう if str_ends_with(dict_form, "う") { return str_drop_last(dict_form, 1) + "おう" } let istem: String = ja_godan_stem_change(dict_form, "i") return istem + "ろう" } // Godan te-form: euphonic te-row stem + て (voiced ぐ ending → いで) if str_eq(form, "te") { let tstem: String = ja_godan_stem_change(dict_form, "te") // Voiced consonants (ぐ) use で instead of て if str_ends_with(dict_form, "ぐ") { return tstem + "いで" } if str_ends_with(dict_form, "gu") { return tstem + "ide" } // Nasal assimilation: ぬ/ぶ/む → んで if str_ends_with(dict_form, "ぬ") { return tstem + "んで" } if str_ends_with(dict_form, "ぶ") { return tstem + "んで" } if str_ends_with(dict_form, "む") { return tstem + "んで" } if str_ends_with(dict_form, "nu") { return tstem + "nde" } if str_ends_with(dict_form, "bu") { return tstem + "nde" } if str_ends_with(dict_form, "mu") { return tstem + "nde" } // す → して if str_ends_with(dict_form, "す") { return tstem + "して" } if str_ends_with(dict_form, "su") { return tstem + "shite" } // く → いて (tstem already has い) if str_ends_with(dict_form, "く") { return tstem + "て" } if str_ends_with(dict_form, "ku") { return tstem + "te" } // つ/る/う → って return tstem + "て" } // Godan plain past: same stem changes as te-form, then た/だ if str_eq(form, "past") { let tstem: String = ja_godan_stem_change(dict_form, "te") if str_ends_with(dict_form, "ぐ") { return tstem + "いだ" } if str_ends_with(dict_form, "gu") { return tstem + "ida" } if str_ends_with(dict_form, "ぬ") { return tstem + "んだ" } if str_ends_with(dict_form, "ぶ") { return tstem + "んだ" } if str_ends_with(dict_form, "む") { return tstem + "んだ" } if str_ends_with(dict_form, "nu") { return tstem + "nda" } if str_ends_with(dict_form, "bu") { return tstem + "nda" } if str_ends_with(dict_form, "mu") { return tstem + "nda" } if str_ends_with(dict_form, "す") { return tstem + "した" } if str_ends_with(dict_form, "su") { return tstem + "shita" } if str_ends_with(dict_form, "く") { return tstem + "た" } if str_ends_with(dict_form, "ku") { return tstem + "ta" } return tstem + "た" } return dict_form } // ── Case particles ──────────────────────────────────────────────────────────── // // Japanese nouns do not inflect; case is indicated by a postpositional particle // placed directly after the noun. fn ja_particle(gram_case: String) -> String { if str_eq(gram_case, "nominative") { return "が" } if str_eq(gram_case, "accusative") { return "を" } if str_eq(gram_case, "dative") { return "に" } if str_eq(gram_case, "genitive") { return "の" } if str_eq(gram_case, "topic") { return "は" } if str_eq(gram_case, "instrumental") { return "で" } if str_eq(gram_case, "locative") { return "に" } if str_eq(gram_case, "ablative") { return "から" } if str_eq(gram_case, "direction") { return "へ" } if str_eq(gram_case, "comitative") { return "と" } return "" } // ── Noun phrase construction ────────────────────────────────────────────────── // // ja_noun_phrase: attach the correct case particle to a noun. // The particle immediately follows the noun with no space. fn ja_noun_phrase(noun: String, gram_case: String) -> String { let p: String = ja_particle(gram_case) if str_eq(p, "") { return noun } return noun + p } // ── Question particle ───────────────────────────────────────────────────────── // // Japanese questions are formed by appending か (ka) to the end of a sentence. fn ja_question_particle() -> String { return "か" } // ── Sentence-final particle attachment ─────────────────────────────────────── // // ja_make_question: append the question particle to a sentence. fn ja_make_question(sentence: String) -> String { return sentence + ja_question_particle() }