Index: gc_core/js/tests.js ================================================================== --- gc_core/js/tests.js +++ gc_core/js/tests.js @@ -21,23 +21,27 @@ } * testParse (bDebug=false) { const t0 = Date.now(); let sURL; - if(typeof(process) !== 'undefined') { + if (typeof(process) !== 'undefined') { sURL = (this.spfTests !== "") ? this.spfTests : "./"+this.gce.lang+"/tests_data.json"; } else { sURL = (this.spfTests !== "") ? this.spfTests : "resource://grammalecte/"+this.gce.lang+"/tests_data.json"; } const aData = JSON.parse(helpers.loadFile(sURL)).aData; let nInvalid = 0; let nTotal = 0; let sErrorText; - let sSugg; + let sExpectedSuggs; let sExpectedErrors; + let nTestWithExpectedError = 0; + let nTestWithExpectedErrorAndSugg = 0; + let nUnexpectedErrors = 0; let sTextToCheck; let sFoundErrors; + let sFoundSuggs; let sListErr; let sLineNum; let i = 1; let sUntestedRules = ""; let bShowUntested = false; @@ -55,57 +59,109 @@ if (m) { sLine = sLine.slice(sLine.indexOf(" ")+1); sOption = m[1]; } if (sLine.includes("->>")) { - [sErrorText, sSugg] = sLine.split("->>"); - sErrorText = sErrorText.trim(); - sSugg = sSugg.trim(); + [sErrorText, sExpectedSuggs] = this._splitTestLine(sLine); + nTestWithExpectedErrorAndSugg += 1 } else { sErrorText = sLine.trim(); + sExpectedSuggs = ""; } sExpectedErrors = this._getExpectedErrors(sErrorText); + if (sExpectedErrors.trim() != "") { + nTestWithExpectedError += 1; + } sTextToCheck = sErrorText.replace(/\{\{/g, "").replace(/\}\}/g, ""); - [sFoundErrors, sListErr] = this._getFoundErrors(sTextToCheck, bDebug, sOption); + [sFoundErrors, sListErr, sFoundSuggs] = this._getFoundErrors(sTextToCheck, bDebug, sOption); if (sExpectedErrors !== sFoundErrors) { yield "\n" + i.toString() + "\n# Line num: " + sLineNum + "\n> to check: " + sTextToCheck + "\n expected: " + sExpectedErrors + "\n found: " + sFoundErrors + "\n errors: \n" + sListErr; nInvalid = nInvalid + 1; + } + else if (sExpectedSuggs) { + if (!this._checkSuggestions(sExpectedSuggs, sFoundSuggs)) { + yield "\n# Line num: " + sLineNum + + "\n> to check: " + sTextToCheck + + "\n expected: " + sExpectedSuggs + + "\n found: " + sFoundSuggs + + "\n errors: \n" + sListErr; + nUnexpectedErrors += 1; + } } nTotal = nTotal + 1; } i = i + 1; if (i % 1000 === 0) { yield i.toString(); } } - bShowUntested = true; + yield "Tests with expected errors: " + nTestWithExpectedError + " and suggestions: " + nTestWithExpectedErrorAndSugg + " > " + nTestWithExpectedErrorAndSugg/nTestWithExpectedError*100 + " %"; + if (nUnexpectedErrors) { + yield "Unexpected errors: " + nUnexpectedErrors; + } + yield* this._showUntestedRules() + } + catch (e) { + console.error(e); + } + const t1 = Date.now(); + yield "Tests parse finished in " + ((t1-t0)/1000).toString() + + " s\nTotal errors: " + nInvalid.toString() + " / " + nTotal.toString(); + } + + * _showUntestedRules () { + let i = 0; + for (let [sOpt, sLineId, sRuleId] of this.gce.listRules()) { + if (sOpt !== "@@@@" && !this._aRuleTested.has(sLineId) && !/^[0-9]+[sp]$|^[pd]_/.test(sRuleId)) { + sUntestedRules += sLineId + "/" + sRuleId + ", "; + i += 1; + } + } + if (i > 0) { + yield sUntestedRules + "\n[" + i.toString() + " untested rules]"; + } + } + + _splitTestLine (sLine) { + let [sText, sSugg] = sLine.split("->>"); + sSugg = sSugg.trim().replace(/ /g, " "); + if (sSugg.startsWith('"') && sSugg.endsWith('"')) { + sSugg = sSugg.slice(1,-1); + } + return [sText.trim(), sSugg]; + } + + _getFoundErrors (sLine, bDebug, sOption) { + try { + let aErrs = []; + if (sOption) { + this.gce.setOption(sOption, true); + aErrs = this.gce.parse(sLine, "FR", bDebug); + this.gce.setOption(sOption, false); + } else { + aErrs = this.gce.parse(sLine, "FR", bDebug); + } + let sRes = " ".repeat(sLine.length); + let sListErr = ""; + let lAllSugg = []; + for (let oErr of aErrs.sort( (a,b) => a["nStart"] - b["nStart"] )) { + sRes = sRes.slice(0, oErr["nStart"]) + "~".repeat(oErr["nEnd"] - oErr["nStart"]) + sRes.slice(oErr["nEnd"]); + sListErr += " * {" + oErr['sLineId'] + " / " + oErr['sRuleId'] + "} at " + oErr['nStart'] + ":" + oErr['nEnd'] + "\n"; + lAllSugg.push(oErr["aSuggestions"].join("|")); + this._aRuleTested.add(oErr["sLineId"]); + } + return [sRes, sListErr, lAllSugg.join("|||")]; } catch (e) { console.error(e); } - - if (bShowUntested) { - i = 0; - for (let [sOpt, sLineId, sRuleId] of this.gce.listRules()) { - if (sOpt !== "@@@@" && !this._aRuleTested.has(sLineId) && !/^[0-9]+[sp]$|^[pd]_/.test(sRuleId)) { - sUntestedRules += sLineId + "/" + sRuleId + ", "; - i += 1; - } - } - if (i > 0) { - yield sUntestedRules + "\n[" + i.toString() + " untested rules]"; - } - } - - const t1 = Date.now(); - yield "Tests parse finished in " + ((t1-t0)/1000).toString() - + " s\nTotal errors: " + nInvalid.toString() + " / " + nTotal.toString(); + return [" ".repeat(sLine.length), "", ""]; } _getExpectedErrors (sLine) { try { let sRes = " ".repeat(sLine.length); @@ -130,36 +186,26 @@ console.error(e); } return " ".repeat(sLine.length); } - _getFoundErrors (sLine, bDebug, sOption) { - try { - let aErrs = []; - if (sOption) { - this.gce.setOption(sOption, true); - aErrs = this.gce.parse(sLine, "FR", bDebug); - this.gce.setOption(sOption, false); - } else { - aErrs = this.gce.parse(sLine, "FR", bDebug); - } - let sRes = " ".repeat(sLine.length); - let sListErr = ""; - for (let dErr of aErrs) { - sRes = sRes.slice(0, dErr["nStart"]) + "~".repeat(dErr["nEnd"] - dErr["nStart"]) + sRes.slice(dErr["nEnd"]); - sListErr += " * {" + dErr['sLineId'] + " / " + dErr['sRuleId'] + "} at " + dErr['nStart'] + ":" + dErr['nEnd'] + "\n"; - this._aRuleTested.add(dErr["sLineId"]); - } - return [sRes, sListErr]; - } - catch (e) { - console.error(e); - } - return [" ".repeat(sLine.length), ""]; - } - + _checkSuggestions (sAllExceptedSuggs, sAllFoundSuggs) { + let lAllExpectedSuggs = sAllExceptedSuggs.split("|||"); + let lAllFoundSuggs = sAllFoundSuggs.split("|||"); + if (lAllExpectedSuggs.length != lAllFoundSuggs.length) { + return false; + } + for (let i = 0; i < lAllExpectedSuggs.length; i++) { + let aExpectedSuggs = new Set(lAllExpectedSuggs[i].split("|")); + let aFoundSuggs = new Set(lAllFoundSuggs[i].split("|")); + if (aExpectedSuggs.size !== aFoundSuggs.size || ![...aExpectedSuggs].every(value => aFoundSuggs.has(value))) { + return false; + } + } + return true; + } } if (typeof(exports) !== 'undefined') { exports.TestGrammarChecking = TestGrammarChecking; } Index: gc_core/py/lang_core/tests_core.py ================================================================== --- gc_core/py/lang_core/tests_core.py +++ gc_core/py/lang_core/tests_core.py @@ -110,14 +110,16 @@ "\n> to check: " + _fuckBackslashUTF8(sTextToCheck) + \ "\n expected: " + sExceptedSuggs + \ "\n found: " + sFoundSuggs + \ "\n errors: \n" + sListErr) nUnexpectedErrors += 1 - print("Tests with expected errors:", nTestWithExpectedError, " and suggestions:", nTestWithExpectedErrorAndSugg, ":{:.4}%".format(nTestWithExpectedErrorAndSugg/nTestWithExpectedError*100)) + print("Tests with expected errors:", nTestWithExpectedError, " and suggestions:", nTestWithExpectedErrorAndSugg, "> {:.4} %".format(nTestWithExpectedErrorAndSugg/nTestWithExpectedError*100)) if nUnexpectedErrors: print("Unexpected errors:", nUnexpectedErrors) - # untested rules + self._showUntestedRules() + + def _showUntestedRules (self): aUntestedRules = set() for _, sOpt, sLineId, sRuleId in gc_engine.listRules(): sRuleId = sRuleId.rstrip("0123456789") if sOpt != "@@@@" and sRuleId not in self._aTestedRules and not re.search("^[0-9]+[sp]$|^[pd]_", sRuleId): aUntestedRules.add(f"{sLineId}/{sRuleId}") @@ -167,13 +169,13 @@ nStart = m.start() - (4 * i) nEnd = m.end() - (4 * (i+1)) sRes = sRes[:nStart] + "~" * (nEnd - nStart) + sRes[nEnd:-4] return sRes - def _checkSuggestions (self, sExceptedSuggs, sFoundSuggs): - lAllExpectedSuggs = sExceptedSuggs.split("|||") - lAllFoundSuggs = sFoundSuggs.split("|||") + def _checkSuggestions (self, sAllExceptedSuggs, sAllFoundSuggs): + lAllExpectedSuggs = sAllExceptedSuggs.split("|||") + lAllFoundSuggs = sAllFoundSuggs.split("|||") if len(lAllExpectedSuggs) != len(lAllFoundSuggs): return False for sExceptedSuggs, sFoundSuggs in zip(lAllExpectedSuggs, lAllFoundSuggs): if set(sExceptedSuggs.split("|")) != set(sFoundSuggs.split("|")): return False Index: gc_lang/fr/modules-js/gce_suggestions.js ================================================================== --- gc_lang/fr/modules-js/gce_suggestions.js +++ gc_lang/fr/modules-js/gce_suggestions.js @@ -79,18 +79,18 @@ } return ""; } function joinVerbAndSuffix (sFlex, sSfx) { - if (/^-[tT]-/.test(sSfx) && /[tdTD]$/.test(sFlex)) { + if (/^-t-/i.test(sSfx) && /[td]$/i.test(sFlex)) { return sFlex + sSfx.slice(2); } - if (/[eacEAC]$/.test(sFlex)) { - if (/-(?:en|y)$/i.test(sSfx)) { + if (/[eac]$/i.test(sFlex)) { + if (/^-(?:en|y)$/i.test(sSfx)) { return sFlex + "s" + sSfx; } - if (/-(?:ie?l|elle|on)$/i.test(sSfx)) { + if (/^-(?:ie?l|elle|on)$/i.test(sSfx)) { return sFlex + "-t" + sSfx; } } return sFlex + sSfx; } @@ -162,18 +162,18 @@ "conjugate according to (and eventually )" let aSugg = new Set(); for (let sMorph of gc_engine.oSpellChecker.getMorph(sFlex)) { let lTenses = [ ...sMorph.matchAll(/:(?:Y|I[pqsf]|S[pq]|K|P|Q)/g) ]; if (sWho) { - for (let sTense of lTenses) { + for (let [sTense, ] of lTenses) { if (conj.hasConj(sStem, sTense, sWho)) { aSugg.add(conj.getConj(sStem, sTense, sWho)); } } } else { - for (let sTense of lTenses) { + for (let [sTense, ] of lTenses) { for (let sWho of [ ...sMorph.matchAll(/:[123][sp]/g) ]) { if (conj.hasConj(sStem, sTense, sWho)) { aSugg.add(conj.getConj(sStem, sTense, sWho)); } } @@ -223,19 +223,17 @@ const _dQuiEst = new Map ([ ["je", ":1s"], ["j’", ":1s"], ["tu", ":2s"], ["il", ":3s"], ["on", ":3s"], ["elle", ":3s"], ["iel", ":3s"], ["nous", ":1p"], ["vous", ":2p"], ["ils", ":3p"], ["elles", ":3p"], ["iels", ":3p"] ]); -const _lIndicatif = [":Ip", ":Iq", ":Is", ":If"]; -const _lSubjonctif = [":Sp", ":Sq"]; function suggVerbMode (sFlex, cMode, sSuj) { let lMode; if (cMode == ":I") { - lMode = _lIndicatif; + lMode = [":Ip", ":Iq", ":Is", ":If"]; } else if (cMode == ":S") { - lMode = _lSubjonctif; + lMode = [":Sp", ":Sq"]; } else if (cMode.startsWith(":I") || cMode.startsWith(":S")) { lMode = [cMode]; } else { return ""; } @@ -257,24 +255,12 @@ return ""; } //// Nouns and adjectives -function suggPlur (sFlex, sWordToAgree=null, bSelfSugg=false) { +function suggPlur (sFlex, bSelfSugg=false) { // returns plural forms assuming sFlex is singular - if (sWordToAgree) { - let lMorph = gc_engine.oSpellChecker.getMorph(sWordToAgree); - if (lMorph.length === 0) { - return ""; - } - let sGender = cregex.getGender(lMorph); - if (sGender == ":m") { - return suggMasPlur(sFlex); - } else if (sGender == ":f") { - return suggFemPlur(sFlex); - } - } let aSugg = new Set(); if (sFlex.endsWith("l")) { if (sFlex.endsWith("al") && sFlex.length > 2 && gc_engine.oSpellChecker.isValid(sFlex.slice(0,-1)+"ux")) { aSugg.add(sFlex.slice(0,-1)+"ux"); } @@ -297,14 +283,14 @@ if (gc_engine.oSpellChecker.isValid(sFlex+"x")) { aSugg.add(sFlex+"x"); } } else { if (gc_engine.oSpellChecker.isValid(sFlex+"S")) { - aSugg.add(sFlex+"s"); + aSugg.add(sFlex+"S"); } if (gc_engine.oSpellChecker.isValid(sFlex+"X")) { - aSugg.add(sFlex+"x"); + aSugg.add(sFlex+"X"); } } if (mfsp.hasMiscPlural(sFlex)) { mfsp.getMiscPlural(sFlex).forEach(function(x) { aSugg.add(x); }); } @@ -316,11 +302,11 @@ return Array.from(aSugg).join("|"); } return ""; } -function suggSing (sFlex, bSelfSugg=false) { +function suggSing (sFlex, bSelfSugg=true) { // returns singular forms assuming sFlex is plural let aSugg = new Set(); if (sFlex.endsWith("ux")) { if (gc_engine.oSpellChecker.isValid(sFlex.slice(0,-2)+"l")) { aSugg.add(sFlex.slice(0,-2)+"l"); @@ -395,11 +381,11 @@ if (sMorph.includes(":m") || sMorph.includes(":e")) { aSugg.add(suggPlur(sFlex)); } else { let sStem = cregex.getLemmaOfMorph(sMorph); if (mfsp.isMasForm(sStem)) { - aSugg.add(suggPlur(sStem, null, true)); + aSugg.add(suggPlur(sStem, true)); } } } else { // a verb let sVerb = cregex.getLemmaOfMorph(sMorph); Index: gc_lang/fr/modules/gce_suggestions.py ================================================================== --- gc_lang/fr/modules/gce_suggestions.py +++ gc_lang/fr/modules/gce_suggestions.py @@ -168,19 +168,17 @@ return "|".join([ sStem for sStem in _oSpellChecker.getLemma(sFlex) if conj.isVerb(sStem) ]) _dQuiEst = { "je": ":1s", "j’": ":1s", "tu": ":2s", "il": ":3s", "on": ":3s", "elle": ":3s", "iel": ":3s", \ "nous": ":1p", "vous": ":2p", "ils": ":3p", "elles": ":3p", "iels": ":3p" } -_lIndicatif = [":Ip", ":Iq", ":Is", ":If"] -_lSubjonctif = [":Sp", ":Sq"] def suggVerbMode (sFlex, cMode, sSuj): "returns other conjugations of acconding to and " if cMode == ":I": - lMode = _lIndicatif + lMode = [":Ip", ":Iq", ":Is", ":If"] elif cMode == ":S": - lMode = _lSubjonctif + lMode = [":Sp", ":Sq"] elif cMode.startswith((":I", ":S")): lMode = [cMode] else: return "" sWho = _dQuiEst.get(sSuj.lower(), ":3s") @@ -196,21 +194,12 @@ return "" ## Nouns and adjectives -def suggPlur (sFlex, sWordToAgree=None, bSelfSugg=False): +def suggPlur (sFlex, bSelfSugg=False): "returns plural forms assuming sFlex is singular" - if sWordToAgree: - lMorph = _oSpellChecker.getMorph(sFlex) - if not lMorph: - return "" - sGender = cr.getGender(lMorph) - if sGender == ":m": - return suggMasPlur(sFlex) - if sGender == ":f": - return suggFemPlur(sFlex) aSugg = set() if sFlex.endswith("l"): if sFlex.endswith("al") and len(sFlex) > 2 and _oSpellChecker.isValid(sFlex[:-1]+"ux"): aSugg.add(sFlex[:-1]+"ux") if sFlex.endswith("ail") and len(sFlex) > 3 and _oSpellChecker.isValid(sFlex[:-2]+"ux"): @@ -225,13 +214,13 @@ aSugg.add(sFlex+"s") if _oSpellChecker.isValid(sFlex+"x"): aSugg.add(sFlex+"x") else: if _oSpellChecker.isValid(sFlex+"S"): - aSugg.add(sFlex+"s") + aSugg.add(sFlex+"S") if _oSpellChecker.isValid(sFlex+"X"): - aSugg.add(sFlex+"x") + aSugg.add(sFlex+"X") if mfsp.hasMiscPlural(sFlex): aSugg.update(mfsp.getMiscPlural(sFlex)) if not aSugg and bSelfSugg and sFlex.endswith(("s", "x", "S", "X")): aSugg.add(sFlex) aSugg.discard("") @@ -300,11 +289,11 @@ if ":m" in sMorph or ":e" in sMorph: aSugg.add(suggPlur(sFlex)) else: sStem = cr.getLemmaOfMorph(sMorph) if mfsp.isMasForm(sStem): - aSugg.add(suggPlur(sStem, None, True)) + aSugg.add(suggPlur(sStem, True)) else: # a verb sVerb = cr.getLemmaOfMorph(sMorph) if conj.hasConj(sVerb, ":PQ", ":Q2"): aSugg.add(conj.getConj(sVerb, ":PQ", ":Q2")) Index: gc_lang/fr/rules.grx ================================================================== --- gc_lang/fr/rules.grx +++ gc_lang/fr/rules.grx @@ -2579,11 +2579,11 @@ TEST: {{Expliques-leur}} comment faire. ->> Explique-leur|Expliquons-leur|Expliquez-leur TEST: {{fou-leur}} la paix ->> fous-leur TEST: {{explique-leurs}} de quoi il est question. ->> explique-leur TEST: {{calcul-leurs}} ça. ->> calcul-leur TEST: {{aller-y}} ->> allez-y|vas-y|allons-y -TEST: {{dépenser-en}} ->> dépensez-en|dépenses-en|dépensons-en +TEST: {{expliquer-en}} ->> expliquez-en|expliques-en|expliquons-en TEST: {{appuis-en}} ->> appuies-en TEST: {{appuis-y}} ->> appuies-y TEST: {{demande-en}} ->> demandes-en TEST: {{demande-y}} comment faire ->> demandes-y TEST: c’est mon chez-moi @@ -6885,11 +6885,11 @@ >coup de foudre [immédiat+s|instantané+ses|soudain] <<- /pleo/ ->> \1 \2 \3 && Pléonasme. [paire+s] de >jumeau - <<- /pleo/ ->> =suggPlur(\-1, "", True) && Pléonasme. + <<- /pleo/ ->> =suggPlur(\-1, True) && Pléonasme. >panacée >universel <<- /pleo/ ->> \1|remède universel && Pléonasme. >secousse [>séismique|>sismique]