diff --git a/background-script.js b/background-script.js index 2f11625..f8e8639 100644 --- a/background-script.js +++ b/background-script.js @@ -9,8 +9,8 @@ // Seriously. let dict = undefined; -let lookup_kan = {}; -let lookup_kana = {}; +let lookup_kan = new Map(); +let lookup_kana = new Map(); function builddict() { @@ -29,18 +29,18 @@ function builddict() if (entry.k_ele != undefined) for (let j = 0; j < entry.k_ele.length; j++) { let s = entry.k_ele[j]; - if (lookup_kan[s.keb] == undefined) - lookup_kan[s.keb] = [i]; + if (!lookup_kan.has(s.keb)) + lookup_kan.set(s.keb, [i]); else - lookup_kan[s.keb].push(i); + lookup_kan.get(s.keb).push(i); } for (let j = 0; j < entry.r_ele.length; j++) { let s = entry.r_ele[j]; - if (lookup_kana[s.reb] == undefined) - lookup_kana[s.reb] = [i]; + if (!lookup_kana.has(s.reb)) + lookup_kana.set(s.reb, [i]); else - lookup_kana[s.reb].push(i); + lookup_kana.get(s.reb).push(i); } } for (let i = 0; i < 5; i++) @@ -133,10 +133,10 @@ function replace_kata_with_hira(text) function search_inner(text) { - if (lookup_kan[text]) - return getfromdict(lookup_kan[text]); - else if (lookup_kana[text]) - return getfromdict(lookup_kana[text]); + if (lookup_kan.has(text)) + return getfromdict(lookup_kan.get(text)); + else if (lookup_kana.has(text)) + return getfromdict(lookup_kana.get(text)); else return; } @@ -152,11 +152,594 @@ function search(text) if(ret) return ret; } -browser.runtime.onMessage.addListener((request, sender, sendResponse) => +// deconjugation rules +let rules = []; + +// TODO: load rules from an underlay + +//////////////////// +// verb stems +//////////////////// + +// "e" stem used for potential and ba +rules.push({type: "neverfinalrule" +, dec_end: +["く","す","つ","う","る","る","ぐ","ぶ","ぬ","む","る","う","く"] +, con_end: +["け","せ","て","え","れ","れ","げ","べ","ね","め","れ","え","け"] +, dec_tag: +["v5k","v5s","v5t","v5u","v5r","v5r-i","v5g","v5b","v5n","v5m","v1","v5u_s","v5k_s"] +, con_tag:"stem_e", detail:"(izenkei)"}); + +// true imperative +rules.push({type: "onlyfinalrule" +, dec_end: +["く","す","つ","う","る","る","ぐ","ぶ","ぬ","む","る","る","う","く"] +, con_end: +["け","せ","て","え","れ","れ","げ","べ","ね","め","ろ","よ","え","け"] // ichidan has two imperatives FIXME: don't let potential conjugate to よ +, dec_tag: +["v5k","v5s","v5t","v5u","v5r","v5r-i","v5g","v5b","v5n","v5m","v1","v1","v5u_s","v5k_s"] +, con_tag:"uninflectable", detail:"imperative"}); + +// ~a stem +rules.push({type: "neverfinalrule" +, dec_end: +["く","す","つ","う","る","ぐ","ぶ","ぬ","む","う","く"] +, con_end: +["か","せ","て","わ","ら","が","ば","な","ま","わ","か"] +, dec_tag: +["v5k","v5s","v5t","v5u","v5r","v5g","v5b","v5n","v5m","v5u_s","v5k_s"] // TODO: support あらへん, あらぬ, etc +, con_tag:"stem_a", detail:"('a' stem)"}); + +// unvoiced past stems +rules.push({type: "neverfinalrule" +, dec_end: +["く","す","つ","う","る","る","る","う","く"] +, con_end: +["い","し","っ","っ","っ","っ","","う","っ"] +, dec_tag: +["v5k","v5s","v5t","v5u","v5r","v5r-i","v1","v5u_s","v5k_s"] +, con_tag:"stem_ren_less", detail:"(unstressed infinitive)"}); +// voiced past stems +rules.push({type: "neverfinalrule" +, dec_end: +["ぐ","ぶ","ぬ","む"] +, con_end: +["い","ん","ん","ん"] +, dec_tag: +["v5g","v5b","v5n","v5m"] +, con_tag:"stem_ren_less_v", detail:"(unstressed infinitive)"}); + +// infinitives (ren'youkei) that are different than the corresponding past stem +rules.push({type: "stdrule" +, dec_end: +["く","つ","う","る","る","ぐ","ぶ","ぬ","む","う","く"] +, con_end: +["き","ち","い","り","り","ぎ","び","に","み","い","き"] +, dec_tag: +["v5k","v5t","v5u","v5r","v5r-i","v5g","v5b","v5n","v5m","v5u_s","v5k_s"] +, con_tag:"stem_ren", detail:"(infinitive)"}); +// ones that need the te trap +rules.push({type: "contextrule", contextrule:"tetrap" +, dec_end: +["す","る"] +, con_end: +["し",""] +, dec_tag: +["v5s","v1"] +, con_tag:"stem_ren", detail:"(infinitive)"}); + +// stem for negatives proper +rules.push({type: "neverfinalrule" +, dec_end: +["","る"] +, con_end: +["",""] +, dec_tag: +["stem_a","v1"] +, con_tag:"stem_mizenkei", detail:"(mizenkei)"}); + +// ~a stem +rules.push({type: "stdrule" +, dec_end: +["く","す","つ","う","る","る","ぐ","ぶ","ぬ","む","る","う","く"] +, con_end: +["こう","そう","とう","おう","ろう","ろう","ごう","ぼう","のう","もう","よう","おう","こう"] +, dec_tag: +["v5k","v5s","v5t","v5u","v5r","v5r-i","v5g","v5b","v5n","v5m","v1","v5u_s","v5k_s"] +, con_tag:"form_volition", detail:"volitional"}); + +//////////////////// +// adjective stems +//////////////////// + +rules.push({type: "stdrule", dec_end:"い", con_end:"く", dec_tag:"adj_i", con_tag:"stem_ku", detail:"(adverbial stem)"}); +rules.push({type: "stdrule", dec_end:"い", con_end:"か", dec_tag:"adj_i", con_tag:"stek_ka", detail:"(ka stem)"}); +rules.push({type: "stdrule", dec_end:"い", con_end:"け", dec_tag:"adj_i", con_tag:"stek_ke", detail:"(ke stem)"}); +rules.push({type: "stdrule", dec_end:"い", con_end:"", dec_tag:"adj_i", con_tag:"stem_adj_base", detail:"(stem)"}); + +// TODO move these +// Forms based on the bare adjective stem +rules.push({type: "stdrule", dec_end:"", con_end:"すぎる", dec_tag:"stem_adj_base", con_tag:"v1", detail:"excess"}); +rules.push({type: "stdrule", dec_end:"", con_end:"そう", dec_tag:"stem_adj_base", con_tag:"adj_na", detail:"seemingness"}); +rules.push({type: "stdrule", dec_end:"", con_end:"がる", dec_tag:"stem_adj_base", con_tag:"v5r", detail:"garu"}); +rules.push({type: "stdrule", dec_end:"", con_end:"さ", dec_tag:"stem_adj_base", con_tag:"n", detail:"noun form"}); + +//////////////////// +// common forms +//////////////////// + +// rules.push({type: "stdrule", dec_end:"", con_end:"た", dec_tag:"stem_ren", con_tag:"stem_past", detail:"past"}); // unusual but real but don't necessarily want it yet +rules.push({type: "stdrule", dec_end:"", con_end:"た", dec_tag:"stem_ren_less", con_tag:"stem_past", detail:"past"}); +rules.push({type: "stdrule", dec_end:"", con_end:"だ", dec_tag:"stem_ren_less_v", con_tag:"stem_past", detail:"past"}); +//rules.push({type: "stdrule", dec_end:"", con_end:"て", dec_tag:"stem_ren", con_tag:"stem_te", detail:"(te form)"}); // unusual but real but don't necessarily want it yet + +// we want some te forms to only apply to verbs, not adjectives +// FIXME: need a way to not break ~ないでください, ~ないでいく, etc +rules.push({type: "stdrule", dec_end:"", con_end:"て", dec_tag:"stem_ren_less", con_tag:"stem_te_verbal", detail:"(te form)"}); +rules.push({type: "stdrule", dec_end:"", con_end:"で", dec_tag:"stem_ren_less_v", con_tag:"stem_te_verbal", detail:"(te form)"}); +// tag alias +rules.push({type: "stdrule", dec_end:"", con_end:"", dec_tag:"stem_te_verbal", con_tag:"stem_te", detail:""}); +// now the te form for adjectives +// adjectives have two te forms, one of which is not used synthetically +rules.push({type: "stdrule", dec_end:"", con_end:"で", dec_tag:"adj_i", con_tag:"stem_te", detail:"(te form)"}); +rules.push({type: "stdrule", dec_end:"", con_end:"で", dec_tag:"stem_ku", con_tag:"stem_te_defective", detail:"(te form)"}); + +// doesn't have anywhere else to go +rules.push({type: "rewriterule", dec_end:"です", con_end:"でした", dec_tag:"exp", con_tag:"stem_past", detail:"past"}); +// TODO: add te form of です? or would it be too confusing to have? +// e.g. it would let people find "でしている" in "自分でしている" as です instead of で + conjugated する + +// negatives +rules.push({type: "contextrule", contextrule: "adjspecial", dec_end:"", con_end:"ない", dec_tag:"stem_mizenkei", con_tag:"adj_i", detail:"negative"}); +rules.push({type: "stdrule", dec_end:"", con_end:"ん", dec_tag:"stem_a", con_tag:"adj_i", detail:"slurred negative"}); // causes problems with ichidan verbs but they can't conjugate to stem_a +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ず", dec_tag:"stem_mizenkei", con_tag:"uninflectable", detail:"adverbial negative"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ずに", dec_tag:"stem_mizenkei", con_tag:"uninflectable", detail:"without doing so"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ぬ", dec_tag:"stem_mizenkei", con_tag:"uninflectable", detail:"archaic negative"}); + +rules.push({type: "contextrule", contextrule: "adjspecial", dec_end:"", con_end:"ない", dec_tag:"stem_ku", con_tag:"adj_i", detail:"negative"}); + +// masu and its conjugations (allowing it to conjugate recursively causes problems because its grammar is unlike typical declarative verbs) +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ます", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"polite"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ません", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"negative polite"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ました", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"past polite"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"まして", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"te polite"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ませんでした", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"past negative polite"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ましょう", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"polite volitional"}); + +//////////////////// +// conditional forms +//////////////////// + +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"たら", dec_tag:"stem_ren_less", con_tag:"uninflectable", detail:"conditional"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"だた", dec_tag:"stem_ren_less_v", con_tag:"uninflectable", detail:"conditional"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ったり", dec_tag:"stem_ka", con_tag:"uninflectable", detail:"conditional"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"たらば", dec_tag:"stem_ren_less", con_tag:"uninflectable", detail:"formal conditional"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"だたば", dec_tag:"stem_ren_less_v", con_tag:"uninflectable", detail:"formal conditional"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ったりば", dec_tag:"stem_ka", con_tag:"uninflectable", detail:"formal conditional"}); + +rules.push({type: "stdrule", dec_end:"", con_end:"ば", dec_tag:"stem_e", con_tag:"uninflectable", detail:"provisional conditional"}); +rules.push({type: "stdrule", dec_end:"", con_end:"れば", dec_tag:"stem_ke", con_tag:"uninflectable", detail:"provisional conditional"}); + +//////////////////// +// non-analytical forms +//////////////////// + +//passive and potential +rules.push({type: "stdrule", dec_end:"", con_end:"る", dec_tag:"stem_e", con_tag:"v1", detail:"potential"}); // FIXME don't allow to attach to the short causative +// note: ichidan (i.e. v1) verbs cannot conjugate to stem_a +rules.push({type: "stdrule", dec_end:"", con_end:"れる", dec_tag:"stem_a", con_tag:"v1", detail:"passive"}); +rules.push({type: "stdrule", dec_end:"る", con_end:"られる", dec_tag:"v1", con_tag:"v1", detail:"passive/potential"}); + +// causative +rules.push({type: "stdrule", dec_end:"", con_end:"せる", dec_tag:"stem_a", con_tag:"v1", detail:"causative"}); +rules.push({type: "stdrule", dec_end:"る", con_end:"させる", dec_tag:"v1", con_tag:"v1", detail:"causative"}); +rules.push({type: "contextrule", contextrule: "saspecial", dec_end:"", con_end:"す", dec_tag:"stem_a", con_tag:"v5s", detail:"short causative"}); + +//////////////////// +// te-based forms +//////////////////// + +// mere te auxiliaries +rules.push({type: "stdrule", dec_end:"", con_end:"しまう", dec_tag:"stem_te", con_tag:"v5u", detail:"completely/end up/perfect"}); +// personal te auxiliaries +rules.push({type: "stdrule", dec_end:"", con_end:"ください", dec_tag:"stem_te", con_tag:"adj_i", detail:"polite request"}); +rules.push({type: "stdrule", dec_end:"", con_end:"あげる", dec_tag:"stem_te", con_tag:"v5r", detail:"do for someone"}); +// garmmatical aspect forms +rules.push({type: "stdrule", dec_end:"", con_end:"いる", dec_tag:"stem_te", con_tag:"v1", detail:"teiru"}); +rules.push({type: "stdrule", dec_end:"", con_end:"おる", dec_tag:"stem_te", con_tag:"v5r", detail:"teoru"}); +rules.push({type: "stdrule", dec_end:"", con_end:"ある", dec_tag:"stem_te", con_tag:"v5r_i", detail:"tearu"}); +rules.push({type: "stdrule", dec_end:"", con_end:"いく", dec_tag:"stem_te", con_tag:"v5k_s", detail:"teiku"}); +rules.push({type: "stdrule", dec_end:"", con_end:"くる", dec_tag:"stem_te", con_tag:"vk", detail:"tekuru"}); +rules.push({type: "stdrule", dec_end:"", con_end:"おく", dec_tag:"stem_te", con_tag:"v5k", detail:"for now"}); +// nonverbal functions of the te form +rules.push({type: "stdrule", dec_end:"", con_end:"は", dec_tag:"stem_te", con_tag:"uninflectable", detail:"topic"}); +rules.push({type: "stdrule", dec_end:"", con_end:"は", dec_tag:"stem_te_defective", con_tag:"uninflectable", detail:"topic"}); +// TODO: ても too? + +//////////////////// +// ren'youkei based forms +//////////////////// + +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"なさい", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"kind request"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"な", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"casual kind request"}); + +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ながら", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"while"}); + +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"たり", dec_tag:"stem_ren_less", con_tag:"uninflectable", detail:"tari"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"だり", dec_tag:"stem_ren_less_v", con_tag:"uninflectable", detail:"tari"}); +rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ったり", dec_tag:"stem_ka", con_tag:"uninflectable", detail:"tari"}); + +rules.push({type: "contextrule", contextrule:"adjspecial", dec_end:"", con_end:"たい", dec_tag:"stem_ren", con_tag:"adj_i", detail:"want"}); + +rules.push({type: "stdrule", dec_end:"", con_end:"すぎる", dec_tag:"stem_ren", con_tag:"v1", detail:"too much"}); + +//////////////////// +// transparent contractions +//////////////////// +rules.push({type: "stdrule", dec_end:"てしまう", con_end:"ちゃう", dec_tag:"v5u", con_tag:"v5u", detail:"(contraction)"}); +rules.push({type: "stdrule", dec_end:"でしまう", con_end:"じゃう", dec_tag:"v5u", con_tag:"v5u", detail:"(contraction)"}); +rules.push({type: "stdrule", dec_end:"てしまう", con_end:"ちまう", dec_tag:"v5u", con_tag:"v5u", detail:"(contraction)"}); +rules.push({type: "stdrule", dec_end:"でしまう", con_end:"じまう", dec_tag:"v5u", con_tag:"v5u", detail:"(contraction)"}); + +rules.push({type: "stdrule", dec_end:"ては", con_end:"ちゃ", dec_tag:"stem_te", con_tag:"uninflectable", detail:"(contraction)"}); +rules.push({type: "stdrule", dec_end:"では", con_end:"じゃ", dec_tag:"stem_te", con_tag:"uninflectable", detail:"(contraction)"}); + +rules.push({type: "onlyfinalrule", dec_end:"ければ", con_end:"きゃ", dec_tag:"uninflectable", con_tag:"uninflectable", detail:"(contraction)"}); + +// other +rules.push({type: "onlyfinalrule", dec_end:"る", con_end:"ん", dec_tag:"v1", con_tag:"uninflectable", detail:"slurred"}); +rules.push({type: "onlyfinalrule", dec_end:"る", con_end:"ん", dec_tag:"v5r", con_tag:"uninflectable", detail:"slurred"}); +rules.push({type: "onlyfinalrule", dec_end:"る", con_end:"ん", dec_tag:"v5aru", con_tag:"uninflectable", detail:"slurred"}); +rules.push({type: "onlyfinalrule", dec_end:"る", con_end:"ん", dec_tag:"vk", con_tag:"uninflectable", detail:"slurred"}); // FIXME: ? + +// TODO: a bunch of missing contractions, vk, vs_i, v5aru, v4r, + +//rules.push({type: "stdrule", dec_end:"", con_end:"たい", dec_tag:"stem_ren", con_tag:"adj_i", detail:"want"}); + +// FIXME implement un-deconjugation to show the actual reading of the deconjugated word - this is what furigana rules are for + +// return deconjugated form if stdrule applies to form, return otherwise +function stdrule_deconjugate_inner(my_form, my_rule) { - if (request.type == 'search') + // can't deconjugate nothingness + if(my_form.text == "") + return; + // ending doesn't match + if(!my_form.text.endsWith(my_rule.con_end)) + return; + // deconjugated form too much longer than conjugated form + if(my_form.text.length > my_form.original_text.length+10) + return; + // impossibly information-dense + if(my_form.tags.length > my_form.original_text.length+6) + return; + // tag doesn't match + if(my_form.tags.length > 0 && my_form.tags[my_form.tags.length-1] != my_rule.con_tag) { - sendResponse(search(request.text)); + if(false)//my_rule.detail=="potential") + { + console.log("Returning from deconjugation: tag doesn't match"); + console.log(my_form); + console.log(my_rule); + } + return; + } + + let newtext = my_form.text.substring(0, my_form.text.length-my_rule.con_end.length)+my_rule.dec_end; + + // I hate javascript reeeeeeeeeeeeeee + let newform = {};//new Object(); + newform.text = newtext; + newform.original_text = my_form.original_text; + newform.tags = my_form.tags.slice(); + newform.seentext = new Set([...my_form.seentext]); + newform.process = my_form.process.slice(); + + newform.text = newtext; + + newform.process.push(my_rule.detail); + + if(newform.tags.length == 0) + newform.tags.push(my_rule.con_tag); + newform.tags.push(my_rule.dec_tag); + + if(newform.seentext.size == 0) + newform.seentext.add(my_form.text); + newform.seentext.add(newtext); + + return newform; +}; +function stdrule_deconjugate(my_form, my_rule) +{ + let array = undefined; + // pick the first one that is an array + // FIXME: use minimum length for safety reasons? assert all arrays equal length? + if(Array.isArray(my_rule.dec_end)) + array = my_rule.dec_end; + else if(Array.isArray(my_rule.con_end)) + array = my_rule.con_end; + else if(Array.isArray(my_rule.dec_tag)) + array = my_rule.dec_tag; + else if(Array.isArray(my_rule.con_tag)) + array = my_rule.con_tag; + + if(array == undefined) + return stdrule_deconjugate_inner(my_form, my_rule); + else + { + let collection = new Set(); + for(let i = 0; i < array.length; i++) + { + let virtual_rule = {}; + virtual_rule.type = my_rule.type; + + let index_or_value = function (variable, index) + { + if(Array.isArray(variable)) + return variable[index]; + else + return variable; + } + + virtual_rule.dec_end = index_or_value(my_rule.dec_end, i); + virtual_rule.con_end = index_or_value(my_rule.con_end, i); + virtual_rule.dec_tag = index_or_value(my_rule.dec_tag, i); + virtual_rule.con_tag = index_or_value(my_rule.con_tag, i); + virtual_rule.detail = my_rule.detail; + + let ret = stdrule_deconjugate_inner(my_form, virtual_rule); + if(ret) collection.add(ret); + } + return collection; + } +}; +function rewriterule_deconjugate(my_form, my_rule) +{ + if(my_form.text != my_rule.con_end) + return; + return stdrule_deconjugate(my_form, my_rule); +}; +function onlyfinalrule_deconjugate(my_form, my_rule) +{ + if(my_form.tags.length != 0) + return; + return stdrule_deconjugate(my_form, my_rule); +}; +function neverfinalrule_deconjugate(my_form, my_rule) +{ + if(my_form.tags.length == 0) + return; + return stdrule_deconjugate(my_form, my_rule); +}; +function contextrule_deconjugate(my_form, my_rule) +{ + if(!context_functions[my_rule.contextrule](my_form, my_rule)) + return; + return stdrule_deconjugate(my_form, my_rule); +}; + +let rule_functions = { +stdrule: stdrule_deconjugate, +rewriterule: rewriterule_deconjugate, +onlyfinalrule: onlyfinalrule_deconjugate, +neverfinalrule: neverfinalrule_deconjugate, +contextrule: contextrule_deconjugate, +}; + +function adjspecial_check(my_form, my_rule) +{ + if(my_form.tags.length != 2) return true; + let my_tag = my_form.tags[my_form.tags.length-2]; + if(my_tag == "stem_adj_base") + return false; + return true; +}; +function tetrap_check(my_form, my_rule) +{ + if(my_form.tags.length < 2) return true; + let my_tag = my_form.tags[my_form.tags.length-2]; + if(my_tag == "stem_te") + return false; + return true; +}; +function saspecial_check(my_form, my_rule) +{ + if(my_form.text == "") return false; + if(!my_form.text.endsWith(my_rule.con_end)) return false; + let base_text = my_form.text.substring(0, my_form.text.length-my_rule.con_end.length); + if(base_text.endsWith("さ")) + return false; + return true; +}; + +let context_functions = { +adjspecial: adjspecial_check, +tetrap: tetrap_check, +saspecial: saspecial_check, +}; + +Set.prototype.union = function(setB) +{ + let union = new Set(this); + for (let elem of setB) + union.add(elem); + return union; +} + +// Returns a set of objects, each containing the "final form" of a series of deconjugations +// The majority of these objects will fail being compared to the dictionary forms +// The algorithm is a right-edge rule checker that finds all applicable series of rules. +// TODO: add all underlay rule types +function deconjugate(mytext) +{ + let processed = new Set(); + let novel = new Set(); + + let start_form = {text: mytext, original_text: mytext, tags: [], seentext: new Set(), process: [] }; + novel.add(start_form); + + while(novel.size > 0) + { + //print_object(novel); + let new_novel = new Set(); + for(let form of novel) + { + for(let i = 0; i < rules.length; i++) + { + let rule = rules[i]; + + let newform = rule_functions[rule.type](form, rule); + + if(newform != undefined && newform.constructor === Set) + { + for(let myform of newform) + { + if(myform != undefined && !processed.has(myform) && !novel.has(myform) && !new_novel.has(myform)) + new_novel.add(myform); + } + } + else if(newform != undefined && !processed.has(newform) && !novel.has(newform) && !new_novel.has(newform)) + new_novel.add(newform); + } + } + processed = processed.union(novel); + novel = new_novel; + } + + return processed; +} + +// takes deconjugated forms and looks them up in a dictionary +// building a list of all matches +function build_lookup_comb (forms) +{ + // FIXME: dumpster fire that shouldn't be anywhere near as complicated as I made it + + // This is the only part of this function that doesn't look like a garbage fire. + // Looks for dictionary definitions of each deconjugated form. + let looked_up = {}; + for(let form of forms) + { + if(!looked_up.hasOwnProperty(form.text)) + { + let result = search(form.text); + if(result) + { + for(let r = 0; r < result.length; r++) + { + let entry = result[r]; + entry.deconj = undefined; // clear if something set it before, we need this later + // store all the parts of speech that this dictionary entry can apply to + // normally, they're scattered across its senses + // FIXME: care about senses with restricted spellings when building part-of-speech tags list + entry.allpos = new Set(); + for(let i = 0; i < entry.sense.length; i++) + { + let sense = entry.sense[i]; + for(let j = 0; j < sense.pos.length; j++) + entry.allpos.add(sense.pos[j]); + } + } + looked_up[form.text] = result; + } + } + } + // FIXME: This is garbage. + // Add deconjugation field to all those entries + for(let form of forms) + { + if(looked_up.hasOwnProperty(form.text)) + { + let result = looked_up[form.text]; + for(let entry of result) + { + // If the form requires definition to have a particular POS tag, check for it + if(form.tags.length > 0) + { + if(entry.allpos.has(form.tags[form.tags.length-1])) + { + if(entry.deconj) + entry.deconj.add(form); + else + entry.deconj = new Set([form]); + } + } + else + { + if(entry.deconj) + entry.deconj.add(form); + else + entry.deconj = new Set([form]); + } + } + } + } + // FIXME: This should merge based on map from JMdict entity ID to entry. + // FIXME: Because it's theoretically possible to get the same definition from different spellings from different deconjugations of the same string. + // Make a list of each definition that has an associated deconjugation (even if it's a zero-depth deconjugation) + let merger = []; + for(let result of Object.values(looked_up)) + { + for(let entry of result) + { + //console.log("testing A"); + //print_object(entry); + if(entry.deconj) + merger.push(entry); + } + } + //print_object(merger); + if(merger.length > 0) + return merger; + else + return; +} + +let last_lookup = ""; +let last_time_lookup = Date.now(); + +function lookup_indirect(text, time) +{ + if(text == "") + return; + //if(time < last_time_lookup+20) // reduces lag buildup + // return; + if(text == last_lookup)// && (exists_div()))// || time < last_time_lookup+100)) // helps reduce double-lookups + return; + last_lookup = text; + last_time_lookup = time; + + // try to look up successively shorter substrings of text + // deconjugate() returns possible deconjugations, one of which has zero deconjugations, i.e. the plain text + // build_lookup_comb looks for dictionary definitions matching any deconjugation, returning a list of them + let forms = deconjugate(text); + let result = build_lookup_comb(forms); + while(result === undefined && text.length > 0) + { + //console.log("trying to look up " + text); + text = text.substring(0, text.length-1); + forms = deconjugate(text); + result = build_lookup_comb(forms); + } + if(result !== undefined) + { + //console.log("found lookup"); + //console.log(text) + return {text:text, result:result}; + } + else + { + //console.log("did not find lookup"); + //console.log(text); + } +} + +browser.runtime.onMessage.addListener((req, sender, sendResponse) => +{ + if (req.type == "search") + { + sendResponse(lookup_indirect(req.text, req.time)); } else { diff --git a/texthook.js b/texthook.js index 78b5198..d5b7cbd 100644 --- a/texthook.js +++ b/texthook.js @@ -3,9 +3,9 @@ /* * TODO: * - * ! Port all of Spark Reader's deconjugation rules https://github.com/wareya/Spark-Reader/blob/master/preferences/underlay https://github.com/wareya/Spark-Reader/tree/master/src/language/deconjugator + * ! Finish porting the underlay's deconjugation rules + * ! Definition priority handling (prefer words with any commonness tags, shorter deconjugations, expressions, non-archaic/non-obscure words, high frequencies) * - Fix katakana-hiragana matching (only happens in lookups right now, not display generation) - * - Definition priority handling (prefer words with any commonness tags, shorter deconjugations, expressions, non-archaic/non-obscure words, high frequencies) * - More configurability * - VNstats frequency data * - Load deconjugation rules from an advanced setting, with a reset button @@ -14,8 +14,6 @@ * */ -// ~~~~ interesting stuff happens a few hundred lines down at stdrule_deconjugate(), get past the DOM-writing-related stuff ~~~~ - 'use strict'; function print_object(message) @@ -421,601 +419,32 @@ function build_div (text, result) return middle; } -// deconjugation rules -let rules = []; -// TODO: load rules from an underlay - -//////////////////// -// verb stems -//////////////////// - -// "e" stem used for potential and ba -rules.push({type: "neverfinalrule" -, dec_end: -["く","す","つ","う","る","る","ぐ","ぶ","ぬ","む","る","う","く"] -, con_end: -["け","せ","て","え","れ","れ","げ","べ","ね","め","れ","え","け"] -, dec_tag: -["v5k","v5s","v5t","v5u","v5r","v5r-i","v5g","v5b","v5n","v5m","v1","v5u_s","v5k_s"] -, con_tag:"stem_e", detail:"(izenkei)"}); - -// true imperative -rules.push({type: "onlyfinalrule" -, dec_end: -["く","す","つ","う","る","る","ぐ","ぶ","ぬ","む","る","る","う","く"] -, con_end: -["け","せ","て","え","れ","れ","げ","べ","ね","め","ろ","よ","え","け"] // ichidan has two imperatives FIXME: don't let potential conjugate to よ -, dec_tag: -["v5k","v5s","v5t","v5u","v5r","v5r-i","v5g","v5b","v5n","v5m","v1","v1","v5u_s","v5k_s"] -, con_tag:"uninflectable", detail:"imperative"}); - -// ~a stem -rules.push({type: "neverfinalrule" -, dec_end: -["く","す","つ","う","る","ぐ","ぶ","ぬ","む","う","く"] -, con_end: -["か","せ","て","わ","ら","が","ば","な","ま","わ","か"] -, dec_tag: -["v5k","v5s","v5t","v5u","v5r","v5g","v5b","v5n","v5m","v5u_s","v5k_s"] // TODO: support あらへん, あらぬ, etc -, con_tag:"stem_a", detail:"('a' stem)"}); - -// unvoiced past stems -rules.push({type: "neverfinalrule" -, dec_end: -["く","す","つ","う","る","る","る","う","く"] -, con_end: -["い","し","っ","っ","っ","っ","","う","っ"] -, dec_tag: -["v5k","v5s","v5t","v5u","v5r","v5r-i","v1","v5u_s","v5k_s"] -, con_tag:"stem_ren_less", detail:"(unstressed infinitive)"}); -// voiced past stems -rules.push({type: "neverfinalrule" -, dec_end: -["ぐ","ぶ","ぬ","む"] -, con_end: -["い","ん","ん","ん"] -, dec_tag: -["v5g","v5b","v5n","v5m"] -, con_tag:"stem_ren_less_v", detail:"(unstressed infinitive)"}); - -// infinitives (ren'youkei) that are different than the corresponding past stem -rules.push({type: "stdrule" -, dec_end: -["く","つ","う","る","る","ぐ","ぶ","ぬ","む","う","く"] -, con_end: -["き","ち","い","り","り","ぎ","び","に","み","い","き"] -, dec_tag: -["v5k","v5t","v5u","v5r","v5r-i","v5g","v5b","v5n","v5m","v5u_s","v5k_s"] -, con_tag:"stem_ren", detail:"(infinitive)"}); -// ones that need the te trap -rules.push({type: "contextrule", contextrule:"tetrap" -, dec_end: -["す","る"] -, con_end: -["し",""] -, dec_tag: -["v5s","v1"] -, con_tag:"stem_ren", detail:"(infinitive)"}); - -// stem for negatives proper -rules.push({type: "neverfinalrule" -, dec_end: -["","る"] -, con_end: -["",""] -, dec_tag: -["stem_a","v1"] -, con_tag:"stem_mizenkei", detail:"(mizenkei)"}); - -// ~a stem -rules.push({type: "stdrule" -, dec_end: -["く","す","つ","う","る","る","ぐ","ぶ","ぬ","む","る","う","く"] -, con_end: -["こう","そう","とう","おう","ろう","ろう","ごう","ぼう","のう","もう","よう","おう","こう"] -, dec_tag: -["v5k","v5s","v5t","v5u","v5r","v5r-i","v5g","v5b","v5n","v5m","v1","v5u_s","v5k_s"] -, con_tag:"form_volition", detail:"volitional"}); - -//////////////////// -// adjective stems -//////////////////// - -rules.push({type: "stdrule", dec_end:"い", con_end:"く", dec_tag:"adj_i", con_tag:"stem_ku", detail:"(adverbial stem)"}); -rules.push({type: "stdrule", dec_end:"い", con_end:"か", dec_tag:"adj_i", con_tag:"stek_ka", detail:"(ka stem)"}); -rules.push({type: "stdrule", dec_end:"い", con_end:"け", dec_tag:"adj_i", con_tag:"stek_ke", detail:"(ke stem)"}); -rules.push({type: "stdrule", dec_end:"い", con_end:"", dec_tag:"adj_i", con_tag:"stem_adj_base", detail:"(stem)"}); - -// TODO move these -// Forms based on the bare adjective stem -rules.push({type: "stdrule", dec_end:"", con_end:"すぎる", dec_tag:"stem_adj_base", con_tag:"v1", detail:"excess"}); -rules.push({type: "stdrule", dec_end:"", con_end:"そう", dec_tag:"stem_adj_base", con_tag:"adj_na", detail:"seemingness"}); -rules.push({type: "stdrule", dec_end:"", con_end:"がる", dec_tag:"stem_adj_base", con_tag:"v5r", detail:"garu"}); -rules.push({type: "stdrule", dec_end:"", con_end:"さ", dec_tag:"stem_adj_base", con_tag:"n", detail:"noun form"}); - -//////////////////// -// common forms -//////////////////// - -// rules.push({type: "stdrule", dec_end:"", con_end:"た", dec_tag:"stem_ren", con_tag:"stem_past", detail:"past"}); // unusual but real but don't necessarily want it yet -rules.push({type: "stdrule", dec_end:"", con_end:"た", dec_tag:"stem_ren_less", con_tag:"stem_past", detail:"past"}); -rules.push({type: "stdrule", dec_end:"", con_end:"だ", dec_tag:"stem_ren_less_v", con_tag:"stem_past", detail:"past"}); -//rules.push({type: "stdrule", dec_end:"", con_end:"て", dec_tag:"stem_ren", con_tag:"stem_te", detail:"(te form)"}); // unusual but real but don't necessarily want it yet - -// we want some te forms to only apply to verbs, not adjectives -// FIXME: need a way to not break ~ないでください, ~ないでいく, etc -rules.push({type: "stdrule", dec_end:"", con_end:"て", dec_tag:"stem_ren_less", con_tag:"stem_te_verbal", detail:"(te form)"}); -rules.push({type: "stdrule", dec_end:"", con_end:"で", dec_tag:"stem_ren_less_v", con_tag:"stem_te_verbal", detail:"(te form)"}); -// tag alias -rules.push({type: "stdrule", dec_end:"", con_end:"", dec_tag:"stem_te_verbal", con_tag:"stem_te", detail:""}); -// now the te form for adjectives -// adjectives have two te forms, one of which is not used synthetically -rules.push({type: "stdrule", dec_end:"", con_end:"で", dec_tag:"adj_i", con_tag:"stem_te", detail:"(te form)"}); -rules.push({type: "stdrule", dec_end:"", con_end:"で", dec_tag:"stem_ku", con_tag:"stem_te_defective", detail:"(te form)"}); - -// doesn't have anywhere else to go -rules.push({type: "rewriterule", dec_end:"です", con_end:"でした", dec_tag:"exp", con_tag:"stem_past", detail:"past"}); -// TODO: add te form of です? or would it be too confusing to have? -// e.g. it would let people find "でしている" in "自分でしている" as です instead of で + conjugated する - -// negatives -rules.push({type: "contextrule", contextrule: "adjspecial", dec_end:"", con_end:"ない", dec_tag:"stem_mizenkei", con_tag:"adj_i", detail:"negative"}); -rules.push({type: "stdrule", dec_end:"", con_end:"ん", dec_tag:"stem_a", con_tag:"adj_i", detail:"slurred negative"}); // causes problems with ichidan verbs but they can't conjugate to stem_a -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ず", dec_tag:"stem_mizenkei", con_tag:"uninflectable", detail:"adverbial negative"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ずに", dec_tag:"stem_mizenkei", con_tag:"uninflectable", detail:"without doing so"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ぬ", dec_tag:"stem_mizenkei", con_tag:"uninflectable", detail:"archaic negative"}); - -rules.push({type: "contextrule", contextrule: "adjspecial", dec_end:"", con_end:"ない", dec_tag:"stem_ku", con_tag:"adj_i", detail:"negative"}); - -// masu and its conjugations (allowing it to conjugate recursively causes problems because its grammar is unlike typical declarative verbs) -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ます", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"polite"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ません", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"negative polite"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ました", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"past polite"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"まして", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"te polite"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ませんでした", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"past negative polite"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ましょう", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"polite volitional"}); - -//////////////////// -// conditional forms -//////////////////// - -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"たら", dec_tag:"stem_ren_less", con_tag:"uninflectable", detail:"conditional"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"だた", dec_tag:"stem_ren_less_v", con_tag:"uninflectable", detail:"conditional"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ったり", dec_tag:"stem_ka", con_tag:"uninflectable", detail:"conditional"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"たらば", dec_tag:"stem_ren_less", con_tag:"uninflectable", detail:"formal conditional"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"だたば", dec_tag:"stem_ren_less_v", con_tag:"uninflectable", detail:"formal conditional"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ったりば", dec_tag:"stem_ka", con_tag:"uninflectable", detail:"formal conditional"}); - -rules.push({type: "stdrule", dec_end:"", con_end:"ば", dec_tag:"stem_e", con_tag:"uninflectable", detail:"provisional conditional"}); -rules.push({type: "stdrule", dec_end:"", con_end:"れば", dec_tag:"stem_ke", con_tag:"uninflectable", detail:"provisional conditional"}); - -//////////////////// -// non-analytical forms -//////////////////// - -//passive and potential -rules.push({type: "stdrule", dec_end:"", con_end:"る", dec_tag:"stem_e", con_tag:"v1", detail:"potential"}); -// note: ichidan (i.e. v1) verbs cannot conjugate to stem_a -rules.push({type: "stdrule", dec_end:"", con_end:"れる", dec_tag:"stem_a", con_tag:"v1", detail:"passive"}); -rules.push({type: "stdrule", dec_end:"る", con_end:"られる", dec_tag:"v1", con_tag:"v1", detail:"passive/potential"}); - -// causative -rules.push({type: "stdrule", dec_end:"", con_end:"せる", dec_tag:"stem_a", con_tag:"v1", detail:"causative"}); -rules.push({type: "stdrule", dec_end:"る", con_end:"させる", dec_tag:"v1", con_tag:"v1", detail:"causative"}); -rules.push({type: "contextrule", contextrule: "saspecial", dec_end:"", con_end:"す", dec_tag:"stem_a", con_tag:"v5s", detail:"short causative"}); - -//////////////////// -// te-based forms -//////////////////// - -// mere te auxiliaries -rules.push({type: "stdrule", dec_end:"", con_end:"しまう", dec_tag:"stem_te", con_tag:"v5u", detail:"completely"}); -// personal te auxiliaries -rules.push({type: "stdrule", dec_end:"", con_end:"ください", dec_tag:"stem_te", con_tag:"adj_i", detail:"polite request"}); -rules.push({type: "stdrule", dec_end:"", con_end:"あげる", dec_tag:"stem_te", con_tag:"v5r", detail:"do for someone"}); -// garmmatical aspect forms -rules.push({type: "stdrule", dec_end:"", con_end:"いる", dec_tag:"stem_te", con_tag:"v1", detail:"teiru"}); -rules.push({type: "stdrule", dec_end:"", con_end:"おる", dec_tag:"stem_te", con_tag:"v5r", detail:"teoru"}); -rules.push({type: "stdrule", dec_end:"", con_end:"ある", dec_tag:"stem_te", con_tag:"v5r_i", detail:"tearu"}); -rules.push({type: "stdrule", dec_end:"", con_end:"いく", dec_tag:"stem_te", con_tag:"v5k_s", detail:"teiku"}); -rules.push({type: "stdrule", dec_end:"", con_end:"くる", dec_tag:"stem_te", con_tag:"vk", detail:"tekuru"}); -rules.push({type: "stdrule", dec_end:"", con_end:"おく", dec_tag:"stem_te", con_tag:"v5k", detail:"for now"}); -// nonverbal functions of the te form -rules.push({type: "stdrule", dec_end:"", con_end:"は", dec_tag:"stem_te", con_tag:"uninflectable", detail:"topic"}); -rules.push({type: "stdrule", dec_end:"", con_end:"は", dec_tag:"stem_te_defective", con_tag:"uninflectable", detail:"topic"}); -// TODO: ても too? - -//////////////////// -// ren'youkei based forms -//////////////////// - -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"なさい", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"kind request"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"な", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"casual kind request"}); - -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ながら", dec_tag:"stem_ren", con_tag:"uninflectable", detail:"while"}); - -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"たり", dec_tag:"stem_ren_less", con_tag:"uninflectable", detail:"tari"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"だり", dec_tag:"stem_ren_less_v", con_tag:"uninflectable", detail:"tari"}); -rules.push({type: "onlyfinalrule", dec_end:"", con_end:"ったり", dec_tag:"stem_ka", con_tag:"uninflectable", detail:"tari"}); - -rules.push({type: "contextrule", contextrule:"adjspecial", dec_end:"", con_end:"たい", dec_tag:"stem_ren", con_tag:"adj_i", detail:"want"}); - -rules.push({type: "stdrule", dec_end:"", con_end:"すぎる", dec_tag:"stem_ren", con_tag:"v1", detail:"too much"}); - -//////////////////// -// transparent contractions -//////////////////// -rules.push({type: "stdrule", dec_end:"てしまう", con_end:"ちゃう", dec_tag:"v5u", con_tag:"v5u", detail:"(contraction)"}); -rules.push({type: "stdrule", dec_end:"でしまう", con_end:"じゃう", dec_tag:"v5u", con_tag:"v5u", detail:"(contraction)"}); -rules.push({type: "stdrule", dec_end:"てしまう", con_end:"ちまう", dec_tag:"v5u", con_tag:"v5u", detail:"(contraction)"}); -rules.push({type: "stdrule", dec_end:"でしまう", con_end:"じまう", dec_tag:"v5u", con_tag:"v5u", detail:"(contraction)"}); - -rules.push({type: "stdrule", dec_end:"ては", con_end:"ちゃ", dec_tag:"stem_te", con_tag:"uninflectable", detail:"(contraction)"}); -rules.push({type: "stdrule", dec_end:"では", con_end:"じゃ", dec_tag:"stem_te", con_tag:"uninflectable", detail:"(contraction)"}); - -rules.push({type: "onlyfinalrule", dec_end:"ければ", con_end:"きゃ", dec_tag:"uninflectable", con_tag:"uninflectable", detail:"(contraction)"}); - -// other -rules.push({type: "onlyfinalrule", dec_end:"る", con_end:"ん", dec_tag:"v1", con_tag:"uninflectable", detail:"slurred"}); -rules.push({type: "onlyfinalrule", dec_end:"る", con_end:"ん", dec_tag:"v5r", con_tag:"uninflectable", detail:"slurred"}); -rules.push({type: "onlyfinalrule", dec_end:"る", con_end:"ん", dec_tag:"v5aru", con_tag:"uninflectable", detail:"slurred"}); -rules.push({type: "onlyfinalrule", dec_end:"る", con_end:"ん", dec_tag:"vk", con_tag:"uninflectable", detail:"slurred"}); // FIXME: ? - -// TODO: a bunch of missing contractions, vk, vs_i, v5aru, v4r, - - - -//rules.push({type: "stdrule", dec_end:"", con_end:"たい", dec_tag:"stem_ren", con_tag:"adj_i", detail:"want"}); - -// FIXME implement un-deconjugation to show the actual reading of the deconjugated word - this is what furigana rules are for - -// return deconjugated form if stdrule applies to form, return otherwise -function stdrule_deconjugate_inner(my_form, my_rule) +let settings_reload_rate = 200; +let settings_reloader = undefined; +async function settings_reload() { - // can't deconjugate nothingness - if(my_form.text == "") - return; - // ending doesn't match - if(!my_form.text.endsWith(my_rule.con_end)) - return; - // deconjugated form too much longer than conjugated form - if(my_form.text.length > my_form.original_text.length+10) - return; - // impossibly information-dense - if(my_form.tags.length > my_form.original_text.length+6) - return; - // tag doesn't match - if(my_form.tags.length > 0 && my_form.tags[my_form.tags.length-1] != my_rule.con_tag) + try { - if(false)//my_rule.detail=="potential") - { - console.log("Returning from deconjugation: tag doesn't match"); - console.log(my_form); - console.log(my_rule); - } - return; + settings.enabled = (await browser.storage.local.get("enabled")).enabled; + if(settings.enabled == undefined) + settings.enabled = false; + settings.compact = (await browser.storage.local.get("compact")).compact; + if(settings.compact == undefined) + settings.compact = true; + //console.log("set settings"); + //console.log(settings.enabled); + //console.log(settings.compact); } - - let newtext = my_form.text.substring(0, my_form.text.length-my_rule.con_end.length)+my_rule.dec_end; - - // I hate javascript reeeeeeeeeeeeeee - let newform = {};//new Object(); - newform.text = newtext; - newform.original_text = my_form.original_text; - newform.tags = my_form.tags.slice(); - newform.seentext = new Set([...my_form.seentext]); - newform.process = my_form.process.slice(); - - newform.text = newtext; - - newform.process.push(my_rule.detail); - - if(newform.tags.length == 0) - newform.tags.push(my_rule.con_tag); - newform.tags.push(my_rule.dec_tag); - - if(newform.seentext.size == 0) - newform.seentext.add(my_form.text); - newform.seentext.add(newtext); - - return newform; -}; -function stdrule_deconjugate(my_form, my_rule) -{ - let array = undefined; - // pick the first one that is an array - // FIXME: use minimum length for safety reasons? assert all arrays equal length? - if(Array.isArray(my_rule.dec_end)) - array = my_rule.dec_end; - else if(Array.isArray(my_rule.con_end)) - array = my_rule.con_end; - else if(Array.isArray(my_rule.dec_tag)) - array = my_rule.dec_tag; - else if(Array.isArray(my_rule.con_tag)) - array = my_rule.con_tag; - - if(array == undefined) - return stdrule_deconjugate_inner(my_form, my_rule); - else + catch(error) { - let collection = new Set(); - for(let i = 0; i < array.length; i++) - { - let virtual_rule = {}; - virtual_rule.type = my_rule.type; - - let index_or_value = function (variable, index) - { - if(Array.isArray(variable)) - return variable[index]; - else - return variable; - } - - virtual_rule.dec_end = index_or_value(my_rule.dec_end, i); - virtual_rule.con_end = index_or_value(my_rule.con_end, i); - virtual_rule.dec_tag = index_or_value(my_rule.dec_tag, i); - virtual_rule.con_tag = index_or_value(my_rule.con_tag, i); - virtual_rule.detail = my_rule.detail; - - let ret = stdrule_deconjugate_inner(my_form, virtual_rule); - if(ret) collection.add(ret); - } - return collection; - } -}; -function rewriterule_deconjugate(my_form, my_rule) -{ - if(my_form.text != my_rule.con_end) - return; - return stdrule_deconjugate(my_form, my_rule); -}; -function onlyfinalrule_deconjugate(my_form, my_rule) -{ - if(my_form.tags.length != 0) - return; - return stdrule_deconjugate(my_form, my_rule); -}; -function neverfinalrule_deconjugate(my_form, my_rule) -{ - if(my_form.tags.length == 0) - return; - return stdrule_deconjugate(my_form, my_rule); -}; -function contextrule_deconjugate(my_form, my_rule) -{ - if(!context_functions[my_rule.contextrule](my_form, my_rule)) - return; - return stdrule_deconjugate(my_form, my_rule); -}; - -let rule_functions = { -stdrule: stdrule_deconjugate, -rewriterule: rewriterule_deconjugate, -onlyfinalrule: onlyfinalrule_deconjugate, -neverfinalrule: neverfinalrule_deconjugate, -contextrule: contextrule_deconjugate, -}; - -function adjspecial_check(my_form, my_rule) -{ - if(my_form.tags.length != 2) return true; - let my_tag = my_form.tags[my_form.tags.length-2]; - if(my_tag == "stem_adj_base") - return false; - return true; -}; -function tetrap_check(my_form, my_rule) -{ - if(my_form.tags.length < 2) return true; - let my_tag = my_form.tags[my_form.tags.length-2]; - if(my_tag == "stem_te") - return false; - return true; -}; -function saspecial_check(my_form, my_rule) -{ - if(my_form.text == "") return false; - if(!my_form.text.endsWith(my_rule.con_end)) return false; - let base_text = my_form.text.substring(0, my_form.text.length-my_rule.con_end.length); - if(base_text.endsWith("さ")) - return false; - return true; -}; - -let context_functions = { -adjspecial: adjspecial_check, -tetrap: tetrap_check, -saspecial: saspecial_check, -}; - -Set.prototype.union = function(setB) -{ - let union = new Set(this); - for (let elem of setB) - union.add(elem); - return union; -} - -// Returns a set of objects, each containing the "final form" of a series of deconjugations -// The majority of these objects will fail being compared to the dictionary forms -// The algorithm is a right-edge rule checker that finds all applicable series of rules. -// TODO: add all underlay rule types -function deconjugate(mytext) -{ - let processed = new Set(); - let novel = new Set(); - - let start_form = {text: mytext, original_text: mytext, tags: [], seentext: new Set(), process: [] }; - novel.add(start_form); - - while(novel.size > 0) - { - //print_object(novel); - let new_novel = new Set(); - for(let form of novel) - { - for(let i = 0; i < rules.length; i++) - { - let rule = rules[i]; - - let newform = rule_functions[rule.type](form, rule); - - if(newform != undefined && newform.constructor === Set) - { - for(let myform of newform) - { - if(myform != undefined && !processed.has(myform) && !novel.has(myform) && !new_novel.has(myform)) - new_novel.add(myform); - } - } - else if(newform != undefined && !processed.has(newform) && !novel.has(newform) && !new_novel.has(newform)) - new_novel.add(newform); - } - } - processed = processed.union(novel); - novel = new_novel; - } - - return processed; -} - -// ask background thread for a list of definitions -async function lookup_word(text) -{ - return browser.runtime.sendMessage({type: "search", text: text}); -} - -// takes deconjugated forms and looks them up in a dictionary -// building a list of all matches -async function build_lookup_comb (forms) -{ - // FIXME: dumpster fire that shouldn't be anywhere near as complicated as I made it - - // This is the only part of this function that doesn't look like a garbage fire. - // Looks for dictionary definitions of each deconjugated form. - let looked_up = {}; - for(let form of forms) - { - if(!looked_up.hasOwnProperty(form.text)) - { - let result = await lookup_word(form.text); - if(result) - { - for(let r = 0; r < result.length; r++) - { - let entry = result[r]; - entry.deconj = undefined; // clear if something set it before, we need this later - // store all the parts of speech that this dictionary entry can apply to - // normally, they're scattered across its senses - // FIXME: care about senses with restricted spellings when building part-of-speech tags list - entry.allpos = new Set(); - for(let i = 0; i < entry.sense.length; i++) - { - let sense = entry.sense[i]; - for(let j = 0; j < sense.pos.length; j++) - entry.allpos.add(sense.pos[j]); - } - } - looked_up[form.text] = result; - } - } - } - // FIXME: This is garbage. - // Add deconjugation field to all those entries - for(let form of forms) - { - if(looked_up.hasOwnProperty(form.text)) - { - let result = looked_up[form.text]; - for(let entry of result) - { - // If the form requires definition to have a particular POS tag, check for it - if(form.tags.length > 0) - { - if(entry.allpos.has(form.tags[form.tags.length-1])) - { - if(entry.deconj) - entry.deconj.add(form); - else - entry.deconj = new Set([form]); - } - } - else - { - if(entry.deconj) - entry.deconj.add(form); - else - entry.deconj = new Set([form]); - } - } - } - } - // FIXME: This should merge based on map from JMdict entity ID to entry. - // FIXME: Because it's theoretically possible to get the same definition from different spellings from different deconjugations of the same string. - // Make a list of each definition that has an associated deconjugation (even if it's a zero-depth deconjugation) - let merger = []; - for(let result of Object.values(looked_up)) - { - for(let entry of result) - { - //console.log("testing A"); - //print_object(entry); - if(entry.deconj) - merger.push(entry); - } - } - //print_object(merger); - if(merger.length > 0) - return merger; - else - return; -} - -let last_lookup = ""; -let last_time_lookup = Date.now(); - -// FIXME: Use a queue of lookups to look for or something. This is really crappy. -// FIXME: Because we want to skip every lookup except the most recent one, and never run lookups more than once at the same "time". -async function lookup_indirect (text, x, y, x2, y2, time) -{ - if(text == "") - return; - //if(time < last_time_lookup+20) // reduces lag buildup - // return; - if(text == last_lookup && (exists_div()))// || time < last_time_lookup+100)) // helps reduce double-lookups - return; - last_lookup = text; - last_time_lookup = time; - - // try to look up successively shorter substrings of text - // deconjugate() returns possible deconjugations, one of which has zero deconjugations, i.e. the plain text - // build_lookup_comb looks for dictionary definitions matching any deconjugation, returning a list of them - let forms = deconjugate(text); - let result = await build_lookup_comb(forms); - while(result === undefined && text.length > 0) - { - //console.log("trying to look up " + text); - text = text.substring(0, text.length-1); - forms = deconjugate(text); - result = await build_lookup_comb(forms); - } - if(result !== undefined) - { - //console.log("found lookup"); - //console.log(text) - let mydiv = build_div(text, result); - if(mydiv) - display_div(mydiv, x2, y2); - } - else - { - //console.log("did not find lookup"); - //console.log(text); - } + console.log("failed to set settings, maybe not stored yet?"); + } // not stored yet, probably + + settings_reloader = setTimeout(settings_reload, settings_reload_rate); } +settings_reloader = setTimeout(settings_reload, settings_reload_rate); // look up words on a timer loop that only uses the most recent lookup request and ignores all the others @@ -1035,7 +464,13 @@ async function lookup_loop() //console.log("queue not empty"); let lookup = lookup_queue.pop(); lookup_queue = []; - await lookup_indirect(lookup[0], lookup[1], lookup[2], lookup[3], lookup[4], Date.now()); + let response = await browser.runtime.sendMessage({type:"search", text:lookup[0], time:Date.now()}); + if(response) + { + let mydiv = build_div(response.text, response.result); + if(mydiv) + display_div(mydiv, lookup[3], lookup[4]); + } } let t_end = Date.now(); let t_to_wait = lookup_rate - (t_end - t_start); @@ -1071,33 +506,6 @@ function lookup_cancel() delete_div(); } -let settings_reload_rate = 200; -let settings_reloader = undefined; -async function settings_reload() -{ - try - { - settings.enabled = (await browser.storage.local.get("enabled")).enabled; - if(settings.enabled == undefined) - settings.enabled = false; - settings.compact = (await browser.storage.local.get("compact")).compact; - if(settings.compact == undefined) - settings.compact = true; - //console.log("set settings"); - //console.log(settings.enabled); - //console.log(settings.compact); - } - catch(error) - { - console.log("failed to set settings, maybe not stored yet?"); - } // not stored yet, probably - - settings_reloader = setTimeout(settings_reload, settings_reload_rate); -} - -settings_reloader = setTimeout(settings_reload, settings_reload_rate); - - let max_search_len = 25; let time_of_last = Date.now(); let throttle = 8;