Index: compile_rules.py ================================================================== --- compile_rules.py +++ compile_rules.py @@ -1,11 +1,15 @@ +""" +Grammalecte: compile rules +""" import re import traceback import json import compile_rules_js_convert as jsconv +import compile_rules_graph as crg dDEF = {} lFUNCTIONS = [] @@ -17,10 +21,11 @@ sWORDLIMITLEFT = r"(?" try: return re.compile(sRegex).groups except: traceback.print_exc() print(sRegex) @@ -110,13 +116,21 @@ def createRule (s, nIdLine, sLang, bParagraph, dOptPriority): "returns rule as list [option name, regex, bCaseInsensitive, identifier, list of actions]" global dJSREGEXES global nRULEWITHOUTNAME - #### OPTIONS sLineId = str(nIdLine) + ("p" if bParagraph else "s") sRuleId = sLineId + + #### GRAPH CALL + if s.startswith("@@@@"): + if bParagraph: + print("Error. Graph call can be made only after the first pass (sentence by sentence)") + exit() + return ["@@@@", s[4:], sLineId] + + #### OPTIONS sOption = False # False or [a-z0-9]+ name nPriority = 4 # Default is 4, value must be between 0 and 9 tGroups = None # code for groups positioning (only useful for JavaScript) cCaseMode = 'i' # i: case insensitive, s: case sensitive, u: uppercasing allowed cWordLimitLeft = '[' # [: word limit, <: no specific limit @@ -147,11 +161,11 @@ if i == -1: print("# Error: no condition at line " + sLineId) return None sRegex = s[:i].strip() s = s[i+4:] - + # JS groups positioning codes m = re.search("@@\\S+", sRegex) if m: tGroups = jsconv.groupsPositioningCodeToList(sRegex[m.start()+2:]) sRegex = sRegex[:m.start()].strip() @@ -202,18 +216,18 @@ else: print("# Unknown case mode [" + cCaseMode + "] at line " + sLineId) ## check regex try: - z = re.compile(sRegex) + re.compile(sRegex) except: print("# Regex error at line ", nIdLine) print(sRegex) traceback.print_exc() return None ## groups in non grouping parenthesis - for x in re.finditer("\(\?:[^)]*\([[\w -]", sRegex): + for x in re.finditer(r"\(\?:[^)]*\([[\w -]", sRegex): print("# Warning: groups inside non grouping parenthesis in regex at line " + sLineId) #### PARSE ACTIONS lActions = [] nAction = 1 @@ -225,39 +239,50 @@ if not lActions: return None return [sOption, sRegex, bCaseInsensitive, sLineId, sRuleId, nPriority, lActions, tGroups] + +def checkReferenceNumbers (sText, sActionId, nToken): + "check if token references in greater than (debugging)" + for x in re.finditer(r"\\(\d+)", sText): + if int(x.group(1)) > nToken: + print("# Error in token index at line " + sActionId + " ("+str(nToken)+" tokens only)") + print(sText) + + +def checkIfThereIsCode (sText, sActionId): + "check if there is code in (debugging)" + if re.search("[.]\\w+[(]|sugg\\w+[(]|\\([0-9]|\\[[0-9]", sText): + print("# Warning at line " + sActionId + ": This message looks like code. Line should probably begin with =") + print(sText) + def createAction (sIdAction, sAction, nGroup): "returns an action to perform as a tuple (condition, action type, action[, iGroup [, message, URL ]])" - global lFUNCTIONS - m = re.search(r"([-~=>])(\d*|)>>", sAction) if not m: print("# No action at line " + sIdAction) return None #### CONDITION sCondition = sAction[:m.start()].strip() if sCondition: sCondition = prepareFunction(sCondition) - lFUNCTIONS.append(("c_"+sIdAction, sCondition)) - for x in re.finditer("[.](?:group|start|end)[(](\d+)[)]", sCondition): - if int(x.group(1)) > nGroup: - print("# Error in groups in condition at line " + sIdAction + " ("+str(nGroup)+" groups only)") + lFUNCTIONS.append(("_c_"+sIdAction, sCondition)) + checkReferenceNumbers(sCondition, sIdAction, nGroup) if ".match" in sCondition: print("# Error. JS compatibility. Don't use .match() in condition, use .search()") - sCondition = "c_"+sIdAction + sCondition = "_c_"+sIdAction else: sCondition = None #### iGroup / positioning iGroup = int(m.group(2)) if m.group(2) else 0 if iGroup > nGroup: print("# Selected group > group number in regex at line " + sIdAction) - + #### ACTION sAction = sAction[m.end():].strip() cAction = m.group(1) if cAction == "-": ## error @@ -272,87 +297,78 @@ sURL = "" mURL = re.search("[|] *(https?://.*)", sMsg) if mURL: sURL = mURL.group(1).strip() sMsg = sMsg[:mURL.start(0)].strip() + checkReferenceNumbers(sMsg, sIdAction, nGroup) if sMsg[0:1] == "=": sMsg = prepareFunction(sMsg[1:]) - lFUNCTIONS.append(("m_"+sIdAction, sMsg)) - for x in re.finditer("group[(](\d+)[)]", sMsg): - if int(x.group(1)) > nGroup: - print("# Error in groups in message at line " + sIdAction + " ("+str(nGroup)+" groups only)") - sMsg = "=m_"+sIdAction + lFUNCTIONS.append(("_m_"+sIdAction, sMsg)) + sMsg = "=_m_"+sIdAction else: - for x in re.finditer(r"\\(\d+)", sMsg): - if int(x.group(1)) > nGroup: - print("# Error in groups in message at line " + sIdAction + " ("+str(nGroup)+" groups only)") - if re.search("[.]\\w+[(]", sMsg): - print("# Error in message at line " + sIdAction + ": This message looks like code. Line should begin with =") - + checkIfThereIsCode(sMsg, sIdAction) + + checkReferenceNumbers(sAction, sIdAction, nGroup) if sAction[0:1] == "=" or cAction == "=": - if "define" in sAction and not re.search(r"define\(\\\d+ *, *\[.*\] *\)", sAction): - print("# Error in action at line " + sIdAction + ": second argument for define must be a list of strings") sAction = prepareFunction(sAction) sAction = sAction.replace("m.group(i[4])", "m.group("+str(iGroup)+")") - for x in re.finditer("group[(](\d+)[)]", sAction): - if int(x.group(1)) > nGroup: - print("# Error in groups in replacement at line " + sIdAction + " ("+str(nGroup)+" groups only)") else: - for x in re.finditer(r"\\(\d+)", sAction): - if int(x.group(1)) > nGroup: - print("# Error in groups in replacement at line " + sIdAction + " ("+str(nGroup)+" groups only)") - if re.search("[.]\\w+[(]|sugg\\w+[(]", sAction): - print("# Error in action at line " + sIdAction + ": This action looks like code. Line should begin with =") + checkIfThereIsCode(sAction, sIdAction) + + if cAction == ">": + ## no action, break loop if condition is False + return [sCondition, cAction, ""] + + if not sAction: + print("# Error in action at line " + sIdAction + ": This action is empty.") + return None if cAction == "-": ## error detected --> suggestion - if not sAction: - print("# Error in action at line " + sIdAction + ": This action is empty.") if sAction[0:1] == "=": - lFUNCTIONS.append(("s_"+sIdAction, sAction[1:])) - sAction = "=s_"+sIdAction + lFUNCTIONS.append(("_s_"+sIdAction, sAction[1:])) + sAction = "=_s_"+sIdAction elif sAction.startswith('"') and sAction.endswith('"'): sAction = sAction[1:-1] if not sMsg: print("# Error in action at line " + sIdAction + ": the message is empty.") return [sCondition, cAction, sAction, iGroup, sMsg, sURL] elif cAction == "~": ## text processor - if not sAction: - print("# Error in action at line " + sIdAction + ": This action is empty.") if sAction[0:1] == "=": - lFUNCTIONS.append(("p_"+sIdAction, sAction[1:])) - sAction = "=p_"+sIdAction + lFUNCTIONS.append(("_p_"+sIdAction, sAction[1:])) + sAction = "=_p_"+sIdAction elif sAction.startswith('"') and sAction.endswith('"'): sAction = sAction[1:-1] return [sCondition, cAction, sAction, iGroup] elif cAction == "=": ## disambiguator if sAction[0:1] == "=": sAction = sAction[1:] - if not sAction: - print("# Error in action at line " + sIdAction + ": This action is empty.") - lFUNCTIONS.append(("d_"+sIdAction, sAction)) - sAction = "d_"+sIdAction + if "define" in sAction and not re.search(r"define\(dTokenPos, *m\.start.*, \[.*\] *\)", sAction): + print("# Error in action at line " + sIdAction + ": second argument for define must be a list of strings") + print(sAction) + lFUNCTIONS.append(("_d_"+sIdAction, sAction)) + sAction = "_d_"+sIdAction return [sCondition, cAction, sAction] - elif cAction == ">": - ## no action, break loop if condition is False - return [sCondition, cAction, ""] else: print("# Unknown action at line " + sIdAction) return None def _calcRulesStats (lRules): + "count rules and actions" d = {'=':0, '~': 0, '-': 0, '>': 0} for aRule in lRules: - for aAction in aRule[6]: - d[aAction[1]] = d[aAction[1]] + 1 + if aRule[0] != "@@@@": + for aAction in aRule[6]: + d[aAction[1]] = d[aAction[1]] + 1 return (d, len(lRules)) def displayStats (lParagraphRules, lSentenceRules): + "display rules numbers" print(" {:>18} {:>18} {:>18} {:>18}".format("DISAMBIGUATOR", "TEXT PROCESSOR", "GRAMMAR CHECKING", "REGEX")) d, nRule = _calcRulesStats(lParagraphRules) print("§ {:>10} actions {:>10} actions {:>10} actions in {:>8} rules".format(d['='], d['~'], d['-'], nRule)) d, nRule = _calcRulesStats(lSentenceRules) print("s {:>10} actions {:>10} actions {:>10} actions in {:>8} rules".format(d['='], d['~'], d['-'], nRule)) @@ -391,11 +407,11 @@ elif sLine.startswith("OPTSOFTWARE:"): lOpt = [ [s, {}] for s in sLine[12:].strip().split() ] # don’t use tuples (s, {}), because unknown to JS elif sLine.startswith("OPT/"): m = re.match("OPT/([a-z0-9]+):(.+)$", sLine) for i, sOpt in enumerate(m.group(2).split()): - lOpt[i][1][m.group(1)] = eval(sOpt) + lOpt[i][1][m.group(1)] = eval(sOpt) elif sLine.startswith("OPTPRIORITY/"): m = re.match("OPTPRIORITY/([a-z0-9]+): *([0-9])$", sLine) dOptPriority[m.group(1)] = int(m.group(2)) elif sLine.startswith("OPTLANG/"): m = re.match("OPTLANG/([a-z][a-z](?:_[A-Z][A-Z]|)):(.+)$", sLine) @@ -415,10 +431,11 @@ dOptions.update({ "dOpt"+k: v for k, v in lOpt }) return dOptions, dOptPriority def printBookmark (nLevel, sComment, nLine): + "print bookmark within the rules file" print(" {:>6}: {}".format(nLine, " " * nLevel + sComment)) def make (spLang, sLang, bJavaScript): "compile rules, returns a dictionary of values" @@ -431,52 +448,73 @@ print("Error. Rules file in project [" + sLang + "] not found.") exit() # removing comments, zeroing empty lines, creating definitions, storing tests, merging rule lines print(" parsing rules...") - global dDEF - lLine = [] lRuleLine = [] lTest = [] lOpt = [] - zBookmark = re.compile("^!!+") - zGraphLink = re.compile(r"^@@@@GRAPHLINK>(\w+)@@@@") + bGraph = False + lGraphRule = [] for i, sLine in enumerate(lRules, 1): if sLine.startswith('#END'): + # arbitrary end printBookmark(0, "BREAK BY #END", i) break elif sLine.startswith("#"): + # comment pass - elif sLine.startswith("@@@@"): - m = re.match(r"^@@@@GRAPHLINK>(\w+)@@@@", sLine.strip()) - if m: - #lRuleLine.append(["@GRAPHLINK", m.group(1)]) - printBookmark(1, "@GRAPHLINK: " + m.group(1), i) elif sLine.startswith("DEF:"): + # definition m = re.match("DEF: +([a-zA-Z_][a-zA-Z_0-9]*) +(.+)$", sLine.strip()) if m: dDEF["{"+m.group(1)+"}"] = m.group(2) else: print("Error in definition: ", end="") print(sLine.strip()) elif sLine.startswith("TEST:"): + # test lTest.append("{:<8}".format(i) + " " + sLine[5:].strip()) elif sLine.startswith("TODO:"): + # todo pass elif sLine.startswith(("OPTGROUP/", "OPTSOFTWARE:", "OPT/", "OPTLANG/", "OPTDEFAULTUILANG:", "OPTLABEL/", "OPTPRIORITY/")): + # options lOpt.append(sLine) - elif re.match("[  \t]*$", sLine): - pass elif sLine.startswith("!!"): - m = zBookmark.search(sLine) + # bookmark + m = re.match("!!+", sLine) nExMk = len(m.group(0)) if sLine[nExMk:].strip(): printBookmark(nExMk-2, sLine[nExMk:].strip(), i) + # Graph rules + elif sLine.startswith("@@@@GRAPH:"): + # rules graph call + m = re.match(r"@@@@GRAPH: *(\w+)", sLine.strip()) + if m: + printBookmark(1, "@GRAPH: " + m.group(1), i) + lRuleLine.append([i, "@@@@"+m.group(1)]) + bGraph = True + lGraphRule.append([i, sLine]) + bGraph = True + elif sLine.startswith("@@@@END_GRAPH"): + #lGraphRule.append([i, sLine]) + bGraph = False + elif re.match("@@@@ *$", sLine): + pass + elif bGraph: + lGraphRule.append([i, sLine]) + # Regex rules + elif re.match("[  \t]*$", sLine): + # empty line + pass elif sLine.startswith((" ", "\t")): - lRuleLine[len(lRuleLine)-1][1] += " " + sLine.strip() + # rule (continuation) + lRuleLine[-1][1] += " " + sLine.strip() else: + # new rule lRuleLine.append([i, sLine.strip()]) # generating options files print(" parsing options...") try: @@ -515,21 +553,20 @@ # creating file with all functions callable by rules print(" creating callables...") sPyCallables = "# generated code, do not edit\n" sJSCallables = "// generated code, do not edit\nconst oEvalFunc = {\n" for sFuncName, sReturn in lFUNCTIONS: - cType = sFuncName[0:1] - if cType == "c": # condition - sParams = "s, sx, m, dDA, sCountry, bCondMemo" - elif cType == "m": # message - sParams = "s, m" - elif cType == "s": # suggestion - sParams = "s, m" - elif cType == "p": # preprocessor - sParams = "s, m" - elif cType == "d": # disambiguator - sParams = "s, m, dDA" + if sFuncName.startswith("_c_"): # condition + sParams = "s, sx, m, dTokenPos, sCountry, bCondMemo" + elif sFuncName.startswith("_m_"): # message + sParams = "s, m" + elif sFuncName.startswith("_s_"): # suggestion + sParams = "s, m" + elif sFuncName.startswith("_p_"): # preprocessor + sParams = "s, m" + elif sFuncName.startswith("_d_"): # disambiguator + sParams = "s, m, dTokenPos" else: print("# Unknown function type in [" + sFuncName + "]") continue sPyCallables += "def {} ({}):\n".format(sFuncName, sParams) sPyCallables += " return " + sReturn + "\n" @@ -540,16 +577,20 @@ displayStats(lParagraphRules, lSentenceRules) print("Unnamed rules: " + str(nRULEWITHOUTNAME)) - d = { "callables": sPyCallables, - "callablesJS": sJSCallables, - "gctests": sGCTests, - "gctestsJS": sGCTestsJS, - "paragraph_rules": mergeRulesByOption(lParagraphRules), - "sentence_rules": mergeRulesByOption(lSentenceRules), - "paragraph_rules_JS": jsconv.writeRulesToJSArray(mergeRulesByOption(lParagraphRulesJS)), - "sentence_rules_JS": jsconv.writeRulesToJSArray(mergeRulesByOption(lSentenceRulesJS)) } - d.update(dOptions) - - return d + dVars = { "callables": sPyCallables, + "callablesJS": sJSCallables, + "gctests": sGCTests, + "gctestsJS": sGCTestsJS, + "paragraph_rules": mergeRulesByOption(lParagraphRules), + "sentence_rules": mergeRulesByOption(lSentenceRules), + "paragraph_rules_JS": jsconv.writeRulesToJSArray(mergeRulesByOption(lParagraphRulesJS)), + "sentence_rules_JS": jsconv.writeRulesToJSArray(mergeRulesByOption(lSentenceRulesJS)) } + dVars.update(dOptions) + + # compile graph rules + dVars2 = crg.make(lGraphRule, dDEF, sLang, bJavaScript) + dVars.update(dVars2) + + return dVars ADDED compile_rules_graph.py Index: compile_rules_graph.py ================================================================== --- /dev/null +++ compile_rules_graph.py @@ -0,0 +1,385 @@ +""" +Grammalecte: compile rules +Create a Direct Acyclic Rule Graphs (DARGs) +""" + +import re +import traceback +import json + +import darg + + +dACTIONS = {} +dFUNCTIONS = {} + + +def prepareFunction (s, bTokenValue=False): + "convert simple rule syntax to a string of Python code" + s = s.replace("__also__", "bCondMemo") + s = s.replace("__else__", "not bCondMemo") + s = re.sub(r"(morph|analyse|displayInfo)[(]\\(\d+)", 'g_\\1(lToken[\\2+nTokenOffset]', s) + s = re.sub(r"(select|exclude|define)[(][\\](\d+)", 'g_\\1(lToken[\\2+nTokenOffset], dTags', s) + s = re.sub(r"(tag_before|tag_after)[(][\\](\d+)", 'g_\\1(lToken[\\2+nTokenOffset], dTags', s) + s = re.sub(r"(switchGender|has(?:Mas|Fem)Form)[(]\\(\d+)", '\\1(lToken[\\2+nTokenOffset]["sValue"]', s) + s = re.sub(r"(morph|analyse)\(>1", 'g_\\1(lToken[nLastToken+1]', s) # next token + s = re.sub(r"(morph|analyse)\(<1", 'g_\\1(lToken[nTokenOffset]', s) # previous token + s = re.sub(r"[\\](\d+)\.is(upper|lower|title)\(\)", 'lToken[\\1+nTokenOffset]["sValue"].is\\2()', s) + s = re.sub(r"\bspell *[(]", '_oSpellChecker.isValid(', s) + s = re.sub(r"\bbefore\(\s*", 'look(sSentence[:lToken[1+nTokenOffset]["nStart"]], ', s) # before(s) + s = re.sub(r"\bafter\(\s*", 'look(sSentence[lToken[nLastToken]["nEnd"]:], ', s) # after(s) + s = re.sub(r"\bbefore0\(\s*", 'look(sSentence0[:lToken[1+nTokenOffset]["nStart"]], ', s) # before0(s) + s = re.sub(r"\bafter0\(\s*", 'look(sSentence[lToken[nLastToken]["nEnd"]:], ', s) # after0(s) + if bTokenValue: + # token values are used as parameter + s = re.sub(r"[\\](\d+)", 'lToken[\\1+nTokenOffset]["sValue"]', s) + else: + # tokens used as parameter + s = re.sub(r"[\\](\d+)", 'lToken[\\1+nTokenOffset]', s) + return s + + +def genTokenLines (sTokenLine, dDef): + "tokenize a string and return a list of lines of tokens" + lToken = sTokenLine.split() + lTokenLines = None + for sToken in lToken: + # optional token? + bNullPossible = sToken.startswith("?") and sToken.endswith("¿") + if bNullPossible: + sToken = sToken[1:-1] + # token with definition? + if sToken.startswith("({") and sToken.endswith("})") and sToken[1:-1] in dDef: + sToken = "(" + dDef[sToken[1:-1]] + ")" + elif sToken.startswith("{") and sToken.endswith("}") and sToken in dDef: + sToken = dDef[sToken] + if ( (sToken.startswith("[") and sToken.endswith("]")) or (sToken.startswith("([") and sToken.endswith("])")) ): + # multiple token + bSelectedGroup = sToken.startswith("(") and sToken.endswith(")") + if bSelectedGroup: + sToken = sToken[1:-1] + lNewToken = sToken[1:-1].split("|") + if not lTokenLines: + lTokenLines = [ [s] for s in lNewToken ] + if bNullPossible: + lTokenLines.extend([ [] for i in range(len(lNewToken)+1) ]) + else: + lNewTemp = [] + if bNullPossible: + for aRule in lTokenLines: + for sElem in lNewToken: + aNewRule = list(aRule) + aNewRule.append(sElem) + lNewTemp.append(aNewRule) + else: + sElem1 = lNewToken.pop(0) + for aRule in lTokenLines: + for sElem in lNewToken: + aNewRule = list(aRule) + aNewRule.append("(" + sElem + ")" if bSelectedGroup else sElem) + lNewTemp.append(aNewRule) + aRule.append("(" + sElem1 + ")" if bSelectedGroup else sElem1) + lTokenLines.extend(lNewTemp) + else: + # simple token + if not lTokenLines: + lTokenLines = [[sToken], []] if bNullPossible else [[sToken]] + else: + if bNullPossible: + lNewTemp = [] + for aRule in lTokenLines: + lNew = list(aRule) + lNew.append(sToken) + lNewTemp.append(lNew) + lTokenLines.extend(lNewTemp) + else: + for aRule in lTokenLines: + aRule.append(sToken) + for aRule in lTokenLines: + yield aRule + + +def createRule (iLine, sRuleName, sTokenLine, iActionBlock, sActions, nPriority, dDef): + "generator: create rule as list" + # print(iLine, "//", sRuleName, "//", sTokenLine, "//", sActions, "//", nPriority) + for lToken in genTokenLines(sTokenLine, dDef): + # Calculate positions + dPos = {} # key: iGroup, value: iToken + iGroup = 0 + for i, sToken in enumerate(lToken): + if sToken.startswith("(") and sToken.endswith(")"): + lToken[i] = sToken[1:-1] + iGroup += 1 + dPos[iGroup] = i + 1 # we add 1, for we count tokens from 1 to n (not from 0) + + # Parse actions + for iAction, sAction in enumerate(sActions.split(" <<- "), 1): + sAction = sAction.strip() + if sAction: + sActionId = sRuleName + "__b" + str(iActionBlock) + "_a" + str(iAction) + "_" + str(len(lToken)) + aAction = createAction(sActionId, sAction, nPriority, len(lToken), dPos) + if aAction: + dACTIONS[sActionId] = aAction + lResult = list(lToken) + lResult.extend(["##"+str(iLine), sActionId]) + yield lResult + + +def changeReferenceToken (sText, dPos): + "change group reference in with values in " + for i in range(len(dPos), 0, -1): + sText = sText.replace("\\"+str(i), "\\"+str(dPos[i])) + return sText + + +def checkTokenNumbers (sText, sActionId, nToken): + "check if token references in greater than (debugging)" + for x in re.finditer(r"\\(\d+)", sText): + if int(x.group(1)) > nToken: + print("# Error in token index at line " + sActionId + " ("+str(nToken)+" tokens only)") + print(sText) + + +def checkIfThereIsCode (sText, sActionId): + "check if there is code in (debugging)" + if re.search("[.]\\w+[(]|sugg\\w+[(]|\\([0-9]|\\[[0-9]", sText): + print("# Warning at line " + sActionId + ": This message looks like code. Line should probably begin with =") + print(sText) + + +def createAction (sActionId, sAction, nPriority, nToken, dPos): + "create action rule as a list" + # Option + sOption = False + m = re.match("/(\\w+)/", sAction) + if m: + sOption = m.group(1) + sAction = sAction[m.end():].strip() + # valid action? + m = re.search("(?P[-~=/])(?P\\d+|)(?P:\\d+|)>> ", sAction) + if not m: + print(" # Error. No action found at: ", sActionId) + print(" ==", sAction, "==") + return None + # Condition + sCondition = sAction[:m.start()].strip() + if sCondition: + sCondition = prepareFunction(sCondition) + sCondition = changeReferenceToken(sCondition, dPos) + dFUNCTIONS["_g_c_"+sActionId] = sCondition + sCondition = "_g_c_"+sActionId + else: + sCondition = "" + # Action + cAction = m.group("action") + sAction = sAction[m.end():].strip() + sAction = changeReferenceToken(sAction, dPos) + if not m.group("start"): + iStartAction = 1 + iEndAction = 0 + else: + iStartAction = int(m.group("start")) + iEndAction = int(m.group("end")[1:]) if m.group("end") else iStartAction + if dPos: + try: + iStartAction = dPos[iStartAction] + iEndAction = dPos[iEndAction] + except: + print("# Error. Wrong groups in: " + sActionId) + + if cAction == "-": + ## error + iMsg = sAction.find(" # ") + if iMsg == -1: + sMsg = "# Error. Error message not found." + sURL = "" + print(sMsg + " Action id: " + sActionId) + else: + sMsg = sAction[iMsg+3:].strip() + sAction = sAction[:iMsg].strip() + sURL = "" + mURL = re.search("[|] *(https?://.*)", sMsg) + if mURL: + sURL = mURL.group(1).strip() + sMsg = sMsg[:mURL.start(0)].strip() + checkTokenNumbers(sMsg, sActionId, nToken) + if sMsg[0:1] == "=": + sMsg = prepareFunction(sMsg[1:], True) + dFUNCTIONS["g_m_"+sActionId] = sMsg + sMsg = "=g_m_"+sActionId + else: + checkIfThereIsCode(sMsg, sActionId) + + # checking consistancy + checkTokenNumbers(sAction, sActionId, nToken) + + if cAction == ">": + ## no action, break loop if condition is False + return [sOption, sCondition, cAction, ""] + + if not sAction: + print("# Error in action at line " + sActionId + ": This action is empty.") + + if sAction[0:1] != "=": + checkIfThereIsCode(sAction, sActionId) + + if cAction == "-": + ## error detected --> suggestion + if sAction[0:1] == "=": + sAction = prepareFunction(sAction, True) + dFUNCTIONS["_g_s_"+sActionId] = sAction[1:] + sAction = "=_g_s_"+sActionId + elif sAction.startswith('"') and sAction.endswith('"'): + sAction = sAction[1:-1] + if not sMsg: + print("# Error in action at line " + sActionId + ": The message is empty.") + return [sOption, sCondition, cAction, sAction, iStartAction, iEndAction, nPriority, sMsg, sURL] + elif cAction == "~": + ## text processor + if sAction[0:1] == "=": + dFUNCTIONS["_g_p_"+sActionId] = sAction[1:] + sAction = "=_g_p_"+sActionId + elif sAction.startswith('"') and sAction.endswith('"'): + sAction = sAction[1:-1] + return [sOption, sCondition, cAction, sAction, iStartAction, iEndAction] + elif cAction == "/": + ## tags + return [sOption, sCondition, cAction, sAction, iStartAction, iEndAction] + elif cAction == "=": + ## disambiguator + if sAction[0:1] == "=": + sAction = sAction[1:] + if "define" in sAction and not re.search(r"define\(\\\d+ *, *\[.*\] *\)", sAction): + print("# Error in action at line " + sActionId + ": second argument for must be a list of strings") + sAction = prepareFunction(sAction) + dFUNCTIONS["_g_d_"+sActionId] = sAction + sAction = "_g_d_"+sActionId + return [sOption, sCondition, cAction, sAction] + else: + print("# Unknown action at line " + sActionId) + return None + + +def make (lRule, dDef, sLang, bJavaScript): + "compile rules, returns a dictionary of values" + # for clarity purpose, don’t create any file here + + # removing comments, zeroing empty lines, creating definitions, storing tests, merging rule lines + print(" parsing rules...") + lTokenLine = [] + sActions = "" + nPriority = 4 + dAllGraph = {} + sGraphName = "" + iActionBlock = 0 + + for i, sLine in lRule: + sLine = sLine.rstrip() + if "\t" in sLine: + # tabulation not allowed + print("Error. Tabulation at line: ", i) + exit() + elif sLine.startswith("@@@@GRAPH: "): + # rules graph call + m = re.match(r"@@@@GRAPH: *(\w+)", sLine.strip()) + if m: + sGraphName = m.group(1) + if sGraphName in dAllGraph: + print("Error. Group name " + sGraphName + " already exists.") + exit() + dAllGraph[sGraphName] = [] + else: + print("Error. Graph name not found at line", i) + exit() + elif sLine.startswith("__") and sLine.endswith("__"): + # new rule group + m = re.match("__(\\w+)(!\\d|)__", sLine) + if m: + sRuleName = m.group(1) + iActionBlock = 1 + nPriority = int(m.group(2)[1:]) if m.group(2) else 4 + else: + print("Error at rule group: ", sLine, " -- line:", i) + break + elif re.search("^ +<<- ", sLine) or sLine.startswith(" ") \ + or re.search("^ +#", sLine) or re.search(r"^ [-~=>/](?:\d(?::\d+|)|)>> ", sLine) : + # actions + sActions += " " + sLine.strip() + elif re.match("[  ]*$", sLine): + # empty line to end merging + if not lTokenLine: + continue + if not sActions: + print("Error. No action found at line:", i) + exit() + if not sGraphName: + print("Error. All rules must belong to a named graph. Line: ", i) + exit() + for j, sTokenLine in lTokenLine: + dAllGraph[sGraphName].append((j, sRuleName, sTokenLine, iActionBlock, sActions, nPriority)) + lTokenLine.clear() + sActions = "" + iActionBlock += 1 + elif sLine.startswith((" ")): + # tokens + lTokenLine.append([i, sLine.strip()]) + else: + print("Unknown line:") + print(sLine) + + # processing rules + print(" preparing rules...") + for sGraphName, lRuleLine in dAllGraph.items(): + lPreparedRule = [] + for i, sRuleGroup, sTokenLine, iActionBlock, sActions, nPriority in lRuleLine: + for lRule in createRule(i, sRuleGroup, sTokenLine, iActionBlock, sActions, nPriority, dDef): + lPreparedRule.append(lRule) + # Graph creation + oDARG = darg.DARG(lPreparedRule, sLang) + dAllGraph[sGraphName] = oDARG.createGraph() + # Debugging + #print("\nGRAPH:", sGraphName) + #for e in lPreparedRule: + # print(e) + #for k, v in dAllGraph[sGraphName].items(): + # print(k, "\t", v) + + # creating file with all functions callable by rules + print(" creating callables...") + sPyCallables = "# generated code, do not edit\n" + #sJSCallables = "// generated code, do not edit\nconst oEvalFunc = {\n" + for sFuncName, sReturn in dFUNCTIONS.items(): + if sFuncName.startswith("_g_c_"): # condition + sParams = "lToken, nTokenOffset, nLastToken, sCountry, bCondMemo, dTags, sSentence, sSentence0" + elif sFuncName.startswith("g_m_"): # message + sParams = "lToken, nTokenOffset" + elif sFuncName.startswith("_g_s_"): # suggestion + sParams = "lToken, nTokenOffset" + elif sFuncName.startswith("_g_p_"): # preprocessor + sParams = "lToken" + elif sFuncName.startswith("_g_d_"): # disambiguator + sParams = "lToken, nTokenOffset" + else: + print("# Unknown function type in [" + sFuncName + "]") + continue + sPyCallables += "def {} ({}):\n".format(sFuncName, sParams) + sPyCallables += " return " + sReturn + "\n" + #sJSCallables += " {}: function ({})".format(sFuncName, sParams) + " {\n" + #sJSCallables += " return " + jsconv.py2js(sReturn) + ";\n" + #sJSCallables += " },\n" + #sJSCallables += "}\n" + + # Debugging + if False: + print("\nActions:") + for sActionName, aAction in dACTIONS.items(): + print(sActionName, aAction) + print("\nFunctions:") + print(sPyCallables) + + # Result + return { + "graph_callables": sPyCallables, + "rules_graphs": dAllGraph, + "rules_actions": dACTIONS + } Index: compile_rules_js_convert.py ================================================================== --- compile_rules_js_convert.py +++ compile_rules_js_convert.py @@ -1,6 +1,8 @@ -# Convert Python code to JavaScript code +""" +Convert Python code and regexes to JavaScript code +""" import copy import re import json @@ -116,11 +118,15 @@ lNegLookBeforeRegex = None return (sRegex, lNegLookBeforeRegex) def pyRuleToJS (lRule, dJSREGEXES, sWORDLIMITLEFT): + "modify Python rules -> JS rules" lRuleJS = copy.deepcopy(lRule) + # graph rules + if lRuleJS[0] == "@@@@": + return lRuleJS del lRule[-1] # tGroups positioning codes are useless for Python # error messages for aAction in lRuleJS[6]: if aAction[1] == "-": aAction[2] = aAction[2].replace(" ", " ") # nbsp --> nnbsp @@ -130,27 +136,35 @@ lRuleJS.append(lNegLookBehindRegex) return lRuleJS def writeRulesToJSArray (lRules): + "create rules as a string of arrays (to be bundled in a JSON string)" sArray = "[\n" for sOption, aRuleGroup in lRules: - sArray += ' ["' + sOption + '", [\n' if sOption else " [false, [\n" - for sRegex, bCaseInsensitive, sLineId, sRuleId, nPriority, lActions, aGroups, aNegLookBehindRegex in aRuleGroup: - sArray += ' [' + sRegex + ", " - sArray += "true, " if bCaseInsensitive else "false, " - sArray += '"' + sLineId + '", ' - sArray += '"' + sRuleId + '", ' - sArray += str(nPriority) + ", " - sArray += json.dumps(lActions, ensure_ascii=False) + ", " - sArray += json.dumps(aGroups, ensure_ascii=False) + ", " - sArray += json.dumps(aNegLookBehindRegex, ensure_ascii=False) + "],\n" - sArray += " ]],\n" + if sOption != "@@@@": + sArray += ' ["' + sOption + '", [\n' if sOption else " [false, [\n" + for sRegex, bCaseInsensitive, sLineId, sRuleId, nPriority, lActions, aGroups, aNegLookBehindRegex in aRuleGroup: + sArray += ' [' + sRegex + ", " + sArray += "true, " if bCaseInsensitive else "false, " + sArray += '"' + sLineId + '", ' + sArray += '"' + sRuleId + '", ' + sArray += str(nPriority) + ", " + sArray += json.dumps(lActions, ensure_ascii=False) + ", " + sArray += json.dumps(aGroups, ensure_ascii=False) + ", " + sArray += json.dumps(aNegLookBehindRegex, ensure_ascii=False) + "],\n" + sArray += " ]],\n" + else: + sArray += ' ["' + sOption + '", [\n' + for sGraphName, sLineId in aRuleGroup: + sArray += ' ["' + sGraphName + '", "' + sLineId + '"],\n"' + sArray += " ]],\n" sArray += "]" return sArray def groupsPositioningCodeToList (sGroupsPositioningCode): + "convert to a list of codes (numbers or strings)" if not sGroupsPositioningCode: return None return [ int(sCode) if sCode.isdigit() or (sCode[0:1] == "-" and sCode[1:].isdigit()) else sCode \ for sCode in sGroupsPositioningCode.split(",") ] ADDED darg.py Index: darg.py ================================================================== --- /dev/null +++ darg.py @@ -0,0 +1,193 @@ +#!python3 + +""" +RULE GRAPH BUILDER +""" + +# by Olivier R. +# License: MPL 2 + + +from graphspell.progressbar import ProgressBar + + +class DARG: + """DIRECT ACYCLIC RULE GRAPH""" + # This code is inspired from Steve Hanov’s DAWG, 2011. (http://stevehanov.ca/blog/index.php?id=115) + + def __init__ (self, lRule, sLangCode): + print("===== Direct Acyclic Rule Graph - Minimal Acyclic Finite State Automaton =====") + + # Preparing DARG + print(" > Preparing list of tokens") + self.sLangCode = sLangCode + self.nRule = len(lRule) + self.aPreviousRule = [] + Node.resetNextId() + self.oRoot = Node() + self.lUncheckedNodes = [] # list of nodes that have not been checked for duplication. + self.lMinimizedNodes = {} # list of unique nodes that have been checked for duplication. + self.nNode = 0 + self.nArc = 0 + + # build + lRule.sort() + oProgBar = ProgressBar(0, len(lRule)) + for aRule in lRule: + self.insert(aRule) + oProgBar.increment(1) + oProgBar.done() + self.finish() + self.countNodes() + self.countArcs() + self.displayInfo() + + # BUILD DARG + def insert (self, aRule): + "insert a new rule (tokens must be inserted in order)" + if aRule < self.aPreviousRule: + exit("# Error: tokens must be inserted in order.") + + # find common prefix between word and previous word + nCommonPrefix = 0 + for i in range(min(len(aRule), len(self.aPreviousRule))): + if aRule[i] != self.aPreviousRule[i]: + break + nCommonPrefix += 1 + + # Check the lUncheckedNodes for redundant nodes, proceeding from last + # one down to the common prefix size. Then truncate the list at that point. + self._minimize(nCommonPrefix) + + # add the suffix, starting from the correct node mid-way through the graph + if len(self.lUncheckedNodes) == 0: + oNode = self.oRoot + else: + oNode = self.lUncheckedNodes[-1][2] + + iToken = nCommonPrefix + for sToken in aRule[nCommonPrefix:]: + oNextNode = Node() + oNode.dArcs[sToken] = oNextNode + self.lUncheckedNodes.append((oNode, sToken, oNextNode)) + if iToken == (len(aRule) - 2): + oNode.bFinal = True + iToken += 1 + oNode = oNextNode + oNode.bFinal = True + self.aPreviousRule = aRule + + def finish (self): + "minimize unchecked nodes" + self._minimize(0) + + def _minimize (self, downTo): + # proceed from the leaf up to a certain point + for i in range( len(self.lUncheckedNodes)-1, downTo-1, -1 ): + oNode, sToken, oChildNode = self.lUncheckedNodes[i] + if oChildNode in self.lMinimizedNodes: + # replace the child with the previously encountered one + oNode.dArcs[sToken] = self.lMinimizedNodes[oChildNode] + else: + # add the state to the minimized nodes. + self.lMinimizedNodes[oChildNode] = oChildNode + self.lUncheckedNodes.pop() + + def countNodes (self): + "count nodes within the whole graph" + self.nNode = len(self.lMinimizedNodes) + + def countArcs (self): + "count arcs within the whole graph" + self.nArc = 0 + for oNode in self.lMinimizedNodes: + self.nArc += len(oNode.dArcs) + + def displayInfo (self): + "display informations about the rule graph" + print(" * {:<12} {:>16,}".format("Rules:", self.nRule)) + print(" * {:<12} {:>16,}".format("Nodes:", self.nNode)) + print(" * {:<12} {:>16,}".format("Arcs:", self.nArc)) + + def createGraph (self): + "create the graph as a dictionary" + dGraph = { 0: self.oRoot.getNodeAsDict() } + for oNode in self.lMinimizedNodes: + sHashId = oNode.__hash__() + if sHashId not in dGraph: + dGraph[sHashId] = oNode.getNodeAsDict() + else: + print("Error. Double node… same id: ", sHashId) + print(str(oNode.getNodeAsDict())) + return dGraph + + + +class Node: + """Node of the rule graph""" + + NextId = 0 + + def __init__ (self): + self.i = Node.NextId + Node.NextId += 1 + self.bFinal = False + self.dArcs = {} # key: arc value; value: a node + + @classmethod + def resetNextId (cls): + "reset to 0 the node counter" + cls.NextId = 0 + + def __str__ (self): + # Caution! this function is used for hashing and comparison! + cFinal = "1" if self.bFinal else "0" + l = [cFinal] + for (key, oNode) in self.dArcs.items(): + l.append(str(key)) + l.append(str(oNode.i)) + return "_".join(l) + + def __hash__ (self): + # Used as a key in a python dictionary. + return self.__str__().__hash__() + + def __eq__ (self, other): + # Used as a key in a python dictionary. + # Nodes are equivalent if they have identical arcs, and each identical arc leads to identical states. + return self.__str__() == other.__str__() + + def getNodeAsDict (self): + "returns the node as a dictionary structure" + dNode = {} + dReValue = {} + dReMorph = {} + dRule = {} + dLemma = {} + dMeta = {} + for sArc, oNode in self.dArcs.items(): + if sArc.startswith("@") and len(sArc) > 1: + dReMorph[sArc[1:]] = oNode.__hash__() + elif sArc.startswith("~") and len(sArc) > 1: + dReValue[sArc[1:]] = oNode.__hash__() + elif sArc.startswith(">") and len(sArc) > 1: + dLemma[sArc[1:]] = oNode.__hash__() + elif sArc.startswith("*") and len(sArc) > 1: + dMeta[sArc[1:]] = oNode.__hash__() + elif sArc.startswith("##"): + dRule[sArc[1:]] = oNode.__hash__() + else: + dNode[sArc] = oNode.__hash__() + if dReValue: + dNode[""] = dReValue + if dReMorph: + dNode[""] = dReMorph + if dLemma: + dNode[""] = dLemma + if dMeta: + dNode[""] = dMeta + if dRule: + dNode[""] = dRule + #if self.bFinal: + # dNode[""] = 1 + return dNode Index: gc_core/js/lang_core/gc_engine.js ================================================================== --- gc_core/js/lang_core/gc_engine.js +++ gc_core/js/lang_core/gc_engine.js @@ -37,11 +37,10 @@ // data let _sAppContext = ""; // what software is running let _dOptions = null; let _aIgnoredRules = new Set(); let _oSpellChecker = null; -let _dAnalyses = new Map(); // cache for data from dictionary var gc_engine = { //// Informations @@ -327,10 +326,11 @@ } else { _oSpellChecker = new SpellChecker("${lang}", sPath, "${dic_main_filename_js}", "${dic_extended_filename_js}", "${dic_community_filename_js}", "${dic_personal_filename_js}"); } _sAppContext = sContext; _dOptions = gc_options.getOptions(sContext).gl_shallowCopy(); // duplication necessary, to be able to reset to default + _oSpellChecker.activateStorage(); } catch (e) { helpers.logerror(e); } }, @@ -376,39 +376,30 @@ // for debugging: info of word if (!aWord) { helpers.echo("> nothing to find"); return true; } - if (!_dAnalyses.has(aWord[1]) && !_storeMorphFromFSA(aWord[1])) { - helpers.echo("> not in FSA"); + let lMorph = _oSpellChecker.getMorph(aWord[1]); + if (lMorph.length === 0) { + helpers.echo("> not in dictionary"); return true; } if (dDA.has(aWord[0])) { helpers.echo("DA: " + dDA.get(aWord[0])); } - helpers.echo("FSA: " + _dAnalyses.get(aWord[1])); + helpers.echo("FSA: " + lMorph); return true; } -function _storeMorphFromFSA (sWord) { - // retrieves morphologies list from _oSpellChecker -> _dAnalyses - //helpers.echo("register: "+sWord + " " + _oSpellChecker.getMorph(sWord).toString()) - _dAnalyses.set(sWord, _oSpellChecker.getMorph(sWord)); - return !!_dAnalyses.get(sWord); -} - function morph (dDA, aWord, sPattern, bStrict=true, bNoWord=false) { // analyse a tuple (position, word), return true if sPattern in morphologies (disambiguation on) if (!aWord) { //helpers.echo("morph: noword, returns " + bNoWord); return bNoWord; } //helpers.echo("aWord: "+aWord.toString()); - if (!_dAnalyses.has(aWord[1]) && !_storeMorphFromFSA(aWord[1])) { - return false; - } - let lMorph = dDA.has(aWord[0]) ? dDA.get(aWord[0]) : _dAnalyses.get(aWord[1]); + let lMorph = dDA.has(aWord[0]) ? dDA.get(aWord[0]) : _oSpellChecker.getMorph(aWord[1]); //helpers.echo("lMorph: "+lMorph.toString()); if (lMorph.length === 0) { return false; } //helpers.echo("***"); @@ -423,14 +414,11 @@ if (!aWord) { //helpers.echo("morph: noword, returns " + bNoWord); return bNoWord; } //helpers.echo("aWord: "+aWord.toString()); - if (!_dAnalyses.has(aWord[1]) && !_storeMorphFromFSA(aWord[1])) { - return false; - } - let lMorph = dDA.has(aWord[0]) ? dDA.get(aWord[0]) : _dAnalyses.get(aWord[1]); + let lMorph = dDA.has(aWord[0]) ? dDA.get(aWord[0]) : _oSpellChecker.getMorph(aWord[1]); //helpers.echo("lMorph: "+lMorph.toString()); if (lMorph.length === 0) { return false; } //helpers.echo("***"); @@ -442,41 +430,32 @@ return lMorph.some(s => (s.search(sPattern) !== -1)); } function analyse (sWord, sPattern, bStrict=true) { // analyse a word, return true if sPattern in morphologies (disambiguation off) - if (!_dAnalyses.has(sWord) && !_storeMorphFromFSA(sWord)) { + let lMorph = _oSpellChecker.getMorph(sWord); + if (lMorph.length === 0) { return false; } if (bStrict) { - return _dAnalyses.get(sWord).every(s => (s.search(sPattern) !== -1)); + return lMorph.every(s => (s.search(sPattern) !== -1)); } - return _dAnalyses.get(sWord).some(s => (s.search(sPattern) !== -1)); + return lMorph.some(s => (s.search(sPattern) !== -1)); } function analysex (sWord, sPattern, sNegPattern) { // analyse a word, returns True if not sNegPattern in word morphologies and sPattern in word morphologies (disambiguation off) - if (!_dAnalyses.has(sWord) && !_storeMorphFromFSA(sWord)) { + let lMorph = _oSpellChecker.getMorph(sWord); + if (lMorph.length === 0) { return false; } // check negative condition - if (_dAnalyses.get(sWord).some(s => (s.search(sNegPattern) !== -1))) { + if (lMorph.some(s => (s.search(sNegPattern) !== -1))) { return false; } // search sPattern - return _dAnalyses.get(sWord).some(s => (s.search(sPattern) !== -1)); -} - -function stem (sWord) { - // returns a list of sWord's stems - if (!sWord) { - return []; - } - if (!_dAnalyses.has(sWord) && !_storeMorphFromFSA(sWord)) { - return []; - } - return _dAnalyses.get(sWord).map( s => s.slice(1, s.indexOf(" ")) ); + return lMorph.some(s => (s.search(sPattern) !== -1)); } //// functions to get text outside pattern scope @@ -565,19 +544,17 @@ return true; } if (dDA.has(nPos)) { return true; } - if (!_dAnalyses.has(sWord) && !_storeMorphFromFSA(sWord)) { + let lMorph = _oSpellChecker.getMorph(sWord); + if (lMorph.length === 0 || lMorph.length === 1) { return true; } - if (_dAnalyses.get(sWord).length === 1) { - return true; - } - let lSelect = _dAnalyses.get(sWord).filter( sMorph => sMorph.search(sPattern) !== -1 ); + let lSelect = lMorph.filter( sMorph => sMorph.search(sPattern) !== -1 ); if (lSelect.length > 0) { - if (lSelect.length != _dAnalyses.get(sWord).length) { + if (lSelect.length != lMorph.length) { dDA.set(nPos, lSelect); } } else if (lDefault) { dDA.set(nPos, lDefaul); } @@ -589,19 +566,17 @@ return true; } if (dDA.has(nPos)) { return true; } - if (!_dAnalyses.has(sWord) && !_storeMorphFromFSA(sWord)) { + let lMorph = _oSpellChecker.getMorph(sWord); + if (lMorph.length === 0 || lMorph.length === 1) { return true; } - if (_dAnalyses.get(sWord).length === 1) { - return true; - } - let lSelect = _dAnalyses.get(sWord).filter( sMorph => sMorph.search(sPattern) === -1 ); + let lSelect = lMorph.filter( sMorph => sMorph.search(sPattern) === -1 ); if (lSelect.length > 0) { - if (lSelect.length != _dAnalyses.get(sWord).length) { + if (lSelect.length != lMorph.length) { dDA.set(nPos, lSelect); } } else if (lDefault) { dDA.set(nPos, lDefault); } Index: gc_core/py/__init__.py ================================================================== --- gc_core/py/__init__.py +++ gc_core/py/__init__.py @@ -1,2 +1,5 @@ +""" +Grammar checker +""" from .grammar_checker import * Index: gc_core/py/grammar_checker.py ================================================================== --- gc_core/py/grammar_checker.py +++ gc_core/py/grammar_checker.py @@ -1,15 +1,17 @@ -# Grammalecte -# Main class: wrapper +""" +Grammalecte, grammar checker +""" import importlib import json from . import text class GrammarChecker: + "GrammarChecker: Wrapper for the grammar checker engine" def __init__ (self, sLangCode, sContext="Python"): self.sLangCode = sLangCode # Grammar checker engine self.gce = importlib.import_module("."+sLangCode, "grammalecte") @@ -20,49 +22,58 @@ self.oLexicographer = None # Text formatter self.oTextFormatter = None def getGCEngine (self): + "return the grammar checker object" return self.gce def getSpellChecker (self): + "return the spell checker object" return self.oSpellChecker def getTextFormatter (self): - if self.oTextFormatter == None: - self.tf = importlib.import_module("."+self.sLangCode+".textformatter", "grammalecte") - self.oTextFormatter = self.tf.TextFormatter() + "load and return the text formatter" + if self.oTextFormatter is None: + tf = importlib.import_module("."+self.sLangCode+".textformatter", "grammalecte") + self.oTextFormatter = tf.TextFormatter() return self.oTextFormatter def getLexicographer (self): - if self.oLexicographer == None: - self.lxg = importlib.import_module("."+self.sLangCode+".lexicographe", "grammalecte") - self.oLexicographer = self.lxg.Lexicographe(self.oSpellChecker) + "load and return the lexicographer" + if self.oLexicographer is None: + lxg = importlib.import_module("."+self.sLangCode+".lexicographe", "grammalecte") + self.oLexicographer = lxg.Lexicographe(self.oSpellChecker) return self.oLexicographer def displayGCOptions (self): + "display the grammar checker options" self.gce.displayOptions() def getParagraphErrors (self, sText, dOptions=None, bContext=False, bSpellSugg=False, bDebug=False): "returns a tuple: (grammar errors, spelling errors)" aGrammErrs = self.gce.parse(sText, "FR", bDebug=bDebug, dOptions=dOptions, bContext=bContext) aSpellErrs = self.oSpellChecker.parseParagraph(sText, bSpellSugg) return aGrammErrs, aSpellErrs def generateText (self, sText, bEmptyIfNoErrors=False, bSpellSugg=False, nWidth=100, bDebug=False): + "[todo]" pass def generateTextAsJSON (self, sText, bContext=False, bEmptyIfNoErrors=False, bSpellSugg=False, bReturnText=False, bDebug=False): + "[todo]" pass def generateParagraph (self, sText, dOptions=None, bEmptyIfNoErrors=False, bSpellSugg=False, nWidth=100, bDebug=False): + "parse text and return a readable text with underline errors" aGrammErrs, aSpellErrs = self.getParagraphErrors(sText, dOptions, False, bSpellSugg, bDebug) if bEmptyIfNoErrors and not aGrammErrs and not aSpellErrs: return "" return text.generateParagraph(sText, aGrammErrs, aSpellErrs, nWidth) def generateParagraphAsJSON (self, iIndex, sText, dOptions=None, bContext=False, bEmptyIfNoErrors=False, bSpellSugg=False, bReturnText=False, lLineSet=None, bDebug=False): + "parse text and return errors as a JSON string" aGrammErrs, aSpellErrs = self.getParagraphErrors(sText, dOptions, bContext, bSpellSugg, bDebug) aGrammErrs = list(aGrammErrs) if bEmptyIfNoErrors and not aGrammErrs and not aSpellErrs: return "" if lLineSet: Index: gc_core/py/lang_core/gc_engine.py ================================================================== --- gc_core/py/lang_core/gc_engine.py +++ gc_core/py/lang_core/gc_engine.py @@ -1,7 +1,9 @@ -# Grammalecte -# Grammar checker engine +""" +Grammalecte +Grammar checker engine +""" import re import sys import os import traceback @@ -9,10 +11,23 @@ from itertools import chain from ..graphspell.spellchecker import SpellChecker from ..graphspell.echo import echo from . import gc_options + +from ..graphspell.tokenizer import Tokenizer +from .gc_rules_graph import dAllGraph, dRule + +try: + # LibreOffice / OpenOffice + from com.sun.star.linguistic2 import SingleProofreadingError + from com.sun.star.text.TextMarkupType import PROOFREADING + from com.sun.star.beans import PropertyValue + #import lightproof_handler_${implname} as opt + _bWriterError = True +except ImportError: + _bWriterError = False __all__ = [ "lang", "locales", "pkg", "name", "version", "author", \ "load", "parse", "getSpellChecker", \ "setOption", "setOptions", "getOptions", "getDefaultOptions", "getOptionsLabels", "resetOptions", "displayOptions", \ @@ -31,30 +46,90 @@ _rules = None # module gc_rules # data _sAppContext = "" # what software is running _dOptions = None -_aIgnoredRules = set() _oSpellChecker = None -_dAnalyses = {} # cache for data from dictionary +_oTokenizer = None +_aIgnoredRules = set() +# functions +_createRegexError = None + + +#### Initialization + +def load (sContext="Python"): + "initialization of the grammar checker" + global _oSpellChecker + global _sAppContext + global _dOptions + global _oTokenizer + global _createRegexError + try: + _oSpellChecker = SpellChecker("${lang}", "${dic_main_filename_py}", "${dic_extended_filename_py}", "${dic_community_filename_py}", "${dic_personal_filename_py}") + _sAppContext = sContext + _dOptions = dict(gc_options.getOptions(sContext)) # duplication necessary, to be able to reset to default + _oTokenizer = _oSpellChecker.getTokenizer() + _oSpellChecker.activateStorage() + _createRegexError = _createRegexWriterError if _bWriterError else _createRegexDictError + except: + traceback.print_exc() + + +def _getRules (bParagraph): + try: + if not bParagraph: + return _rules.lSentenceRules + return _rules.lParagraphRules + except: + _loadRules() + if not bParagraph: + return _rules.lSentenceRules + return _rules.lParagraphRules + + +def _loadRules (): + from . import gc_rules + global _rules + _rules = gc_rules + # compile rules regex + for sOption, lRuleGroup in chain(_rules.lParagraphRules, _rules.lSentenceRules): + if sOption != "@@@@": + for aRule in lRuleGroup: + try: + aRule[0] = re.compile(aRule[0]) + except: + echo("Bad regular expression in # " + str(aRule[2])) + aRule[0] = "(?i)" #### Parsing + +_zEndOfSentence = re.compile(r'([.?!:;…][ .?!… »”")]*|.$)') +_zBeginOfParagraph = re.compile(r"^\W*") +_zEndOfParagraph = re.compile(r"\W*$") + +def _getSentenceBoundaries (sText): + iStart = _zBeginOfParagraph.match(sText).end() + for m in _zEndOfSentence.finditer(sText): + yield (iStart, m.end()) + iStart = m.end() + def parse (sText, sCountry="${country_default}", bDebug=False, dOptions=None, bContext=False): "analyses the paragraph sText and returns list of errors" #sText = unicodedata.normalize("NFC", sText) aErrors = None - sAlt = sText - dDA = {} # Disambiguisator. Key = position; value = list of morphologies + sRealText = sText dPriority = {} # Key = position; value = priority dOpt = _dOptions if not dOptions else dOptions + bShowRuleId = option('idrule') # parse paragraph try: - sNew, aErrors = _proofread(sText, sAlt, 0, True, dDA, dPriority, sCountry, dOpt, bDebug, bContext) + sNew, aErrors = _proofread(None, sText, sRealText, 0, True, dPriority, sCountry, dOpt, bShowRuleId, bDebug, bContext) if sNew: sText = sNew except: raise @@ -69,74 +144,80 @@ sText = sText.replace("‑", "-") # nobreakdash # parse sentences for iStart, iEnd in _getSentenceBoundaries(sText): if 4 < (iEnd - iStart) < 2000: - dDA.clear() try: - _, errs = _proofread(sText[iStart:iEnd], sAlt[iStart:iEnd], iStart, False, dDA, dPriority, sCountry, dOpt, bDebug, bContext) + oSentence = TokenSentence(sText[iStart:iEnd], sRealText[iStart:iEnd], iStart) + _, errs = _proofread(oSentence, sText[iStart:iEnd], sRealText[iStart:iEnd], iStart, False, dPriority, sCountry, dOpt, bShowRuleId, bDebug, bContext) aErrors.update(errs) except: raise return aErrors.values() # this is a view (iterable) -def _getSentenceBoundaries (sText): - iStart = _zBeginOfParagraph.match(sText).end() - for m in _zEndOfSentence.finditer(sText): - yield (iStart, m.end()) - iStart = m.end() - - -def _proofread (s, sx, nOffset, bParagraph, dDA, dPriority, sCountry, dOptions, bDebug, bContext): +def _proofread (oSentence, s, sx, nOffset, bParagraph, dPriority, sCountry, dOptions, bShowRuleId, bDebug, bContext): dErrs = {} - bChange = False - bIdRule = option('idrule') - + bParagraphChange = False + bSentenceChange = False + dTokenPos = oSentence.dTokenPos if oSentence else {} for sOption, lRuleGroup in _getRules(bParagraph): - if not sOption or dOptions.get(sOption, False): + if sOption == "@@@@": + # graph rules + if not bParagraph and bSentenceChange: + oSentence.update(s) + bSentenceChange = False + for sGraphName, sLineId in lRuleGroup: + if bDebug: + print("\n>>>> GRAPH:", sGraphName, sLineId) + bParagraphChange, s = oSentence.parse(dAllGraph[sGraphName], dPriority, sCountry, dOptions, bShowRuleId, bDebug, bContext) + dErrs.update(oSentence.dError) + elif not sOption or dOptions.get(sOption, False): + # regex rules for zRegex, bUppercase, sLineId, sRuleId, nPriority, lActions in lRuleGroup: if sRuleId not in _aIgnoredRules: for m in zRegex.finditer(s): bCondMemo = None for sFuncCond, cActionType, sWhat, *eAct in lActions: # action in lActions: [ condition, action type, replacement/suggestion/action[, iGroup[, message, URL]] ] try: - bCondMemo = not sFuncCond or globals()[sFuncCond](s, sx, m, dDA, sCountry, bCondMemo) + bCondMemo = not sFuncCond or globals()[sFuncCond](s, sx, m, dTokenPos, sCountry, bCondMemo) if bCondMemo: if cActionType == "-": # grammar error nErrorStart = nOffset + m.start(eAct[0]) - if nErrorStart not in dErrs or nPriority > dPriority[nErrorStart]: - dErrs[nErrorStart] = _createError(s, sx, sWhat, nOffset, m, eAct[0], sLineId, sRuleId, bUppercase, eAct[1], eAct[2], bIdRule, sOption, bContext) + if nErrorStart not in dErrs or nPriority > dPriority.get(nErrorStart, -1): + dErrs[nErrorStart] = _createRegexError(s, sx, sWhat, nOffset, m, eAct[0], sLineId, sRuleId, bUppercase, eAct[1], eAct[2], bShowRuleId, sOption, bContext) dPriority[nErrorStart] = nPriority elif cActionType == "~": # text processor s = _rewrite(s, sWhat, eAct[0], m, bUppercase) - bChange = True + bParagraphChange = True + bSentenceChange = True if bDebug: echo("~ " + s + " -- " + m.group(eAct[0]) + " # " + sLineId) elif cActionType == "=": # disambiguation - globals()[sWhat](s, m, dDA) - if bDebug: - echo("= " + m.group(0) + " # " + sLineId + "\nDA: " + str(dDA)) + if not bParagraph: + globals()[sWhat](s, m, dTokenPos) + if bDebug: + echo("= " + m.group(0) + " # " + sLineId) elif cActionType == ">": # we do nothing, this test is just a condition to apply all following actions pass else: echo("# error: unknown action at " + sLineId) elif cActionType == ">": break except Exception as e: raise Exception(str(e), "# " + sLineId + " # " + sRuleId) - if bChange: + if bParagraphChange: return (s, dErrs) return (False, dErrs) -def _createWriterError (s, sx, sRepl, nOffset, m, iGroup, sLineId, sRuleId, bUppercase, sMsg, sURL, bIdRule, sOption, bContext): +def _createRegexWriterError (s, sx, sRepl, nOffset, m, iGroup, sLineId, sRuleId, bUppercase, sMsg, sURL, bShowRuleId, sOption, bContext): "error for Writer (LO/OO)" xErr = SingleProofreadingError() #xErr = uno.createUnoStruct( "com.sun.star.linguistic2.SingleProofreadingError" ) xErr.nErrorStart = nOffset + m.start(iGroup) xErr.nErrorLength = m.end(iGroup) - m.start(iGroup) @@ -158,30 +239,27 @@ if bUppercase and m.group(iGroup)[0:1].isupper(): xErr.aSuggestions = tuple(map(str.capitalize, m.expand(sRepl).split("|"))) else: xErr.aSuggestions = tuple(m.expand(sRepl).split("|")) # Message - if sMsg[0:1] == "=": - sMessage = globals()[sMsg[1:]](s, m) - else: - sMessage = m.expand(sMsg) + sMessage = globals()[sMsg[1:]](s, m) if sMsg[0:1] == "=" else m.expand(sMsg) xErr.aShortComment = sMessage # sMessage.split("|")[0] # in context menu xErr.aFullComment = sMessage # sMessage.split("|")[-1] # in dialog - if bIdRule: + if bShowRuleId: xErr.aShortComment += " # " + sLineId + " # " + sRuleId # URL if sURL: - p = PropertyValue() - p.Name = "FullCommentURL" - p.Value = sURL - xErr.aProperties = (p,) + xProperty = PropertyValue() + xProperty.Name = "FullCommentURL" + xProperty.Value = sURL + xErr.aProperties = (xProperty,) else: xErr.aProperties = () return xErr -def _createDictError (s, sx, sRepl, nOffset, m, iGroup, sLineId, sRuleId, bUppercase, sMsg, sURL, bIdRule, sOption, bContext): +def _createRegexDictError (s, sx, sRepl, nOffset, m, iGroup, sLineId, sRuleId, bUppercase, sMsg, sURL, bShowRuleId, sOption, bContext): "error as a dictionary" dErr = {} dErr["nStart"] = nOffset + m.start(iGroup) dErr["nEnd"] = nOffset + m.end(iGroup) dErr["sLineId"] = sLineId @@ -194,25 +272,21 @@ if bUppercase and m.group(iGroup)[0:1].isupper(): dErr["aSuggestions"] = list(map(str.capitalize, sugg.split("|"))) else: dErr["aSuggestions"] = sugg.split("|") else: - dErr["aSuggestions"] = () + dErr["aSuggestions"] = [] elif sRepl == "_": - dErr["aSuggestions"] = () + dErr["aSuggestions"] = [] else: if bUppercase and m.group(iGroup)[0:1].isupper(): dErr["aSuggestions"] = list(map(str.capitalize, m.expand(sRepl).split("|"))) else: dErr["aSuggestions"] = m.expand(sRepl).split("|") # Message - if sMsg[0:1] == "=": - sMessage = globals()[sMsg[1:]](s, m) - else: - sMessage = m.expand(sMsg) - dErr["sMessage"] = sMessage - if bIdRule: + dErr["sMessage"] = globals()[sMsg[1:]](s, m) if sMsg[0:1] == "=" else m.expand(sMsg) + if bShowRuleId: dErr["sMessage"] += " # " + sLineId + " # " + sRuleId # URL dErr["URL"] = sURL if sURL else "" # Context if bContext: @@ -220,39 +294,40 @@ dErr['sBefore'] = sx[max(0,m.start(iGroup)-80):m.start(iGroup)] dErr['sAfter'] = sx[m.end(iGroup):m.end(iGroup)+80] return dErr -def _rewrite (s, sRepl, iGroup, m, bUppercase): - "text processor: write sRepl in s at iGroup position" +def _rewrite (sSentence, sRepl, iGroup, m, bUppercase): + "text processor: write in at position" nLen = m.end(iGroup) - m.start(iGroup) if sRepl == "*": sNew = " " * nLen - elif sRepl == ">" or sRepl == "_" or sRepl == "~": + elif sRepl == "_": sNew = sRepl + " " * (nLen-1) - elif sRepl == "@": - sNew = "@" * nLen elif sRepl[0:1] == "=": - sNew = globals()[sRepl[1:]](s, m) + sNew = globals()[sRepl[1:]](sSentence, m) sNew = sNew + " " * (nLen-len(sNew)) if bUppercase and m.group(iGroup)[0:1].isupper(): sNew = sNew.capitalize() else: sNew = m.expand(sRepl) sNew = sNew + " " * (nLen-len(sNew)) - return s[0:m.start(iGroup)] + sNew + s[m.end(iGroup):] + return sSentence[0:m.start(iGroup)] + sNew + sSentence[m.end(iGroup):] def ignoreRule (sRuleId): + "disable rule " _aIgnoredRules.add(sRuleId) def resetIgnoreRules (): + "clear all ignored rules" _aIgnoredRules.clear() def reactivateRule (sRuleId): + "(re)activate rule " _aIgnoredRules.discard(sRuleId) def listRules (sFilter=None): "generator: returns typle (sOption, sLineId, sRuleId)" @@ -261,220 +336,158 @@ zFilter = re.compile(sFilter) except: echo("# Error. List rules: wrong regex.") sFilter = None for sOption, lRuleGroup in chain(_getRules(True), _getRules(False)): - for _, _, sLineId, sRuleId, _, _ in lRuleGroup: - if not sFilter or zFilter.search(sRuleId): - yield (sOption, sLineId, sRuleId) + if sOption != "@@@@": + for _, _, sLineId, sRuleId, _, _ in lRuleGroup: + if not sFilter or zFilter.search(sRuleId): + yield (sOption, sLineId, sRuleId) def displayRules (sFilter=None): + "display the name of rules, with the filter " echo("List of rules. Filter: << " + str(sFilter) + " >>") for sOption, sLineId, sRuleId in listRules(sFilter): echo("{:<10} {:<10} {}".format(sOption, sLineId, sRuleId)) - -#### init - -try: - # LibreOffice / OpenOffice - from com.sun.star.linguistic2 import SingleProofreadingError - from com.sun.star.text.TextMarkupType import PROOFREADING - from com.sun.star.beans import PropertyValue - #import lightproof_handler_${implname} as opt - _createError = _createWriterError -except ImportError: - _createError = _createDictError - - -def load (sContext="Python"): - global _oSpellChecker - global _sAppContext - global _dOptions - try: - _oSpellChecker = SpellChecker("${lang}", "${dic_main_filename_py}", "${dic_extended_filename_py}", "${dic_community_filename_py}", "${dic_personal_filename_py}") - _sAppContext = sContext - _dOptions = dict(gc_options.getOptions(sContext)) # duplication necessary, to be able to reset to default - except: - traceback.print_exc() - def setOption (sOpt, bVal): + "set option with if it exists" if sOpt in _dOptions: _dOptions[sOpt] = bVal def setOptions (dOpt): + "update the dictionary of options with " for sKey, bVal in dOpt.items(): if sKey in _dOptions: _dOptions[sKey] = bVal def getOptions (): + "return the dictionary of current options" return _dOptions def getDefaultOptions (): + "return the dictionary of default options" return dict(gc_options.getOptions(_sAppContext)) def getOptionsLabels (sLang): + "return options labels" return gc_options.getUI(sLang) def displayOptions (sLang): + "display the list of grammar checking options" echo("List of options") echo("\n".join( [ k+":\t"+str(v)+"\t"+gc_options.getUI(sLang).get(k, ("?", ""))[0] for k, v in sorted(_dOptions.items()) ] )) echo("") def resetOptions (): + "set options to default values" global _dOptions _dOptions = dict(gc_options.getOptions(_sAppContext)) def getSpellChecker (): + "return the spellchecker object" return _oSpellChecker - -def _getRules (bParagraph): - try: - if not bParagraph: - return _rules.lSentenceRules - return _rules.lParagraphRules - except: - _loadRules() - if not bParagraph: - return _rules.lSentenceRules - return _rules.lParagraphRules - - -def _loadRules (): - from . import gc_rules - global _rules - _rules = gc_rules - # compile rules regex - for lRuleGroup in chain(_rules.lParagraphRules, _rules.lSentenceRules): - for rule in lRuleGroup[1]: - try: - rule[0] = re.compile(rule[0]) - except: - echo("Bad regular expression in # " + str(rule[2])) - rule[0] = "(?i)" - def _getPath (): return os.path.join(os.path.dirname(sys.modules[__name__].__file__), __name__ + ".py") #### common functions - -# common regexes -_zEndOfSentence = re.compile('([.?!:;…][ .?!… »”")]*|.$)') -_zBeginOfParagraph = re.compile("^\W*") -_zEndOfParagraph = re.compile("\W*$") -_zNextWord = re.compile(" +(\w[\w-]*)") -_zPrevWord = re.compile("(\w[\w-]*) +$") - def option (sOpt): - "return True if option sOpt is active" + "return True if option is active" return _dOptions.get(sOpt, False) -def displayInfo (dDA, tWord): +def displayInfo (dTokenPos, tWord): "for debugging: retrieve info of word" if not tWord: echo("> nothing to find") return True - if tWord[1] not in _dAnalyses and not _storeMorphFromFSA(tWord[1]): - echo("> not in FSA") + lMorph = _oSpellChecker.getMorph(tWord[1]) + if not lMorph: + echo("> not in dictionary") return True - if tWord[0] in dDA: - echo("DA: " + str(dDA[tWord[0]])) - echo("FSA: " + str(_dAnalyses[tWord[1]])) + if tWord[0] in dTokenPos and "lMorph" in dTokenPos[tWord[0]]: + echo("DA: " + str(dTokenPos[tWord[0]]["lMorph"])) + echo("FSA: " + str(lMorph)) return True -def _storeMorphFromFSA (sWord): - "retrieves morphologies list from _oSpellChecker -> _dAnalyses" - global _dAnalyses - _dAnalyses[sWord] = _oSpellChecker.getMorph(sWord) - return True if _dAnalyses[sWord] else False - - -def morph (dDA, tWord, sPattern, bStrict=True, bNoWord=False): +def morph (dTokenPos, tWord, sPattern, bStrict=True, bNoWord=False): "analyse a tuple (position, word), return True if sPattern in morphologies (disambiguation on)" if not tWord: return bNoWord - if tWord[1] not in _dAnalyses and not _storeMorphFromFSA(tWord[1]): - return False - lMorph = dDA[tWord[0]] if tWord[0] in dDA else _dAnalyses[tWord[1]] + lMorph = dTokenPos[tWord[0]]["lMorph"] if tWord[0] in dTokenPos and "lMorph" in dTokenPos[tWord[0]] else _oSpellChecker.getMorph(tWord[1]) if not lMorph: return False - p = re.compile(sPattern) + zPattern = re.compile(sPattern) if bStrict: - return all(p.search(s) for s in lMorph) - return any(p.search(s) for s in lMorph) + return all(zPattern.search(s) for s in lMorph) + return any(zPattern.search(s) for s in lMorph) -def morphex (dDA, tWord, sPattern, sNegPattern, bNoWord=False): +def morphex (dTokenPos, tWord, sPattern, sNegPattern, bNoWord=False): "analyse a tuple (position, word), returns True if not sNegPattern in word morphologies and sPattern in word morphologies (disambiguation on)" if not tWord: return bNoWord - if tWord[1] not in _dAnalyses and not _storeMorphFromFSA(tWord[1]): + lMorph = dTokenPos[tWord[0]]["lMorph"] if tWord[0] in dTokenPos and "lMorph" in dTokenPos[tWord[0]] else _oSpellChecker.getMorph(tWord[1]) + if not lMorph: return False - lMorph = dDA[tWord[0]] if tWord[0] in dDA else _dAnalyses[tWord[1]] # check negative condition - np = re.compile(sNegPattern) - if any(np.search(s) for s in lMorph): + zNegPattern = re.compile(sNegPattern) + if any(zNegPattern.search(s) for s in lMorph): return False # search sPattern - p = re.compile(sPattern) - return any(p.search(s) for s in lMorph) + zPattern = re.compile(sPattern) + return any(zPattern.search(s) for s in lMorph) def analyse (sWord, sPattern, bStrict=True): "analyse a word, return True if sPattern in morphologies (disambiguation off)" - if sWord not in _dAnalyses and not _storeMorphFromFSA(sWord): + lMorph = _oSpellChecker.getMorph(sWord) + if not lMorph: return False - if not _dAnalyses[sWord]: - return False - p = re.compile(sPattern) + zPattern = re.compile(sPattern) if bStrict: - return all(p.search(s) for s in _dAnalyses[sWord]) - return any(p.search(s) for s in _dAnalyses[sWord]) + return all(zPattern.search(s) for s in lMorph) + return any(zPattern.search(s) for s in lMorph) def analysex (sWord, sPattern, sNegPattern): "analyse a word, returns True if not sNegPattern in word morphologies and sPattern in word morphologies (disambiguation off)" - if sWord not in _dAnalyses and not _storeMorphFromFSA(sWord): + lMorph = _oSpellChecker.getMorph(sWord) + if not lMorph: return False # check negative condition - np = re.compile(sNegPattern) - if any(np.search(s) for s in _dAnalyses[sWord]): + zNegPattern = re.compile(sNegPattern) + if any(zNegPattern.search(s) for s in lMorph): return False # search sPattern - p = re.compile(sPattern) - return any(p.search(s) for s in _dAnalyses[sWord]) - - -def stem (sWord): - "returns a list of sWord's stems" - if not sWord: - return [] - if sWord not in _dAnalyses and not _storeMorphFromFSA(sWord): - return [] - return [ s[1:s.find(" ")] for s in _dAnalyses[sWord] ] + zPattern = re.compile(sPattern) + return any(zPattern.search(s) for s in lMorph) + ## functions to get text outside pattern scope # warning: check compile_rules.py to understand how it works +_zNextWord = re.compile(r" +(\w[\w-]*)") +_zPrevWord = re.compile(r"(\w[\w-]*) +$") + def nextword (s, iStart, n): "get the nth word of the input string or empty string" m = re.match("(?: +[\\w%-]+){" + str(n-1) + "} +([\\w%-]+)", s[iStart:]) if not m: return None @@ -512,11 +525,11 @@ if re.search(sPattern, s): return True return False -def look_chk1 (dDA, s, nOffset, sPattern, sPatternGroup1, sNegPatternGroup1=None): +def look_chk1 (dTokenPos, s, nOffset, sPattern, sPatternGroup1, sNegPatternGroup1=None): "returns True if s has pattern sPattern and m.group(1) has pattern sPatternGroup1" m = re.search(sPattern, s) if not m: return False try: @@ -523,63 +536,569 @@ sWord = m.group(1) nPos = m.start(1) + nOffset except: return False if sNegPatternGroup1: - return morphex(dDA, (nPos, sWord), sPatternGroup1, sNegPatternGroup1) - return morph(dDA, (nPos, sWord), sPatternGroup1, False) + return morphex(dTokenPos, (nPos, sWord), sPatternGroup1, sNegPatternGroup1) + return morph(dTokenPos, (nPos, sWord), sPatternGroup1, False) + + +#### Disambiguator + +def select (dTokenPos, nPos, sWord, sPattern, lDefault=None): + "Disambiguation: select morphologies of matching " + if not sWord: + return True + if nPos not in dTokenPos: + print("Error. There should be a token at this position: ", nPos) + return True + lMorph = _oSpellChecker.getMorph(sWord) + if not lMorph or len(lMorph) == 1: + return True + lSelect = [ sMorph for sMorph in lMorph if re.search(sPattern, sMorph) ] + if lSelect: + if len(lSelect) != len(lMorph): + dTokenPos[nPos]["lMorph"] = lSelect + elif lDefault: + dTokenPos[nPos]["lMorph"] = lDefault + return True + + +def exclude (dTokenPos, nPos, sWord, sPattern, lDefault=None): + "Disambiguation: exclude morphologies of matching " + if not sWord: + return True + if nPos not in dTokenPos: + print("Error. There should be a token at this position: ", nPos) + return True + lMorph = _oSpellChecker.getMorph(sWord) + if not lMorph or len(lMorph) == 1: + return True + lSelect = [ sMorph for sMorph in lMorph if not re.search(sPattern, sMorph) ] + if lSelect: + if len(lSelect) != len(lMorph): + dTokenPos[nPos]["lMorph"] = lSelect + elif lDefault: + dTokenPos[nPos]["lMorph"] = lDefault + return True + + +def define (dTokenPos, nPos, lMorph): + "Disambiguation: set morphologies of token at with " + if nPos not in dTokenPos: + print("Error. There should be a token at this position: ", nPos) + return True + dTokenPos[nPos]["lMorph"] = lMorph + return True + + + + +#### TOKEN SENTENCE CHECKER + +class TokenSentence: + "Text parser" + + def __init__ (self, sSentence, sSentence0, nOffset): + self.sSentence = sSentence + self.sSentence0 = sSentence0 + self.nOffsetWithinParagraph = nOffset + self.lToken = list(_oTokenizer.genTokens(sSentence, True)) + self.dTokenPos = { dToken["nStart"]: dToken for dToken in self.lToken } + self.dTags = {} + self.dError = {} + self.createError = self._createWriterError if _bWriterError else self._createDictError + + def update (self, sSentence): + "update and retokenize" + self.sSentence = sSentence + self.lToken = list(_oTokenizer.genTokens(sSentence, True)) + + def _getNextMatchingNodes (self, dToken, dGraph, dNode, bDebug=False): + "generator: return nodes where “values” match arcs" + # token value + if dToken["sValue"] in dNode: + if bDebug: + print(" MATCH:", dToken["sValue"]) + yield dGraph[dNode[dToken["sValue"]]] + if dToken["sValue"][0:2].istitle(): # we test only 2 first chars, to make valid words such as "Laissez-les", "Passe-partout". + sValue = dToken["sValue"].lower() + if sValue in dNode: + if bDebug: + print(" MATCH:", sValue) + yield dGraph[dNode[sValue]] + elif dToken["sValue"].isupper(): + sValue = dToken["sValue"].lower() + if sValue in dNode: + if bDebug: + print(" MATCH:", sValue) + yield dGraph[dNode[sValue]] + sValue = dToken["sValue"].capitalize() + if sValue in dNode: + if bDebug: + print(" MATCH:", sValue) + yield dGraph[dNode[sValue]] + # token lemmas + if "" in dNode: + for sLemma in _oSpellChecker.getLemma(dToken["sValue"]): + if sLemma in dNode[""]: + if bDebug: + print(" MATCH: >" + sLemma) + yield dGraph[dNode[""][sLemma]] + # regex value arcs + if "" in dNode: + for sRegex in dNode[""]: + if "¬" not in sRegex: + # no anti-pattern + if re.search(sRegex, dToken["sValue"]): + if bDebug: + print(" MATCH: ~" + sRegex) + yield dGraph[dNode[""][sRegex]] + else: + # there is an anti-pattern + sPattern, sNegPattern = sRegex.split("¬", 1) + if sNegPattern and re.search(sNegPattern, dToken["sValue"]): + continue + if not sPattern or re.search(sPattern, dToken["sValue"]): + if bDebug: + print(" MATCH: ~" + sRegex) + yield dGraph[dNode[""][sRegex]] + # regex morph arcs + if "" in dNode: + for sRegex in dNode[""]: + if "¬" not in sRegex: + # no anti-pattern + if any(re.search(sRegex, sMorph) for sMorph in _oSpellChecker.getMorph(dToken["sValue"])): + if bDebug: + print(" MATCH: @" + sRegex) + yield dGraph[dNode[""][sRegex]] + else: + # there is an anti-pattern + sPattern, sNegPattern = sRegex.split("¬", 1) + if sNegPattern == "*": + # all morphologies must match with + if sPattern and all(re.search(sPattern, sMorph) for sMorph in _oSpellChecker.getMorph(dToken["sValue"])): + if bDebug: + print(" MATCH: @" + sRegex) + yield dGraph[dNode[""][sRegex]] + else: + if sNegPattern and any(re.search(sNegPattern, sMorph) for sMorph in _oSpellChecker.getMorph(dToken["sValue"])): + continue + if not sPattern or any(re.search(sPattern, sMorph) for sMorph in _oSpellChecker.getMorph(dToken["sValue"])): + if bDebug: + print(" MATCH: @" + sRegex) + yield dGraph[dNode[""][sRegex]] + # meta arc (for token type) + if "" in dNode: + for sMeta in dNode[""]: + # not regex here, we just search if exists within + if sMeta == "*": + if bDebug: + print(" MATCH: *" + sMeta) + yield dGraph[dNode[""]["*"]] + elif "¬" in sMeta: + if dNode["sType"] not in sMeta: + if bDebug: + print(" MATCH: *" + sMeta) + yield dGraph[dNode[""][sMeta]] + elif dNode["sType"] in sMeta: + if bDebug: + print(" MATCH: *" + sMeta) + yield dGraph[dNode[""][sMeta]] + + + def parse (self, dGraph, dPriority, sCountry="${country_default}", dOptions=None, bShowRuleId=False, bDebug=False, bContext=False): + "parse tokens from the text and execute actions encountered" + self.dError = {} + dPriority = {} # Key = position; value = priority + dOpt = _dOptions if not dOptions else dOptions + lPointer = [] + bTagAndRewrite = False + for dToken in self.lToken: + if bDebug: + print("TOKEN:", dToken["sValue"]) + # check arcs for each existing pointer + lNextPointer = [] + for dPointer in lPointer: + for dNode in self._getNextMatchingNodes(dToken, dGraph, dPointer["dNode"], bDebug): + lNextPointer.append({"iToken": dPointer["iToken"], "dNode": dNode}) + lPointer = lNextPointer + # check arcs of first nodes + for dNode in self._getNextMatchingNodes(dToken, dGraph, dGraph[0], bDebug): + lPointer.append({"iToken": dToken["i"], "dNode": dNode}) + # check if there is rules to check for each pointer + for dPointer in lPointer: + #if bDebug: + # print("+", dPointer) + if "" in dPointer["dNode"]: + bChange, dErr = self._executeActions(dGraph, dPointer["dNode"][""], dPointer["iToken"]-1, dToken["i"], dPriority, dOpt, sCountry, bShowRuleId, bDebug, bContext) + self.dError.update(dErr) + if bChange: + bTagAndRewrite = True + if bTagAndRewrite: + self.rewrite(bDebug) + return (bTagAndRewrite, self.sSentence) + + def _executeActions (self, dGraph, dNode, nTokenOffset, nLastToken, dPriority, dOptions, sCountry, bShowRuleId, bDebug, bContext): + "execute actions found in the DARG" + dError = {} + bChange = False + for sLineId, nextNodeKey in dNode.items(): + bCondMemo = None + for sRuleId in dGraph[nextNodeKey]: + try: + if bDebug: + print("ACTION:", sRuleId) + print(dRule[sRuleId]) + sOption, sFuncCond, cActionType, sWhat, *eAct = dRule[sRuleId] + # Suggestion [ option, condition, "-", replacement/suggestion/action, iTokenStart, iTokenEnd, nPriority, message, URL ] + # TextProcessor [ option, condition, "~", replacement/suggestion/action, iTokenStart, iTokenEnd ] + # Disambiguator [ option, condition, "=", replacement/suggestion/action ] + # Sentence Tag [ option, condition, "/", replacement/suggestion/action, iTokenStart, iTokenEnd ] + # Test [ option, condition, ">", "" ] + if not sOption or dOptions.get(sOption, False): + bCondMemo = not sFuncCond or globals()[sFuncCond](self.lToken, nTokenOffset, nLastToken, sCountry, bCondMemo, self.dTags, self.sSentence, self.sSentence0) + if bCondMemo: + if cActionType == "-": + # grammar error + nTokenErrorStart = nTokenOffset + eAct[0] + if "bImmune" not in self.lToken[nTokenErrorStart]: + nTokenErrorEnd = (nTokenOffset + eAct[1]) if eAct[1] else nLastToken + nErrorStart = self.nOffsetWithinParagraph + self.lToken[nTokenErrorStart]["nStart"] + nErrorEnd = self.nOffsetWithinParagraph + self.lToken[nTokenErrorEnd]["nEnd"] + if nErrorStart not in dError or eAct[2] > dPriority.get(nErrorStart, -1): + dError[nErrorStart] = self.createError(sWhat, nTokenOffset, nTokenErrorStart, nErrorStart, nErrorEnd, sLineId, sRuleId, True, eAct[3], eAct[4], bShowRuleId, "notype", bContext) + dPriority[nErrorStart] = eAct[2] + if bDebug: + print("-", sRuleId, dError[nErrorStart]) + elif cActionType == "~": + # text processor + nEndToken = (nTokenOffset + eAct[1]) if eAct[1] else nLastToken + self._tagAndPrepareTokenForRewriting(sWhat, nTokenOffset + eAct[0], nEndToken, bDebug) + if bDebug: + print("~", sRuleId) + bChange = True + elif cActionType == "=": + # disambiguation + globals()[sWhat](self.lToken, nTokenOffset) + if bDebug: + print("=", sRuleId) + elif cActionType == ">": + # we do nothing, this test is just a condition to apply all following actions + if bDebug: + print(">", sRuleId) + pass + elif cActionType == "/": + # tags + nTokenTag = nTokenOffset + eAct[0] + if sWhat not in self.dTags: + self.dTags[sWhat] = (nTokenTag, nTokenTag) + elif nTokenTag > self.dTags[sWhat][1]: + self.dTags[sWhat] = (self.dTags[sWhat][0], nTokenTag) + if bDebug: + print("/", sRuleId) + else: + print("# error: unknown action at " + sLineId) + elif cActionType == ">": + if bDebug: + print(">!", sRuleId) + break + except Exception as e: + raise Exception(str(e), sLineId, sRuleId, self.sSentence) + return bChange, dError + + def _createWriterError (self, sSugg, nTokenOffset, iFirstToken, nStart, nEnd, sLineId, sRuleId, bUppercase, sMsg, sURL, bShowRuleId, sOption, bContext): + "error for Writer (LO/OO)" + xErr = SingleProofreadingError() + #xErr = uno.createUnoStruct( "com.sun.star.linguistic2.SingleProofreadingError" ) + xErr.nErrorStart = nStart + xErr.nErrorLength = nEnd - nStart + xErr.nErrorType = PROOFREADING + xErr.aRuleIdentifier = sRuleId + # suggestions + if sSugg[0:1] == "=": + sSugg = globals()[sSugg[1:]](self.lToken, nTokenOffset) + if sSugg: + if bUppercase and self.lToken[iFirstToken]["sValue"][0:1].isupper(): + xErr.aSuggestions = tuple(map(str.capitalize, sSugg.split("|"))) + else: + xErr.aSuggestions = tuple(sSugg.split("|")) + else: + xErr.aSuggestions = () + elif sSugg == "_": + xErr.aSuggestions = () + else: + if bUppercase and self.lToken[iFirstToken]["sValue"][0:1].isupper(): + xErr.aSuggestions = tuple(map(str.capitalize, self._expand(sSugg, nTokenOffset).split("|"))) + else: + xErr.aSuggestions = tuple(self._expand(sSugg, nTokenOffset).split("|")) + # Message + sMessage = globals()[sMsg[1:]](self.lToken) if sMsg[0:1] == "=" else self._expand(sMsg, nTokenOffset) + xErr.aShortComment = sMessage # sMessage.split("|")[0] # in context menu + xErr.aFullComment = sMessage # sMessage.split("|")[-1] # in dialog + if bShowRuleId: + xErr.aShortComment += " " + sLineId + " # " + sRuleId + # URL + if sURL: + xProperty = PropertyValue() + xProperty.Name = "FullCommentURL" + xProperty.Value = sURL + xErr.aProperties = (xProperty,) + else: + xErr.aProperties = () + return xErr + + def _createDictError (self, sSugg, nTokenOffset, iFirstToken, nStart, nEnd, sLineId, sRuleId, bUppercase, sMsg, sURL, bShowRuleId, sOption, bContext): + "error as a dictionary" + dErr = {} + dErr["nStart"] = nStart + dErr["nEnd"] = nEnd + dErr["sLineId"] = sLineId + dErr["sRuleId"] = sRuleId + dErr["sType"] = sOption if sOption else "notype" + # suggestions + if sSugg[0:1] == "=": + sSugg = globals()[sSugg[1:]](self.lToken, nTokenOffset) + if sSugg: + if bUppercase and self.lToken[iFirstToken]["sValue"][0:1].isupper(): + dErr["aSuggestions"] = list(map(str.capitalize, sSugg.split("|"))) + else: + dErr["aSuggestions"] = sSugg.split("|") + else: + dErr["aSuggestions"] = [] + elif sSugg == "_": + dErr["aSuggestions"] = [] + else: + if bUppercase and self.lToken[iFirstToken]["sValue"][0:1].isupper(): + dErr["aSuggestions"] = list(map(str.capitalize, self._expand(sSugg, nTokenOffset).split("|"))) + else: + dErr["aSuggestions"] = self._expand(sSugg, nTokenOffset).split("|") + # Message + dErr["sMessage"] = globals()[sMsg[1:]](self.lToken) if sMsg[0:1] == "=" else self._expand(sMsg, nTokenOffset) + if bShowRuleId: + dErr["sMessage"] += " " + sLineId + " # " + sRuleId + # URL + dErr["URL"] = sURL if sURL else "" + # Context + if bContext: + dErr['sUnderlined'] = self.sSentence0[dErr["nStart"]:dErr["nEnd"]] + dErr['sBefore'] = self.sSentence0[max(0,dErr["nStart"]-80):dErr["nStart"]] + dErr['sAfter'] = self.sSentence0[dErr["nEnd"]:dErr["nEnd"]+80] + return dErr + + def _expand (self, sMsg, nTokenOffset): + #print("*", sMsg) + for m in re.finditer(r"\\([0-9]+)", sMsg): + sMsg = sMsg.replace(m.group(0), self.lToken[int(m.group(1))+nTokenOffset]["sValue"]) + #print(">", sMsg) + return sMsg + + def _tagAndPrepareTokenForRewriting (self, sWhat, nTokenRewriteStart, nTokenRewriteEnd, bUppercase=True, bDebug=False): + "text processor: rewrite tokens between and position" + if bDebug: + print("REWRITING:", nTokenRewriteStart, nTokenRewriteEnd) + if sWhat == "*": + # purge text + if nTokenRewriteEnd - nTokenRewriteStart == 0: + self.lToken[nTokenRewriteStart]["bToRemove"] = True + else: + for i in range(nTokenRewriteStart, nTokenRewriteEnd+1): + self.lToken[i]["bToRemove"] = True + elif sWhat == "_": + # merge tokens + self.lToken[nTokenRewriteStart]["nMergeUntil"] = nTokenRewriteEnd + elif sWhat == "!": + # immunity + if nTokenRewriteEnd - nTokenRewriteStart == 0: + self.lToken[nTokenRewriteStart]["bImmune"] = True + else: + for i in range(nTokenRewriteStart, nTokenRewriteEnd+1): + self.lToken[i]["bImmune"] = True + else: + if sWhat.startswith("="): + sWhat = globals()[sWhat[1:]](self.lToken) + bUppercase = bUppercase and self.lToken[nTokenRewriteStart]["sValue"][0:1].isupper() + if nTokenRewriteEnd - nTokenRewriteStart == 0: + sWhat = sWhat + " " * (len(self.lToken[nTokenRewriteStart]["sValue"])-len(sWhat)) + if bUppercase: + sWhat = sWhat[0:1].upper() + sWhat[1:] + self.lToken[nTokenRewriteStart]["sNewValue"] = sWhat + else: + lTokenValue = sWhat.split("|") + if len(lTokenValue) != (nTokenRewriteEnd - nTokenRewriteStart + 1): + print("Error. Text processor: number of replacements != number of tokens.") + return + for i, sValue in zip(range(nTokenRewriteStart, nTokenRewriteEnd+1), lTokenValue): + if bUppercase: + sValue = sValue[0:1].upper() + sValue[1:] + self.lToken[i]["sNewValue"] = sValue + + def rewrite (self, bDebug=False): + "rewrite the sentence, modify tokens, purge the token list" + lNewToken = [] + nMergeUntil = 0 + dTokenMerger = None + for dToken in self.lToken: + bKeepToken = True + if "bImmune" in dToken: + nErrorStart = self.nOffsetWithinParagraph + dToken["nStart"] + if nErrorStart in self.dError: + if bDebug: + print("immunity -> error removed:", self.dError[nErrorStart]) + del self.dError[nErrorStart] + if nMergeUntil and dToken["i"] <= nMergeUntil: + dTokenMerger["sValue"] += " " * (dToken["nStart"] - dTokenMerger["nEnd"]) + dToken["sValue"] + dTokenMerger["nEnd"] = dToken["nEnd"] + if bDebug: + print("Merged token:", dTokenMerger["sValue"]) + bKeepToken = False + if "nMergeUntil" in dToken: + if dToken["i"] > nMergeUntil: # this token is not already merged with a previous token + dTokenMerger = dToken + if dToken["nMergeUntil"] > nMergeUntil: + nMergeUntil = dToken["nMergeUntil"] + del dToken["nMergeUntil"] + elif "bToRemove" in dToken: + # remove useless token + self.sSentence = self.sSentence[:dToken["nStart"]] + " " * (dToken["nEnd"] - dToken["nStart"]) + self.sSentence[dToken["nEnd"]:] + if bDebug: + print("removed:", dToken["sValue"]) + bKeepToken = False + # + if bKeepToken: + lNewToken.append(dToken) + if "sNewValue" in dToken: + # rewrite token and sentence + if bDebug: + print(dToken["sValue"], "->", dToken["sNewValue"]) + dToken["sRealValue"] = dToken["sValue"] + dToken["sValue"] = dToken["sNewValue"] + nDiffLen = len(dToken["sRealValue"]) - len(dToken["sNewValue"]) + sNewRepl = (dToken["sNewValue"] + " " * nDiffLen) if nDiffLen >= 0 else dToken["sNewValue"][:len(dToken["sRealValue"])] + self.sSentence = self.sSentence[:dToken["nStart"]] + sNewRepl + self.sSentence[dToken["nEnd"]:] + del dToken["sNewValue"] + if bDebug: + print(self.sSentence) + self.lToken.clear() + self.lToken = lNewToken + + + +#### Analyse tokens + +def g_morph (dToken, sPattern, sNegPattern=""): + "analyse a token, return True if not in morphologies and in morphologies" + if "lMorph" in dToken: + lMorph = dToken["lMorph"] + else: + lMorph = _oSpellChecker.getMorph(dToken["sValue"]) + if not lMorph: + return False + # check negative condition + if sNegPattern: + if sNegPattern == "*": + # all morph must match sPattern + zPattern = re.compile(sPattern) + return all(zPattern.search(sMorph) for sMorph in lMorph) + else: + zNegPattern = re.compile(sNegPattern) + if any(zNegPattern.search(sMorph) for sMorph in lMorph): + return False + # search sPattern + zPattern = re.compile(sPattern) + return any(zPattern.search(sMorph) for sMorph in lMorph) + + +def g_analyse (dToken, sPattern, sNegPattern=""): + "analyse a token, return True if not in morphologies and in morphologies (disambiguation off)" + lMorph = _oSpellChecker.getMorph(dToken["sValue"]) + if not lMorph: + return False + # check negative condition + if sNegPattern: + if sNegPattern == "*": + zPattern = re.compile(sPattern) + return all(zPattern.search(sMorph) for sMorph in lMorph) + else: + zNegPattern = re.compile(sNegPattern) + if any(zNegPattern.search(sMorph) for sMorph in lMorph): + return False + # search sPattern + zPattern = re.compile(sPattern) + return any(zPattern.search(sMorph) for sMorph in lMorph) + + +def g_tag_before (dToken, dTags, sTag): + if sTag not in dTags: + return False + if dToken["i"] > dTags[sTag][0]: + return True + return False + + +def g_tag_after (dToken, dTags, sTag): + if sTag not in dTags: + return False + if dToken["i"] < dTags[sTag][1]: + return True + return False #### Disambiguator -def select (dDA, nPos, sWord, sPattern, lDefault=None): - if not sWord: - return True - if nPos in dDA: - return True - if sWord not in _dAnalyses and not _storeMorphFromFSA(sWord): - return True - if len(_dAnalyses[sWord]) == 1: - return True - lSelect = [ sMorph for sMorph in _dAnalyses[sWord] if re.search(sPattern, sMorph) ] - if lSelect: - if len(lSelect) != len(_dAnalyses[sWord]): - dDA[nPos] = lSelect - #echo("= "+sWord+" "+str(dDA.get(nPos, "null"))) - elif lDefault: - dDA[nPos] = lDefault - #echo("= "+sWord+" "+str(dDA.get(nPos, "null"))) - return True - - -def exclude (dDA, nPos, sWord, sPattern, lDefault=None): - if not sWord: - return True - if nPos in dDA: - return True - if sWord not in _dAnalyses and not _storeMorphFromFSA(sWord): - return True - if len(_dAnalyses[sWord]) == 1: - return True - lSelect = [ sMorph for sMorph in _dAnalyses[sWord] if not re.search(sPattern, sMorph) ] - if lSelect: - if len(lSelect) != len(_dAnalyses[sWord]): - dDA[nPos] = lSelect - #echo("= "+sWord+" "+str(dDA.get(nPos, "null"))) - elif lDefault: - dDA[nPos] = lDefault - #echo("= "+sWord+" "+str(dDA.get(nPos, "null"))) - return True - - -def define (dDA, nPos, lMorph): - dDA[nPos] = lMorph - #echo("= "+str(nPos)+" "+str(dDA[nPos])) - return True +def g_select (dToken, sPattern, lDefault=None): + "select morphologies for according to , always return True" + lMorph = dToken["lMorph"] if "lMorph" in dToken else _oSpellChecker.getMorph(dToken["sValue"]) + if not lMorph or len(lMorph) == 1: + if lDefault: + dToken["lMorph"] = lDefault + #print("DA:", dToken["sValue"], dToken["lMorph"]) + return True + lSelect = [ sMorph for sMorph in lMorph if re.search(sPattern, sMorph) ] + if lSelect: + if len(lSelect) != len(lMorph): + dToken["lMorph"] = lSelect + elif lDefault: + dToken["lMorph"] = lDefault + #print("DA:", dToken["sValue"], dToken["lMorph"]) + return True + + +def g_exclude (dToken, sPattern, lDefault=None): + "select morphologies for according to , always return True" + lMorph = dToken["lMorph"] if "lMorph" in dToken else _oSpellChecker.getMorph(dToken["sValue"]) + if not lMorph or len(lMorph) == 1: + if lDefault: + dToken["lMorph"] = lDefault + #print("DA:", dToken["sValue"], dToken["lMorph"]) + return True + lSelect = [ sMorph for sMorph in lMorph if not re.search(sPattern, sMorph) ] + if lSelect: + if len(lSelect) != len(lMorph): + dToken["lMorph"] = lSelect + elif lDefault: + dToken["lMorph"] = lDefault + #print("DA:", dToken["sValue"], dToken["lMorph"]) + return True + + +def g_define (dToken, lMorph): + "set morphologies of , always return True" + dToken["lMorph"] = lMorph + #print("DA:", dToken["sValue"], lMorph) + return True + #### GRAMMAR CHECKER PLUGINS ${plugins} +#### CALLABLES FOR REGEX RULES (generated code) + ${callables} + + +#### CALLABLES FOR GRAPH RULES (generated code) + +${graph_callables} Index: gc_core/py/lang_core/gc_options.py ================================================================== --- gc_core/py/lang_core/gc_options.py +++ gc_core/py/lang_core/gc_options.py @@ -1,14 +1,20 @@ +""" +Grammar checker default options +""" + # generated code, do not edit def getUI (sLang): + "returns dictionary of UI labels" if sLang in _dOptLabel: return _dOptLabel[sLang] return _dOptLabel["fr"] def getOptions (sContext="Python"): + "returns dictionary of options" if sContext in dOpt: return dOpt[sContext] return dOpt["Python"] Index: gc_core/py/lang_core/gc_rules.py ================================================================== --- gc_core/py/lang_core/gc_rules.py +++ gc_core/py/lang_core/gc_rules.py @@ -1,5 +1,9 @@ +""" +Grammar checker regex rules +""" + # generated code, do not edit lParagraphRules = ${paragraph_rules} lSentenceRules = ${sentence_rules} ADDED gc_core/py/lang_core/gc_rules_graph.py Index: gc_core/py/lang_core/gc_rules_graph.py ================================================================== --- /dev/null +++ gc_core/py/lang_core/gc_rules_graph.py @@ -0,0 +1,9 @@ +""" +Grammar checker graph rules +""" + +# generated code, do not edit + +dAllGraph = ${rules_graphs} + +dRule = ${rules_actions} Index: gc_core/py/text.py ================================================================== --- gc_core/py/text.py +++ gc_core/py/text.py @@ -1,6 +1,10 @@ #!python3 + +""" +Text tools +""" import textwrap from itertools import chain @@ -41,27 +45,27 @@ lSpellErrs = sorted(aSpellErrs, key=lambda d: d['nStart']) sText = "" nOffset = 0 for sLine in wrap(sParagraph, nWidth): # textwrap.wrap(sParagraph, nWidth, drop_whitespace=False) sText += sLine + "\n" - ln = len(sLine) + nLineLen = len(sLine) sErrLine = "" nLenErrLine = 0 nGrammErr = 0 nSpellErr = 0 for dErr in lGrammErrs: nStart = dErr["nStart"] - nOffset - if nStart < ln: + if nStart < nLineLen: nGrammErr += 1 if nStart >= nLenErrLine: sErrLine += " " * (nStart - nLenErrLine) + "^" * (dErr["nEnd"] - dErr["nStart"]) nLenErrLine = len(sErrLine) else: break for dErr in lSpellErrs: nStart = dErr['nStart'] - nOffset - if nStart < ln: + if nStart < nLineLen: nSpellErr += 1 nEnd = dErr['nEnd'] - nOffset if nEnd > len(sErrLine): sErrLine += " " * (nEnd - len(sErrLine)) sErrLine = sErrLine[:nStart] + "°" * (nEnd - nStart) + sErrLine[nEnd:] @@ -73,11 +77,11 @@ sText += getReadableErrors(lGrammErrs[:nGrammErr], nWidth) del lGrammErrs[0:nGrammErr] if nSpellErr: sText += getReadableErrors(lSpellErrs[:nSpellErr], nWidth, True) del lSpellErrs[0:nSpellErr] - nOffset += ln + nOffset += nLineLen return sText def getReadableErrors (lErrs, nWidth, bSpell=False): "Returns lErrs errors as readable errors" @@ -95,19 +99,19 @@ def getReadableError (dErr, bSpell=False): "Returns an error dErr as a readable error" try: if bSpell: - s = u"* {nStart}:{nEnd} # {sValue}:".format(**dErr) + sText = u"* {nStart}:{nEnd} # {sValue}:".format(**dErr) else: - s = u"* {nStart}:{nEnd} # {sLineId} / {sRuleId}:\n".format(**dErr) - s += " " + dErr.get("sMessage", "# error : message not found") + sText = u"* {nStart}:{nEnd} # {sLineId} / {sRuleId}:\n".format(**dErr) + sText += " " + dErr.get("sMessage", "# error : message not found") if dErr.get("aSuggestions", None): - s += "\n > Suggestions : " + " | ".join(dErr.get("aSuggestions", "# error : suggestions not found")) + sText += "\n > Suggestions : " + " | ".join(dErr.get("aSuggestions", "# error : suggestions not found")) if dErr.get("URL", None): - s += "\n > URL: " + dErr["URL"] - return s + sText += "\n > URL: " + dErr["URL"] + return sText except KeyError: return u"* Non-compliant error: {}".format(dErr) def createParagraphWithLines (lLine): ADDED gc_lang/fr/French_language.txt Index: gc_lang/fr/French_language.txt ================================================================== --- /dev/null +++ gc_lang/fr/French_language.txt @@ -0,0 +1,64 @@ +# NOTES SUR LA LANGUE FRANÇAISE + +## CE QUI ENTOURE UN VERBE + + PRONOMS (avant) + COD COI + le / l’ + la / l’ + les + en + me / m’ me / m’ + te / t’ te / t’ + se / s’ lui + nous nous + vous nous + se / s’ leur + y + + ADVERBE DE NÉGATION (avant) + ne / n’ + + SOMME + [le|la|l’|les|en|me|m’|te|t’|se|s’|nous|vous|lui|leur|y] + + COMBINAISONS VALIDES + ?[ne|n’]¿ [me|te|se] [le|la|l’|les] + ?[ne|n’]¿ [m’|t’|s’] [le|la|l’|les|en|y] + ?[ne|n’]¿ [le|la] [lui|leur] + ?[ne|n’]¿ [l’|les] [lui|leur|en|y] + ?[ne|n’]¿ [lui|leur] en + ?[ne|n’]¿ [nous|vous] [le|la|l’|les|en|y] + ne [le|la|l’|les|me|m’|te|t’|se|s’|nous|vous|lui|leur] + n’ [en|y] + + RÉSUMÉ & SIMPLIFICATION + ?[ne|n’]¿ [le|la|l’|les|en|me|m’|te|t’|se|s’|nous|vous|lui|leur|y] + ?[ne|n’]¿ [me|m’|te|t’|se|s’|nous|vous] [le|la|l’|les|en|y] + ?[ne|n’]¿ [le|la|l’|les] [lui|leur|en|y] + ?[ne|n’]¿ [lui|leur] en + + ADVERBE DE NÉGATION (après) + pas + jamais + point + guère + que / qu’ + rien + + PRONOMS À L’IMPÉRATIF + APRÈS + -moi + -toi + -lui + -leur + -nous + -vous + -le + -la + -les + -en + -y + + AVANT + Uniquement les combinaisons avec l’adverbe de négation [ne|n’] Index: gc_lang/fr/modules-js/conj.js ================================================================== --- gc_lang/fr/modules-js/conj.js +++ gc_lang/fr/modules-js/conj.js @@ -85,11 +85,11 @@ getSimil: function (sWord, sMorph, bSubst=false) { if (!sMorph.includes(":V")) { return new Set(); } - let sInfi = sMorph.slice(1, sMorph.indexOf(" ")); + let sInfi = sMorph.slice(1, sMorph.indexOf("/")); let aSugg = new Set(); let tTags = this._getTags(sInfi); if (tTags) { if (!bSubst) { // we suggest conjugated forms Index: gc_lang/fr/modules-js/gce_analyseur.js ================================================================== --- gc_lang/fr/modules-js/gce_analyseur.js +++ gc_lang/fr/modules-js/gce_analyseur.js @@ -20,12 +20,11 @@ } if (s2 == "eux") { return "ils"; } if (s2 == "elle" || s2 == "elles") { - // We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - if (cregex.mbNprMasNotFem(_dAnalyses.gl_get(s1, ""))) { + if (cregex.mbNprMasNotFem(_oSpellChecker.getMorph(s1))) { return "ils"; } // si épicène, indéterminable, mais OSEF, le féminin l’emporte return "elles"; } @@ -32,41 +31,40 @@ return s1 + " et " + s2; } function apposition (sWord1, sWord2) { // returns true if nom + nom (no agreement required) - // We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - return cregex.mbNomNotAdj(_dAnalyses.gl_get(sWord2, "")) && cregex.mbPpasNomNotAdj(_dAnalyses.gl_get(sWord1, "")); + return cregex.mbNomNotAdj(_oSpellChecker.getMorph(sWord2)) && cregex.mbPpasNomNotAdj(_oSpellChecker.getMorph(sWord1)); } function isAmbiguousNAV (sWord) { // words which are nom|adj and verb are ambiguous (except être and avoir) - if (!_dAnalyses.has(sWord) && !_storeMorphFromFSA(sWord)) { + let lMorph = _oSpellChecker.getMorph(sWord); + if (lMorph.length === 0) { return false; } - if (!cregex.mbNomAdj(_dAnalyses.gl_get(sWord, "")) || sWord == "est") { + if (!cregex.mbNomAdj(lMorph) || sWord == "est") { return false; } - if (cregex.mbVconj(_dAnalyses.gl_get(sWord, "")) && !cregex.mbMG(_dAnalyses.gl_get(sWord, ""))) { + if (cregex.mbVconj(lMorph) && !cregex.mbMG(lMorph)) { return true; } return false; } function isAmbiguousAndWrong (sWord1, sWord2, sReqMorphNA, sReqMorphConj) { //// use it if sWord1 won’t be a verb; word2 is assumed to be true via isAmbiguousNAV - // We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - let a2 = _dAnalyses.gl_get(sWord2, null); - if (!a2 || a2.length === 0) { + let a2 = _oSpellChecker.getMorph(sWord2); + if (a2.length === 0) { return false; } if (cregex.checkConjVerb(a2, sReqMorphConj)) { // verb word2 is ok return false; } - let a1 = _dAnalyses.gl_get(sWord1, null); - if (!a1 || a1.length === 0) { + let a1 = _oSpellChecker.getMorph(sWord1); + if (a1.length === 0) { return false; } if (cregex.checkAgreement(a1, a2) && (cregex.mbAdj(a2) || cregex.mbAdj(a1))) { return false; } @@ -73,21 +71,20 @@ return true; } function isVeryAmbiguousAndWrong (sWord1, sWord2, sReqMorphNA, sReqMorphConj, bLastHopeCond) { //// use it if sWord1 can be also a verb; word2 is assumed to be true via isAmbiguousNAV - // We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - let a2 = _dAnalyses.gl_get(sWord2, null); - if (!a2 || a2.length === 0) { + let a2 = _oSpellChecker.getMorph(sWord2); + if (a2.length === 0) { return false; } if (cregex.checkConjVerb(a2, sReqMorphConj)) { // verb word2 is ok return false; } - let a1 = _dAnalyses.gl_get(sWord1, null); - if (!a1 || a1.length === 0) { + let a1 = _oSpellChecker.getMorph(sWord1); + if (a1.length === 0) { return false; } if (cregex.checkAgreement(a1, a2) && (cregex.mbAdj(a2) || cregex.mbAdjNb(a1))) { return false; } @@ -101,17 +98,16 @@ } return false; } function checkAgreement (sWord1, sWord2) { - // We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - let a2 = _dAnalyses.gl_get(sWord2, null); - if (!a2 || a2.length === 0) { + let a2 = _oSpellChecker.getMorph(sWord2); + if (a2.length === 0) { return true; } - let a1 = _dAnalyses.gl_get(sWord1, null); - if (!a1 || a1.length === 0) { + let a1 = _oSpellChecker.getMorph(sWord1); + if (a1.length === 0) { return true; } return cregex.checkAgreement(a1, a2); } 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 @@ -10,20 +10,19 @@ //// verbs function suggVerb (sFlex, sWho, funcSugg2=null) { - // we don’t check if word exists in _dAnalyses, for it is assumed it has been done before let aSugg = new Set(); - for (let sStem of stem(sFlex)) { + for (let sStem of _oSpellChecker.getLemma(sFlex)) { let tTags = conj._getTags(sStem); if (tTags) { // we get the tense let aTense = new Set(); - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { let m; - let zVerb = new RegExp (">"+sStem+" .*?(:(?:Y|I[pqsf]|S[pq]|K))", "g"); + let zVerb = new RegExp (">"+sStem+"/.*?(:(?:Y|I[pqsf]|S[pq]|K))", "g"); while ((m = zVerb.exec(sMorph)) !== null) { // stem must be used in regex to prevent confusion between different verbs (e.g. sauras has 2 stems: savoir and saurer) if (m) { if (m[1] === ":Y") { aTense.add(":Ip"); @@ -59,11 +58,11 @@ return ""; } function suggVerbPpas (sFlex, sWhat=null) { let aSugg = new Set(); - for (let sStem of stem(sFlex)) { + for (let sStem of _oSpellChecker.getLemma(sFlex)) { let tTags = conj._getTags(sStem); if (tTags) { if (!sWhat) { aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1")); aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q2")); @@ -109,11 +108,11 @@ return ""; } function suggVerbTense (sFlex, sTense, sWho) { let aSugg = new Set(); - for (let sStem of stem(sFlex)) { + for (let sStem of _oSpellChecker.getLemma(sFlex)) { if (conj.hasConj(sStem, sTense, sWho)) { aSugg.add(conj.getConj(sStem, sTense, sWho)); } } if (aSugg.size > 0) { @@ -122,11 +121,11 @@ return ""; } function suggVerbImpe (sFlex) { let aSugg = new Set(); - for (let sStem of stem(sFlex)) { + for (let sStem of _oSpellChecker.getLemma(sFlex)) { let tTags = conj._getTags(sStem); if (tTags) { if (conj._hasConjWithTags(tTags, ":E", ":2s")) { aSugg.add(conj._getConjWithTags(sStem, tTags, ":E", ":2s")); } @@ -143,11 +142,11 @@ } return ""; } function suggVerbInfi (sFlex) { - return stem(sFlex).filter(sStem => conj.isVerb(sStem)).join("|"); + return _oSpellChecker.getLemma(sFlex).filter(sStem => conj.isVerb(sStem)).join("|"); } const _dQuiEst = new Map ([ ["je", ":1s"], ["j’", ":1s"], ["j’en", ":1s"], ["j’y", ":1s"], @@ -174,11 +173,11 @@ return ""; } sWho = ":3s"; } let aSugg = new Set(); - for (let sStem of stem(sFlex)) { + for (let sStem of _oSpellChecker.getLemma(sFlex)) { let tTags = conj._getTags(sStem); if (tTags) { for (let sTense of lMode) { if (conj._hasConjWithTags(tTags, sTense, sWho)) { aSugg.add(conj._getConjWithTags(sStem, tTags, sTense, sWho)); @@ -195,14 +194,15 @@ //// Nouns and adjectives function suggPlur (sFlex, sWordToAgree=null) { // returns plural forms assuming sFlex is singular if (sWordToAgree) { - if (!_dAnalyses.has(sWordToAgree) && !_storeMorphFromFSA(sWordToAgree)) { + let lMorph = _oSpellChecker.getMorph(sWordToAgree); + if (lMorph.length === 0) { return ""; } - let sGender = cregex.getGender(_dAnalyses.gl_get(sWordToAgree, [])); + let sGender = cregex.getGender(lMorph); if (sGender == ":m") { return suggMasPlur(sFlex); } else if (sGender == ":f") { return suggFemPlur(sFlex); } @@ -256,13 +256,12 @@ return ""; } function suggMasSing (sFlex, bSuggSimil=false) { // returns masculine singular forms - // we don’t check if word exists in _dAnalyses, for it is assumed it has been done before let aSugg = new Set(); - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { if (!sMorph.includes(":V")) { // not a verb if (sMorph.includes(":m") || sMorph.includes(":e")) { aSugg.add(suggSing(sFlex)); } else { @@ -292,13 +291,12 @@ return ""; } function suggMasPlur (sFlex, bSuggSimil=false) { // returns masculine plural forms - // we don’t check if word exists in _dAnalyses, for it is assumed it has been done before let aSugg = new Set(); - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { if (!sMorph.includes(":V")) { // not a verb if (sMorph.includes(":m") || sMorph.includes(":e")) { aSugg.add(suggPlur(sFlex)); } else { @@ -333,13 +331,12 @@ } function suggFemSing (sFlex, bSuggSimil=false) { // returns feminine singular forms - // we don’t check if word exists in _dAnalyses, for it is assumed it has been done before let aSugg = new Set(); - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { if (!sMorph.includes(":V")) { // not a verb if (sMorph.includes(":f") || sMorph.includes(":e")) { aSugg.add(suggSing(sFlex)); } else { @@ -367,13 +364,12 @@ return ""; } function suggFemPlur (sFlex, bSuggSimil=false) { // returns feminine plural forms - // we don’t check if word exists in _dAnalyses, for it is assumed it has been done before let aSugg = new Set(); - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { if (!sMorph.includes(":V")) { // not a verb if (sMorph.includes(":f") || sMorph.includes(":e")) { aSugg.add(suggPlur(sFlex)); } else { @@ -400,11 +396,11 @@ } return ""; } function hasFemForm (sFlex) { - for (let sStem of stem(sFlex)) { + for (let sStem of _oSpellChecker.getLemma(sFlex)) { if (mfsp.isFemForm(sStem) || conj.hasConj(sStem, ":PQ", ":Q3")) { return true; } } if (phonet.hasSimil(sFlex, ":f")) { @@ -412,11 +408,11 @@ } return false; } function hasMasForm (sFlex) { - for (let sStem of stem(sFlex)) { + for (let sStem of _oSpellChecker.getLemma(sFlex)) { if (mfsp.isFemForm(sStem) || conj.hasConj(sStem, ":PQ", ":Q1")) { // what has a feminine form also has a masculine form return true; } } @@ -425,14 +421,13 @@ } return false; } function switchGender (sFlex, bPlur=null) { - // we don’t check if word exists in _dAnalyses, for it is assumed it has been done before let aSugg = new Set(); if (bPlur === null) { - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { if (sMorph.includes(":f")) { if (sMorph.includes(":s")) { aSugg.add(suggMasSing(sFlex)); } else if (sMorph.includes(":p")) { aSugg.add(suggMasPlur(sFlex)); @@ -447,19 +442,19 @@ aSugg.add(suggFemPlur(sFlex)); } } } } else if (bPlur) { - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { if (sMorph.includes(":f")) { aSugg.add(suggMasPlur(sFlex)); } else if (sMorph.includes(":m")) { aSugg.add(suggFemPlur(sFlex)); } } } else { - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { if (sMorph.includes(":f")) { aSugg.add(suggMasSing(sFlex)); } else if (sMorph.includes(":m")) { aSugg.add(suggFemSing(sFlex)); } @@ -471,11 +466,11 @@ return ""; } function switchPlural (sFlex) { let aSugg = new Set(); - for (let sMorph of _dAnalyses.gl_get(sFlex, [])) { // we don’t check if word exists in _dAnalyses, for it is assumed it has been done before + for (let sMorph of _oSpellChecker.getMorph(sFlex)) { if (sMorph.includes(":s")) { aSugg.add(suggPlur(sFlex)); } else if (sMorph.includes(":p")) { aSugg.add(suggSing(sFlex)); } @@ -491,11 +486,11 @@ } function suggSimil (sWord, sPattern=null, bSubst=false) { // return list of words phonetically similar to sWord and whom POS is matching sPattern let aSugg = phonet.selectSimil(sWord, sPattern); - for (let sMorph of _dAnalyses.gl_get(sWord, [])) { + for (let sMorph of _oSpellChecker.getMorph(sWord)) { for (let e of conj.getSimil(sWord, sMorph, bSubst)) { aSugg.add(e); } } if (aSugg.size > 0) { @@ -513,12 +508,11 @@ } return "ce"; } function suggLesLa (sWord) { - // we don’t check if word exists in _dAnalyses, for it is assumed it has been done before - if (_dAnalyses.gl_get(sWord, []).some(s => s.includes(":p"))) { + if (_oSpellChecker.getMorph(sWord).some(s => s.includes(":p"))) { return "les|la"; } return "la"; } Index: gc_lang/fr/modules-js/tests_data.json ================================================================== --- gc_lang/fr/modules-js/tests_data.json +++ gc_lang/fr/modules-js/tests_data.json @@ -1,1 +1,1 @@ -${gctestsJS} +${regex_gctestsJS} Index: gc_lang/fr/modules/conj.py ================================================================== --- gc_lang/fr/modules/conj.py +++ gc_lang/fr/modules/conj.py @@ -1,6 +1,9 @@ -# Grammalecte - Conjugueur +""" +Grammalecte - Conjugueur +""" + # License: GPL 3 import re import traceback @@ -27,10 +30,11 @@ _dTenseIdx = { ":PQ": 0, ":Ip": 1, ":Iq": 2, ":Is": 3, ":If": 4, ":K": 5, ":Sp": 6, ":Sq": 7, ":E": 8 } def isVerb (sVerb): + "return True if it’s a existing verb" return sVerb in _dVerb def getConj (sVerb, sTense, sWho): "returns conjugation (can be an empty string)" @@ -54,13 +58,14 @@ return None return _lVtyp[_dVerb[sVerb][0]] def getSimil (sWord, sMorph, bSubst=False): + "returns a set of verbal forms similar to , according to " if ":V" not in sMorph: return set() - sInfi = sMorph[1:sMorph.find(" ")] + sInfi = sMorph[1:sMorph.find("/")] aSugg = set() tTags = _getTags(sInfi) if tTags: if not bSubst: # we suggest conjugated forms @@ -98,10 +103,11 @@ aSugg.clear() return aSugg def getConjSimilInfiV1 (sInfi): + "returns verbal forms phonetically similar to infinitive form (for verb in group 1)" if sInfi not in _dVerb: return set() aSugg = set() tTags = _getTags(sInfi) if tTags: @@ -140,16 +146,18 @@ return "" if sSfx == "0": return sWord try: return sWord[:-(ord(sSfx[0])-48)] + sSfx[1:] if sSfx[0] != '0' else sWord + sSfx[1:] # 48 is the ASCII code for "0" - except: + except (IndexError, TypeError): return "## erreur, code : " + str(sSfx) + " ##" - + class Verb (): + "Verb and its conjugation" + def __init__ (self, sVerb, sVerbPattern=""): # conjugate a unknown verb with rules from sVerbPattern if not isinstance(sVerb, str): raise TypeError("sVerb should be a string") if not sVerb: @@ -165,11 +173,11 @@ self.bProWithEn = (self._sRawInfo[5] == "e") self._tTags = _getTags(sVerbPattern) if not self._tTags: raise ValueError("Unknown verb.") self._tTagsAux = _getTags(self.sVerbAux) - self.cGroup = self._sRawInfo[0]; + self.cGroup = self._sRawInfo[0] self.dConj = { ":Y": { "label": "Infinitif", ":": sVerb, }, @@ -289,10 +297,11 @@ except: traceback.print_exc() return "# erreur" def infinitif (self, bPro, bNeg, bTpsCo, bInt, bFem): + "returns string (conjugaison à l’infinitif)" try: if bTpsCo: sInfi = self.sVerbAux if not bPro else "être" else: sInfi = self.sVerb @@ -311,17 +320,19 @@ except: traceback.print_exc() return "# erreur" def participePasse (self, sWho): + "returns past participle according to " try: return self.dConj[":Q"][sWho] except: traceback.print_exc() return "# erreur" def participePresent (self, bPro, bNeg, bTpsCo, bInt, bFem): + "returns string (conjugaison du participe présent)" try: if not self.dConj[":P"][":"]: return "" if bTpsCo: sPartPre = _getConjWithTags(self.sVerbAux, self._tTagsAux, ":PQ", ":P") if not bPro else getConj("être", ":PQ", ":P") @@ -348,10 +359,11 @@ except: traceback.print_exc() return "# erreur" def conjugue (self, sTemps, sWho, bPro, bNeg, bTpsCo, bInt, bFem): + "returns string (conjugue le verbe au temps pour ) " try: if not self.dConj[sTemps][sWho]: return "" if not bTpsCo and bInt and sWho == ":1s" and self.dConj[sTemps].get(":1ś", False): sWho = ":1ś" @@ -370,16 +382,16 @@ if bNeg: sConj = "n’" + sConj if bEli and not bPro else "ne " + sConj if bInt: if sWho == ":3s" and not _zNeedTeuph.search(sConj): sConj += "-t" - sConj += "-" + self._getPronom(sWho, bFem) + sConj += "-" + self._getPronomSujet(sWho, bFem) else: if sWho == ":1s" and bEli and not bNeg and not bPro: sConj = "j’" + sConj else: - sConj = self._getPronom(sWho, bFem) + " " + sConj + sConj = self._getPronomSujet(sWho, bFem) + " " + sConj if bNeg: sConj += " pas" if bTpsCo: sConj += " " + self._seekPpas(bPro, bFem, sWho.endswith("p") or self._sRawInfo[5] == "r") if bInt: @@ -387,11 +399,11 @@ return sConj except: traceback.print_exc() return "# erreur" - def _getPronom (self, sWho, bFem): + def _getPronomSujet (self, sWho, bFem): try: if sWho == ":3s": if self._sRawInfo[5] == "r": return "on" elif bFem: @@ -402,10 +414,11 @@ except: traceback.print_exc() return "# erreur" def imperatif (self, sWho, bPro, bNeg, bTpsCo, bFem): + "returns string (conjugaison à l’impératif)" try: if not self.dConj[":E"][sWho]: return "" if bTpsCo: sImpe = _getConjWithTags(self.sVerbAux, self._tTagsAux, ":E", sWho) if not bPro else getConj(u"être", ":E", sWho) Index: gc_lang/fr/modules/conj_generator.py ================================================================== --- gc_lang/fr/modules/conj_generator.py +++ gc_lang/fr/modules/conj_generator.py @@ -1,22 +1,26 @@ -# Conjugation generator -# beta stage, unfinished, the root for a new way to generate flexions… +""" +Conjugation generator +beta stage, unfinished, the root for a new way to generate flexions… +""" import re def conjugate (sVerb, sVerbTag="i_____a", bVarPpas=True): + "conjugate and returns a list of tuples (conjugation form, tags)" lConj = [] cGroup = getVerbGroupChar(sVerb) for nCut, sAdd, sFlexTags, sPattern in getConjRules(sVerb, bVarPpas): if not sPattern or re.search(sPattern, sVerb): sFlexion = sVerb[0:-nCut] + sAdd if nCut else sVerb + sAdd lConj.append((sFlexion, ":V" + cGroup + "_" + sVerbTag + sFlexTags)) return lConj -def getVerbGroupChar (sVerb, ): +def getVerbGroupChar (sVerb): + "returns the group number of guessing on its ending" sVerb = sVerb.lower() if sVerb.endswith("er"): return "1" if sVerb.endswith("ir"): return "2" @@ -26,10 +30,11 @@ return "3" return "4" def getConjRules (sVerb, bVarPpas=True, nGroup=2): + "returns a list of lists to conjugate a verb, guessing on its ending" if sVerb.endswith("er"): # premier groupe, conjugaison en fonction de la terminaison du lemme # 5 lettres if sVerb[-5:] in oConj["V1"]: lConj = list(oConj["V1"][sVerb[-5:]]) @@ -115,11 +120,11 @@ [2, "ît", ":Sq:3s/*", False], [2, "is", ":E:2s/*", False], [2, "issons", ":E:1p/*", False], [2, "issez", ":E:2p/*", False] ], - + # premier groupe (bien plus irrégulier que prétendu) "V1": { # a # verbes en -er, -ger, -yer, -cer "er": [ Index: gc_lang/fr/modules/cregex.py ================================================================== --- gc_lang/fr/modules/cregex.py +++ gc_lang/fr/modules/cregex.py @@ -1,11 +1,13 @@ -# Grammalecte - Compiled regular expressions +""" +Grammalecte - Compiled regular expressions +""" import re #### Lemme -Lemma = re.compile("^>(\w[\w-]*)") +Lemma = re.compile(r"^>(\w[\w-]*)") #### Analyses Gender = re.compile(":[mfe]") Number = re.compile(":[spi]") @@ -78,13 +80,15 @@ #### FONCTIONS def getLemmaOfMorph (s): + "return lemma in morphology " return Lemma.search(s).group(1) def checkAgreement (l1, l2): + "returns True if agreement in gender and number is possible between morphologies and " # check number agreement if not mbInv(l1) and not mbInv(l2): if mbSg(l1) and not mbSg(l2): return False if mbPl(l1) and not mbPl(l2): @@ -97,10 +101,11 @@ if mbFem(l1) and not mbFem(l2): return False return True def checkConjVerb (lMorph, sReqConj): + "returns True if in " return any(sReqConj in s for s in lMorph) def getGender (lMorph): "returns gender of word (':m', ':f', ':e' or empty string)." sGender = "" @@ -115,11 +120,11 @@ def getNumber (lMorph): "returns number of word (':s', ':p', ':i' or empty string)." sNumber = "" for sMorph in lMorph: - m = Number.search(sWord) + m = Number.search(sMorph) if m: if not sNumber: sNumber = m.group(0) elif sNumber != m.group(0): return ":i" @@ -129,98 +134,126 @@ # mbWhat (lMorph) returns True if lMorph contains What at least once ## isXXX = it’s certain def isNom (lMorph): + "returns True if all morphologies are “nom”" return all(":N" in s for s in lMorph) def isNomNotAdj (lMorph): + "returns True if all morphologies are “nom”, but not “adjectif”" return all(NnotA.search(s) for s in lMorph) def isAdj (lMorph): + "returns True if all morphologies are “adjectif”" return all(":A" in s for s in lMorph) def isNomAdj (lMorph): + "returns True if all morphologies are “nom” or “adjectif”" return all(NA.search(s) for s in lMorph) def isNomVconj (lMorph): + "returns True if all morphologies are “nom” or “verbe conjugué”" return all(NVconj.search(s) for s in lMorph) def isInv (lMorph): + "returns True if all morphologies are “invariable”" return all(":i" in s for s in lMorph) def isSg (lMorph): + "returns True if all morphologies are “singulier”" return all(":s" in s for s in lMorph) def isPl (lMorph): + "returns True if all morphologies are “pluriel”" return all(":p" in s for s in lMorph) def isEpi (lMorph): + "returns True if all morphologies are “épicène”" return all(":e" in s for s in lMorph) def isMas (lMorph): + "returns True if all morphologies are “masculin”" return all(":m" in s for s in lMorph) def isFem (lMorph): + "returns True if all morphologies are “féminin”" return all(":f" in s for s in lMorph) ## mbXXX = MAYBE XXX def mbNom (lMorph): + "returns True if one morphology is “nom”" return any(":N" in s for s in lMorph) def mbAdj (lMorph): + "returns True if one morphology is “adjectif”" return any(":A" in s for s in lMorph) def mbAdjNb (lMorph): + "returns True if one morphology is “adjectif” or “nombre”" return any(AD.search(s) for s in lMorph) def mbNomAdj (lMorph): + "returns True if one morphology is “nom” or “adjectif”" return any(NA.search(s) for s in lMorph) def mbNomNotAdj (lMorph): - b = False + "returns True if one morphology is “nom”, but not “adjectif”" + bResult = False for s in lMorph: if ":A" in s: return False if ":N" in s: - b = True - return b + bResult = True + return bResult def mbPpasNomNotAdj (lMorph): + "returns True if one morphology is “nom” or “participe passé”, but not “adjectif”" return any(PNnotA.search(s) for s in lMorph) def mbVconj (lMorph): + "returns True if one morphology is “nom” or “verbe conjugué”" return any(Vconj.search(s) for s in lMorph) def mbVconj123 (lMorph): + "returns True if one morphology is “nom” or “verbe conjugué” (but not “avoir” or “être”)" return any(Vconj123.search(s) for s in lMorph) def mbMG (lMorph): + "returns True if one morphology is “mot grammatical”" return any(":G" in s for s in lMorph) def mbInv (lMorph): + "returns True if one morphology is “invariable”" return any(":i" in s for s in lMorph) def mbSg (lMorph): + "returns True if one morphology is “singulier”" return any(":s" in s for s in lMorph) def mbPl (lMorph): + "returns True if one morphology is “pluriel”" return any(":p" in s for s in lMorph) def mbEpi (lMorph): + "returns True if one morphology is “épicène”" return any(":e" in s for s in lMorph) def mbMas (lMorph): + "returns True if one morphology is “masculin”" return any(":m" in s for s in lMorph) def mbFem (lMorph): + "returns True if one morphology is “féminin”" return any(":f" in s for s in lMorph) def mbNpr (lMorph): + "returns True if one morphology is “nom propre” or “titre de civilité”" return any(NP.search(s) for s in lMorph) def mbNprMasNotFem (lMorph): + "returns True if one morphology is “nom propre masculin” but not “féminin”" if any(NPf.search(s) for s in lMorph): return False return any(NPm.search(s) for s in lMorph) Index: gc_lang/fr/modules/gce_analyseur.py ================================================================== --- gc_lang/fr/modules/gce_analyseur.py +++ gc_lang/fr/modules/gce_analyseur.py @@ -2,11 +2,11 @@ from . import cregex as cr def rewriteSubject (s1, s2): - # s1 is supposed to be prn/patr/npr (M[12P]) + "rewrite complex subject: a prn/patr/npr (M[12P]) followed by “et” and " if s2 == "lui": return "ils" if s2 == "moi": return "nous" if s2 == "toi": @@ -16,62 +16,57 @@ if s2 == "vous": return "vous" if s2 == "eux": return "ils" if s2 == "elle" or s2 == "elles": - # We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - if cr.mbNprMasNotFem(_dAnalyses.get(s1, False)): + if cr.mbNprMasNotFem(_oSpellChecker.getMorph(s1)): return "ils" # si épicène, indéterminable, mais OSEF, le féminin l’emporte return "elles" return s1 + " et " + s2 def apposition (sWord1, sWord2): "returns True if nom + nom (no agreement required)" - # We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - return cr.mbNomNotAdj(_dAnalyses.get(sWord2, False)) and cr.mbPpasNomNotAdj(_dAnalyses.get(sWord1, False)) + return cr.mbNomNotAdj(_oSpellChecker.getMorph(sWord2)) and cr.mbPpasNomNotAdj(_oSpellChecker.getMorph(sWord1)) def isAmbiguousNAV (sWord): "words which are nom|adj and verb are ambiguous (except être and avoir)" - if sWord not in _dAnalyses and not _storeMorphFromFSA(sWord): + lMorph = _oSpellChecker.getMorph(sWord) + if not cr.mbNomAdj(lMorph) or sWord == "est": return False - if not cr.mbNomAdj(_dAnalyses[sWord]) or sWord == "est": - return False - if cr.mbVconj(_dAnalyses[sWord]) and not cr.mbMG(_dAnalyses[sWord]): + if cr.mbVconj(lMorph) and not cr.mbMG(lMorph): return True return False def isAmbiguousAndWrong (sWord1, sWord2, sReqMorphNA, sReqMorphConj): - "use it if sWord1 won’t be a verb; word2 is assumed to be True via isAmbiguousNAV" - # We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - a2 = _dAnalyses.get(sWord2, None) + "use it if won’t be a verb; is assumed to be True via isAmbiguousNAV" + a2 = _oSpellChecker.getMorph(sWord2) if not a2: return False if cr.checkConjVerb(a2, sReqMorphConj): # verb word2 is ok return False - a1 = _dAnalyses.get(sWord1, None) + a1 = _oSpellChecker.getMorph(sWord1) if not a1: return False if cr.checkAgreement(a1, a2) and (cr.mbAdj(a2) or cr.mbAdj(a1)): return False return True def isVeryAmbiguousAndWrong (sWord1, sWord2, sReqMorphNA, sReqMorphConj, bLastHopeCond): - "use it if sWord1 can be also a verb; word2 is assumed to be True via isAmbiguousNAV" - # We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - a2 = _dAnalyses.get(sWord2, None) + "use it if can be also a verb; is assumed to be True via isAmbiguousNAV" + a2 = _oSpellChecker.getMorph(sWord2) if not a2: return False if cr.checkConjVerb(a2, sReqMorphConj): # verb word2 is ok return False - a1 = _dAnalyses.get(sWord1, None) + a1 = _oSpellChecker.getMorph(sWord1) if not a1: return False if cr.checkAgreement(a1, a2) and (cr.mbAdj(a2) or cr.mbAdjNb(a1)): return False # now, we know there no agreement, and conjugation is also wrong @@ -82,24 +77,25 @@ return True return False def checkAgreement (sWord1, sWord2): - # We don’t check if word exists in _dAnalyses, for it is assumed it has been done before - a2 = _dAnalyses.get(sWord2, None) + "check agreement between and " + a2 = _oSpellChecker.getMorph(sWord2) if not a2: return True - a1 = _dAnalyses.get(sWord1, None) + a1 = _oSpellChecker.getMorph(sWord1) if not a1: return True return cr.checkAgreement(a1, a2) _zUnitSpecial = re.compile("[µ/⁰¹²³⁴⁵⁶⁷⁸⁹Ωℓ·]") _zUnitNumbers = re.compile("[0-9]") def mbUnit (s): + "returns True it can be a measurement unit" if _zUnitSpecial.search(s): return True if 1 < len(s) < 16 and s[0:1].islower() and (not s[1:].islower() or _zUnitNumbers.search(s)): return True return False @@ -110,10 +106,11 @@ _zEndOfNG1 = re.compile(" *$| +(?:, +|)(?:n(?:’|e |o(?:u?s|tre) )|l(?:’|e(?:urs?|s|) |a )|j(?:’|e )|m(?:’|es? |a |on )|t(?:’|es? |a |u )|s(?:’|es? |a )|c(?:’|e(?:t|tte|s|) )|ç(?:a |’)|ils? |vo(?:u?s|tre) )") _zEndOfNG2 = re.compile(r" +(\w[\w-]+)") _zEndOfNG3 = re.compile(r" *, +(\w[\w-]+)") def isEndOfNG (dDA, s, iOffset): + "returns True if next word doesn’t belong to a noun group" if _zEndOfNG1.match(s): return True m = _zEndOfNG2.match(s) if m and morphex(dDA, (iOffset+m.start(1), m.group(1)), ":[VR]", ":[NAQP]"): return True @@ -126,10 +123,11 @@ _zNextIsNotCOD1 = re.compile(" *,") _zNextIsNotCOD2 = re.compile(" +(?:[mtsnj](e +|’)|[nv]ous |tu |ils? |elles? )") _zNextIsNotCOD3 = re.compile(r" +([a-zéèî][\w-]+)") def isNextNotCOD (dDA, s, iOffset): + "returns True if next word is not a COD" if _zNextIsNotCOD1.match(s) or _zNextIsNotCOD2.match(s): return True m = _zNextIsNotCOD3.match(s) if m and morphex(dDA, (iOffset+m.start(1), m.group(1)), ":[123][sp]", ":[DM]"): return True @@ -138,10 +136,11 @@ _zNextIsVerb1 = re.compile(" +[nmts](?:e |’)") _zNextIsVerb2 = re.compile(r" +(\w[\w-]+)") def isNextVerb (dDA, s, iOffset): + "returns True if next word is a verb" if _zNextIsVerb1.match(s): return True m = _zNextIsVerb2.match(s) if m and morph(dDA, (iOffset+m.start(1), m.group(1)), ":[123][sp]", False): return True @@ -151,6 +150,6 @@ #### Exceptions aREGULARPLURAL = frozenset(["abricot", "amarante", "aubergine", "acajou", "anthracite", "brique", "caca", "café", \ "carotte", "cerise", "chataigne", "corail", "citron", "crème", "grave", "groseille", \ "jonquille", "marron", "olive", "pervenche", "prune", "sable"]) -aSHOULDBEVERB = frozenset(["aller", "manger"]) +aSHOULDBEVERB = frozenset(["aller", "manger"]) Index: gc_lang/fr/modules/gce_suggestions.py ================================================================== --- gc_lang/fr/modules/gce_suggestions.py +++ gc_lang/fr/modules/gce_suggestions.py @@ -6,18 +6,19 @@ ## Verbs def suggVerb (sFlex, sWho, funcSugg2=None): + "change conjugation according to " aSugg = set() - for sStem in stem(sFlex): + for sStem in _oSpellChecker.getLemma(sFlex): tTags = conj._getTags(sStem) if tTags: # we get the tense aTense = set() - for sMorph in _dAnalyses.get(sFlex, []): # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before - for m in re.finditer(">"+sStem+" .*?(:(?:Y|I[pqsf]|S[pq]|K|P))", sMorph): + for sMorph in _oSpellChecker.getMorph(sFlex): + for m in re.finditer(">"+sStem+"/.*?(:(?:Y|I[pqsf]|S[pq]|K|P))", sMorph): # stem must be used in regex to prevent confusion between different verbs (e.g. sauras has 2 stems: savoir and saurer) if m: if m.group(1) == ":Y": aTense.add(":Ip") aTense.add(":Iq") @@ -38,43 +39,44 @@ if aSugg: return "|".join(aSugg) return "" -def suggVerbPpas (sFlex, sWhat=None): +def suggVerbPpas (sFlex, sPattern=None): + "suggest past participles for " aSugg = set() - for sStem in stem(sFlex): + for sStem in _oSpellChecker.getLemma(sFlex): tTags = conj._getTags(sStem) if tTags: - if not sWhat: + if not sPattern: aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1")) aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q2")) aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q3")) aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q4")) aSugg.discard("") - elif sWhat == ":m:s": + elif sPattern == ":m:s": aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1")) - elif sWhat == ":m:p": + elif sPattern == ":m:p": if conj._hasConjWithTags(tTags, ":PQ", ":Q2"): aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q2")) else: aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1")) - elif sWhat == ":f:s": + elif sPattern == ":f:s": if conj._hasConjWithTags(tTags, ":PQ", ":Q3"): aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q3")) else: aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1")) - elif sWhat == ":f:p": + elif sPattern == ":f:p": if conj._hasConjWithTags(tTags, ":PQ", ":Q4"): aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q4")) else: aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1")) - elif sWhat == ":s": + elif sPattern == ":s": aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1")) aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q3")) aSugg.discard("") - elif sWhat == ":p": + elif sPattern == ":p": aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q2")) aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q4")) aSugg.discard("") else: aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1")) @@ -82,22 +84,24 @@ return "|".join(aSugg) return "" def suggVerbTense (sFlex, sTense, sWho): + "change to a verb according to and " aSugg = set() - for sStem in stem(sFlex): + for sStem in _oSpellChecker.getLemma(sFlex): if conj.hasConj(sStem, sTense, sWho): aSugg.add(conj.getConj(sStem, sTense, sWho)) if aSugg: return "|".join(aSugg) return "" def suggVerbImpe (sFlex): + "change to a verb at imperative form" aSugg = set() - for sStem in stem(sFlex): + for sStem in _oSpellChecker.getLemma(sFlex): tTags = conj._getTags(sStem) if tTags: if conj._hasConjWithTags(tTags, ":E", ":2s"): aSugg.add(conj._getConjWithTags(sStem, tTags, ":E", ":2s")) if conj._hasConjWithTags(tTags, ":E", ":1p"): @@ -108,19 +112,21 @@ return "|".join(aSugg) return "" def suggVerbInfi (sFlex): - return "|".join([ sStem for sStem in stem(sFlex) if conj.isVerb(sStem) ]) + "returns infinitive forms of " + return "|".join([ sStem for sStem in _oSpellChecker.getLemma(sFlex) if conj.isVerb(sStem) ]) _dQuiEst = { "je": ":1s", "j’": ":1s", "j’en": ":1s", "j’y": ":1s", \ "tu": ":2s", "il": ":3s", "on": ":3s", "elle": ":3s", "nous": ":1p", "vous": ":2p", "ils": ":3p", "elles": ":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 elif cMode == ":S": lMode = _lSubjonctif elif cMode.startswith((":I", ":S")): @@ -131,11 +137,11 @@ if not sWho: if sSuj[0:1].islower(): # pas un pronom, ni un nom propre return "" sWho = ":3s" aSugg = set() - for sStem in stem(sFlex): + for sStem in _oSpellChecker.getLemma(sFlex): tTags = conj._getTags(sStem) if tTags: for sTense in lMode: if conj._hasConjWithTags(tTags, sTense, sWho): aSugg.add(conj._getConjWithTags(sStem, tTags, sTense, sWho)) @@ -147,13 +153,14 @@ ## Nouns and adjectives def suggPlur (sFlex, sWordToAgree=None): "returns plural forms assuming sFlex is singular" if sWordToAgree: - if sWordToAgree not in _dAnalyses and not _storeMorphFromFSA(sWordToAgree): + lMorph = _oSpellChecker.getMorph(sFlex) + if not lMorph: return "" - sGender = cr.getGender(_dAnalyses.get(sWordToAgree, [])) + sGender = cr.getGender(lMorph) if sGender == ":m": return suggMasPlur(sFlex) elif sGender == ":f": return suggFemPlur(sFlex) aSugg = set() @@ -191,13 +198,12 @@ return "" def suggMasSing (sFlex, bSuggSimil=False): "returns masculine singular forms" - # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before aSugg = set() - for sMorph in _dAnalyses.get(sFlex, []): + for sMorph in _oSpellChecker.getMorph(sFlex): if not ":V" in sMorph: # not a verb if ":m" in sMorph or ":e" in sMorph: aSugg.add(suggSing(sFlex)) else: @@ -219,13 +225,12 @@ return "" def suggMasPlur (sFlex, bSuggSimil=False): "returns masculine plural forms" - # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before aSugg = set() - for sMorph in _dAnalyses.get(sFlex, []): + for sMorph in _oSpellChecker.getMorph(sFlex): if not ":V" in sMorph: # not a verb if ":m" in sMorph or ":e" in sMorph: aSugg.add(suggPlur(sFlex)) else: @@ -250,13 +255,12 @@ return "" def suggFemSing (sFlex, bSuggSimil=False): "returns feminine singular forms" - # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before aSugg = set() - for sMorph in _dAnalyses.get(sFlex, []): + for sMorph in _oSpellChecker.getMorph(sFlex): if not ":V" in sMorph: # not a verb if ":f" in sMorph or ":e" in sMorph: aSugg.add(suggSing(sFlex)) else: @@ -276,13 +280,12 @@ return "" def suggFemPlur (sFlex, bSuggSimil=False): "returns feminine plural forms" - # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before aSugg = set() - for sMorph in _dAnalyses.get(sFlex, []): + for sMorph in _oSpellChecker.getMorph(sFlex): if not ":V" in sMorph: # not a verb if ":f" in sMorph or ":e" in sMorph: aSugg.add(suggPlur(sFlex)) else: @@ -301,33 +304,35 @@ return "|".join(aSugg) return "" def hasFemForm (sFlex): - for sStem in stem(sFlex): + "return True if there is a feminine form of " + for sStem in _oSpellChecker.getLemma(sFlex): if mfsp.isFemForm(sStem) or conj.hasConj(sStem, ":PQ", ":Q3"): return True if phonet.hasSimil(sFlex, ":f"): return True return False def hasMasForm (sFlex): - for sStem in stem(sFlex): + "return True if there is a masculine form of " + for sStem in _oSpellChecker.getLemma(sFlex): if mfsp.isFemForm(sStem) or conj.hasConj(sStem, ":PQ", ":Q1"): # what has a feminine form also has a masculine form return True if phonet.hasSimil(sFlex, ":m"): return True return False def switchGender (sFlex, bPlur=None): - # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before + "return feminine or masculine form(s) of " aSugg = set() if bPlur == None: - for sMorph in _dAnalyses.get(sFlex, []): + for sMorph in _oSpellChecker.getMorph(sFlex): if ":f" in sMorph: if ":s" in sMorph: aSugg.add(suggMasSing(sFlex)) elif ":p" in sMorph: aSugg.add(suggMasPlur(sFlex)) @@ -338,17 +343,17 @@ aSugg.add(suggFemPlur(sFlex)) else: aSugg.add(suggFemSing(sFlex)) aSugg.add(suggFemPlur(sFlex)) elif bPlur: - for sMorph in _dAnalyses.get(sFlex, []): + for sMorph in _oSpellChecker.getMorph(sFlex): if ":f" in sMorph: aSugg.add(suggMasPlur(sFlex)) elif ":m" in sMorph: aSugg.add(suggFemPlur(sFlex)) else: - for sMorph in _dAnalyses.get(sFlex, []): + for sMorph in _oSpellChecker.getMorph(sFlex): if ":f" in sMorph: aSugg.add(suggMasSing(sFlex)) elif ":m" in sMorph: aSugg.add(suggFemSing(sFlex)) if aSugg: @@ -355,13 +360,13 @@ return "|".join(aSugg) return "" def switchPlural (sFlex): - # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before + "return plural or singular form(s) of " aSugg = set() - for sMorph in _dAnalyses.get(sFlex, []): + for sMorph in _oSpellChecker.getMorph(sFlex): if ":s" in sMorph: aSugg.add(suggPlur(sFlex)) elif ":p" in sMorph: aSugg.add(suggSing(sFlex)) if aSugg: @@ -368,43 +373,45 @@ return "|".join(aSugg) return "" def hasSimil (sWord, sPattern=None): + "return True if there is words phonetically similar to (according to if required)" return phonet.hasSimil(sWord, sPattern) def suggSimil (sWord, sPattern=None, bSubst=False): "return list of words phonetically similar to sWord and whom POS is matching sPattern" - # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before aSugg = phonet.selectSimil(sWord, sPattern) - for sMorph in _dAnalyses.get(sWord, []): + for sMorph in _oSpellChecker.getMorph(sWord): aSugg.update(conj.getSimil(sWord, sMorph, bSubst)) break if aSugg: return "|".join(aSugg) return "" def suggCeOrCet (sWord): + "suggest “ce” or “cet” or both according to the first letter of " if re.match("(?i)[aeéèêiouyâîï]", sWord): return "cet" if sWord[0:1] == "h" or sWord[0:1] == "H": return "ce|cet" return "ce" def suggLesLa (sWord): - # we don’t check if word exists in _dAnalyses, for it is assumed it has been done before - if any( ":p" in sMorph for sMorph in _dAnalyses.get(sWord, []) ): + "suggest “les” or “la” according to " + if any( ":p" in sMorph for sMorph in _oSpellChecker.getMorph(sWord) ): return "les|la" return "la" _zBinary = re.compile("^[01]+$") def formatNumber (s): + "add spaces or hyphens to big numbers" nLen = len(s) if nLen < 4: return s sRes = "" # nombre ordinaire @@ -435,10 +442,11 @@ sRes += "|" + s[0:2] + " " + s[2:5] + " " + s[5:7] + " " + s[7:9] # fixe belge 2 return sRes def formatNF (s): + "typography: format NF reference (norme française)" try: m = re.match("NF[  -]?(C|E|P|Q|S|X|Z|EN(?:[  -]ISO|))[  -]?([0-9]+(?:[/‑-][0-9]+|))", s) if not m: return "" return "NF " + m.group(1).upper().replace(" ", " ").replace("-", " ") + " " + m.group(2).replace("/", "‑").replace("-", "‑") @@ -446,10 +454,11 @@ traceback.print_exc() return "# erreur #" def undoLigature (c): + "typography: split ligature character in several chars" if c == "fi": return "fi" elif c == "fl": return "fl" elif c == "ff": @@ -470,10 +479,11 @@ _xNormalizedCharsForInclusiveWriting = str.maketrans({ '(': '_', ')': '_', '.': '_', '·': '_', '–': '_', '—': '_', '/': '_' - }) +}) def normalizeInclusiveWriting (sToken): + "typography: replace word separators used in inclusive writing by underscore (_)" return sToken.translate(_xNormalizedCharsForInclusiveWriting) Index: gc_lang/fr/modules/lexicographe.py ================================================================== --- gc_lang/fr/modules/lexicographe.py +++ gc_lang/fr/modules/lexicographe.py @@ -1,14 +1,17 @@ -# Grammalecte - Lexicographe +""" +Grammalecte - Lexicographe +""" + # License: MPL 2 import re import traceback -_dTAGS = { +_dTAGS = { ':N': (" nom,", "Nom"), ':A': (" adjectif,", "Adjectif"), ':M1': (" prénom,", "Prénom"), ':M2': (" patronyme,", "Patronyme, matronyme, nom de famille…"), ':MP': (" nom propre,", "Nom propre"), @@ -78,11 +81,11 @@ ':C': (" conjonction,", "Conjonction"), ':Ĉ': (" conjonction (él.),", "Conjonction (élément)"), ':Cc': (" conjonction de coordination,", "Conjonction de coordination"), ':Cs': (" conjonction de subordination,", "Conjonction de subordination"), ':Ĉs': (" conjonction de subordination (él.),", "Conjonction de subordination (élément)"), - + ':Ñ': (" locution nominale (él.),", "Locution nominale (élément)"), ':Â': (" locution adjectivale (él.),", "Locution adjectivale (élément)"), ':Ṽ': (" locution verbale (él.),", "Locution verbale (élément)"), ':Ŵ': (" locution adverbiale (él.),", "Locution adverbiale (élément)"), ':Ŕ': (" locution prépositive (él.),", "Locution prépositive (élément)"), @@ -125,18 +128,18 @@ 'elle': " pronom personnel sujet, 3ᵉ pers. fém. sing.", 'nous': " pronom personnel sujet/objet, 1ʳᵉ pers. plur.", 'vous': " pronom personnel sujet/objet, 2ᵉ pers. plur.", 'ils': " pronom personnel sujet, 3ᵉ pers. masc. plur.", 'elles': " pronom personnel sujet, 3ᵉ pers. masc. plur.", - + "là": " particule démonstrative", "ci": " particule démonstrative", - + 'le': " COD, masc. sing.", 'la': " COD, fém. sing.", 'les': " COD, plur.", - + 'moi': " COI (à moi), sing.", 'toi': " COI (à toi), sing.", 'lui': " COI (à lui ou à elle), sing.", 'nous2': " COI (à nous), plur.", 'vous2': " COI (à vous), plur.", @@ -153,18 +156,20 @@ "s'en": " (se) pronom personnel objet + (en) pronom adverbial", } class Lexicographe: + "Lexicographer - word analyzer" def __init__ (self, oSpellChecker): self.oSpellChecker = oSpellChecker self._zElidedPrefix = re.compile("(?i)^([dljmtsncç]|quoiqu|lorsqu|jusqu|puisqu|qu)['’](.+)") self._zCompoundWord = re.compile("(?i)(\\w+)-((?:les?|la)-(?:moi|toi|lui|[nv]ous|leur)|t-(?:il|elle|on)|y|en|[mts][’'](?:y|en)|les?|l[aà]|[mt]oi|leur|lui|je|tu|ils?|elles?|on|[nv]ous)$") self._zTag = re.compile("[:;/][\\w*][^:;/]*") def analyzeWord (self, sWord): + "returns a tuple (a list of morphologies, a set of verb at infinitive form)" try: if not sWord: return (None, None) if sWord.count("-") > 4: return (["élément complexe indéterminé"], None) @@ -192,17 +197,18 @@ aMorph.append( "{} : inconnu du dictionnaire".format(sWord) ) # suffixe d’un mot composé if m2: aMorph.append( "-{} : {}".format(m2.group(2), self._formatSuffix(m2.group(2).lower())) ) # Verbes - aVerb = set([ s[1:s.find(" ")] for s in lMorph if ":V" in s ]) + aVerb = set([ s[1:s.find("/")] for s in lMorph if ":V" in s ]) return (aMorph, aVerb) except: traceback.print_exc() return (["#erreur"], None) def formatTags (self, sTags): + "returns string: readable tags" sRes = "" sTags = re.sub("(?<=V[1-3])[itpqnmr_eaxz]+", "", sTags) sTags = re.sub("(?<=V0[ea])[itpqnmr_eaxz]+", "", sTags) for m in self._zTag.finditer(sTags): sRes += _dTAGS.get(m.group(0), " [{}]".format(m.group(0)))[0] Index: gc_lang/fr/modules/mfsp.py ================================================================== --- gc_lang/fr/modules/mfsp.py +++ gc_lang/fr/modules/mfsp.py @@ -1,6 +1,8 @@ -# Masculins, féminins, singuliers et pluriels +""" +Masculins, féminins, singuliers et pluriels +""" from .mfsp_data import lTagMiscPlur as _lTagMiscPlur from .mfsp_data import lTagMasForm as _lTagMasForm from .mfsp_data import dMiscPlur as _dMiscPlur from .mfsp_data import dMasForm as _dMasForm Index: gc_lang/fr/modules/phonet.py ================================================================== --- gc_lang/fr/modules/phonet.py +++ gc_lang/fr/modules/phonet.py @@ -1,6 +1,9 @@ -# Grammalecte - Suggestion phonétique +""" +Grammalecte - Suggestion phonétique +""" + # License: GPL 3 import re from .phonet_data import dWord as _dWord Index: gc_lang/fr/modules/tests.py ================================================================== --- gc_lang/fr/modules/tests.py +++ gc_lang/fr/modules/tests.py @@ -1,7 +1,10 @@ #! python3 -# coding: UTF-8 + +""" +Grammar checker tests for French language +""" import unittest import os import re import time Index: gc_lang/fr/modules/textformatter.py ================================================================== --- gc_lang/fr/modules/textformatter.py +++ gc_lang/fr/modules/textformatter.py @@ -1,6 +1,10 @@ #!python3 + +""" +Text formatter +""" import re dReplTable = { @@ -65,11 +69,11 @@ "ts_apostrophe": [ ("(?i)\\b([ldnjmtscç])['´‘′`](?=\\w)", "\\1’"), ("(?i)(qu|jusqu|lorsqu|puisqu|quoiqu|quelqu|presqu|entr|aujourd|prud)['´‘′`]", "\\1’") ], "ts_ellipsis": [ ("\\.\\.\\.", "…"), ("(?<=…)[.][.]", "…"), ("…[.](?![.])", "…") ], - "ts_n_dash_middle": [ (" [-—] ", " – "), + "ts_n_dash_middle": [ (" [-—] ", " – "), (" [-—],", " –,") ], "ts_m_dash_middle": [ (" [-–] ", " — "), (" [-–],", " —,") ], "ts_n_dash_start": [ ("^[-—][  ]", "– "), ("^– ", "– "), Index: gc_lang/fr/rules.grx ================================================================== --- gc_lang/fr/rules.grx +++ gc_lang/fr/rules.grx @@ -202,10 +202,11 @@ !! !! Définitions pour les regex !! !! +# REGEX DEF: avoir [aeo]\w* DEF: etre [êeésf]\w+ DEF: avoir_etre [aeêésfo]\w* DEF: aller (?:ai?ll|v[ao]|ir[aio])\w* DEF: ppas \w[\w-]+[éiust]e?s? @@ -217,11 +218,14 @@ DEF: w1 \w+ DEF: w2 \w\w+ DEF: w3 \w\w\w+ DEF: w4 \w\w\w\w+ - +# GRAPH +DEF: mois [>janvier|>février|>mars|>avril|>mai|>juin|>juillet|>août|>aout|>septembre|>octobre|>novembre|>décembre|>vendémiaire|>brumaire|>frimaire|>nivôse|>pluviôse|>ventôse|>germinal|>floréal|>prairial|>messidor|>thermidor|>fructidor] +DEF: mi_mois [>mi-janvier|>mi-février|>mi-mars|>mi-avril|>mi-mai|>mi-juin|>mi-juillet|>mi-août|>mi-aout|>mi-septembre|>mi-octobre|>mi-novembre|>mi-décembre|mi-vendémiaire|mi-brumaire|mi-frimaire|mi-nivôse|mi-pluviôse|mi-ventôse|mi-germinal|mi-floréal|mi-prairial|mi-messidor|mi-thermidor|mi-fructidor] +DEF: pronom_obj [moi|toi|soi|lui|elle|nous|vous|eux|elles|moi-même|toi-même|soi-même|lui-même|elle-même|nous-mêmes|vous-même|vous-mêmes|eux-mêmes|elles-mêmes] !! !! !! @@ -390,11 +394,10 @@ https?://[\w./?&!%=+*"'@$#-]+ <<- ~>> * __> * <<- ~2>> =\2.capitalize() - <<- =>> define(\2, [":MP:e:i"]) <<- ~3>> * # Numéro de chapitre __(p_chapitre)__ ^\d+[.][\d.-]* <<- ~>> * @@ -686,11 +689,11 @@ ({w_1})( car)(?= (?:j[e’]|tu|ils?|nous|vous|elles?|on|les?|l[a’]|ces?|des?|cette|[mts](?:on|a|es))\b) @@0,$ <<- not morph(\1, ":[DR]", False) -2>> , car # Si « car » est la conjonction de coordination, une virgule est peut-être souhaitable.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=3447 __[i>/virg(virgule_manquante_avant_mais)__ ({w_1})( mais)(?= (?:j[e’]|tu|ils?|nous|vous|elles?|on)\b) @@0,$ - <<- not morph(\1, ">(?:[mtscl]es|[nv]os|quels) ", False) -2>> , mais + <<- not morph(\1, ">(?:[mtscl]es|[nv]os|quels)/", False) -2>> , mais # Si « mais » est la conjonction de coordination, une virgule est souhaitable si elle introduit une nouvelle proposition.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=3445 __[i>/virg(virgule_manquante_avant_donc)__ ({w_1})( donc)(?= (?:j[e’]|tu|ils?|elles?|on)\b) @@0,$ <<- not morph(\1, ":V", False) -2>> , donc # Si « mais » est la conjonction de coordination, une virgule est souhaitable si elle introduit une nouvelle proposition.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=3448 @@ -1238,11 +1241,11 @@ !!!! Redondances !! !! __[i]/redon1(redondances_paragraphe)__ ({w_4})[  ,.;!?:].*[  ](\1) @@0,$ - <<- not morph(\1, ":(?:G|V0)|>(?:t(?:antôt|emps|rès)|loin|souvent|parfois|quelquefois|côte|petit|même) ", False) and not \1[0].isupper() + <<- not morph(\1, ":(?:G|V0)|>(?:t(?:antôt|emps|rès)|loin|souvent|parfois|quelquefois|côte|petit|même)/", False) and not \1[0].isupper() -2>> _ # Dans ce paragraphe, répétition de « \1 » (à gauche). <<- __also__ -1>> _ # Dans ce paragraphe, répétition de « \1 » (à droite). TEST: __redon1__ Tu es son {{avenir}}. Et lui aussi est ton {{avenir}}. TEST: __redon1__ Car parfois il y en a. Mais parfois il n’y en a pas. @@ -1509,15 +1512,15 @@ __> - # Le “t” euphonique n’est pas nécessaire avec “\2”.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?T1=t+euphonique&id=2513 <<- __else__ and \1 != "-t-" and \1 != "-T-" -1>> -t- # Pour le “t” euphonique, il faut deux traits d’union. Pas d’apostrophe. Pas d’espace. - <<- ~1>> -t- + <<- \1 != "-t-" ~1>> -t- __> - # Le “t” euphonique est superflu quand le verbe se termine par “t” ou “d”.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?T1=t+euphonique&id=2513 - <<- ~1>> -t- + <<- \1 != "-t-" ~1>> -t- __> -t-\2 # Il faut un “t” euphonique.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?T1=t+euphonique&id=2513 TEST: va{{ t’}}il y parvenir ? ->> -t- TEST: A{{ t’}}elle soif ? ->> -t- @@ -1759,11 +1762,11 @@ # est-ce … ? __[i]/tu(tu_est_ce)__ (?qui ") and morph(word(-1), ":Cs", False, True) + <<- morphex(\2, ":", ":N.*:[me]:[si]|>qui/") and morph(word(-1), ":Cs", False, True) -1>> est-ce # S’il s’agit d’une interrogation, il manque un trait d’union. TEST: {{est ce}} que c’est grave ? ->> est-ce TEST: qu’{{est ce}} que c’est ? ->> est-ce TEST: elles reviendront, {{n’est ce pas}} ? @@ -1898,11 +1901,11 @@ TEST: Peu {{d’entre-nous}} savent ce dont il s’agit. __[i]/tu(tu_y_attaché)__ (y[’-])({avoir_etre})(?:-(?:t-|)(?:ils?|elles?|je|tu|on|nous|vous)|) @@0,2 - <<- morph(\2, ":V0|>en ", False) -1>> "y " # Ici, ni apostrophe, ni trait d’union. + <<- morph(\2, ":V0|>en/", False) -1>> "y " # Ici, ni apostrophe, ni trait d’union. TEST: {{Y’}}a trop de malheureux sur Terre. TEST: {{Y’}}en a marre, de ces conneries. TEST: {{y-}}a-t-il des beignets ? ->> "y " @@ -1941,11 +1944,11 @@ TEST: Tape-toi Patrick. __[u]/virg(virgule_après_verbe_COD)__ l(?:es?|a) ({w_2}(?:[ei]r|re)) ([A-ZÉÂÔÈ][\w-]+) @@w,$ - <<- morph(\1, ":Y", False) and morph(\2, ":M", False) and not morph(word(-1), ">à ", False, False) + <<- morph(\1, ":Y", False) and morph(\2, ":M", False) and not morph(word(-1), ">à/", False, False) -1>> \1, # Une virgule est probablement souhaitable. TEST: Tu vas les {{donner}} Rachel. TEST: Il va la {{tuer}} Paul. TEST: Cependant les promesses n’engagent que ceux qui les croient, comme aimait à le dire Jacques Chirac. @@ -1964,11 +1967,11 @@ !!!! A / À: accentuation la préposition en début de phrase __(?:priori|post[eé]riori|contrario|capella|fortiori) ") + <<- morphex(\2, ":[GNAY]", ":(?:Q|3s)|>(?:priori|post[eé]riori|contrario|capella|fortiori)/") or (\2 == "bientôt" and isEnd()) -1>> À # S’il s’agit de la préposition « à », il faut accentuer la majuscule. __/typo(typo_À_début_phrase2)__ ^ *(A) [ldnms]’ @@* <<- -1>> À # S’il s’agit de la préposition « à », il faut accentuer la majuscule. __/typo(typo_À_début_phrase3)__ @@ -2002,15 +2005,15 @@ !!! # mots grammaticaux __[i](d_dans)__ dans - <<- not morph(word(-1), ":D.*:p|>[a-z]+ièmes ", False, False) =>> select(\0, ":R") + <<- not morph(word(-1), ":D.*:p|>[a-z]+ièmes/", False, False) =>> select(\0, ":R") __[i](d_ton_son)__ (\w+) ([ts]on) @@0,$ - <<- morph(\1, ">(?:le|ce[st]?|ton|mon|son|quel(?:que|)s?|[nv]otre|un|leur|ledit|dudit) ") =>> exclude(\2, ":D") + <<- morph(\1, ">(?:le|ce[st]?|ton|mon|son|quel(?:que|)s?|[nv]otre|un|leur|ledit|dudit)/") =>> exclude(\2, ":D") # Pronoms le/la/les __[i](d_je_le_la_les)__ je (l(?:e(?:ur|s|)|a)) @@$ <<- not morph(word(-1), ":1s", False, False) =>> select(\1, ":Oo") __[i](d_tu_le_la_les)__ @@ -2052,11 +2055,11 @@ <<- morph(word(-1), ":Cs", False, True) and not morph(\1, ":(?:Oo|X)", False) =>> select(\1, ":[123][sp]") __[s](d_nom_propre_verbe)__ ([A-ZÉÈ]{w_1}) +({w_1}) @@0,$ <<- morph(\1, ":M") and \2.islower() and morphex(\2, ":[123][sg]", ":Q") and morph(\2, ":N", False) and morph(word(-1), ":Cs", False, True) =>> select(\2, ":[123][sp]") - <<- morph(\1, ":M", False) and morphex(\2, ":[123]s|>(?:[nmts]e|nous|vous) ", ":A") and isStart() =>> =select(\1, ":M") + <<- morph(\1, ":M", False) and morphex(\2, ":[123]s|>(?:[nmts]e|nous|vous)/", ":A") and isStart() =>> =select(\1, ":M") __[i](d_que_combien_pourquoi_en_y_verbe)__ (?:que?|combien|pourquoi) +(?:en +|y +|)({w_3}) @@$ <<- =>> exclude(\1, ":E") # groupe nominal @@ -2204,11 +2207,11 @@ TEST: __ocr__ on poirautait, {{cotte}} mariée n’arrivait pas à se décider. # Comme / Gomme -__[s]/ocr(ocr_comme)__ Gomme <<- not morph(word(1), ">(?:et|o[uù]) ") ->> Comme # Erreur de numérisation ? +__[s]/ocr(ocr_comme)__ Gomme <<- not morph(word(1), ">(?:et|o[uù])/") ->> Comme # Erreur de numérisation ? TEST: __ocr__ {{Gomme}} il était sage à cette époque-là ! # Comment / Gomment @@ -2467,11 +2470,11 @@ # Mais / Hais / Mats / niais __[u]/ocr(ocr_mais1)__ Hais <<- ->> Mais # Erreur de numérisation ? __[i]/ocr(ocr_mais2)__ mats <<- not morph(word(-1), ":D:[me]:p", False, False) ->> mais # Erreur de numérisation ? __[i]/ocr(ocr_mais3)__ maïs <<- not morph(word(-1), ":D:(?:m:s|e:p)", False, False) ->> mais # Erreur de numérisation ? __[s]/ocr(ocr_mais4)__ - niais <<- not morph(word(-1), ">(?:homme|ce|quel|être) ", False, False) ->> mais # Erreur de numérisation ? + niais <<- not morph(word(-1), ">(?:homme|ce|quel|être)/", False, False) ->> mais # Erreur de numérisation ? TEST: __ocr__ {{Hais}} il en sait trop. TEST: __ocr__ c’était bien, {{mats}} quelle journée TEST: __ocr__ c’est bien, {{niais}} trop subtil. TEST: __ocr__ c’est parfait, {{maïs}} trop subtil. @@ -2695,11 +2698,11 @@ ## Casse __[s]/ocr(ocr_casse1)__ [A-ZÉÈÂÊÎÔ]{w_1} <<- \0.istitle() and before(r"(?i)\w") >>> <<- morphex(\0, ":G", ":M") ->> =\0.lower() # Erreur de numérisation ? Casse improbable. - <<- __else__ and morphex(\0, ":[123][sp]", ":[MNA]|>Est ") ->> =\0.lower() # Erreur de numérisation ? Casse improbable. + <<- __else__ and morphex(\0, ":[123][sp]", ":[MNA]|>Est/") ->> =\0.lower() # Erreur de numérisation ? Casse improbable. TEST: __ocr__ votre ami la regarde, {{Vous}} ne l’avez pas achetée TEST: __ocr__ pour accommoder son regard, {{La}} lourde forme demeure TEST: __ocr__ parler de Nicole, {{Le}} sommeil ne vient pas. TEST: __ocr__ a fait de toi, Charles, {{Tu}} étais beau quand @@ -2780,13 +2783,13 @@ __[s](incohérence_globale_au_qqch)__ ([aA]u) ({w2}) @@0,$ <<- not \2.isupper() >>> - <<- morph(\2, ">(?:[cdlmst]es|[nv]os|cettes?|[mts]a|mon|je|tu|ils?|elle?|[vn]ous|on|parce) ", False) + <<- morph(\2, ">(?:[cdlmst]es|[nv]os|cettes?|[mts]a|mon|je|tu|ils?|elle?|[vn]ous|on|parce)/", False) -2>> =suggSimil(\2, ":[NA].*:[si]", True) # Incohérence : les mots “\1” et “\2” ne devraient pas se succéder. - <<- __else__ and morph(\2, ">quelle ", False) ->> auquel|auxquels|auxquelles # Incohérence. Soudez les deux mots.|https://fr.wiktionary.org/wiki/auquel + <<- __else__ and morph(\2, ">quelle/", False) ->> auquel|auxquels|auxquelles # Incohérence. Soudez les deux mots.|https://fr.wiktionary.org/wiki/auquel <<- __else__ and \2 == "combien" and morph(word(1), ":[AY]", False) -1>> ô # Incohérence probable.|https://fr.wiktionary.org/wiki/%C3%B4_combien TEST: au {{nos}} enfants. TEST: {{Au quel}} faut-il s’adresser ? TEST: Au MES, rien de nouveau. @@ -2793,13 +2796,13 @@ __[s](incohérence_globale_aux_qqch)__ ([aA]ux) ({w2}) @@0,$ <<- not \2.isupper() >>> - <<- morph(\2, ">(?:[cdlmst]es|[nv]os|cettes?|[mts]a|mon|je|tu|ils?|elle?|[vn]ous|on|parce) ", False) + <<- morph(\2, ">(?:[cdlmst]es|[nv]os|cettes?|[mts]a|mon|je|tu|ils?|elle?|[vn]ous|on|parce)/", False) -2>> =suggSimil(\2, ":[NA].*:[pi]", True) # Incohérence : les mots “\1” et “\2” ne devraient pas se succéder. - <<- __else__ and morph(\2, ">quelle ", False) ->> auxquels|auxquelles # Incohérence. Soudez les deux mots.|https://fr.wiktionary.org/wiki/auquel + <<- __else__ and morph(\2, ">quelle/", False) ->> auxquels|auxquelles # Incohérence. Soudez les deux mots.|https://fr.wiktionary.org/wiki/auquel <<- __else__ and \2 == "combien" and morph(word(1), ":[AY]", False) -1>> ô # Incohérence probable.|https://fr.wiktionary.org/wiki/%C3%B4_combien TEST: ils jouent aux {{des}}. TEST: {{Aux quels}} a-t-il adressé sa requête. ? TEST: Des individus {{aux}} combien sensibles aux usages. @@ -2832,11 +2835,11 @@ # avoir été __[i]/bs(bs_avoir_été_chez)__ (?avoir ", False) + <<- not re.search("(?i)^avoir$", \1) and morph(\1, ">avoir/", False) ->> _ # Tournure familière. Utilisez « être allé ». TEST: J’{{ai été chez}} le coiffeur. TEST: Chez les intellectuels, le mot utopie n’a jamais été synonyme de folie, mais il l’a été pour l’homme de la rue. @@ -2849,11 +2852,11 @@ TEST: La mise en {{abîme}}. # à date / jusqu’à date __[i]/bs(bs_à_date)__ - ({etre}|m\w+) ([aà] date) @@0,$ <<- morph(\1, ">(?:être|mettre) ", False) -2>> à jour # Anglicisme incompris hors du Québec. + ({etre}|m\w+) ([aà] date) @@0,$ <<- morph(\1, ">(?:être|mettre)/", False) -2>> à jour # Anglicisme incompris hors du Québec. __[i]/bs(bs_jusquà_date)__ jusqu [àa] date <<- ->> jusqu’ici|jusqu’à maintenant|jusqu’à ce jour|à ce jour # Anglicisme incompris hors du Québec. TEST: être {{à date}} TEST: mettre {{a date}} @@ -2929,49 +2932,49 @@ !! !!!! Pléonasmes !! !! -__[i]/pleo(pleo_abolir)__ (abol\w+) (?:absolument|entièrement|compl[èé]tement|totalement) @@0 <<- morph(\1, ">abolir ", False) ->> \1 # Pléonasme. -__[i]/pleo(pleo_acculer)__ (accul\w+) aux? pieds? du mur @@0 <<- morph(\1, ">acculer ", False) ->> \1 # Pléonasme. -__[i]/pleo(pleo_achever)__ (ach[eè]v\w+) (?:absolument|entièrement|compl[èé]tement|totalement) @@0 <<- morph(\1, ">achever ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_abolir)__ (abol\w+) (?:absolument|entièrement|compl[èé]tement|totalement) @@0 <<- morph(\1, ">abolir/", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_acculer)__ (accul\w+) aux? pieds? du mur @@0 <<- morph(\1, ">acculer/", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_achever)__ (ach[eè]v\w+) (?:absolument|entièrement|compl[èé]tement|totalement) @@0 <<- morph(\1, ">achever/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_en_cours)__ actuellement en cours <<- not after(r" +de?\b") ->> en cours # Pléonasme. __[i]/pleo(pleo_en_train_de)__ (actuellement en train) d(?:e(?! nuit)|’{w_2}) @@0 <<- -1>> en train # Pléonasme. __[i]/pleo(pleo_ajouter)__ (ajout\w+) en plus @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_apanage)__ (apanages?) exclusifs? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_applaudir)__ (applaudi\w+) des deux mains @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_aujourd_hui)__ au jour d’aujourd’hui <<- ->> aujourd’hui # Pléonasme. -__[i]/pleo(pleo_avancer)__ (avan[cç]\w+) en avant @@0 <<- morph(\1, ">avancer ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_avancer)__ (avan[cç]\w+) en avant @@0 <<- morph(\1, ">avancer/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_s_avérer)__ s’av([éè]r\w+) vrai(e?s?) @@4,$ <<- ->> s’av\1 exact\2 # Pléonasme. __[i]/pleo(pleo_avéré)__ (avérée?s?) vraie?s? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_avenir)__ avenir devant (?:lui|[mts]oi|eux|[nv]ous) <<- morph(word(-1), ":A|>un", False) ->> avenir # Pléonasme. __[i]/pleo(pleo_bourrasque)__ (bourrasques?) de vent @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_car_en_effet)__ car en effet <<- ->> car|en effet # Pléonasme. __[i]/pleo(pleo_cirrhose)__ (cirrhoses?) du foie @@0 <<- ->> \1 # Pléonasme. -__[i]/pleo(pleo_collaborer)__ (collabor\w+) ensemble @@0 <<- morph(\1, ">collaborer ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_collaborer)__ (collabor\w+) ensemble @@0 <<- morph(\1, ">collaborer/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_comme_par_exemple)__ comme par exemple <<- ->> comme|par exemple # Pléonasme. -__[i]/pleo(pleo_comparer)__ (compar\w+) entre (?:eux|elles) @@0 <<- morph(\1, ">comparer ", False) ->> \1 # Pléonasme. -__[i]/pleo(pleo_contraindre)__ (contrai\w+) malgré (?:soi|eux|lui|moi|elle|toi) @@0 <<- morph(\1, ">contraindre ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_comparer)__ (compar\w+) entre (?:eux|elles) @@0 <<- morph(\1, ">comparer/", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_contraindre)__ (contrai\w+) malgré (?:soi|eux|lui|moi|elle|toi) @@0 <<- morph(\1, ">contraindre/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_descendre)__ (descend\w+) en bas(?! de) @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_dessiner)__ (dessin\w+) un dessin @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_dorénavant)__ à (?:partir|compter) de dorénavant <<- ->> dorénavant|à partir de maintenant # Pléonasme. __[i]/pleo(pleo_donc_par_conséquent)__ donc par conséquent <<- ->> donc|par conséquent|c’est pourquoi # Pléonasme. -__[i]/pleo(pleo_enchevêtrer)__ (enchevêtr\w+) les uns dans les autres @@0 <<- morph(\1, ">enchevêtrer ", False) ->> \1 # Pléonasme. -__[i]/pleo(pleo_entraider)__ (entraid\w+) (?:mutuellement|les uns les autres) @@0 <<- morph(\1, ">entraider ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_enchevêtrer)__ (enchevêtr\w+) les uns dans les autres @@0 <<- morph(\1, ">enchevêtrer/", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_entraider)__ (entraid\w+) (?:mutuellement|les uns les autres) @@0 <<- morph(\1, ">entraider/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_entraide)__ (entraides?) mutuelles? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_erreur)__ (erreurs?) involontaires? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_étape)__ (étapes?) intermédiaires? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_hasard)__ (hasards?) imprévus? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_hémorragie)__ (hémorragies?) de sang @@0 <<- ->> \1 # Pléonasme. -__[i]/pleo(pleo_joindre)__ (join\w+) ensemble @@0 <<- morph(\1, ">joindre ") ->> \1|mettre ensemble # Pléonasme. +__[i]/pleo(pleo_joindre)__ (join\w+) ensemble @@0 <<- morph(\1, ">joindre/") ->> \1|mettre ensemble # Pléonasme. __[i]/pleo(pleo_lever)__ lever debout <<- ->> lever # Pléonasme. __[i]/pleo(pleo_mais_qqch)__ mais (cependant|pourtant|toutefois) @@5 <<- ->> mais|cependant|pourtant|toutefois # Pléonasme. __[i]/pleo(pleo_marche)__ (marches?) à pieds? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_méandre)__ (méandres?) sinueux @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_media)__ (m[eé]dias?) d’informations? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_monopole)__ (monopoles?) exclusifs? @@0 <<- ->> \1 # Pléonasme. -__[i]/pleo(pleo_monter)__ (mont\w+) en haut(?! d[eu’]) @@0 <<- morph(\1, ">monter ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_monter)__ (mont\w+) en haut(?! d[eu’]) @@0 <<- morph(\1, ">monter/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_opportunité)__ (opportunités?) à saisir @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_orage)__ (orages?) électriques? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_jumelles)__ paires? de jumelles? <<- ->> jumelles # Pléonasme. __[i]/pleo(pleo_panacée)__ (panacées?) universelles? @@0 <<- ->> \1|remède universel # Pléonasme. __[i]/pleo(pleo_perspective)__ (perspectives?) d’avenir @@0 <<- ->> \1 # Pléonasme. @@ -2978,19 +2981,19 @@ __[i]/pleo(pleo_balbutiement)__ premiers? (balbutiements?) @@$ <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_priorité)__ premières? (priorités?) @@$ <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_projet1)__ (projets?) futurs? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_projet2)__ futurs? (projets?) @@$ <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_prototype)__ (prototypes?) expérimenta(?:l|ux) @@0 <<- ->> \1 # Pléonasme. -__[i]/pleo(pleo_rénover)__ (rénov\w+) à neuf @@0 <<- morph(\1, ">rénov(?:er|ation) ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_rénover)__ (rénov\w+) à neuf @@0 <<- morph(\1, ">rénov(?:er|ation)/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_puis_qqch)__ puis (?:après|ensuite|alors) <<- ->> puis|après|ensuite|alors # Pléonasme. -__[i]/pleo(pleo_réunir)__ (réuni\w*) ensemble @@0 <<- morph(\1, ">réunir ", False) ->> \1 # Pléonasme. -__[i]/pleo(pleo_reculer)__ (recul\w*) en arrière @@0 <<- morph(\1, ">recul(?:er|) ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_réunir)__ (réuni\w*) ensemble @@0 <<- morph(\1, ">réunir/", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_reculer)__ (recul\w*) en arrière @@0 <<- morph(\1, ">recul(?:er|)/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_risque)__ (risques?) (?:potentiels?|de menaces?) @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_secousse)__ (secousses?) sé?ismiques? @@0 <<- ->> secousse tellurique|secousses telluriques|tremblement de terre # Pléonasme. __[i]/pleo(pleo_solidaire)__ (solidaires?) les uns des autres @@0 <<- ->> \1 # Pléonasme. -__[i]/pleo(pleo_suffire)__ (suffi\w+) simplement @@0 <<- morph(\1, ">suffire ", False) ->> \1 # Pléonasme. -__[i]/pleo(pleo_talonner)__ (talonn\w+) de près @@0 <<- morph(\1, ">talonner ", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_suffire)__ (suffi\w+) simplement @@0 <<- morph(\1, ">suffire/", False) ->> \1 # Pléonasme. +__[i]/pleo(pleo_talonner)__ (talonn\w+) de près @@0 <<- morph(\1, ">talonner/", False) ->> \1 # Pléonasme. __[i]/pleo(pleo_taux_alcoolémie)__ taux d’alcoolémies? @@7 <<- ->> taux d’alcool|alcoolémie # Pléonasme. L’alcoolémie est le taux d’alcool dans le sang. __[i]/pleo(pleo_tunnel)__ (tunnels?) souterrains? @@0 <<- ->> \1 # Pléonasme. __[i]/pleo(pleo_hardes)__ vieilles hardes <<- ->> hardes # Pléonasme. __[i]/pleo(pleo_voire_même)__ voire même <<- ->> voire|même # Pléonasme.|https://fr.wiktionary.org/wiki/voire_m%C3%AAme @@ -3061,11 +3064,11 @@ # d’avance / à l’avance __[i]/pleo(pleo_verbe_à_l_avance)__ ((?:pré[pvds]|pressen|pronostiqu|réserv|dev(?:an[cç]|in)|avert)\w+) (?:d’avance|à l’avance) @@0 - <<- morph(\1, ">(?:prévenir|prévoir|prédire|présager|préparer|pressentir|pronostiquer|avertir|devancer|deviner|réserver) ", False) + <<- morph(\1, ">(?:prévenir|prévoir|prédire|présager|préparer|pressentir|pronostiquer|avertir|devancer|deviner|réserver)/", False) ->> \1 # Pléonasme. TEST: {{prédire à l’avance}} ->> prédire TEST: {{pronostiquer d’avance}} ->> pronostiquer TEST: {{réserver d’avance}} ->> réserver @@ -3072,11 +3075,11 @@ # plus tard / à une date ultérieure __[i]/pleo(pleo_différer_ajourner_reporter)__ ((?:diff|ajourn|report)\w+) à (?:plus tard|date ultérieure|une date ultérieure) @@0 - <<- morph(\1, ">(?:ajourner|différer|reporter) ", False) + <<- morph(\1, ">(?:ajourner|différer|reporter)/", False) ->> \1 # Pléonasme. TEST: {{Ajourner à une date ultérieure}} ->> Ajourner TEST: {{différer à une date ultérieure}} ->> différer TEST: {{reporter à plus tard}} ->> reporter @@ -3282,19 +3285,19 @@ TEST: plus d’une sont parties aussi vite qu’elles étaient venues __[i]/conf(conf_il_on_pas_verbe)__ (?ou/") and morphex(word(-1), ":", ":3s", True) -1>> =suggSimil(\1, ":(?:3s|Oo)", False) # Incohérence : « \1 » devrait être un verbe, un pronom objet, un adverbe de négation, etc. TEST: il {{et}} parti. __[i]/conf(conf_ils_pas_verbe)__ (?ou/") and morphex(word(-1), ":", ":3p", True) -1>> =suggSimil(\1, ":(?:3p|Oo)", False) # Incohérence avec « ils » : « \1 » devrait être un verbe, un pronom objet, un adverbe de négation, etc. TEST: ils {{son}} du même bois. TEST: Ils {{étai}} partie au {{restaurent}} @@ -3340,11 +3343,11 @@ __[i]/conf(conf_très_verbe)__ très +(?!envie)({w_2}) @@$ <<- morphex(\1, ":(?:Y|[123][sp])", ":[AQW]") -1>> =suggSimil(\1, ":[AW]", True) # Incohérence avec « très » : « \1 » n’est ni un adjectif, ni un participe passé, ni un adverbe. - <<- morph(\1, ">jeûne ", False) -1>> =\1.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune + <<- morph(\1, ">jeûne/", False) -1>> =\1.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune TEST: Il est très {{cite}}. TEST: très {{suivit}} par ce détective TEST: il était très {{habille}} TEST: Très {{jeûne}}, elle a su qu’elle ne voulait pas d’une vie ordinaire. @@ -3394,11 +3397,11 @@ TEST: Ça ira mieux demain, surtout si émerge une demande forte de la part des consommateurs. __[i]/conf(conf_de_plus_en_plus_verbe)__ de plus en plus +({w_2}) @@$ - <<- morphex(\1, ":(?:[123][sp]|Y)", ":(?:[GAQW]|3p)") and not morph(word(-1), ":V[123].*:[123][sp]|>(?:pouvoir|vouloir|falloir) ", False, False) + <<- morphex(\1, ":(?:[123][sp]|Y)", ":(?:[GAQW]|3p)") and not morph(word(-1), ":V[123].*:[123][sp]|>(?:pouvoir|vouloir|falloir)/", False, False) -1>> =suggVerbPpas(@) # Incohérence avec « de plus en plus » : « \1 » n’est ni un adjectif, ni un participe passé, ni un adverbe. TEST: de plus en plus {{gagnait}} par la folie. TEST: de plus en plus {{concerner}} par ce problème @@ -3416,11 +3419,11 @@ __[i]/conf(conf_a_à_face_à)__ face (a) @@5 <<- not before(r"(?i)\b(?:[lmts]a|leur|une|en) +$") -1>> à # Confusion. __[i]/conf(conf_a_à_pas_à_pas)__ pas (a) pas @@4 <<- -1>> à # Confusion. __[i]/conf(conf_a_à_par_rapport)__ par rapport (a) ({w_2}) @@12,$ <<- morph(\2, ":(?:D|Oo|M)", False) -1>> à # Confusion. __[i]/conf(conf_a_à_être_à)__ ({etre}) (a)(?! priori| posteriori| fortiori) @@0,$ - <<- morph(\1, ">être :V") and not before(r"(?i)\bce que? ") -2>> à # Confusion. Utilisez la préposition « à ». + <<- morph(\1, ">être/:V") and not before(r"(?i)\bce que? ") -2>> à # Confusion. Utilisez la préposition « à ». __[i]/conf(conf_a_à_peu_près)__ (?:a peu[tx]? (?:près|prés?|prêts?)|à peu[tx] (?:près|prés?|prêts?)|à peu (?:prés?|prêts?)) <<- ->> à peu près # Confusion. <<- ~>> * __[i]/conf(conf_a_à_pronoms1)__ ne +l(?:es?|a) +(?:l(?:eur|ui) +|)(à) @@$ <<- -1>> a # Confusion : “à” est une préposition. Pour le verbe avoir, écrivez : @@ -3442,11 +3445,11 @@ __[i]/conf(conf_a_à_il_on_à)__ (?:il|on) +(?:l(?:es +|’)|en +|y +(?:en +|)|[vn]ous +|)(à) @@$ <<- not morph(word(-1), ":3s", False, False) -1>> a # Confusion probable : “à” est une préposition. Pour le verbe avoir, écrivez : __[i]/conf(conf_a_à_elle_à)__ elle +(?:l(?:es +|’)|en +|y +(?:en |)|[vn]ous +|)(à) @@$ - <<- not morph(word(-1), ":(?:3s|R)", False, False) and not morph(word(1), ":Oo|>qui ", False, False) + <<- not morph(word(-1), ":(?:3s|R)", False, False) and not morph(word(1), ":Oo|>qui/", False, False) -1>> a # Confusion probable : “à” est une préposition. Pour le verbe avoir, écrivez : __[i]/conf(conf_a_à_qui_pronom_à)__ qui (?:l(?:ui|eur)(?: en|)|nous|vous|en|y) +(à) @@$ <<- -1>> a # Confusion : “à” est une préposition. Pour le verbe avoir, écrivez : __[i]/conf(conf_a_à_qui_a)__ qui (à) +({w_2}) @@4,$ <<- morphex(\2, ":Q", ":M[12P]") -1>> a # Confusion : “à” est une préposition. Pour le verbe avoir, écrivez : @@ -3580,11 +3583,11 @@ TEST: Il y a qui au dîner ce soir ? __[i]/conf(conf_mener_à_bien)__ (m[eèé]n\w+) (a) bien @@0,w - <<- morph(\1, ">mener ", False) and ( not before(r"\bque? ") or morph(word(-1), ">(?:falloir|aller|pouvoir) ", False, True) ) + <<- morph(\1, ">mener/", False) and ( not before(r"\bque? ") or morph(word(-1), ">(?:falloir|aller|pouvoir)/", False, True) ) -2>> à # Confusion probable. Dans cette locution, utilisez la préposition « à ».|https://fr.wiktionary.org/wiki/mener_%C3%A0_bien <<- __also__ ~>> \1 TEST: Mener {{a}} bien cette guerre sera plus difficile qu’on le pense. TEST: Je peux mener {{a}} bien cette opération. @@ -3591,11 +3594,11 @@ TEST: Cette coalition que tu penses mener a bien l’intention de te trahir. __[i]/conf(conf_mettre_à_profit)__ (m(?:i[st]|ett)\w*).* (a) profit @@0,w - <<- morph(\1, ">mettre ", False) -2>> à # Confusion probable. Dans « mettre à profit », utilisez la préposition « à ».|https://fr.wiktionary.org/wiki/mettre_%C3%A0_profit + <<- morph(\1, ">mettre/", False) -2>> à # Confusion probable. Dans « mettre à profit », utilisez la préposition « à ».|https://fr.wiktionary.org/wiki/mettre_%C3%A0_profit TEST: Mettre {{a}} profit ses compétences TEST: Il a mis son talent {{a}} profit. @@ -3640,11 +3643,11 @@ # ça / çà / sa __[i]/conf(conf_ça_sa)__ (ça) ({w_2}) @@0,3 <<- morph(\2, ":[NAQ].*:f") and not re.search("^seule?s?", \2) -1>> sa # Confusion : “sa” (sa maison, sa passion) ≠ “ça” (ça vient, ça heurte). __[i]/conf(conf_sa_ça1)__ (sa) +({w_2}) @@0,$ - <<- morphex(\2, ":G", ">(?:tr(?:ès|op)|peu|bien|plus|moins|toute) |:[NAQ].*:f") -1>> ça # Confusion : “sa” (sa maison, sa passion) ≠ “ça” (ça vient, ça heurte). + <<- morphex(\2, ":G", ">(?:tr(?:ès|op)|peu|bien|plus|moins|toute)/|:[NAQ].*:f") -1>> ça # Confusion : “sa” (sa maison, sa passion) ≠ “ça” (ça vient, ça heurte). __[i>/conf(conf_sa_ça2)__ (sa) +(?:[dnmtsjl]’|lorsqu |qu |puisqu ) @@0 <<- -1>> ça # Confusion : “sa” (sa maison, sa passion) ≠ “ça” (ça vient, ça heurte). __[i]/conf(conf_çà_ça)__ çà(?! et là) <<- not before(r"\b(?:[oO]h|[aA]h) +$") ->> ça # Confusion : « çà » ne s’emploie plus guère que dans l’expression « çà et là ». __[i]/conf(conf_çà_et_là)__ ça et là <<- not morph(word(-1), ":R") ->> çà et là # Confusion : « ça » équivaut à « cela ». Dans l’expression « çà et là », « çà » équivaut à « ici ». __[s]/conf(conf_sa_fin)__ (sa) *$ @@0 <<- -1>> ça # Confusion probable : “sa” est un déterminant féminin singulier. Pour l’équivalent de “cela” ou “ceci”, écrivez : @@ -3663,11 +3666,11 @@ # ce / se / ceux __[s]/conf(conf_se_verbe)__ ([cC]e) ({w_2}) @@0,3 <<- \2[0].islower() and \2 != "faire" - and ( morphex(\2, ":V[123].*:(?:Y|[123][sp])", ":[NAGM]|>(?:devoir|pouvoir|sembler) ") or re.search("-(?:ils?|elles?|on)$", \2) ) + and ( morphex(\2, ":V[123].*:(?:Y|[123][sp])", ":[NAGM]|>(?:devoir|pouvoir|sembler)/") or re.search("-(?:ils?|elles?|on)$", \2) ) -1>> se # Confusion : « \2 » est un verbe. Exemples : ce bâtiment, se perdre. __[i]/conf(conf_pour_ce_faire)__ pour (se) faire,? ({w_2}) @@5,$ <<- (\0.find(",") >= 0 or morphex(\2, ":G", ":[AYD]")) -1>> ce # Confusion probable. Dans cette locution, il faut employer “ce”.|http://fr.wiktionary.org/wiki/pour_ce_faire @@ -3684,21 +3687,21 @@ __[i]/conf(conf_ceux_ce_être)__ (ceux) (?:ne |)(?:sont|serai(?:en|)[ts]?|f[uû](?:ren|)t|n’(?!ayant|étant)\w+) @@0 <<- -1>> ce # Confusion.|http://www.intellego.fr/soutien-scolaire-6eme/aide-scolaire-francais/ce-ceux-ou-se/3829 __[s]/conf(conf_ce_ne_être_doit)__ ([sS]e) n(?:e |’)({être}|d[eouû]\w+|p[oeuû]\w+) @@0,$ - <<- morph(\2, ">(?:être|pouvoir|devoir) .*:3s", False) + <<- morph(\2, ">(?:être|pouvoir|devoir)/.*:3s", False) -1>> ce # Confusion probable.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=2440 __[i]/conf(conf_ce_ne)__ (ceux) ne ({w_2}) @@0,$ <<- morphex(\2, ":[123]s", ":P") -1>> ce # Confusion.|http://www.intellego.fr/soutien-scolaire-6eme/aide-scolaire-francais/ce-ceux-ou-se/3829 __[i]/conf(conf_ce_nom1)__ (se) ({w1}) @@0,3 - <<- morphex(\2, ":[NAQ]", ":([123][sp]|Y|P|Q)|>l[ea]? ") -1>> ce # Confusion. Ce chien, ce chat… Se demander, se croire… + <<- morphex(\2, ":[NAQ]", ":([123][sp]|Y|P|Q)|>l[ea]?/") -1>> ce # Confusion. Ce chien, ce chat… Se demander, se croire… __[i]/conf(conf_ce_nom2)__ (ceux) (?!l[aà] |qu[ie]? )({w_2}) @@0,$ - <<- morphex(\2, ":N.*:s", ":(?:A.*:[pi]|P|R)|>autour ") -1>> ce # Confusion probable.|http://www.intellego.fr/soutien-scolaire-6eme/aide-scolaire-francais/ce-ceux-ou-se/3829 + <<- morphex(\2, ":N.*:s", ":(?:A.*:[pi]|P|R)|>autour/") -1>> ce # Confusion probable.|http://www.intellego.fr/soutien-scolaire-6eme/aide-scolaire-francais/ce-ceux-ou-se/3829 TEST: il ne {{ce}} compte pas parmi eux TEST: il ne {{ç’}}avançait jamais sans avoir pesé toutes les conséquences TEST: {{Se}} seraient des histoires. TEST: {{se}} seraient des jours heureux. @@ -3743,11 +3746,11 @@ __[s]/conf(conf_c_est3)__ ([scSC]es) (?:qu(?:lle|el?|)|comme|ce(?:t|tte|)|[nv]os|les?|eux|elles) @@0 <<- -1>> c’est # Confusion probable. Écrivez « c’est » pour dire « ceci est… ». __[s]/conf(conf_c_est4)__ ([scSC]es) ({w_1}) ({w_1}) @@0,w,$ - <<- morph(\2, ":[WX]", ":N:.*:[pi]") and morph(\3, ":[RD]|>pire ", False) -1>> c’est # Confusion probable. Écrivez « c’est » pour dire « ceci est… ». + <<- morph(\2, ":[WX]", ":N:.*:[pi]") and morph(\3, ":[RD]|>pire/", False) -1>> c’est # Confusion probable. Écrivez « c’est » pour dire « ceci est… ». __[i]/conf(conf_ces_ses)__ (c’est) ({w_2}) @@0,6 <<- morphex(\2, ":N.*:p", ":(?:G|W|M|A.*:[si])") -1>> ces|ses # Confusion. Exemples : c’est facile ; ces chats (désignation) ; ses chats (possession)… TEST: {{ses}} au-dessus de ses forces. TEST: {{ces}} comme la peste @@ -3782,11 +3785,11 @@ __[i]/conf(règlement_de_comptes)__ r[éè]glements? de (co[mn]tes?) @@$ <<- -1>> comptes # Confusion.|https://fr.wiktionary.org/wiki/r%C3%A8glement_de_comptes __[i]/conf(régler_son_compte)__ (r[éè]gl\w+) +(?:[mts]on|leurs?|[vn]otre) (co[mn]tes?) @@0,$ - <<- morph(\1, ">régler ", False) -2>> compte # Confusion. Un conte est un récit fictif, “comte” est un titre de noblesse. Pour un état chiffré, un calcul… écrivez :|https://fr.wiktionary.org/wiki/r%C3%A9gler_son_compte + <<- morph(\1, ">régler/", False) -2>> compte # Confusion. Un conte est un récit fictif, “comte” est un titre de noblesse. Pour un état chiffré, un calcul… écrivez :|https://fr.wiktionary.org/wiki/r%C3%A9gler_son_compte __[i]/conf(conf_tout_compte_fait)__ tout (co[mn]te) fait @@w <<- -1>> compte # Confusion. Locution “tout compte fait”.|https://fr.wiktionary.org/wiki/tout_compte_fait TEST: il s’en est tiré à bon {{conte}}. @@ -3847,15 +3850,15 @@ __[i]/conf(conf_être_davantage_ppas)__ ({etre}) (d’avantages?) ({w_2}) @@0,w,$ <<- morph(\1, ":V0e", False) and morphex(\3, ":[NAQ]", ":G") -2>> davantage # Confusion possible : “davantage” signifie “plus” ; un “avantage” signifie “faveur”, “bénéfice”, “profit”… __[i]/conf(conf_davantage1)__ ({w1}) (d’avantages?) @@0,$ - <<- morphex(\1, ":V", ":Q|>(?:profiter|bénéficier|nombre) ") and not morph(word(1), ">(?:financi[eè]re?|pécuni(?:er|aire)|sociaux)s? ", False, False) + <<- morphex(\1, ":V", ":Q|>(?:profiter|bénéficier|nombre)/") and not morph(word(1), ">(?:financi[eè]re?|pécuni(?:er|aire)|sociaux)s?/", False, False) -2>> davantage # Confusion probable : “davantage” signifie “plus” ; un “avantage” signifie “faveur”, “bénéfice”, “profit”… __[i]/conf(conf_davantage2)__ ({w_1})-(?:je|tu|ils?|elles?|[nv]ous|on) +(d’avantages?) @@0,$ - <<- not morph(\1, ">(?:profiter|bénéficier) ", False) and not morph(word(1), ">(?:financi[eè]re?|pécuni(?:er|aire)|sociaux)s? ", False, False) + <<- not morph(\1, ">(?:profiter|bénéficier)/", False) and not morph(word(1), ">(?:financi[eè]re?|pécuni(?:er|aire)|sociaux)s?/", False, False) -2>> davantage # Confusion probable : “davantage” signifie “plus” ; un “avantage” signifie “faveur”, “bénéfice”, “profit”… __[i>/conf(conf_davantage3)__ (d’avantages?) d(?:e +|’) @@0 <<- -1>> davantage # Confusion possible : “davantage” signifie “plus” ; un “avantage” signifie “faveur”, “bénéfice”, “profit”… @@ -3932,11 +3935,11 @@ # faut / faux __[i]/conf(conf_faux)__ faut - <<- not morph(word(-1), ">(?:ils?|ne|en|y|leur|lui|nous|vous|[mtsl]e|la|les) ", False, True) and morphex(word(1), ":", ":(?:Y|Oo|X|M)", True) + <<- not morph(word(-1), ">(?:ils?|ne|en|y|leur|lui|nous|vous|[mtsl]e|la|les)/", False, True) and morphex(word(1), ":", ":(?:Y|Oo|X|M)", True) ->> faux # Confusion probable : “faut” est une conjugaison de “falloir”. Pour indiquer la fausseté d’une chose, écrivez : TEST: un homme {{faut}} TEST: c’est {{faut}} TEST: il m’en faut plus. @@ -3967,15 +3970,15 @@ __[i]/conf(conf_flanc)__ (flans?) (?:des? (?:la |)(?:colline|montagne)s?|gauches?|droites?|nord|sud|ouest) @@0 <<- -1>> =\0.replace("an", "anc").replace("AN", "ANC") # Confusion probable. Le flan est une pâtisserie.|https://fr.wiktionary.org/wiki/flanc __[i]/conf(conf_sur_le_flanc)__ ((?:attaqu|allong|bless|couch|étend|touch)\w+) +sur (?:les?|[mts](?:on|es)|[nv]o(?:tre|s)) (flans?) @@0,$ - <<- morph(\1, ">(?:attaquer|allonger|blesser|coucher|étendre|toucher) ", False) + <<- morph(\1, ">(?:attaquer|allonger|blesser|coucher|étendre|toucher)/", False) -2>> =\0.replace("an", "anc").replace("AN", "ANC") # Confusion probable. Le flan est une pâtisserie.|https://fr.wiktionary.org/wiki/flanc __[i]/conf(conf_tirer_au_flanc)__ (tir\w*)[ -]+aux?[ -](flans?) @@0,$ - <<- morph(\1, ">tir(?:er|) ", False) -2>> =\0.replace("an", "anc").replace("AN", "ANC") # Confusion. Le flan est une pâtisserie.|https://fr.wiktionary.org/wiki/flanc + <<- morph(\1, ">tir(?:er|)/", False) -2>> =\0.replace("an", "anc").replace("AN", "ANC") # Confusion. Le flan est une pâtisserie.|https://fr.wiktionary.org/wiki/flanc TEST: attaqué sur son {{flan}} droit TEST: elle possède une maison à {{flan}} de colline. TEST: étendu sur son {{flan}}. TEST: Ce sale tir-au-{{flan}} le paiera cher. @@ -4030,11 +4033,11 @@ # la / là __[s]/conf(conf_la_là)__ ([lL]a) (?:a(?:fin|lors|près|uprès|ux?|vant|vec)|au(?:-de(?:dans|hors|là|sso?us|vant)|x|)|c(?:e(?:t|te|s|)|ar|hez|omme)|ça|d(?:ans|evant|es?|ès|onc|urant|’{w_1})|e(?:lles?|n|t)|ils?|je?|l(?:es?|a|orsque?|’{w_1})|m(?:algré|es|on|a|e)|n(?:e|ous)|o[uùn]|par(?:ce|fois|mi|)|p(?:arce|endant|our|uisque)|qu(?:e?|and)|s(?:on|a|es?|ouvent|ur)|t(?:andis|on|a|es?|u)|un|vous) @@0 - <<- not morph(word(-1), ":E|>le ", False, False) + <<- not morph(word(-1), ":E|>le/", False, False) -1>> là # Confusion probable. Écrivez “là” si vous voulez dire “ici”. TEST: nous serions encore {{la}} l’année prochaine TEST: en reprenant le chandail de John {{la}} où elle l’avait abandonné. TEST: Qui serait la ou le plus à même à occuper ce poste selon vous ? @@ -4082,11 +4085,11 @@ # loin s’en faut __[i]/conf(conf_loin_s_en_faut)__ loins? +(?:[sc]ens|san[gs]?s?|s[’ ]en) +fau[xt] - <<- not re.search("(?i)loin s’en faut", \0) and morph(word(-1), ":N", ">(?:aller|venir|partir) ", True) + <<- not re.search("(?i)loin s’en faut", \0) and morph(word(-1), ":N", ">(?:aller|venir|partir)/", True) ->> loin s’en faut # Confusion probable. Cette locution s’écrit :|https://fr.wiktionary.org/wiki/loin_s%E2%80%99en_faut TEST: Ils n’étaient guère prêts à ça, {{loins sans faux}}. TEST: Et les intellectuels ? En France comme ailleurs, tous n’ont pas, loin s’en faut, une pleine lucidité sur cette précarité galopante. @@ -4216,11 +4219,11 @@ # par-dessus / pardessus __[i]/conf(conf_par_dessus)__ (pardessus) +({w1}) @@0,$ - <<- morph(\2, ":D|>bord ", False) and not morph(word(-1), ":D.*:[me]|>(?:grande|petite) ", False, False) + <<- morph(\2, ":D|>bord/", False) and not morph(word(-1), ":D.*:[me]|>(?:grande|petite)/", False, False) -1>> par-dessus # Confusion probable. Un pardessus est un vêtement. Pour la préposition, écrivez : TEST: {{Pardessus}} les montagnes. TEST: Il passa {{pardessus}} les collines. TEST: Mets ton pardessus ce matin. @@ -4235,14 +4238,14 @@ # prêt / près / pré __[i]/conf(conf_prêt_à)__ (près) à ({w_2}) @@0,$ - <<- not before("(?i)(?:peu|de|au plus) $") and morph(\2, ":Y|>(?:tout|les?|la) ") -1>> prêt|prêts # Confusion. Être près de (faire) quelque chose. Prêt à faire quelque chose. + <<- not before("(?i)(?:peu|de|au plus) $") and morph(\2, ":Y|>(?:tout|les?|la)/") -1>> prêt|prêts # Confusion. Être près de (faire) quelque chose. Prêt à faire quelque chose. __[i]/conf(conf_près_de)__ (prêts?) d(?:e +|’)({w_1}) @@0,$ - <<- morph(\2, ":(?:Y|M[12P])|>(?:en|y|les?) ", False) -1>> près # Confusion. Être près de (faire) quelque chose. Prêt à faire quelque chose. + <<- morph(\2, ":(?:Y|M[12P])|>(?:en|y|les?)/", False) -1>> près # Confusion. Être près de (faire) quelque chose. Prêt à faire quelque chose. __[i]/conf(conf_près)__ de(?: plus|puis) (prêts?) @@$ <<- -1>> près # Confusion. Être prêt(e) à faire quelque chose. Être près de quelque chose. __[i]/conf(conf_très_près)__ très (pr(?:êt|é)s?) @@$ <<- -1>> près # Confusion probable. Pour évoquer la proximité, utilisez : TEST: ils se sont approchés très {{prêts}}. TEST: Je suis si {{prêt}} d’y arriver. @@ -4251,11 +4254,11 @@ # quand / quant / qu’en __[i]/conf(conf_quant_à)__ (?(?:arriver|venir|à|revenir|partir|aller) ") + <<- not morph(word(-1), ">(?:arriver|venir|à|revenir|partir|aller)/") and not(\0.endswith("à") and after("^ +[mts]on tour[, ]")) -1>> quant # Confusion probable. Quand = à quel moment. Quant à = à propos de. __[i]/conf(conf_quand1)__ quant(?! à| aux?| est[ -]il d(?:es?|u) ) <<- ->> quand # Confusion. Quand = à quel moment. Quant à = à propos de. __[i]/conf(conf_qu_en1)__ (quan[dt]) est[ -]il d(?:es?|u) @@0 <<- -1>> qu’en # Confusion. Ce qu’il en est de… → Qu’en est-il de… ? __[i]/conf(conf_qu_en2)__ (quan[dt]) ({w_2}ant) @@0,$ <<- morph(\2, ":P", False) -1>> qu’en # Confusion probable. __[i]/conf(conf_quand2)__ @@ -4292,12 +4295,12 @@ TEST: {{qu’elle}} emmerdeuse. __[i]/conf(conf_qu_elle_verbe)__ (quelles?) +({w_1}) @@0,$ - <<- \2.islower() and (morphex(\2, ":V|>(?:ne?|me?|te?|se?|[nv]ous|l(?:e|a|es|ui|leur|)|en|y) ", ":[NA].*:[fe]|>(?:plus|moins)") or \2 == "t" or \2 == "s") - and not (morph(\2, ">(?:pouvoir|devoir|en)", False) and morph(word(1), ":V0e", False)) >>> + <<- \2.islower() and (morphex(\2, ":V|>(?:ne?|me?|te?|se?|[nv]ous|l(?:e|a|es|ui|leur|)|en|y)/", ":[NA].*:[fe]|>(?:plus|moins)") or \2 == "t" or \2 == "s") + and not (morph(\2, ">(?:pouvoir|devoir|en)/", False) and morph(word(1), ":V0e", False)) >>> <<- \1.endswith("e") and not morph(\2, ":V0e", False) and not (morph(\2, ":V0a", False) and after("^ +été ")) -1>> qu’elle # Confusion. Le sujet “elle” doit être séparée de la conjonction “que”. 1 <<- __else__ and \1.endswith("s") and not morph(\2, ":V0e", False) and not (morph(\2, ":V0a", False) and after("^ +été ")) -1>> qu’elles # Confusion. Le sujet “elles” doit être séparée de la conjonction “que”. 2 <<- __else__ and morph(\2, ":V0e", False) and morphex(word(1), ":[QA]", ":G", False) >>> @@ -4356,12 +4359,12 @@ __[i]/conf(conf_me_te_se_son)!6__ [mts]e (son) @@3 <<- -1>> sont # Confusion : “son” est un déterminant ou un nom masculin. Le verbe “être” à la 3ᵉ personne du pluriel s’écrit “sont”. __[i]/conf(conf_son_qqch)__ (sont) ({w_2}) @@0,$ - <<- morphex(\2, ":[NA].*:[me]:s|>[aeéiîou].* :[NA].*:f:s", ":[GW]") - and morphex(word(-1), ":V|>(?:à|avec|chez|dès|contre|devant|derrière|en|par|pour|sans|sur) ", ":[NA].*:[pi]|>(?:ils|elles|vous|nous|leur|lui|[nmts]e) ", True) + <<- morphex(\2, ":[NA].*:[me]:s|>[aeéiîou].*/:[NA].*:f:s", ":[GW]") + and morphex(word(-1), ":V|>(?:à|avec|chez|dès|contre|devant|derrière|en|par|pour|sans|sur)/", ":[NA].*:[pi]|>(?:ils|elles|vous|nous|leur|lui|[nmts]e)/", True) and not before(r"(?i)\bce que? |[mts]’en +$") -1>> son # Confusion : “sont” est le verbe “être” à la 3ᵉ personne du pluriel. Pour le déterminant, écrivez “son”. __[i]/conf(conf_qui_sont_les)__ (?:qu[ie]|comment|pourquoi) +(son) @@$ <<- morph(word(1), ":[DR]", False, True) -1>> sont # Confusion probable : “son” est un déterminant ou un nom masculin. Le verbe “être” à la 3ᵉ personne du pluriel s’écrit “sont”. @@ -4393,10 +4396,38 @@ <<- morph(\2, ":M[12]", False) -1>> sûr # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur TEST: Je suis {{sur}} de Patrick. +__[i]/conf(conf_sûr_que)__ + (sure?s?) que? @@0 + <<- -1>> =\1.replace("sur", "sûr") + # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur +__[i]/conf(conf_sûre_surs_de)__ + (sur(?:es?|s)) de? @@0 + <<- -1>> =\1.replace("sur", "sûr") + # Confusion probable : “sur” un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur +__[i]/conf(conf_sûr_de)__ + (sur) d(?:e (?:m(?:oi|es?|on|a)|t(?:oi|es?|on|a)|vous|nous|l(?:ui|es?)|s(?:oi|es?|on|a)|ce(?:ci|la|s|tte|t|)|ça)|’(?:elles?|eux)) @@0 + <<- -1>> sûr + # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur +__[i]/conf(conf_sûr_de_vinfi)__ + (sur) de (?:l(?:a |’|es? |ui |eur )|)({infi}) @@0,$ + <<- morph(\2, ":Y", False) + -1>> =\1.replace("sur", "sûr") + # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur +__[i]/conf(conf_en_lieu_sûr)__ + en lieu (sur) @@8 + <<- -1>> sûr + # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur + +TEST: Je suis {{sure}} qu’il ne va pas tarder à venir +TEST: {{sures}} d’elles-mêmes, elles ne s’en laissent pas conter. +TEST: {{sur}} de toi et de moi, que peut-il nous arriver, sinon le meilleur. +TEST: Il est tellement {{sur}} de la trouver. +TEST: ils sont en lieu {{sur}} et introuvables. + # tant / temps (1re partie) __[i]/conf(conf_en_temps_de)__ en (tant?) de? @@3 <<- not after("^[ ’](?:lieux|endroits|places|mondes|villes|pays|régions|cités)") -1>> temps # Confusion. Écrivez « en temps de » si vous évoquez une période de temps. @@ -4437,11 +4468,11 @@ __[i]/conf(conf_sur_la_bonne_voie)__ sur la bonne (voix) @@$ <<- -1>> voie # Confusion.|http://fr.wiktionary.org/wiki/voix __[i]/conf(conf_en_voie_de)__ en (voix) d(?:e (?:développement|disparition|guérison|résorption)|’(?:acquisition|achèvement|extinction|obtention)) @@3 <<- -1>> voie # Confusion.|http://fr.wiktionary.org/wiki/voie __[i]/conf(conf_ouvrir_la_voix)__ - (ouv\w+) +la (voix) (?:à|aux?) @@0,w <<- morph(\1, ">ouvrir ", False) -2>> voie # Confusion.|http://fr.wiktionary.org/wiki/voie + (ouv\w+) +la (voix) (?:à|aux?) @@0,w <<- morph(\1, ">ouvrir/", False) -2>> voie # Confusion.|http://fr.wiktionary.org/wiki/voie __[i]/conf(conf_par_voie_de_conséquence)__ par (voix) de conséquence @@4 <<- -1>> voie # Confusion.|http://fr.wiktionary.org/wiki/voie __[i]/conf(conf_voie_adj)__ (voix) (?:abdominale|anale|biliaire|carrossable|communale|express|interdite|intramusculaire|intraveineuse|piétonne|principale|prioritaire|privée|publique|déserte|romaine|appienne|flaminienne|ferrée|ferroviaire|lactée|lacrymale|aérienne|maritime|fluviale|terrestre|navigable|détournée|déviée|buccale|digestive|urinaire|respiratoire|parallèle|administrative|diplomatique|gouvernementale|législative|hiérarchique|rectiligne|sinueuse|souterraine|urbaine)s? @@0 <<- -1>> voie # Confusion.|http://fr.wiktionary.org/wiki/voie @@ -4454,17 +4485,17 @@ # voire / voir __[i]/conf(conf_voir_voire)__ (voir) ({w_2}) @@0,$ <<- not re.search("^(?:grand|petit|rouge)$", \2) and morphex(\2, ":A", ":[NGM]") and not \2.istitle() - and not before(r"(?i)\b[ndmts](?:e |’(?:en |y ))(?:pas |jamais |) *$") and not morph(word(-1), ":O[os]|>(?:[ndmts]e|falloir|pouvoir|savoir|de) ", False) + and not before(r"(?i)\b[ndmts](?:e |’(?:en |y ))(?:pas |jamais |) *$") and not morph(word(-1), ":O[os]|>(?:[ndmts]e|falloir|pouvoir|savoir|de)/", False) -1>> voire # Confusion probable : “voir” est un verbe concernant la perception visuelle. Pour signifier “et même possiblement”, écrivez :|https://fr.wiktionary.org/wiki/voire __[i]/conf(conf_voire_voir)__ voire - <<- morph(word(-1), ":Cs|>(?:ni|et|sans|pour|falloir|[pv]ouvoir|aller) ", True, False) ->> voir + <<- morph(word(-1), ":Cs|>(?:ni|et|sans|pour|falloir|[pv]ouvoir|aller)/", True, False) ->> voir # Confusion probable : “voire” signifie “et même possiblement”. Pour le verbe, écrivez “voir”.|https://fr.wiktionary.org/wiki/voire TEST: Elles sont fatiguées, {{voir}} épuisées. TEST: Ce serait pour aider, ainsi que {{voire}} l’avancement du projet. TEST: Elles vont voir rouge en apprenant cet échec. @@ -4496,11 +4527,11 @@ (j’(?:en +|y +|))({w_1}) @@0,$ <<- morphex(\2, ":", ":(?:[123][sp]|O[onw])") -2>> =suggSimil(\2, ":1s", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe. __[i]/conf(conf_ne_qqch)__ (n(?:e +|’))({w_1}) @@0,$ - <<- morphex(\2, ":", ":(?:[123][sp]|Y|P|O[onw]|X)|>(?:[lmtsn]|surtout|guère|presque|même|tout|parfois|vraiment|réellement|justement) ") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) + <<- morphex(\2, ":", ":(?:[123][sp]|Y|P|O[onw]|X)|>(?:[lmtsn]|surtout|guère|presque|même|tout|parfois|vraiment|réellement|justement)/") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) -2>> =suggSimil(\2, ":(?:[123][sp]|Oo|Y)", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe ou un pronom personnel objet. __[i]/conf(conf_n_y_en_qqch)__ (n’(?:en|y)) ({w_1}) @@0,$ <<- morphex(\2, ":", ":(?:[123][sp]|Y|P|O[onw]|X)") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) -2>> =suggSimil(\2, ":(?:[123][sp]|Y)", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe. @@ -4509,27 +4540,27 @@ <<- morphex(\2, ":", ":(?:[123][sp]|Y|P|O[onw]|X)") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) -2>> =suggSimil(\2, ":(?:[123][sp]|Y)", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe. __[i]/conf(conf_me_te_se_qqch)__ ([mts]e +(?:les? |la |l’|))(?!voi(?:là|ci))({w_1}) @@0,$ <<- not re.search("(?i)^se que?", \0) - and morphex(\2, ":", ":(?:[123][sp]|Y|P|Oo)|>[lmts] ") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) + and morphex(\2, ":", ":(?:[123][sp]|Y|P|Oo)|>[lmts]/") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) -2>> =suggSimil(\2, ":(?:[123][sp]|Oo|Y)", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe ou un pronom personnel objet. __[i]/conf(conf_m_t_s_y_en_qqch)__ ([mts]’(?:en|y)) (?!voilà)({w_1}) @@0,$ - <<- morphex(\2, ":", ":(?:[123][sp]|Y|P|X|Oo)|rien ") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) + <<- morphex(\2, ":", ":(?:[123][sp]|Y|P|X|Oo)|>rien/") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) -2>> =suggSimil(\2, ":(?:[123][sp]|Y)", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe. __[i]/conf(conf_m_s_qqch)__ ([ms]’)({w_1}) @@0,2 - <<- morphex(\2, ":", ":(?:[123][sp]|Y|P)|>(?:en|y|ils?) ") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) + <<- morphex(\2, ":", ":(?:[123][sp]|Y|P)|>(?:en|y|ils?)/") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) -2>> =suggSimil(\2, ":(?:[123][sp]|Y)", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe. __[i]/conf(conf_t_qqch)__ (t’)({w_1}) @@0,2 - <<- morphex(\2, ":", ":(?:[123][sp]|Y|P)|>(?:en|y|ils?|elles?) ") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) + <<- morphex(\2, ":", ":(?:[123][sp]|Y|P)|>(?:en|y|ils?|elles?)/") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|ce)$", \2) -2>> =suggSimil(\2, ":(?:[123][sp]|Y)", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe. __[i]/conf(conf_c_ç_qqch)__ ([cç]’)({w_1}) @@0,2 - <<- morphex(\2, ":", ":[123][sp]|>(?:en|y|que?) ") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|dire)$", \2) + <<- morphex(\2, ":", ":[123][sp]|>(?:en|y|que?)/") and not re.search("(?i)-(?:ils?|elles?|[nv]ous|je|tu|on|dire)$", \2) -2>> =suggSimil(\2, ":3s", False) # Incohérence avec « \1 » : « \2 » devrait être un verbe. TEST: ne l’{{oubli}} pas TEST: elle ne la {{croix}} pas TEST: ils me les {{laissés}}. @@ -4554,53 +4585,162 @@ TEST: j’ai l’impression de ne même pas savoir ce qu’est un « juif français ». TEST: C’que j’comprends, c’est qu’il y a des limites à ce qu’on peut supporter. TEST: la tentation pour certains médias de ne tout simplement pas rémunérer notre travail si celui-ci n’est finalement pas publié. TEST: Ne parfois pas être celui qui sabote l’ambiance. + +#__[i](p_notre_père_qui_es_au_cieux)__ notre père (qui est? aux cieux) @@11 <<- ~1>> * + + +@@@@ +@@@@ +@@@@ +@@@@ +@@@@GRAPH: graphe1 +@@@@ +@@@@ +@@@@ +@@@@ + +__p_notre_père_qui_es_au_cieux__ + notre père qui [es|est] aux cieux + <<- ~4>> ! + <<- ~3:0>> _ + !! !! !!!! Formes verbales sans sujet !! !! + +__tag_sujets__ + [je|j’] + [moi|moi-même] qui + [moi|moi-même] [seul|seule] + <<- />> 1s + + tu + t’ @:2s + [toi|toi-même] ?,¿ qui + [toi|toi-même] [seul|seule] + <<- />> 2s + + nous + nous ?,¿ qui + nous-même + nous-mêmes + nous [seul|seuls|seules] + [et|ou] [moi|moi-même] + ni [moi|moi-même] + [moi|moi-même] et + <<- />> 1p + + vous + vous ?,¿ qui + vous-même + vous-mêmes + vous [seul|seule|seuls|seules] + [et|ou] [toi|toi-même] + ni [toi|toi-même] + [toi|toi-même] et + <<- />> 2p + ## Incohérences avec formes verbales 1sg et 2sg sans sujet -__[i](p_notre_père_qui_es_au_cieux)__ notre père (qui est? aux cieux) @@11 <<- ~1>> * - -__[i]/conj(conj_xxxai_sans_sujet)!3__ - \w*ai(?! je) - <<- ( morph(\0, ":1s") or ( before("> +$") and morph(\0, ":1s", False) ) ) and not (\0[0:1].isupper() and before0(r"\w")) - and not before(r"(?i)\b(?:j(?:e |[’'])|moi(?:,? qui| seul) )") - ->> =suggVerb(@, ":3s") # Incohérence. Ceci est un verbe à la 1ʳᵉ personne du singulier. Sujet (“je” ou “moi qui”) introuvable. -__[i]/conj(conj_xxxes_sans_sujet)!3__ - \w*es(?! tu) - <<- morphex(\0, ":2s", ":(?:E|G|W|M|J|[13][sp]|2p)") and not \0[0:1].isupper() and not isRealStart() - and ( not morph(\0, ":[NAQ]", False) or before("> +$") ) - and not before(r"(?i)\bt(?:u |[’']|oi,? qui |oi seul )") - ->> =suggVerb(@, ":3s") # Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable. -__[i]/conj(conj_xxxas_sans_sujet)!3__ - \w+as(?! tu) - <<- morphex(\0, ":2s", ":(?:G|W|M|J|[13][sp]|2p)") and not (\0[0:1].isupper() and before0(r"\w")) - and ( not morph(\0, ":[NAQ]", False) or before("> +$") ) - and not before(r"(?i)\bt(?:u |[’']|oi,? qui |oi seul )") - ->> =suggVerb(@, ":3s") # Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable. -__[i]/conj(conj_xxxxs_sans_sujet)!3__ - \w+[iudnrtpcï]s(?! (?:tu|je)) - <<- morphex(\0, ":[12]s", ":(?:E|G|W|M|J|3[sp]|2p|1p)") and not (\0[0:1].isupper() and before0(r"\w")) - and ( not morph(\0, ":[NAQ]", False) or before("> +$") or ( re.search("(?i)^étais$", \0) and not morph(word(-1), ":[DA].*:p", False, True) ) ) - and not before(r"(?i)\b(?:j(?:e |[’'])|moi(?:,? qui| seul) |t(?:u |[’']|oi,? qui |oi seul ))") - ->> =suggVerb(@, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. -__[i]/conj(conj_peux_veux_sans_sujet)!3__ - [pv]eux(?! (?:tu|je)) - <<- not (\0[0:1].isupper() and before0(r"\w")) and not before(r"(?i)\b(?:j(?:e |[’'])|moi(?:,? qui| seul) |t(?:u |[’']|oi,? qui |oi seul ))") - ->> =suggVerb(@, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. -__[i]/conj(conj_équivaux_prévaux_sans_sujet)!3__ - (?:équi|pré|)vaux(?! (?:tu|je)) - <<- not (\0[0:1].isupper() and before0(r"\w")) - and not (\0 == "vaux" and morph(word(-1), ":(?:R|D.*:p)", False, False)) - and not before(r"(?i)\b(?:j(?:e |[’'])|moi(?:,? qui| seul) |t(?:u |[’']|oi,? qui |oi seul ))") - ->> =suggVerb(@, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + +__conj_xxxai__sans_sujet!3__ + [se|s’] ?[en|y|le|la|l’|les]¿ (~ai$) + <<- morph(\1, ":1s", ":(?:G|W|M|J|3[sp])") + -1>> =suggVerb(\1, ":3s") # Incohérence. Ceci est un verbe à la 1ʳᵉ personne du singulier. Sujet (“je” ou “moi qui”) introuvable. + + [ne|n’] ?[le|la|l’|les|en|me|m’|te|t’|nous|vous|lui|leur|y]¿ (~ai$) ~¬[jJ]e + <<- morph(\1, ":1s", ":(?:E|G|W|M|J|3[sp])") and not tag_before(\1, "1s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Ceci est un verbe à la 1ʳᵉ personne du singulier. Sujet (“je” ou “moi qui”) introuvable. + + [me|m’|te|t’|nous|vous] ?[le|la|l’|les|en|y]¿ (~ai$) ~¬[jJ]e + [le|la|l’|les] [lui|leur|en|y] (~ai$) ~¬[jJ]e + [lui|leur] en (~ai$) ~¬[jJ]e + <<- morph(\1, ":1s", ":(?:E|G|W|M|J|3[sp])") and not tag_before(\1, "1s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Ceci est un verbe à la 1ʳᵉ personne du singulier. Sujet (“je” ou “moi qui”) introuvable. + + ~ai$ ~¬[jJ]e + <<- morph(\1, ":1s", ":(?:E|G|W|M|J|3[sp]|N|A|Q)") and not (\1.istitle() and before0(r"\w")) and not tag_before(\1, "1s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Ceci est un verbe à la 1ʳᵉ personne du singulier. Sujet (“je” ou “moi qui”) introuvable. + + +__conj_xxxas_xxxes__sans_sujet!3__ + [se|s’] ?[en|y|le|la|l’|les]¿ (~[ae]s$) + <<- morph(\1, ":2s", ":(?:G|W|M|J|3[sp])") + -1>> =suggVerb(\1, ":3s") # Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable. + + [ne|n’] ?[le|la|l’|les|en|me|m’|te|t’|nous|vous|lui|leur|y]¿ (~[ae]s$) ~¬[tT]u + <<- morph(\1, ":2s", ":(?:E|G|W|M|J|3[sp])") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable. + + [me|m’|te|t’|nous|vous] ?[le|la|l’|les|en|y]¿ (~[ae]s$) ~¬[tT]u + [le|la|l’|les] [lui|leur|en|y] (~[ae]s$) ~¬[tT]u + [lui|leur] en (~[ae]s$) ~¬[tT]u + <<- morph(\1, ":2s", ":(?:E|G|W|M|J|3[sp])") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable. + + ~[ae]s$ ~¬[tT]u + <<- morph(\1, ":2s", ":(?:E|G|W|M|J|3[sp]|N|A|Q)") and not (\1.istitle() and before0(r"\w")) and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Ceci est un verbe à la 2ᵉ personne du singulier. Sujet (“tu” ou “toi qui”) introuvable. + + +__conj_xxxxxs_sans_sujet!3__ + [se|s’] ?[en|y|le|la|l’|les]¿ (~[iudnrtpcï]s$) + <<- morph(\1, ":[12]s", ":(?:G|W|M|J|3[sp]|2p|1p)") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + [ne|n’] ?[le|la|l’|les|en|me|m’|te|t’|nous|vous|lui|leur|y]¿ (~[iudnrtpcï]s$) ~¬(?:[tT]u|[jJ]e) + <<- morph(\1, ":[12]s", ":(?:E|G|W|M|J|3[sp]|2p|1p)") + and not tag_before(\1, "1s") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + [me|m’|te|t’|nous|vous] ?[le|la|l’|les|en|y]¿ (~[iudnrtpcï]s$) ~¬(?:[tT]u|[jJ]e) + [le|la|l’|les] [lui|leur|en|y] (~[iudnrtpcï]s$) ~¬(?:[tT]u|[jJ]e) + [lui|leur] en (~[iudnrtpcï]s$) ~¬(?:[tT]u|[jJ]e) + <<- morph(\1, ":[12]s", ":(?:E|G|W|M|J|3[sp]|2p|1p)") + and not tag_before(\1, "1s") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + étais ~¬(?:[tT]u|[jJ]e) + <<- not (\1.istitle() and before0(r"\w")) and not morph(<1, ":[DA].*:p") + and not tag_before(\1, "1s") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + ~[iudnrtpcï]s$ ~¬(?:[tT]u|[jJ]e) + <<- morph(\1, ":[12]s", ":(?:E|G|W|M|J|3[sp]|2p|1p|V0e|N|A|Q)") and not (\1.istitle() and before0(r"\w")) + and not tag_before(\1, "1s") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + +__conj_peux_veux_vaux_équivaux_prévaux_sans_sujet!3__ + [se|s’] ?[en|y|le|la|l’|les]¿ ([peux|veux|vaux|équivaux|prévaux]) + <<- /conj/ -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + [ne|n’] ?[le|la|l’|les|en|me|m’|te|t’|nous|vous|lui|leur|y]¿ ([peux|veux|vaux|équivaux|prévaux]) ~¬(?:[tT]u|[jJ]e) + <<- not tag_before(\1, "1s") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + [me|m’|te|t’|nous|vous] ?[le|la|l’|les|en|y]¿ ([peux|veux|vaux|équivaux|prévaux]) ~¬(?:[tT]u|[jJ]e) + [le|la|l’|les] [lui|leur|en|y] ([peux|veux|vaux|équivaux|prévaux]) ~¬(?:[tT]u|[jJ]e) + [lui|leur] en ([peux|veux|vaux|équivaux|prévaux]) ~¬(?:[tT]u|[jJ]e) + <<- not tag_before(\1, "1s") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + vaux ~¬(?:[tT]u|[jJ]e) + <<- /conj/ not (\1.istitle() and before0(r"\w")) and not tag_before(\1, "1s") and not tag_before(\1, "2s") + and not morph(<1, ":(?:R|D.*:p)") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + + [peux|veux|équivaux|prévaux] ~¬(?:[tT]u|[jJ]e) + <<- /conj/ not (\1.istitle() and before0(r"\w")) and not tag_before(\1, "1s") and not tag_before(\1, "2s") + -1>> =suggVerb(\1, ":3s") # Incohérence. Le sujet de cette forme verbale est introuvable. + TEST: Caroline, quand l’heure viendra, {{décideras}} de la conduite à tenir. TEST: ceux-là, dans tous les cas de figure et dans tous les coups ratés, {{comprenais}} mal pourquoi on leur en voulait. TEST: Lui, quand il y pensait, en {{arrivai}} à chaque fois à la même conclusion. TEST: Elle, ici et dans tous les cas de figure, {{veux}} toujours en faire plus. @@ -4611,20 +4751,17 @@ TEST: ces gens qui vont par monts et par vaux. TEST: pour ne justement pas donner l’impression de s’être trompé. ## Incohérences avec formes verbales 1pl et 2pl sans sujet -__[i]/conj(conj_xxxons_sans_sujet)!3__ - \w+(?:ons|[âîûn]mes)(?! nous) - <<- morphex(\0, ":V.*:1p", ":[EGMNAJ]") and not (\0[0:1].isupper() and before(r"\w")) - and not before0(r"\b(?:[nN]ous(?:-mêmes?|)|(?:[eE]t|[oO]u) moi(?:-même|)|[nN]i (?:moi|nous)),? ") - ->> =suggVerb(@, ":3p") # Incohérence. Ceci est un verbe à la 1ʳᵉ personne du pluriel. Sujet (“nous” ou équivalent) introuvable. -__[i]/conj(conj_xxxez_sans_sujet)!3__ - \w+(?:ez|[âîûn]tes)(?! vous) - <<- morphex(\0, ":V.*:2p", ":[EGMNAJ]") and not (\0[0:1].isupper() and before(r"\w")) - and not before0(r"\b(?:[vV]ous(?:-mêmes?|)|(?:[eE]t|[oO]u) toi(?:-même|)|[tT]oi(?:-même|) et|[nN]i (?:vous|toi)),? ") - ->> _ # Incohérence. Ceci est un verbe à la 2ᵉ personne du pluriel. Sujet (“vous” ou équivalent) introuvable. +__conj_xxxons_sans_sujet!3__ + @:1p¬:[EGMNAJ] ~¬[nN]ous + <<- /conj/ not (\1.istitle() and before0(r"\w")) and not tag_before(\1, "1p") -1>> =suggVerb(\1, ":3p") # Ceci est un verbe à la 1ʳᵉ personne du pluriel. Sujet (“nous” ou équivalent) introuvable. + +__conj_xxxez_sans_sujet!3__ + @:2p¬:[EGMNAJ] ~¬[vV]ous + <<- /conj/ not (\1.istitle() and before0(r"\w")) and not tag_before(\2, "2p") -1>> =suggVerb(\1, ":3p") # Ceci est un verbe à la 2ᵉ personne du pluriel. Sujet (“vous” ou équivalent) introuvable. TEST: les hommes et les femmes, qui sans un bruit, sans une parole amère, {{continuerons}} leur tâche n’en seront pas plus récompensés. TEST: il était dit que cette femme et son frère {{promènerez}} leur chien à cette heure de la journée. TEST: cet homme et cette femme {{pouvez}} y parvenir avec de la persévérance TEST: Comme on lui disait que vous-même aviez déjà consulté le notaire @@ -4640,15 +4777,29 @@ !! !!!! Locutions invariables !! !! -## plus que prévu / mois que prévu -__[i]/sgpl(sgpl_que_prévu1)__ (plus|moins|autant) +que (prévu(?:es?|s)) @@0,$ <<- -2>> prévu # Invariable. Implicitement, \1 que ce qui était prévu. -__[i]/sgpl(sgpl_que_prévu2)__ (plus|moins|aussi) +({w_2}) +que (prévu(?:es?|s)) @@0,w,$ <<- -3>> prévu # Invariable. Implicitement, \1 \2 que ce qui était prévu. -__[i]/sgpl(sgpl_que_prévu3)__ (plus|moins|autant) +d(?:e |’)({w_2}) +que (prévu(?:es?|s)) @@0,w,$ <<- -3>> prévu # Invariable. Implicitement, \1 \2 que ce qui était prévu. -__[i]/sgpl(sgpl_comme_adj)__ comme ((annoncé|convenu|prévu)(?:es?|s)) @@6,6 <<- -1>> \2 # Invariable. Implicitement, comme ce qui était \2. +__locutions_invariables__ + [plus|moins|autant] que [prévue|prévus|prévues] + <<- -3>> prévu # Invariable. Implicitement, \1 que ce qui était prévu. + + [plus|moins|aussi] ** que [prévue|prévus|prévues] + <<- -4>> prévu # Invariable. Implicitement, \1 \2 que ce qui était prévu. + + [plus|moins|autant] [de|d’] ** que [prévue|prévus|prévues] + <<- -5>> prévu # Invariable. Implicitement, \1 \2 \3 que ce qui était prévu. + + comme [annoncés|annoncée|annoncées] + <<- -2>> annoncé # Invariable. Implicitement, comme ce qui était annoncé. + + comme [convenus|convenue|convenues] + <<- -2>> convenu # Invariable. Implicitement, comme ce qui était convenu. + + comme [prévue|prévus|prévues] + <<- -2>> prévu # Invariable. Implicitement, comme ce qui était prévu. + TEST: il y en a autant que {{prévus}}. TEST: elles sont plus nombreuses plus que {{prévues}} TEST: il y a moins de bouffe que {{prévue}} TEST: comme {{annoncés}}, ils sont arrivés @@ -4664,97 +4815,129 @@ !! !!!! Tout, tous, toute, toutes !! !! -__[i](p_fais_les_tous)__ - fai(?:tes|sons|s)-(?:les|[nv]ous) (tou(?:te|)s) @@$ <<- ~1>> * -__[i](p_tout_débuts_petits)__ - (tout) (?:débuts|petits) @@0 <<- before(r"\b(aux|[ldmtsc]es|[nv]os|leurs) +$") ~1>> * -__[i](p_les_tout_xxx)__ - (?:[ldmtsc]es|[nv]os|leurs|aux) (tout) ({w_2}) @@w,$ - <<- morph(\2, ":[AQ].*:[pi]", False) ~1>> * +__purge_tout_tous_toutes__ + [fais-les|fais-nous] [tous|toutes] + [faisons-les|faisons-nous|faisons-vous] [tous|toutes] + [faites-les|faites-nous|faites-vous] [tous|toutes] + <<- ~2>> * + + [laisse-les|laisse-nous] [tous|toutes] + [laissons-les|laissons-nous|laissons-vous] [tous|toutes] + [laissez-les|laissez-nous|laissez-vous] [tous|toutes] + <<- ~2>> * + + [les|des|mes|tes|ses|ces|nos|vos|leurs|aux] tout [débuts|petits] + [les|des|mes|tes|ses|ces|nos|vos|leurs|aux] tout @:A.*:[pi] + <<- ~2>> * -__[i]/gn(gn_tous_deux)__ - (tout) deux @@0 <<- isStart() -1>> tous # Locution pronominale : « tous deux ».|https://fr.wiktionary.org/wiki/tous_deux +__tout_det__ + [|,] tout [deux|trois] + <<- -2>> tous # Locution pronominale : « tous deux ».|https://fr.wiktionary.org/wiki/tous_deux + + tout [mes|tes|ses|ces|nos|vos|leurs|ceux|celles] + <<- not morph(<1, ">(?:d[eu]|avant|après|malgré)/") -1>> tous # Erreur d’accord probable avec « \2 ». + + tout les @:¬:(?:3s|Oo) + <<- not morph(<1, ">(?:d[eu]|avant|après|malgré)/") -1>> tous # Erreur d’accord probable avec « les \3 ». TEST: {{Tout}} deux sont partis les premiers. - - -__[i]/gn(gn_tous_déterminant_pluriel)__ - tout(?= [cmts]es\b) - <<- not before(r"(?i)\b(?:d[eu]|avant|après|sur|malgré) +$") ->> tous # Erreur d’accord probable. - TEST: {{Tout}} mes hommes sont venus. -TEST: Malgré tout ces hommes sont quand même revenus. - - -__[i]/gn(gn_tous_les)__ - (tout) les ({w_2}) @@0,$ - <<- not before(r"(?i)\b(?:d[eu]|avant|après|sur|malgré) +$") and not morph(\2, ":(?:3s|Oo)", False) - -1>> tous # Erreur d’accord probable avec « les \2 ». - TEST: {{Tout}} les hommes sont dingues. - - -__[i]/gn(gn_tous_ceux)__ - tout(?= ceux\b) - <<- not before(r"(?i)\b(?:d[eu]|avant|après|sur|malgré) +$") ->> tous # Erreur d’accord probable avec « ceux ». - TEST: Donne à manger à {{tout}} ceux qui sont là. TEST: Revenus de tout ceux qui sont partis ont perdu la foi. +TEST: car malgré tout ceux qui persistent obtiennent parfois justice. +TEST: je ne connais pas du tout ceux dont tu parles. +TEST: Malgré tout ces hommes sont quand même revenus. +TEST: Les tout premiers hommes. +TEST: Les tout petits ne sont pas des légumes. -__[i]/gn(gn_toutes_déterminant_fem_plur)__ toute(?= (?:celles|[clmtsd]es)\b) <<- ->> toutes # Erreur d’accord probable. -__[i]/gn(gn_tout_ce)__ toute(?= cet?\b) <<- ->> tout # Erreur d’accord probable. -__[i]/gn(gn_tout_mon)__ toute(?= mon [bcdfgjklmnpqrstvwxz]) <<- ->> tout # Erreur d’accord probable. +__toute_det__ + toute [celles|les|des|mes|tes|ses|ces] + <<- /gn/ -1>> toutes # Erreur d’accord probable avec “\2”. + + toute [ce|cet] + <<- /gn/ -1>> tout # Erreur d’accord probable avec “\2”. + + toute mon ~^[bcdfgjklmnpqrstvwxz] + <<- /gn/ -1>> tout # Erreur d’accord probable avec “\2”. TEST: {{Toute}} celles qui viendront… TEST: et {{toute}} ce barouf ne nous a apporté que des ennuis. TEST: car {{toute}} mon savoir vient d’elle -__[i]/gn(gn_toutes_déterminant_nom_fem_plur)__ - (tous) +(?:[lcmtsd]es) +({w_2}) @@0,$ - <<- morphex(\2, ":f", ":(?:[123][sp]|[me])") and morphex(word(-1), ":", ":(?:R|[123][sp]|Q)|>(?:[nv]ous|eux) ", True) - -1>> toutes # Erreur d’accord probable. « \2 » est féminin. - <<- __also__ and hasFemForm(\2) -2>> =suggMasPlur(@, True) # Erreur d’accord probable. « \1 » est masculin. -__[i]/gn(gn_tous_déterminant_nom_mas_plur)__ - (toutes) +(?:[lcmtsd]es) +({w_2}) @@0,$ - <<- morphex(\2, ":m", ":(?:[123][sp]|[fe])") and morphex(word(-1), ":", ":(?:R|[123][sp]|Q)|>(?:[nv]ous|eux) ", True) - -1>> tous # Erreur d’accord probable. « \2 » est masculin. - <<- __also__ and hasFemForm(\2) -2>> =suggFemPlur(@, True) # Erreur d’accord probable. « \1 » est féminin. +__tous_det_nom__ + [|,] tous [des|mes|tes|ses|ces] @:[NA].*:f¬:[me] + [|,] tous [les] @:[NA].*:f¬:(?:3p|[me]) + <<- /gn/ -2>> toutes # Erreur d’accord probable : « \4 » est féminin. + <<- /gn/ __also__ and hasFemForm(\4) -4>> =suggMasPlur(\4, True) # Erreur d’accord probable : « \2 » est masculin. + + tous [des|mes|tes|ses|ces] @:[NA].*:f¬:[me] + tous [les] @:[NA].*:f¬:(?:3p|[me]) + <<- /gn/ morph(<1, ":", ":(?:R|[123][sp]|Q)|>(?:[nv]ous|eux)/") -1>> toutes # Erreur d’accord probable : « \3 » est féminin. + <<- /gn/ __also__ and hasFemForm(\3) -3>> =suggMasPlur(\3, True) # Erreur d’accord probable : « \1 » est masculin. TEST: {{tous}} ces {{idiotes}} +TEST: indubitablement {{tous}} des {{privilégiées}} + + +__toutes_det_nom__ + [|,] toutes [des|mes|tes|ses|ces] @:[NA].*:m¬:[fe] + [|,] toutes [les] @:[NA].*:m¬:(?:3p|[fe]) + <<- /gn/ -2>> tous # Erreur d’accord probable : « \4 » est masculin. + <<- /gn/ __also__ and hasFemForm(\4) -4>> =suggFemPlur(\4, True) # Erreur d’accord probable : « \2 » est féminin. + + toutes [des|mes|tes|ses|ces] @:[NA].*:m¬:[fe] + toutes [les] @:[NA].*:m¬:(?:3p|[fe]) + <<- /gn/ morph(<1, ":", ":(?:R|[123][sp]|Q)|>(?:[nv]ous|eux)/") -1>> tous # Erreur d’accord probable : « \3 » est masculin. + <<- /gn/ __also__ and hasFemForm(\3) -3>> =suggFemPlur(\3, True) # Erreur d’accord probable : « \1 » est féminin. + TEST: {{toutes}} mes {{bars}} - - -__[i]/gn(gn_tout_nom_mas_sing)__ - tout ({w3}) @@5 - <<- morphex(\1, ":N.*:[fp]", ":(?:A|W|G|M[12P]|Y|[me]:i|3s)") and morph(word(-1), ":R|>de ", False, True) - -1>> =suggMasSing(@, True) # “\1” devrait être au masculin singulier. - -__[i]/gn(gn_toute_nom_fem_sing)__ - toute ({w3}) @@6 - <<- morph(\1, ":[NAQ].*:[mp]") and morph(word(-1), ":R|>de ", False, True) - -1>> =suggFemSing(@, True) # “\1” devrait être au féminin singulier. - -__[i]/gn(gn_tous_nom_mas_plur)__ - tous ({w3}) @@5 - <<- morph(\1, ":[NAQ].*:[fs]") and morph(word(-1), ":R|>de ", False, True) - -1>> =suggMasPlur(@, True) # “\1” devrait être au masculin pluriel. - -__[i]/gn(gn_toutes_nom_fem_plur)__ - toutes ({w3}) @@7 - <<- morph(\1, ":[NAQ].*:[ms]") and morph(word(-1), ":R|>de ", False, True) - -1>> =suggFemPlur(@, True) # “\1” devrait être au féminin pluriel. +TEST: vraiment {{toutes}} des {{costauds}} + + +__tout_nom__ + [|,] tout @:N.*:[fp]¬:(?:A|W|G|M|Y|[me]:[is]|3s) + de tout @:N.*:[fp]¬:(?:A|W|G|M|Y|[me]:[is]|3s) + <<- /gn/ -3>> =suggMasSing(\3, True) # Accord avec “tout” : “\3” devrait être au masculin singulier. + + tout @:N.*:[fp]¬:(?:A|W|G|M|Y|[me]:[is]|3s) + <<- /gn/ morph(<1, ":R", ":D.*:p") -2>> =suggMasSing(\2, True) # Accord avec “tout” : “\2” devrait être au masculin singulier. + +__toute_nom__ + [|,] toute @:[NA].*:[mp]¬:(?:W|G|M|[fe]:[is]) + de toute @:[NA].*:[mp]¬:(?:W|G|M|Y|[fe]:[is]) + <<- /gn/ -3>> =suggFemSing(\3, True) # Accord avec “toute” : “\3” devrait être au féminin singulie + + toute @:[NA].*:[mp]¬:(?:W|G|M|Y|[fe]:[is]) + <<- /gn/ morph(<1, ":R") -2>> =suggFemSing(\2, True) # Accord avec “toute” : “\2” devrait être au féminin singulier. + +__tous_nom__ + [|,] tous @:[NA].*:[fs]¬:(?:W|G|M|[me]:[ip]) + de tous @:[NA].*:[fs]¬:(?:W|G|M|Y|[me]:[ip]) + <<- /gn/ -3>> =suggMasPlur(\3, True) # Accord avec “tous” : “\3” devrait être au masculin pluriel. + + tous @:[NA].*:[fs]¬:(?:W|G|M|Y|[me]:[ip]) + <<- /gn/ morph(<1, ":R") -2>> =suggMasPlur(\2, True) # Accord avec “tous” : “\2” devrait être au masculin pluriel. + +__toutes_nom__ + [|,] toutes @:[NA].*:[ms]¬:(?:W|G|M|[fe]:[ip]) + de toutes @:[NA].*:[ms]¬:(?:W|G|M|Y|[fe]:[ip]) + <<- /gn/ -3>> =suggFemPlur(\3, True) # Accord avec “toutes” : “\3” devrait être au féminin pluriel. + + toutes @:[NA].*:[ms]¬:(?:W|G|M|Y|[fe]:[ip]) + <<- /gn/ morph(<1, ":R") -2>> =suggFemPlur(\2, True) # Accord avec “toutes” : “\2” devrait être au féminin pluriel. TEST: Tout {{hommes}} TEST: De tous {{âge}} ! -TEST: avec toutes {{femme}} ->> femmes -TEST: sur toutes {{armure}} ->> armures +TEST: avec toutes {{femme}} ->> femmes +TEST: sur toutes {{armure}} ->> armures TEST: Toute {{époux}} doit faire preuve de bienveillance TEST: Il se souvient de toute mon histoire. TEST: Tout les sépare. TEST: les tout débuts du mouvement ouvrier TEST: vos tout débuts furent difficiles @@ -4767,64 +4950,126 @@ !! !!!! Adverbes de négation !! !! -__[i]/neg(ne_manquant1)__ - (?:je|tu|ils?|on|elles?) ([bcdfgjklmnpqrstvwxz][\w-]*) (pas|rien|jamais|guère) @@w,$ - <<- morph(\1, ":[123][sp]", False) and not (re.search("(?i)^(?:jamais|rien)$", \2) and before(r"\b(?:que?|plus|moins) ")) - -1>> ne \1 # Ne … \2 : il manque l’adverbe de négation. - -__[i]/neg(ne_manquant2)__ - (?:je|tu|ils?|on|elles?) ([aeéiouœ][\w-]*) (pas|rien|jamais|guère) @@w,$ - <<- morph(\1, ":[123][sp]", False) and not (re.search("(?i)^(?:jamais|rien)$", \2) and before(r"\b(?:que?|plus|moins) ")) - -1>> n’\1 # Ne … \2 : il manque l’adverbe de négation. - -__[i]/neg(ne_manquant3)__ - (?:je|tu|ils?|on|elles?) ([mts](?:e +|’(?:en|y) +|’)|[vn]ous +|l(?:e +|a +|eur +|ui +|l’))({w_1}) (pas|rien|jamais|guère) @@*,w,$ - <<- morph(\2, ":[123][sp]", False) and not (re.search("(?i)^(?:jamais|rien)$", \3) and before(r"\b(?:que?|plus|moins) ")) - -1>> ne \1 # Ne … \3 : il manque l’adverbe de négation. - -__[i]/neg(ne_manquant4)__ - (?:je|tu|ils?|on|elles?) (y|en) ({w_1}) (pas|rien|jamais|guère) @@w,w,$ - <<- morph(\2, ":[123][sp]", False) and not (re.search("(?i)^(?:jamais|rien)$", \3) and before(r"\b(?:que?|plus|moins) ")) - -1>> n’\1 # Ne … \3 : il manque l’adverbe de négation. +__ne_manquant__ + [|,] je [le|la|l’|les|me|m’|te|t’|se|s’|nous|vous|lui|leur] @:1s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] tu [le|la|l’|les|me|m’|te|t’|se|s’|nous|vous|lui|leur] @:2s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [il|elle|on] [le|la|l’|les|me|m’|te|t’|se|s’|nous|vous|lui|leur] @:3s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] nous [le|la|l’|les|me|m’|te|t’|se|s’|nous|vous|lui|leur] @:1p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] vous [le|la|l’|les|me|m’|te|t’|se|s’|nous|vous|lui|leur] @:2p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [ils|elles] [le|la|l’|les|me|m’|te|t’|se|s’|nous|vous|lui|leur] @:3p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + <<- /neg/ -3>> ne \3 # Ne … \5 : il manque l’adverbe de négation. + + [|,] [je|j’] [en|y] @:1s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] tu [en|y] @:2s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [il|elle|on] [en|y] @:3s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] nous [en|y] @:1p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] vous [en|y] @:2p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [ils|elles] [en|y] @:3p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + <<- /neg/ -3>> n’\3 # Ne … \5 : il manque l’adverbe de négation. + + [|,] je [me|m’|te|t’|se|s’|nous|vous] [le|la|l’|les|en|y] @:1s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] tu [me|m’|te|t’|se|s’|nous|vous] [le|la|l’|les|en|y] @:2s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [il|elle|on] [me|m’|te|t’|se|s’|nous|vous] [le|la|l’|les|en|y] @:3s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] nous [me|m’|te|t’|se|s’|nous|vous] [le|la|l’|les|en|y] @:1p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] vous [me|m’|te|t’|se|s’|nous|vous] [le|la|l’|les|en|y] @:2p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [ils|elles] [me|m’|te|t’|se|s’|nous|vous] [le|la|l’|les|en|y] @:3p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] je [le|la|l’|les] [lui|leur|en|y] @:1s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] tu [le|la|l’|les] [lui|leur|en|y] @:2s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [il|elle|on] [le|la|l’|les] [lui|leur|en|y] @:3s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] nous [le|la|l’|les] [lui|leur|en|y] @:1p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] vous [le|la|l’|les] [lui|leur|en|y] @:2p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [ils|elles] [le|la|l’|les] [lui|leur|en|y] @:3p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] je [lui|leur] en @:1s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] tu [lui|leur] en @:2s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [il|elle|on] [lui|leur] en @:3s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] nous [lui|leur] en @:1p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] vous [lui|leur] en @:2p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [ils|elles] [lui|leur] en @:3p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + <<- /neg/ -3>> ne \3 # Ne … \6 : il manque l’adverbe de négation. + + [|,] [je|j’] @>[aeéiouœ].*:1s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] tu @>[aeéiouœ].*:2s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [il|elle|on] @>[aeéiouœ].*:3s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] nous @>[aeéiouœ].*:1p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] vous @>[aeéiouœ].*:2p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [ils|elles] @>[aeéiouœ].*:3p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + <<- /neg/ -3>> n’\3 # Ne … \4 : il manque l’adverbe de négation. + + [|,] je @>[bcdfgjklmnpqrstvwxz].*:1s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] tu @>[bcdfgjklmnpqrstvwxz].*:2s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [il|elle|on] @>[bcdfgjklmnpqrstvwxz].*:3s¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] nous @>[bcdfgjklmnpqrstvwxz].*:1p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] vous @>[bcdfgjklmnpqrstvwxz].*:2p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + [|,] [ils|elles] @>[bcdfgjklmnpqrstvwxz].*:3p¬:(?:Oo|X) [pas|rien|jamais|guère|point] + <<- /neg/ -3>> ne \3 # Ne … \4 : il manque l’adverbe de négation. TEST: __neg__ On {{a}} pas compris. TEST: __neg__ Il {{part}} pas encore. -TEST: __neg__ On {{vous }}a pas compris. +TEST: __neg__ On {{vous}} a pas compris. TEST: __neg__ On {{en}} a pas. TEST: __neg__ Il {{y}} a jamais d’eau. - +TEST: __neg__ je {{deviendrai}} pas hargneux. +TEST: __neg__ il {{le}} lui donne pas souvent. !! !! !!!! Infinitif !! !! -__[i](p_ne_plus_pas_jamais_beaucoup_trop_rien)__ - ne (?:pas|plus|jamais) +(beaucoup|trop|rien) @@$ <<- ~1>> * - -__[i]/infi(infi_ne)__ - ne (?:pas|rien|jamais(?: rien| plus|)|plus(?: jamais| rien| guère|)|guère|point) (?:non plus |)(?:l(?:e(?:ur|s|)|a|ui) |nous |vous |[mtsl]’(?:en |y |)|[mts]e |en |y |)({w_1}) - @@$ - <<- not morph(\1, ":(?:Y|W|O[ow])|>que? ", False) and spell(\1) - -1>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. - -TEST: ne jamais {{cédé}} +__infi_ne_pas_jamais_etc__ + ne [pas|rien|guère|point] ?[le|la|l’|les|leur|lui|nous|vous|me|m’|te|t’|se|s’|en|y]¿ (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] [trop|beaucoup] ?[le|la|l’|les|leur|lui|nous|vous|me|m’|te|t’|se|s’|en|y]¿ (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] non plus ?[le|la|l’|les|leur|lui|nous|vous|me|m’|te|t’|se|s’|en|y]¿ (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne jamais ?[rien|plus|trop|beaucoup]¿ ?[le|la|l’|les|leur|lui|nous|vous|me|m’|te|t’|se|s’|en|y]¿ (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne jamais ?[rien|plus]¿ non plus ?[le|la|l’|les|leur|lui|nous|vous|me|m’|te|t’|se|s’|en|y]¿ (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne plus ?[jamais|rien|guère|trop|beaucoup]¿ ?[le|la|l’|les|leur|lui|nous|vous|me|m’|te|t’|se|s’|en|y]¿ (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne plus ?[jamais|rien|guère]¿ non plus ?[le|la|l’|les|leur|lui|nous|vous|me|m’|te|t’|se|s’|en|y]¿ (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] [m’|t’|s’|nous|vous|les|lui|leur|l’] [en|y] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] [trop|beaucoup] [m’|t’|s’|nous|vous|les|lui|leur|l’] [en|y] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] non plus [m’|t’|s’|nous|vous|les|lui|leur|l’] [en|y] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne jamais ?[rien|plus|trop|beaucoup]¿ [m’|t’|s’|nous|vous|les|lui|leur|l’] [en|y] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne jamais ?[rien|plus]¿ non plus [m’|t’|s’|nous|vous|les|lui|leur|l’] [en|y] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne plus ?[jamais|rien|guère|trop|beaucoup]¿ [m’|t’|s’|nous|vous|les|lui|leur|l’] [en|y] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne plus ?[jamais|rien|guère]¿ non plus [m’|t’|s’|nous|vous|les|lui|leur|l’] [en|y] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] [me|te|nous|vous] [le|la|les] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] [trop|beaucoup] [me|te|nous|vous] [le|la|les] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] non plus [me|te|nous|vous] [le|la|les] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne jamais ?[rien|plus|trop|beaucoup]¿ [me|te|nous|vous] [le|la|les] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne jamais ?[rien|plus]¿ non plus [me|te|nous|vous] [le|la|les] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne plus ?[jamais|rien|guère|trop|beaucoup]¿ [me|te|nous|vous] [le|la|les] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne plus ?[jamais|rien|guère]¿ non plus [me|te|nous|vous] [le|la|les] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] [le|la|les] [lui|leur] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] [trop|beaucoup] [le|la|les] [lui|leur] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne [pas|rien|guère|point] non plus [le|la|les] [lui|leur] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne jamais ?[rien|plus|trop|beaucoup]¿ [le|la|les] [lui|leur] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne jamais ?[rien|plus]¿ non plus [le|la|les] [lui|leur] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne plus ?[jamais|rien|guère|trop|beaucoup]¿ [le|la|les] [lui|leur] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + ne plus ?[jamais|rien|guère]¿ non plus [le|la|les] [lui|leur] (@:[VNA]¬:(?:Y|W|X|O[ow])|>que?/) + <<- /infi/ -1>> =suggVerbInfi(\1) # Après “ne pas”, “ne jamais”, “ne plus”, “ne rien”… le verbe devrait être à l’infinitif. + + ne [pas|jamais|plus|rien|guère|point] [beaucoup|trop] + <<- ~3>> * + +TEST: ne jamais les {{cédé}} +TEST: ne point nous {{donné}} TEST: ne rien {{finit}} TEST: ne jamais plus s’y {{frottait}} TEST: ne plus guère y {{pensée}} TEST: ne pas les {{contrariés}} TEST: Ne rien m’en {{dit}} TEST: Ne jamais lui {{donnait}} sa chance. +TEST: Ne jamais les leur {{montré}} TEST: Il a décidé de ne plus {{mangés}} avec nous. TEST: ne plus {{mangez}} fait maigrir TEST: ne plus {{mangées}} fait maigrir TEST: ne pas {{allé}} +TEST: ne jamais plus me les {{montrés}} TEST: Ne jamais {{mangez}} de viande ! TEST: J’espère ne pas te déranger TEST: Ne pas te le donner, ce serait une insulte. TEST: ne jamais vraiment évoquer le sujet TEST: déterminés à ne pas se laisser récupérer @@ -4831,15 +5076,15 @@ TEST: de ne pas en élire du tout TEST: Mais gare à ne pas non plus trop surestimer la menace TEST: ne jamais beaucoup bosser, c’est sa devise. -__[i]/imp(imp_infinitif_erroné)__ - n(?:e +|’)({w_2}er) +(?:pas|jamais) @@w - <<- morph(\1, ":V1.*:Y", False) and isStart() -1>> =suggVerbTense(\1, ":E", ":2p") # Confusion probable : “\1” est un verbe à l’infinitif. Si vous vouliez utiliser l’impératif, écrivez : +__imp_ne_infinitif_negadv__ + [|,] [ne|n’] @:V1.*:Y [pas|plus|jamais] + <<- /imp/ -3>> =suggVerbTense(\3, ":E", ":2p") # Confusion probable : “\1” est un verbe à l’infinitif. Si vous vouliez utiliser l’impératif, écrivez : -TEST: Non, ne {{manger}} pas ça. +TEST: Non, ne {{manger}} pas ça. ->> mangez TEST: Ne {{donner}} jamais à manger ces saloperies au chat. ->> donnez !!! @@ -4846,286 +5091,1155 @@ !!! !!! Processeur: épuration des adverbes, locutions adverbiales, interjections et expressions usuelles !!! !!! -# Dates -__[s](p_date)__ - (?:[dD]epuis le|[lL]e|[dD]u|[aA]u|[jJ]usqu au|[àÀ] compter du) (?:1(?:er|ᵉʳ)|\d\d?) (?:janvier|février|mars|avril|mai|juin|juillet|ao[ûu]t|septembre|octobre|novembre|décembre|vendémiaire|brumaire|frimaire|nivôse|pluviôse|ventôse|germinal|floréal|prairial|messidor|thermidor|fructidor)(?: \d+| dernier| prochain|) <<- ~>> * -__[i](p_en_l_an_de_grâce_année)__ - en l’an (?:de grâce |)\d+ <<- ~>> * -__[s](p_en_de_mois_année)__ - (?:[eE]n +|[dD](?:e +|’))(?:janvier|février|mars|avril|mai|juin|juillet|ao[ûu]t|septembre|octobre|novembre|décembre|vendémiaire|brumaire|frimaire|nivôse|pluviôse|ventôse|germinal|floréal|prairial|messidor|thermidor|fructidor) +\d{2,4} <<- ~>> * -__[i](p_en_année)__ - en \d\d+ <<- not morph(word(1), ":[AN].*:[pi]", False, False) ~>> * -__[i](p_de_année)__ - (de \d\d+) ({w_2}) @@0,$ <<- morph(\2, ":A.*:s", False) ~1>> * -__[s](p_à_la_mi_mois)__ - [àÀ] la mi-(?:janvier|février|mars|avril|mai|juin|juillet|ao[ûu]t|septembre|octobre|novembre|décembre|vendémiaire|brumaire|frimaire|nivôse|pluviôse|ventôse|germinal|floréal|prairial|messidor|thermidor|fructidor)(?:\d{2,4}|) <<- ~>> * -__[i](p_à_l_été_automne_hiver)__ - à l’(?:été|automne|hiver) \d{2,4} <<- ~>> * -__[i](p_au_printemps)__ - au printemps \d{2,4} <<- ~>> * +__purge_dates__ + depuis le [1er|1ᵉʳ|~\d\d?] {mois} ?[dernier|prochain|~\d{2,5}]¿ + [le|du|au] [1er|1ᵉʳ|~\d\d?] {mois} ?[dernier|prochain|~\d{2,5}]¿ + [jusqu’|jusqu] au [1er|1ᵉʳ|~\d\d?] {mois} ?[dernier|prochain|~\d{2,5}]¿ + à compter du [1er|1ᵉʳ|~\d\d?] {mois} ?[dernier|prochain|~\d{2,5}]¿ + en l’ an ~\d{2,5} + en l’ an de grâce ~\d{2,5} + en {mois} ~\d{2,5} + [de|d’|D’] {mois} ~\d{2,5} + en ~\d{2,5} [,|] + en ~\d{2,5} @:¬:[AN].*:[pi] + de ~\d{2,5} @:A.*:s + à la {mi_mois} ?~\d{2,5}¿ + <<- ~>> * + +TEST: ils sont depuis le 2 janvier {{parti}} à l’étranger. +TEST: ils sont depuis le 2 janvier 2012 {{parti}} à l’étranger. + + +__purge_saisons__ + à l’ [été|automne|hiver] ~\d{2,4} + au printemps ~\d{2,4} + <<- ~>> * TEST: Une étude de 2005 publiée dans le Journal TEST: Les cinq variantes de la couverture du magazine Wired d’avril 2016 consacrée à Silicon Valley. TEST: c’est donc la cinquième en 50 ans -# nombres -__[i](p_un_nombre)__ - un (\d+) ({w_2}) @@w,$ <<- morph(\2, ":A.*:s") ~1>> * +__purge_un_nombre__ + un ~\d+ @:A.*:s¬:G + <<- ~2>> * TEST: l’équipe veut aussi voir dans la lettre le nombre d’or, un symbole d’harmonie, ainsi qu’un 6 retourné. ## moi/toi/lui/elle/nous/vous/eux/elles seul·e·s -__[i](p_moi_toi_seul)__ [mt]oi (seule?) @@4 <<- ~1>> * -__[i](p_lui_seul)__ lui (seul) @@4 <<- ~1>> * -__[i](p_elle_seule)__ elle (seule) @@5 <<- ~1>> * -__[i](p_nous_seuls)__ [nv]ous (seule?s) @@5 <<- ~1>> * -__[i](p_eux_seuls)__ eux (seuls) @@4 <<- ~1>> * -__[i](p_elles_seules)__ elles (seules) @@6 <<- ~1>> * - -## personne d’autre que… -__[i](p_personne_d_autre_que)__ - personne (d’autre qu(?:e |’)(?:lui|elles?|[nv]ous|eux)) @@$ <<- ~1>> * - -## Avant -__[i](p_dès_qqch)__ dès (?:à présent|aujourd’hui|maintenant|lors|que possible|(?:demain|hier)(?: (?:soir|matin|après-midi)|)) <<- ~>> * -__[i](p_et_qqch)__ et (?:ainsi de suite|tutti quanti) <<- ~>> * -__[i](p_et_ou)__ et(/ou) @@2 <<- ~1>> * -__[i](p_quant_à_présent)__ quant à présent <<- ~>> * -__[i](p_ni_qqch_ni_qqch)__ - ni (?:à|avec|contre|pour|chez|sur|sous|devant|derrière) *(?:[tm]oi|lui|elles?|eux|[nv]ous|),? ni (?:à|avec|contre|pour|chez|sur|sous|devant|derrière) (?:[mt]oi|lui|elles?|eux|[nv]ous) <<- ~>> * - - -## Inconditionnel -__[i](p_24h_sur_24)__ 24 ?h(?:eures|) ?(?:sur |/ ?)24 <<- ~>> * -__[i](p_7j_sur_7)__ 7 ?j(?:ours|) ?(?:sur |/ ?)7 <<- ~>> * -__[i](p_sept_j_sur_sept)__ sept jours sur sept <<- ~>> * -__[i](p_vq_h_sur_vq_)__ vingt-quatre heures sur vingt-quatre <<- ~>> * -__> * -__[i](p_à_nn_pour_cent)__ à \d+(?:,\d+|) % <<- ~>> * -__[i](p_à_côté_de)__ à côté (?:de (?:ça|lui|[mt]oi|[nv]ous)|d’(?:elles|eux))(?! et) <<- ~>> * -__[i](p_à_la_qqch)__ à la (?:bo(?:nne franquette|urre)|con|dér(?:ive|obée)|diable|fois|leur|limite du supportable|longue|lumière de tout ce(?:ci|la)|manque|mords-moi-le-nœud|papa|petite semaine|pointe du progrès|première occasion|queue leu leu|ramasse|re(?:nverse|dresse|scousse)|sauvette|surprise générale|virgule près|volée) <<- ~>> * -__[i](p_à_heure)__ à \d\d? ?h(?: ?\d\d|)(?: (?:du (?:matin|soir)|de l’après-midi|ce (?:matin|soir)|cet après-midi|demain (?:matin|soir|après-midi)|)|) <<- ~>> * -__[i](p_à_loc_qqch1)__ à (?:califourchon|chacun|confesse|contre(?:cœur|temps)|demi-mot|foison|grand-peine|loisir|merveille|moitié|nouveau|outrance|peine|perpétuité|présent|raison|rallonge|rebrousse-poil|reculons|regret|renverse|risque|tâtons|tort|tout-va) <<- ~>> * -__[i](p_à_loc_qqch2)__ à (?:au(?:cun prix|trui|tre chose)|bas (?:co[ûu]t|prix)|bâ(?:bord|tons rompus)|beaucoup près|belles dents|bien (?:des égards|pire|y (?:penser|réfléchir|songer|repenser))|bon (?:compte|escient|droit)|bout (?:de (?:bras|souffle|forces?)|nerfs|portant|touchant)|bras (?:ouverts|le corps)|brève échéance|but (?:non |)lucratif|cause d(?:e (?:ça|[mt]oi|lui|[nv]ous)|’e(?:lles?|ux))|ce (?:compte-là|moment-là|titre)|cet (?:égard|instant(?: précis|))|cette (?:date|époque(?: de l’année|)|heure de la (?:journée|nuit)|occasion)|chaque (?:fois|instant)|chaudes larmes|cœur (?:joie|ouvert|perdu)|ciel ouvert|contre-cœur|corps perdu|cou(?:p sûr|per le souffle|rt terme|rte (?:échéance|portée))|couilles rabattues|de (?:nombreuses|multiples) reprises|des kilomètres à la ronde|défaut d’autre chose|dose homéopathique|double (?:titre|tranchant)|durée limitée|en (?:juger par (?:[mts]on|[nv]otre|leur) expérience|perdre (?:haleine|la tête))|faible (?:allure|revenu)|feu et à sang|flanc de (?:colline|montagne)|fleur de peau|franchement parler|géométrie variable|grand(?:-peine|e échelle)|haut risque|hue et à dia|huis clos|intervalles (?:ir|)réguliers|juste (?:raison|titre)|long terme|longue(?: échéance| portée|ur (?:de (?:temps|journée))|d’année)|loyer modéré|main(?: (?:armée|droite|gauche|levée)|s nues)|maint(?:s égards|es reprises)|marche forcée|merveille|mi-(?:course|distance|temps)|mi(?:di|nuit)(?: pile|)|moindres frais|mots couverts|moyen(?: terme|ne échéance)|n’en (?:pas douter|point douter|plus finir)|outrance|parler franc|part (?:entière|ça|ce(?:la|ci))|partir de là|part(?:ir de rien|s égales)|pas de (?:géant|loup|tortue|velours)|personne en danger|perte de vue|petit(?: feu|e (?:dose|échelle))|peu (?:de (?:distance|choses près|frais)|près)|pieds joints|pile ou face|plat(?: ventre|e couture)|plein(?: (?:régime|temps|nez)|s poumons)|plus (?:forte raison|d’un titre)|point nommé|portée de (?:main|tir)|première vue|prix (?:cassé|modique)s?|proprement parler|qui (?:mieux mieux|que ce soit|de droit)|quelque(?: distance|s (?:exceptions|nuances) près)|ras bords?|rude épreuve|s’y méprendre|somme nulle|tel point|temps (?:plein|partiel|complet)|tête reposée|tire[ -]d’aile|titre (?:conservatoire|d’exemple|expérimental|indicatif|informatif|grâcieux|personnel|posthume)|tombeau ouvert|tort (?:ou à raison|et à travers)|tour de (?:bras|rôle)|tous (?:crins|points de vue)|toutes (?:fins utiles|jambes)|tribord|tu et à toi|un moment donné|usage interne|visage (?:découvert|humain)|vive allure|voix (?:haute|basse)|vol d’oiseau|vrai dire|vue d’œil|y (?:regarder de plus près|réfléchir)) <<- ~>> * -__[i](p_à_partir_de)__ à partir (?:de (?:demain(?: matin| midi| soir|)|là|maintenant|rien)|d’(?:aujourd’hui|hier(?: matin| midi| soir|)|ici)) <<- ~>> * -__[i](p_à_quelques_uns)__ à quelques-un(?:s d’entre (?:eux|nous|vous)|es d’entre (?:nous|vous|elles)) <<- ~>> * -__[i](p_à_tout_qqch)__ à tout(?: (?:âge|bout de champ|crin|instant|jamais|le (?:moins|monde)|moment|point de vue|prix|un chacun)|e (?:allure|bride|épreuve|force|heure(?: d(?:u jour|e la nuit)|)|vitesse|volée)) <<- ~>> * -__[i](p_à_l_qqch)__ à l’(?:heure (?:actuelle|qu il est)|accoutumée|amiable|avance|aven(?:ir(?: incertain)|ant)|air libre|aveuglette|emporte-pièce|échelle (?:nationale|mondiale|régionale|départementale|cantonale|locale|galactique|universelle)|évidence|exclusion de toute autre chose|improviste|inverse|occasion|ordre du jour|œil nu|en croire|un(?:animité| (?:d’entre eux|des leurs)|e (?:d’entre elles|des leurs))) <<- ~>> * -__[i](p_à_det_plur_qqch)__ à (?:[mts]es|[nv]os|leurs) (?:côtés|dépens|risques et périls|trousses) <<- ~>> * -__[i](p_à_det_sing_fem_qqch)__ à (?:[mts]a|[nv]otre|leur) (?:connaissance|disposition|grande (?:surprise|tristesse)|guise|juste mesure|portée) <<- ~>> * -__[i](p_à_det_sing_mas_qqch)__ à (?:[mts]on|[nv]otre|leur) (?:avis|c(?:œur|orps) défendant|détriment|encontre|égard|grand (?:désarroi|soulagement)|insu|sujet|tour) <<- ~>> * -__[i](p_à_midi_minuit)__ à mi(?:di|nuit)(?: pile|) <<- ~>> * -__[i](p_à_cette_heure)__ à cette heure(?: (?:du jour|de la nuit|tardive|matinale)|) <<- ~>> * -__[i](p_a_loc_latine)__ [aà] (?:priori|post[eé]riori|contrario|cappella|minima) <<- ~>> * -__[i](p_ab_loc_latine)__ ab (?:absurdo|initio) <<- ~>> * -__[i](p_ad_loc_latine)__ ad (?:hoc|vitam æternam|hominem|infinitum|nauseam|valorem|patres) <<- ~>> * -__[i](p_advienne_que_pourra)__ advienne que pourra <<- ~>> * -__[i](p_après_qqch)__ après (?:[mts]oi|lui|eux|mûre réflexion|tout,|un certain temps|cette date(?: fatidique|)|un bon bout de temps) <<- ~>> * -__[i](p_qqch_après_identique)__ (heure|minute|seconde|jour|nuit|semaine|trimestre|semestre|mois|décennie|année|siècle|génération) après \1 @@0 <<- ~>> * -__[i](p_au_dessus_delà_qqch)__ au-de(?:ssus (?:de (?:[mts]oi|lui|[nv]ous)|d’(?:eux|elles?))|là du descriptible) <<- ~>> * -__[i](p_au_qqch)__ au (?:[xXvViI]+[eᵉ] siècle|bas mot|beau fixe|bon moment|bout (?:du (?:compte|rouleau)|d’un moment)|cas par cas|commencement|contraire|coude à coude|coup par coup|déb(?:otté|but)|demeurant|doigt mouillé|fil (?:des ans|du temps)|grand (?:complet|jamais)|hasard|jour (?:et à l’heure dits|le jour)|jugé|leur|lieu de (?:ce(?:la|ci)|ça|quoi)|loin|même titre que n’importe l(?:aquelle|equel) d’entre (?:nous|vous|eux|elles)|milieu de nulle part|moment opportun|pas de (?:charge|course)|plus (?:haut point|près|pressé|vite|tôt|tard)|premier abord|préalable|propre comme au figuré|quotidien|ras des pâquerettes|saut du lit|sens (?:figuré|large|propre)|surplus) <<- ~>> * -__[i](p_au_adj_moment)__ au (?:dernier|même|bon|mauvais) (?:moment|instant) <<- ~>> * -__[i](p_au_cours_des)__ au cours des (?:deux|trois|quatre|cinq|six|sept|huit|neux|dix|onze|douze|treize|quatorze|quinze|seize|dix-(?:sept|huit|neuf)|vingt|trente|quarante|cinquante|soixante|soixante-dix|quatre-vingt|quatre-vingt-dix|cent) (?:derni(?:ère|er)s|prochaine?s) (?:années|mois|siècles) <<- ~>> * -__[i](p_au_fond_de_qqch)__ (?:tout |)au fond (?:de (?:[mts]oi|lui|[nv]ous)|d’(?:elles?|eux))(?:-mêmes?|) <<- ~>> * -__[i](p_aux_qqch)__ aux (?:abois|leurs|mien(?:ne|)s|tien(?:ne|)s|sien(?:ne|)s) <<- ~>> * -__[i](p_autant_que_qqch)__ autant que (?:nécessaire|possible|prévu|faire se peut) <<- ~>> * -__[i](p_autour_de_qqch)__ autour (?:d’(?:eux|elles?)|de (?:lui|[nv]ous|[mt]oi)) <<- ~>> * -__[i](p_autrement_dit)__ autrement dit <<- ~>> * -__[i](p_av_JC)__ av. J.-C. <<- ~>> * -__[i](p_avant_qqch)__ avant (?:longtemps|terme|tout le monde|toute(?: chose|s choses)|d’aller plus loin|J.-C.|Jésus-Christ|d’en arriver là|de faire quoi que ce soit(?: de stupide|)|qu il ne soit trop tard|un bon bout de temps) <<- ~>> * -__[i](p_avec_qqch1)__ avec (?:brio|joie|légèreté|insistance|peine|autre chose|pertes et fracas|un peu de chance|tout le respect que (?:je (?:vous|te|l(?:eur|ui)) dois|nous (?:vous|te|l(?:eur|ui)) devons)|tout un chacun|un peu de chance) <<- ~>> * -__[i](p_avec_qqch2)__ avec (?:autrui|[mts]oi|lui|e(?:ux|lles?)|[nv]ous(?: autres)|le plus grand soin|tout le monde|tout ça|on ne sait quo?i)(?! qui) <<- ~>> * -__[i](p_beaucoup_plus_moins)__ beaucoup (?:plus|moins) <<- ~>> * -__[i](p_bel_et_bien)__ bel et bien <<- ~>> * -__[i](p_bien_adv_temps)__ bien (?:assez tôt|des fois|souvent) <<- ~>> * -__[i](p_bon_gré_mal_gré)__ bon gré,? mal gré <<- ~>> * -__[i](p_bras_dessus_dessous)__ bras dessus,? bras dessous <<- ~>> * -__[i](p_çà_et_là)__ çà et là <<- ~>> * -__[i](p_ce_faisant)__ ce faisant <<- ~>> * -__[i](p_ceci_qqch)__ ceci (?:mis à part|va sans dire) <<- ~>> * -__[i](p_cela_qqch)__ cela (?:mis à part|va sans dire) <<- ~>> * -__[i](p_ces_derniers_temps)__ ces derniers temps <<- ~>> * -__[i](p_ceux_d_entre_pronom)__ ce(?:lui|lles?|ux) (d’entre (?:[nv]ous|eux|elles)) @@$ <<- ~1>> * -__[i](p_cette_fois_là)__ cette fois-(?:là|ci) <<- ~>> * -__[i](p_chacun_d_entre_nous)__ chacune? (d’entre (?:[nv]ous|eux|elles)) @@$ <<- ~1>> * -__[i](p_chaque_fois)__ chaque fois <<- ~>> * -__[i](p_chemin_de_fer)__ chemins? (de fer) @@$ <<- ~1>> * -__[i](p_chez)__ chez (?:[mt]oi|lui|e(?:ux|lles?)|[nv]ous|autrui|quelqu’une?|on ne sait qui) <<- ~>> * -__[i](p_comme_qqch)__ comme (?:avant|autrefois|d’habitude|toujours|de juste|bon (?:me|te|l(?:ui|eur)|[nv]ous) semble|au bon vieux temps|cul et chemise|frappée?s? par la foudre|n’importe où(?: ailleurs|)|par (?:enchantement|magie|un fait exprès)|promis|qui dirait|si de rien n’était|tout un chacun) <<- ~>> * -__[i](p_comme_tant_d_autres)__ comme tant d’autres (?:avant|après) (?:[mts]oi|lui|[nv]ous|eux|elles?)(?! qui) <<- ~>> * -__[i](p_contrairement_aux_apparences)__ contrairement aux apparences <<- ~>> * -__[i](p_contre_qqch)__ contre (?:mauvaise fortune,? bon cœur|nature|toute (?:attente|vraisemblance)|vents et marées|[mts]oi|lui|elles?|[nv]ous|eux|(?:[mts]on|[nv]otre|leur) gré) <<- ~>> * -__[i](loc_côte_à_côte)__ - c[ôo]tt?es? [àaá] c[ôo]tt?es? - <<- not re.search("(?i)^côte à côte$", \0) ->> côte à côte # Locution adverbiale invariable. Écrivez “côte à côte”.|https://fr.wiktionary.org/wiki/c%C3%B4te_%C3%A0_c%C3%B4te - <<- ~>> * -__[i](p_coute_que_coute)__ co[ûu]te que co[ûu]te <<- ~>> * -__[i](p_crois_le_ou_non)__ cro(?:yez|ois)-le ou (?:non|pas) <<- ~>> * -__[i](p_cul_par_dessur_tête)__ cul par-dessus tête <<- ~>> * -__[i](p_dans_qqch)__ dans (?:ces? cas(?: précis|-là|-ci| particuliers?|)|l’i(?:déal|mmédiat)|la mesure du possible|les années \d\d+|peu de temps|tout (?:ce(?:la|ci)|ça)|très peu de temps|un(?: cas comme dans l’autre|e (?:certaine|large|moindre) mesure)) <<- ~>> * -__[i](p_début_mois)__ début (?:janvier|février|mars|avril|mai|juin|juillet|ao[ûu]t|septembre|octobre|novembre|décembre)(?: \d\d\d\d|) <<- ~>> * -__[i](p_d_qqch)__ d’(?:abord|affilée|ailleurs|année en année|aujourd’hui|antan|autant (?:plus|moins)|autre(?:fois|s fois| part)|arr(?:arrache-?pied|ière en avant)|avant en arrière|à côté|âge mûr|emblée|empoigne|en face|entr(?:e (?:[nv]ous|eux|elles)|ée de jeu)|est en ouest|extrême[ -](?:droite|gauche)|égale? à égale?|habitude|heure en heure|hier(?: (?:matin|soir|après-midi)|)|ici(?: là| peu(?: de temps|)| très peu(?: de temps|)|)|ordinaire|origine (?:inconnue|douteuse)|ordre général|ouest en est|ore?s et déjà|un (?:autre côté|(?:bout à|côté comme de) l’autre|commun accord)) <<- ~>> * -__[i](p_d_une_qqch)__ d’une (?:autre trempe|(?:façon|manière) ou d’une autre|certaine (?:façon|manière)|tout autre ampleur|(?:minute|seconde) à l’autre) <<- ~>> * -__[i](p_d_où_que)__ d’où qu (?:(?:il|elle|on) vienne|(?:ils|elles) viennent) <<- ~>> * -__[i](p_de_ci_de_là)__ de-ci,? de-là <<- ~>> * -__[i](p_de_heure)__ de \d\d? ?h(?: ?\d\d|)(?: (?:du (?:matin|soir)|de l’après-midi|ce (?:matin|soir)|cet après-midi|demain (?:matin|soir|après-midi))|) <<- ~>> * -__[i](p_de_qqch)__ de (?:\d+(?:,\d+|) ?%|cesse|conserve|facto|fait|guingois|luxe|nouveau|permanence|partout|préférence|profundis|rechange|routine|surcro[îi]t|visu|A à Z|bas(?: (?:en haut|étage)|se (?:condition|extraction|))|bon (?:aloi|cœur|gré|matin|sens|ton)|bonne (?:facture|famille|foi|heure|humeur|grâce|qualité|compagnie)|bric et de broc|but en blanc|ce(?: (?:fait(?: même|)|seul fait|point de vue)|tte sorte|t acabit)|courte (?:durée|vue)|dernière minute|demain(?: (?:matin|soir|après-midi)|)|droite (?:à|comme de) gauche|fâcheuse mémoise|fil en aiguille|fond en comble|fort (?:loin|près)|fra[iî]che date|ga[îi]e?té de cœur|gauche (?:à|comme de) droite|grande (?:taille|envergure)|gré ou de force|guerre lasse|haut(?: (?:en bas|rang|vol)|e (?:lutte|stature|volée))|jour comme de nuit|là-bas|la (?:meilleure (?:manière|façon) possible|même (?:façon|manière)|sorte|tête aux pieds|veille)|loin(?: en loin|)|longue (?:date|durée|haleine)|main de ma[îi]tre|mauvais(?: (?:aloi|go[ûu]t|gré)|e (?:foi|grâce|humeur))|mieux en mieux|nature (?:inconnue|indéterminée|insolite)|nombreuses (?:fois|années plus (?:tôt|tard))|nos jours|notoriété publique|nulle part|pire en pire|près(?: ou de loin|)|par(?: le monde(?: entier|)|t et d’autre)|petite taille|pied ferme|premi(?:er (?:ordre|plan)|ère main)|plein (?:droit|fouet)|plus (?:belle|près)|première (?:catégorie|nécessité)|prime abord|proche en proche|pure forme|sang-froid|seconde (?:zone|importance|main)|si bon(?: matin|ne heure)|source sûre|taille moyenne|telle sorte|temps (?:en temps|à autre)|tr(?:ès|op) (?:loin|près)|vive voix) <<- ~>> * -__[i](p_de_nous_vous_tous)__ de [nv]ous tous <<- ~>> * -__[i](p_de_tout_qqch)__ de tou(?:t (?:poil|temps|à l’heure|premier (?:ordre|plan))|tes (?:parts|pièces|sortes|(?:[mts]es|leurs|[nv]os) forces)|te (?:éternité|évidence|façon|urgence)|s (?:côtés|bords)) <<- ~>> * -__[i](p_de_ceux_celles)__ de ce(?:ux|lles)-(?:ci|là)(?! qui) <<- ~>> * -__[i](p_de_det_mas_qqch)__ de (?:[mts]on|[nv]otre|leur) (?:mieux|plein gré|point de vue|propre (?:cru|chef)|vivant) <<- ~>> * -__[i](p_de_det_fem_qqch)__ de (?:[mts]a|[nv]otre|leur) part <<- ~>> * -__[i](p_de_qqch_en_identique)__ de (moins|plus|mieux|pire|jour|minute|semaine|mois|trimestre|semestre|siècle|millénaire|décennie) en \1 @@3 <<- ~>> * -__> * -__[i](p_des_qqch)__ des (?:fois|pieds à la tête|uns et des autres|(?:années|mois|siècles|millénaires|décennies|semaines) plus t(?:ôt|ard)) <<- ~>> * -__[i](p_depuis_qqch)__ depuis (?:assez longtemps|belle lurette|bien longtemps|de (?:très |)longues années|des lustres|longtemps|lors|peu de temps|quelque temps|quelques (?:secondes|minutes|heures|jours|semaines|mois|trimestres|semestres|années|décennies|siècles|millénaires)|si longtemps|toujours|tout ce temps|très longtemps) <<- ~>> * -__[i](p_depuis_tps)__ depuis (\d+ (?:ans|années|mois|semaines|jours|heures|minutes|secondes|)|les années \d\d+) @@$ <<- ~>> * -__[i](p_Dieu_en_garde_témoin)__ Dieu (?:[mt]’en (?:garde|soit témoin)|[nv]ous en (?:garde|soit témoin)|l(?:es |’)en garde|l(?:eur|ui) en soit témoin) <<- ~>> * -__[i](p_du_moins)__ du moins <<- ~>> _ -__[i](p_du_qqch)__ du (?:[xXvViI]+[eᵉ] siècle|bout des lèvres|début à la fin|fond du cœur|jour au lendemain|haut en bas|même (?:acabit|tonneau)|moins,? pas|(?:nord|sud) au (?:nord|sud)|tout au tout) <<- ~>> * -__[i](p_demain)__ (?:après-|avant |)demain(?: matin| soir| après-midi|) <<- ~>> * -__[i](p_don_Juan)__ (don) Juan @@0 <<- ~1>> * -__[i](p_du_même_ordre_coup)__ du même (?:ordre|coup) <<- ~>> * -__[i](p_en_nombre_années)__ en \d\d+(?: ans| années| mois| semaines| jours| heures| minutes| secondes|) <<- ~>> * -__[i](p_en_cours)__ en cours(?! d[e’]) <<- ~>> * -__[i](p_en_pronom)__ en (?:[mt]oi|eux|elles?) <<- ~>> * -__[i](p_en_qqch1)__ en (?:aparté|apparence|arrière|avance|avant|cachette|ceci|cela|clair|commun|conséquence|continu|contrepartie|définitive|détail|direct|douce|effet|émoi|filigrane|général|goguette|hâte|majorité|outre|pâmoison|parallèle|partie|particulier|permanence|personne|pratique|prime|privé|principe|priorité|public|réalité|retour|revanche|rien|rogne|route|secret|silence|somme|suspens|théorie|trompe-l’œil|vain|vérité|ville|vitesse) <<- ~>> * -__[i](p_en_qqch2)__ en (?:aucun(?: cas|e (?:circonstance|façon|manière))|bon(?: état|ne (?:compagnie|et due forme|posture|santé(?: physique| mentale|)|voie))|bout de course|cas d(?:e (?:besoin|doute)|’urgence)|chacune? d(?:e [nv]ous|’(?:eux|elles))|chair et en os|chute libre|comparution immédiate|connaissance de cause|coupe réglée|cours de route|d’autres (?:circonstances|termes|temps)|de telles circonstances|début d(?:e (?:journée|matinée|soirée)|’après-midi)|définitive|dehors de (?:tout|)(?:ça|cela|ceci)|dents de scie|dernier (?:lieu|recours|ressort)|désespoir de cause|détention provisoire|direction d(?:u (?:nord|sud)(?:-est|-ouest|)|e l’(?:est|ouest))|état (?:de (?:choc(?: circulatoire|)|marche)|d’ébriété(?: avancée|))|excellent état|file indienne|fin d(?:e (?:compte|journée|matinée|soirée)|’après-midi)|forte (?:baisse|hausse)|gage de bonne foi|garde à vue(?: prolongée|)|grand(?: nombre|e (?:difficulté|majorité|partie|pompe))|haut lieu|l’occurrence|lieu sûr|ligne de (?:compte|mire)|mains propres|mauvais(?: état|e (?:posture|santé))|même temps|milieu d(?:e (?:journée|matinée|soirée)|’après-midi)|nombre (?:plus que |)suffisant|partant de zéro|plein(?: air| cœur| jour|e (?:gueule|figure|forme|poire|nuit|tronche))|perte de vitesse|peu de temps|piteux état|point d(?:e mire|’orgue)|position de (?:force|faiblesse)|premi(?:er lieu|ère (?:instance|ligne))|pure perte|quantité (?:plus que |)suffisante|quelque sorte|queue de peloton|rangs serrés|rase campagne|règle générale|roue libre|sens inverse|si peu de temps|sous-main|tête à tête|temps (?:et en heure|normal|opportun|ordinaire|utile|voulu)|termes choisis|toile de fond|tous (?:les cas|sens)|tout (?:bien tout honneur|cas|genre|lieu|et pour tout|état de cause|premier lieu|sens|temps)|toute(?: (?:bonne foi|circonstance|connaissance de cause|confiance|discrétion|franchise|hâte|impartialité|impunité|innocence|légalité|liberté|logique|sécurité|simplicité)|s circonstances)|un (?:clin d’œil|rien de temps)|une autre occasion|vase clos|voie de développement|y réfléchissant bien) <<- ~>> * -__[i](p_en_mois_dernier)__ en (?:janvier|février|mars|avril|mai|jui(?:n|llet)|ao[ûu]t|septembre|octobre|novembre|décembre) dernier <<- ~>> * -__[i](p_en_dat_mas_qqch)__ en (?:[mts]on|leur|[nv]otre) (?:âme et conscience|for intérieur|nom propre) <<- ~>> * -__[i](p_en_ce_qqch)__ en ce(?: (?:moment|temps-là|qui (?:[mt]e|l(?:es?|a)|[nv]ous) concern(?:e|ait))|t instant) <<- ~>> * -__[i](p_encore_qqch)__ encore (?:une fois|et (?:encore|toujours)) <<- ~>> * -__[i](p_envers_qqch)__ envers (?:autrui|et contre tout|les uns et les autres|tout le monde) <<- ~>> * -__[i](p_entre_qqch)__ entre (?:(?:[mt]oi|lui|elles?|[nv]ous|eux) et (?:[mt]oi|lui|elles?|[nv]ous|eux)|chien et loup|de (?:bonnes|mauvaises) mains|l’une? et l’autre|les uns et les autres|quat(?:re[- ]z-?yeux|’ z-?yeux)) <<- ~>> * -__[i](p_entre_date)__ entre (?:janvier|février|mars|avril|mai|juin|juillet|ao[ûu]t|septembre|octobre|novembre|décembre) (?:\d\d{1,3} |)et (?:janvier|février|mars|avril|mai|juin|juillet|ao[ûu]t|septembre|octobre|novembre|décembre)(?: \d\d{1,3}|) <<- ~>> * -__[i](p_épaule_contre_épaule)__ épaule contre épaule <<- ~>> * -__[i](p_été_comme_hiver)__ été comme hiver <<- ~>> * -__[i](p_oh_ah_euh_eh_bien)__ (?:oh|ah|euh|eh bien) <<- ~>> * -__[i](p_ex_loc_latine)__ ex (?:nihilo|cathedra|absurdo|abrupto) <<- ~>> * -__[i](p_face_à_face)__ face à face <<- ~>> * -__[i](p_nombre_fois_de_suite)__ (?:deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize|vingt|trente|quarante|cinquante|soixante|cent) fois de suite <<- ~>> * -__[i](p_grosso_modo)__ grosso modo <<- ~>> * -__[i](p_grand_bien_lui_fasse)__ grand bien lui fasse <<- isStart() ~>> * -__[i](p_hier)__ (?:avant-|)hier(?: matin| soir| après-midi|) <<- ~>> * -__[i](p_hors_de_qqch)__ hors (?:de (?:contrôle|portée)|d’(?:atteinte|état de nuire)|du commun) <<- ~>> * -__[i](p_ici_qqch)__ ici(?: comme ailleurs| ou ailleurs| et (?:là|maintenant)| même|-bas) <<- ~>> * -__[i](p_id_est)__ id est <<- ~>> * -__[i](p_il_y_a_qqch)__ il y a (?:longtemps|peu de temps|très (?:longtemps|peu de temps)|(?:quelques|moins de \d+|\d+) (?:secondes|minutes|heures|jours|semaines|mois|an(?:née|)s|siècles|millénaires)|quelque temps) <<- ~>> * -__[i](p_il_n_y_a_pas_qqch)__ il n’y a pas (?:si |)longtemps <<- ~>> * -__[i](p_illico_presto)__ illico presto <<- ~>> * -__[i](p_in_loc_latine)__ in (?:abstracto|extenso|extremis|fine|petto|situ|utero|vitro|vivo) <<- ~>> * -__[i](p_ipso_facto)__ ipso facto <<- ~>> * -__[i](p_j_en_passe)__ j’en passe et des meilleure?s <<- ~>> * -__[i](p_jour_pour_jour)__ jour pour jour <<- ~>> * -__[i](p_jusque_là)__ jusque-là <<- ~>> * -__[i](p_jusque_qqch)__ jusqu (?:alors|ici|aujourd’hui|au bout des ongles) <<- ~>> * -__[i](p_jusque_à_qqch)__ jusqu à (?:aujourd’hui|bac|présent|maintenant|récemment|(?:demain|hier)(?: matin| soir| après-midi|)|nouvel ordre|plus (?:ample informé|soif)|preuve du contraire|la (?:fin de(?: (?:[mts]es|[nv]os|leurs) jours|s temps)|tombée de la nuit)|(?:[mts]on|leur|[nv]otre) dernier souffle(?: de vie|)|ce que (?:mort s’ensuive|(?:j’en sache|tu en saches|(?:il|elle|on) en sache|nous en sachions|vous en sachiez|(?:ils|elles) en sachent) plus)|Noël|Pâques) <<- ~>> * -__[i](p_la_qqch)__ la (?:plupart du temps|main dans la main|mort dans l’âme) <<- ~>> * -__[i](p_le_qqch)__ le (?:cas échéant|moins (?:du monde|souvent)|plus (?:tôt|tard|souvent|de (?:temps|monde)) possible|moment venu|plus souvent) <<- ~>> * -__[i](p_là_qqch)__ là(?:-bas|-haut|-de(?:dans|hors|rrière|sso?us|vant)| non plus) <<- ~>> * -__[i](p_l_un_qqch)__ l’une? (?:après|pour|de(?:rrière|)|avec|contre|sur|près de) l’autre <<- ~>> * -__[i](p_le_pour_et_le_contre)__ le pour et le contre <<- ~>> =\0.replace(" ", "_") -__[i](p_les_uns_les_autres)__ les une?s (?:des |(?:après |pour |avec |contre |sur |derrière |devant |)les) autres <<- ~>> * -__[i](p_non_loin)__ non loin (?:d’ici|de là) <<- ~>> * -__[i](p_loin_qqch)__ loin (?:de (?:là|tout ça)|d’(?:être|ici)|s’en fa(?:ut|llait)) <<- ~>> * -__[i](p_maintes_fois)__ (?:[lcd]es |)maintes fois <<- ~>> * -__[i](p_malgré_pronom)__ malgré (?:[mt]oi|lui|elles?|[nv]ous|eux)(?! qui) <<- ~>> * -__[i](p_malgré_ça)__ malgré (?:ça|cela|tout) <<- ~>> * -__[i](p_manu_militari)__ manu militari <<- ~>> * -__[i](p_mieux_vaut_tard_que_jamais)__ mieux va(?:u|lai)t tard que jamais <<- ~>> * -__[i](p_moins_que_nécessaire)__ moins que (?:nécessaire|prévu) <<- ~>> * -__[i](p_moitié_qqch_moitié_qqch)__ moitié ({w2}),? moitié ({w2}) @@7,$ <<- ~>> * -__[i](p_mot_pour_mot)__ mot pour mot <<- ~>> * -__[i](p_mutatis_mutandis)__ mutatis mutandis <<- ~>> * -__[i](p_ne_vous_en_déplaise)__ ne (?:vous |l(?:ui|eur) |t’)en déplaise <<- ~>> * -__[i](p_nez_à_nez)__ nez à nez <<- ~>> * -__[i](p_ni_qqch)__ ni (?:de près,? ni de loin|plus ni moins|vu,? ni connu) <<- ~>> * -__[i](p_non_qqch)__ non (?:plus|sans raison|seulement) <<- ~>> * -__[i](p_nulle_part)__ nulle part <<- ~>> * -__[i](p_ô_combien)__ ô combien <<- ~>> * -__[i](p_ou_bien)__ ou (bien) @@3 <<- ~1>> * -__[i](p_ou_qqch_d_approchant)__ ou quelque chose d’approchant <<- ~>> * -__[i](p_où_bon_nous_semble)__ où bon (?:me|te|lui|nous|vous|leur) semble <<- ~>> * -__[i](p_oui_et_ou_non)__ oui (?:ou|et) non <<- ~>> * -__[i](p_outre_mesure)__ outre mesure <<- ~>> * -__[i](p_qqch_par_qqch)__ (une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize|vingt|trente|quarante|cinquante|soixante|cent|mille|éta[pg]e|morceau|pièce) par \1 @@0 <<- ~>> * -__[i](p_par_qqch1)__ par (?:à-coups|ailleurs|avance|chance|conséquent|curiosité|contre|défaut|définition|endroits|essence|ex(?:cellence|emple)|hasard|ici|inadvertance|là|moments|monts et par vaux|nature|principe|terre) <<- ~>> * -__[i](p_par_qqch2)__ par (?:la (?:même occasion|suite)|(?:bien des|certains) (?:aspects|côtés)|acquit de conscience|beau temps|bonté de cœur|ce biais|égard pour (?:moi|toi|lui|elles?|eux|nous|vous)(?! qui)|lui-même|elle(?:-même|)|eux(?:-mêmes|)|elles(?:-mêmes|)|le passé|les temps qui courent|[nv]ous-mêmes?|[mt]oi(?:-même|)|temps de pluie|tout le monde|voie (?:de (?:conséquence|mer|terre)|d’exception)) <<- ~>> * -__[i](p_par_ci_par_là)__ par-ci,? par-là <<- ~>> * -__[i](p_par_position)__ par-de(?:vant|rrière|ssus (?:le marché|tout)) <<- ~>> * -__[i](p_par_devers_pronom)__ par-devers (?:moi|toi|lui|elles?|lui|eux|nous|vous) <<- ~>> * -__[i](p_par_nombre_fois)__ par (?:deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize|vingt|trente|quarante|cinquante|soixante|cent) fois <<- ~>> * -__[i](p_parmi_qqch)__ parmi (?:[nv]ous(?: autres|)|eux|elles) <<- ~>> * -__[i](p_partant_de_là)__ partant de là <<- ~>> * -__[i](p_pas_qqch)__ pas (?:du tout|à pas|le moins du monde) <<- ~>> * -__[i](p_pendant_qqch)__ pendant (?:ce temps-là|(?:bien |si |assez |très |)longtemps|plusieurs (?:heures|minutes|secondes|mois|semaines|jours|années|siècles|millénaires|décennies)|quelque temps) <<- ~>> * -__[i](p_petit_à_petit)__ petit à petit <<- ~>> * -__[i](p_peu_qqch)__ peu (?:à peu|de temps auparavant|ou prou) <<- ~>> * -__[i](p_pile_poil)__ pile poil <<- ~>> * -__[i](p_plein_qqch)__ plein (?:nord|sud|ouest|de fois) <<- ~>> * +__purge_pronom_seul__ + [moi|toi] [seul|seule] + lui seul + elle seule + [nous|vous] [seuls|seules] + eux seuls + elles seules + <<- ~1>> * + + +__purge_début_phrase__ + car + de plus + et ?puis¿ + mais + m’ est avis [que|qu’|qu] + or donc + puis + [|,] grand bien lui fasse + <<- ~1:0>> * + + +__purge_horaires_et_durée__ + 24 [heures|h] [sur|/] 24 + 7 [jours|j] [sur|/] 7 + sept [jours|j] [sur|/] sept + vingt-quatre heures [sur|/] vingt-quatre + <<- ~>> * + + heure après heure + minute après minute + seconde après seconde + jour après jour + nuit après nuit + semaine après semaine + trimestre après trimestre + semestre après semestre + mois après mois + décennie après décennie + année après année + siècle après siècle + génération après génération + <<- ~>> * + + [à|de] ~\d\d? h ?~\d\d?¿ + [à|de] ~\d\d? h ?~\d\d?¿ [du|ce] [matin|soir] + [à|de] ~\d\d? h ?~\d\d?¿ de l’ après-midi + [à|de] ~\d\d? h ?~\d\d?¿ cet après-midi + [à|de] ~\d\d? h ?~\d\d?¿ demain [matin|soir|après-midi] + <<- ~>> * + +TEST: Le train de 2 h 47 {{arriveraient}} en retard. +TEST: Le train de 2 h 47 du matin {{arriveraient}} en retard. + + +__purge_prépositions_qqn__ + [après|avant|avec|pour|contre|sans|envers|chez|en|malgré|selon] les uns et les autres [|,|@:[VXG]¬>qui] + <<- ~1:6>> * + + [après|avant|avec|pour|contre|sans|envers|chez|d’|D’|malgré|selon] on ne sait [qui|quoi] [|,|@:[VXG]¬>qui] + <<- ~1:5>> * + + [après|avant|avec|pour|contre|sans|envers|chez|de|en|malgré|selon] tout un chacun [|,|@:[VXG]¬>qui] + [après|avant|avec|pour|contre|sans|envers|chez|de|en|malgré|selon] tout le monde [|,|@:[VXG]¬>qui] + <<- ~1:4>> * + + [après|avant|avec|pour|contre|sans|envers|chez|de|en|malgré] tout ça [|,|@:[VXG]¬>qui] + [après|avant|avec|pour|contre|sans|envers|chez|de|en|malgré|selon] [vous|nous] autres [|,|@:[VXG]¬>qui] + <<- ~1:3>> * + + [après|avant|avec|pour|contre|sans|envers|chez|de|d’|D’|en|malgré|selon] [autrui|quelqu’un|quelqu’une] [|,|@:[VXG]¬>qui] + [après|avant|avec|envers|chez|malgré|selon] {pronom_obj} [|,|@:[VXG]¬>qui] + [contre|pour|sans|de|en] [moi|toi|soi|elle|eux|elles|moi-même|toi-même|soi-même|lui-même|elle-même|nous-mêmes|vous-même|vous-mêmes|eux-mêmes|elles-mêmes] [|,|@:[VXG]¬>qui] + <<- ~1:2>> * + + par égard pour [moi|toi|soi|elle|eux|elles|moi-même|toi-même|soi-même|lui-même|elle-même|nous-mêmes|vous-même|vous-mêmes|eux-mêmes|elles-mêmes] [|,|@:[VXG]¬>qui] + <<- ~1:4>> * + + en [moi|toi|soi|elle|eux|elles|moi-même|toi-même|soi-même|lui-même|elle-même|nous-mêmes|vous-même|vous-mêmes|eux-mêmes|elles-mêmes] + <<- ~>> * + + [après|avant|avec|pour|contre|sans|envers|chez|de|en|malgré|selon] [celui-ci|celui-là|celle-ci|celle-là|ceux-ci|ceux-là|celles-ci|celles-là] + <<- ~>> * + + entre [moi|toi|lui|elle|elles|nous|vous|eux] et [moi|toi|lui|elle|elles|nous|vous|eux] + entre [nous|vous|eux|elles] [deux|trois|quatre|cinq|six|sept|huit|neuf|dix] + <<- ~>> * + + ni [après|avec|chez|contre|de|derrière|devant|envers|malgré|pour|sans|sous|sur] [moi|toi|lui|elle|elles|eux|nous|vous] ?,¿ ni [après|avec|chez|contre|de|derrière|devant|envers|malgré|pour|sans|sous|sur] [moi|toi|lui|elle|elles|eux|nous|vous] + <<- ~>> * + + parmi [nous|vous] ?autres¿ + parmi [eux|elles] + <<- ~>> * + + par-devers [moi|toi|lui|elle|elles|lui|eux|nous|vous] + <<- ~>> * + + quant à [moi|toi|lui|elle|elles|lui|eux|nous|vous] [|,|@:[VXG]¬>qui] + <<- ~1:3>> * + +TODO: comme + + +__simplifications_partielles__ + comme tant d’ autres @:R + <<- ~1:4>> * + + en cours @¬>de + <<- ~1:2>> * + + et / ou + <<- ~2:3>> * + + personne d’ autre [que|qu’|qu] [moi|toi|lui|elle|elles|nous|vous|eux] + <<- ~2:0>> * + + +__purge_locutions_latines__ + [a|à] [priori|postériori|posteriori|contrario|cappella|minima] + ab [absurdo|initio] + ad [hoc|hominem|infinitum|nauseam|valorem|patres] + ad vitam æternam + ex [nihilo|cathedra|absurdo|abrupto] + id est + in [abstracto|extenso|extremis|fine|petto|situ|utero|vitro|vivo] + ipso facto + mutatis mutandis + <<- ~>> * + + +__purge_locutions__ + à ~\d+(?:,\d+|) % + à [autrui|bâbord|califourchon|chacun|confesse|contrecœur|contre-cœur|contretemps|demi-mot|foison|grand-peine|loisir|merveille|moitié|nouveau|outrance|peine|perpétuité|présent|raison|rallonge|rebrousse-poil|reculons|regret|renverse|risque|tâtons|tort|tribord|tout-va] + à aucun prix + à autre chose + à bas [cout|coût|prix] + à bâtons rompus + à beaucoup près + à belles dents + à bien des égards + à bien pire + à bon [compte|escient|droit] + à bout de [bras|souffle|force|forces|nerf|nerfs] + à bout [portant|touchant] + à bras ouverts + à bras le corps + à brève échéance + à but ?non¿ lucratif + à cause [de|d’] [ça|moi|toi|lui|nous|vous|elle|elles|eux] + à ce [compte-là|moment-là|titre] + à cet égard + à cet instant ?[exact|précis]¿ + à cette [date|occasion] + à cette époque + à cette époque de l’ année + à cette heure + à cette heure du jour + à cette heure de la [journée|nuit] + à cette heure [tardive|matinale] + à ciel ouvert + à chaque [fois|instant] + à chaudes larmes + à cœur [joie|ouvert|perdu] + à corps perdu + à côté [de|d’] [ça|moi|toi|lui|nous|vous|elle|elles|eux] + à couilles rabattues + à coup sûr + à couper le souffle + à court terme + à courte [échéance|portée] + à des kilomètres à la ronde + à défaut d’autre chose + à dose homéopathique + à durée limitée + à de [nombreuses|multiples] reprises + à double [titre|tranchant] + à en juger par [mon|ton|son|notre|votre|leur] expérience + à en perdre haleine + à en perdre la tête + à faible [allure|revenu] + à feu et à sang + à flanc de [colline|montagne] + à fleur de peau + à franchement parler + à géométrie variable + à grande échelle + à haut risque + à hue et à dia + à huis clos + à intervalles [irréguliers|réguliers] + à juste [raison|titre] + à l’ heure actuelle + à l’ heure [qu’|qu] il est + à l’ accoutumée + à l’ amiable + à l’ avance + à l’ avenir + à l’ avenir incertain + à l’ avenant + à l’ air libre + à l’ aveuglette + à l’ emporte-pièce + à l’ échelle [nationale|mondiale|régionale|départementale|cantonale|locale|galactique|universelle] + à l’ évidence + à l’ exclusion de toute autre chose + à l’ improviste + à l’ inverse + à l’ occasion + à l’ ordre du jour + à l’ œil nu + à l’ en croire + à l’ unanimité + à l’ un d’ entre eux + à l’ une d’ entre elles + à l’ [un|une] des leurs + à la [bourre|con|dérive|dérobée|diable|fois|leur|longue|manque|mords-moi-le-nœud|papa|ramasse|renverse|redresse|rescousse|sauvette|volée] + à la bonne franquette + à la limite du supportable + à la lumière de tout [ceci|cela|ça] + à la petite semaine + à la pointe du progrès + à la première occasion + à la queue leu leu + à la surprise générale + à la virgule près + à long terme + à longue [échéance|portée] + à longueur [de|d’] [temps|journée|année] + à loyer modéré + à main [armée|droite|gauche|levée] + à mains nues + à maints égards + à maintes reprises + à marche forcée + à merveille + à [midi|minuit] ?pile¿ + à [mi-course|mi-distance|mi-temps] + à moindres frais + à mots couverts + à moyen terme + à moyenne échéance + à [mes|tes|ses|nos|vos|leurs] [côtés|dépens|trousses] + à [mes|tes|ses|nos|vos|leurs] risques et périls + à [ma|ta|sa|notre|votre|leur] [connaissance|disposition|guise|portée] + à [ma|ta|sa|notre|votre|leur] grande [surprise|tristesse] + à [ma|ta|sa|notre|votre|leur] juste mesure + à [mon|ton|son|notre|votre|leur] [avis|détriment|encontre|égard|insu|sujet|tour] + à [mon|ton|son|notre|votre|leur] [cœur|corps] défendant + à [mon|ton|son|notre|votre|leur] grand [désarroi|soulagement] + à n’ en pas douter + à n’ en plus finir + à n’ en point douter + à parler franc + à part [entière|ça|cela|ceci] + à parts égales + à partir [de|d’] [aujourd’hui|ici|là|maintenant|rien] + à partir [de|d’] [demain|hier] ?[matin|midi|soir]¿ + à pas de [géant|loup|tortue|velours] + à personne en danger + à perte de vue + à petit feu + à petite [dose|échelle] + à peu de choses près + à peu de [distance|frais] + à peu près + à pieds joints + à pile ou face + à plat ventre + à plate couture + à plein [régime|temps|nez] + à pleins poumons + à plus forte raison + à plus d’un titre + à point nommé + à portée de [main|tir] + à première vue + à prix [cassé|modique|cassés|modiques] + à proprement parler + à qui de droit + à qui mieux mieux + à qui que ce soit + à quelque distance + à quelques [exceptions|nuances] près + à quelques-uns d’ entre [nous|vous|eux] + à quelques-unes d’ entre [nous|vous|elles] + à ras [bord|bords] + à rude épreuve + à s’ y méprendre + à somme nulle + à tel point + à temps [plein|partiel|complet] + à tête reposée + à tire d’ [aile|ailes] + à [tire-d’aile|tire-d’ailes] + à titre [conservatoire|expérimental|indicatif|informatif|grâcieux|personnel|posthume] + à titre d’ exemple + à tombeau ouvert + à tort ou à raison + à tort et à travers + à tour de [bras|rôle] + à tout [âge|crin|instant|jamais|moment|prix] + à tout bout de champ + à tout le [moins|monde] + à tout point de vue + à tout un chacun + à toute [allure|bride|épreuve|force|vitesse|volée] + à toute heure + à toute heure du jour + à toute heure du jour et de la nuit + à toute heure de la nuit + à toute heure de la nuit et du jour + à tous crins + à tous points de vue + à toutes fins utiles + à toutes jambes + à tu et à toi + à un moment donné + à usage interne + à visage découvert + à visage humain + à vive allure + à voix [haute|basse] + à vol d’ oiseau + à vrai dire + à vue d’ œil + à ?bien¿ y regarder de plus près + à ?bien¿ y [penser|réfléchir|songer|repenser] + advienne que pourra + ah + après cette date ?fatidique¿ + après [moi|toi|soi|lui|eux] + après mûre réflexion + après tout , + après un certain temps + après un bon bout de temps + au-dessus [de|d’] {pronom_obj} + au-delà du descriptible + au [dernier|même|bon|mauvais] [moment|instant] + au bas mot + au beau fixe + au bon moment + au bout du [compte|rouleau] + au bout d’ un moment + au cas par cas + au commencement + au contraire + au coude à coude + au coup par coup + au cours des @:B [dernières|derniers|prochaines|prochains] [années|mois|siècles] <<- ~>> * + au demeurant + au doigt mouillé + au débotté + au début + au fil des ans + au fil du temps + au grand [complet|jamais] + au hasard + au jour et à l’ heure dits + au jugé + au le jour + au leur + au lieu de [cela|ceci|ça|quoi] + au loin + au milieu de nulle part + au moment opportun + au même titre que n’ importe [laquelle|lequel] d’ entre [nous|vous|eux|elles] + au pas de [charge|course] + au plus [près|pressé|vite|tôt|tard] + au plus haut point + au premier abord + au propre comme au figuré + au préalable + au quotidien + au ras des pâquerettes + au saut du lit + au sens [figuré|large|propre] + au surplus + au ~[xXvViI]+[eᵉ] siècle + ?tout¿ au fond [de|d’] {pronom_obj} + aux [abois|leurs|mien|miens|mienne|miennes|tien|tiens|tienne|tiennes|sien|siens|sienne|siennes|nôtres|vôtres] + autant que [nécessaire|possible|prévu] + autant que faire se peut + autour [de|d’] {pronom_obj} + autrement dit + av. J.-C. + avant longtemps + avant terme + avant tout le monde + avant toute chose + avant toutes choses + avant d’ aller plus loin + avant J.-C. + avant Jésus-Christ + avant d’ en arriver là + avant de faire quoi que ce soit + avant de faire quoi que ce soit [de|d’] ?@:W¿ [stupide|crétin|con|idiot] + avant [qu’|qu] il ne soit trop tard + avant un bon bout de temps + avec [brio|joie|légèreté|insistance|peine] + avec autre chose + avec le plus grand soin + avec pertes et fracas + avec un peu de chance + avec tout le respect que je [vous|te|leur|lui] dois + avec tout le respect que nous [vous|te|leur|lui] devons + avec tout un chacun + avec un peu de chance + beaucoup [plus|moins] + bel et bien + bien assez tôt + bien des fois + bien souvent + bon gré ?,¿ mal gré + bras dessus ?,¿ bras dessous + çà et là + ce faisant + [cela|ça|ceci] mis à part + [cela|ça|ceci] va sans dire + ces derniers temps + cette [fois-là|fois-ci] + chaque fois + comme avant + comme autrefois + comme d’ habitude + comme toujours + comme de juste + comme bon [me|te|lui|leur|nous|vous] semble + comme au bon vieux temps + comme cul et chemise + comme [frappé|frappée|frappés|frappées] par la foudre + comme n’ importe où ?ailleurs¿ + comme par [enchantement|magie] + comme par un fait exprès + comme promis + comme qui dirait + comme si de rien n’ était + contrairement aux apparences + contre mauvaise fortune,? bon cœur + contre nature + contre toute [attente|vraisemblance] + contre vents et marées + contre [mon|ton|son|notre|votre|leur] gré + côte à côte + [coute|coûte] que [coute|coûte] + [croyez-le|crois-le] ou [non|pas] + cul par-dessus tête + dans [ce|ces] [cas-là|cas-ci] + dans ce cas [précis|particulier] + dans ces cas [précis|particuliers] + dans l’ [idéal|immédiat] + dans la mesure du possible + dans les années ~\d\d+ + dans peu de temps + dans tout [cela|ça|ceci] + dans très peu de temps + dans un cas comme dans l’autre + dans une [certaine|large|moindre] mesure + début {mois} ~\d\d{2,5} + au début {mois} ~\d\d{2,5} + en ce début {mois} ~\d\d{2,5} + d’ abord + d’ affilée + d’ ailleurs + d’ année en année + d’ aujourd’hui + d’ antan + d’ autant [plus|moins] + d’ [autrefois|part] + d’ autres fois + d’ [arrache-pied|arrachepied] + d’ arrière en avant + d’ avant en arrière + d’ à côté + d’ âge mûr + d’ emblée + d’ empoigne + d’ en face + d’ entre [nous|vous|eux|elles] + d’ entrée de jeu + d’ est en ouest + d’ extrême [droite|gauche] + d’ [extrême-droite|extrême-gauche] + d’ [égal|égale] à [égal|égale] + d’ habitude + d’ heure en heure + d’ hier ?[matin|soir|après-midi]¿ + d’ ici ?[là|peu]¿ + d’ ici peu de temps + d’ ordinaire + d’ origine [inconnue|douteuse|plébéienne|aristocratique] + d’ ordre général + d’ où [qu’|qu] [il|elle|on] vienne + d’ où [qu’|qu] [ils|elles] viennent + d’ ouest en est + d’ [ors|ores] et déjà + d’ un autre côté + d’ un [bout|jour] à l’ autre + d’ un côté comme de l’ autre + d’ un commun accord + d’ une autre trempe + d’ une [façon|manière] ou d’une autre + d’ une certaine [façon|manière] + d’ une tout autre ampleur + d’ une [minute|seconde] à l’ autre + de-ci ?,¿ de-là + de ~\d+(?:,\d+|) % + de [cesse|conserve|facto|fait|guingois|luxe|nouveau|permanence|partout|préférence|profundis|rechange|routine|surcroît|surcroit|visu] + de A à Z + de bas (?:en haut|étage) + de basse [condition|extraction] + de bon [aloi|cœur|gré|matin|sens|ton] + de bonne [facture|famille|foi|heure|humeur|grâce|qualité|compagnie] + de bric et de broc + de but en blanc + de ce fait ?[incontestable|irréfutable|même]¿ + de ce seul fait + de ce point de vue + de cette sorte + de cet acabit + de courte [durée|vue] + de dernière minute + de demain [matin|soir|après-midi] + de droite à gauche + de droite comme de gauche + de fâcheuse mémoise + de fil en aiguille + de fond en comble + de fort [loin|près] + de [fraîche|fraiche] date + de [gaieté|gaîté|gaité] de cœur + de gauche à droite + de gauche comme de droite + de grande [taille|envergure] + de gré ou de force + de guerre lasse + de haut en bas + de haut [rang|vol] + de haute [lutte|stature|volée] + de jour comme de nuit + de là-bas + de la meilleure [manière|façon] possible + de la même [façon|manière] + de la sorte + de la tête aux pieds + de la veille + de loin + de loin en loin + de longue [date|durée|haleine] + de main de [maître|maitre] + de mauvais [aloi|goût|gout|gré] + de mauvaise [foi|grâce|humeur] + de mieux en mieux + de nature [étrangère|inconnue|indéterminée|insolite] + de nombreuses années plus [tôt|tard] + de nombreuses fois + de nos jours + de notoriété publique + de nulle part + de pire en pire + de près + de près ou de loin + de par le monde ?entier¿ + de part et d’autre + de petite taille + de pied ferme + de plein [droit|fouet] + de plus [belle|près] + de premier [ordre|plan] + de première [catégorie|main|nécessité] + de prime abord + de proche en proche + de pure forme + de sang-froid + de seconde [catégorie|zone|importance|main] + de si bon matin + de si bonne heure + de source sûre + de taille moyenne + de telle sorte + de temps à autre + de temps en temps + de [très|trop] [loin|près] + de vive voix + de [nous|vous] tous + de tous [côtés|bords] + de tout [poil|temps] + de tout à l’ heure + de tout premier [ordre|plan] + de toute [éternité|évidence|façon|urgence] + de toutes [parts|pièces|sortes] + de toutes [mes|tes|ses|nos|vos|leurs] forces + de [mon|ton|son|notre|votre|leur] mieux + de [mon|ton|son|notre|votre|leur] plein gré + de [mon|ton|son|notre|votre|leur] point de vue + de [mon|ton|son|notre|votre|leur] propre [cru|chef] + de [mon|ton|son|notre|votre|leur] vivant + de [ma|ta|sa|notre|votre|leur] part + de moins en moins + de plus en plus + de mieux en mieux + de pire en pire + de jour en jour + de minute en minute + de semaine en semaine + de mois en mois + de trimestre en trimestre + de semestre en semestre + de siècle en siècle + de millénaire en millénaire + de décennie en décennie + [après-demain|demain] ?[matin|soir|après-midi]¿ + avant demain ?[matin|soir|après-midi]¿ + depuis @:B [ans|années|mois|semaines|jours|heures|minutes|secondes] + depuis ~\d+ [ans|années|mois|semaines|jours|heures|minutes|secondes] + depuis assez longtemps + depuis belle lurette + depuis bien longtemps + depuis de ?très¿ longues années + depuis des lustres + depuis les années ~\d\d+ + depuis longtemps + depuis lors + depuis peu de temps + depuis quelque temps + depuis quelques [secondes|minutes|heures|jours|semaines|mois|trimestres|semestres|années|décennies|siècles|millénaires] + depuis si longtemps + depuis toujours + depuis tout ce temps + depuis très longtemps + des fois + des pieds à la tête + des uns et des autres + des [années|mois|siècles|millénaires|décennies|semaines] plus [tôt|tard] + dès [maintenant|lors|aujourd’hui] + dès à présent + dès que possible + dès [demain|hier] ?[soir|matin|après-midi]¿ + Dieu [m’|t’] en [garde|préserve] + Dieu [m’|t’] en soit témoin + Dieu [nous|vous] en [garde|préserve] + Dieu [nous|vous] en soit témoin + Dieu [les|l’] en [garde|préserve] + Dieu [leur|lui] en soit témoin + du ~[xXvViI]+[eᵉ] siècle + du [Ier|Iᵉʳ|1er|1ᵉʳ] siècle + du bout des lèvres + du début à la fin + du fond du cœur + du jour au lendemain + du haut en bas + du même [acabit|coup|ordre|tonneau] + du moins ?,¿ pas + du [nord|sud] au [nord|sud] + du tout au tout + eh bien + en \d\d+ [ans|années|mois|semaines|jours|heures|minutes|secondes] + en [aparté|apparence|arrière|avance|avant|cachette|ceci|cela|clair|commun|conséquence|continu|contrepartie|définitive|détail|direct|douce|effet|émoi|filigrane|général|goguette|hâte|majorité|outre|pâmoison|parallèle|partie|particulier|permanence|personne|pratique|prime|privé|principe|priorité|public|réalité|retour|revanche|rien|rogne|route|secret|silence|somme|suspens|théorie|trompe-l’œil|vain|vérité|ville|vitesse] + en aucun cas + en aucune [circonstance|façon|manière] + en bon état + en bonne [compagnie|posture|voie] + en bonne et due forme| + en bonne santé ?[physique|mentale|psychique]¿ + en bout de course + en cas [de|d’] [besoin|doute|urgence] + en [chacun|chacune] [de|d’] [nous|vous|eux|elles] + en chair et en os + en chute libre + en comparution immédiate + en connaissance de cause + en coupe réglée + en cours de route + en d’autres [circonstances|termes|temps] + en de telles circonstances + en début [de|d’] [journée|matinée|soirée|après-midi] + en définitive + en dehors de ?tout¿ [ça|cela|ceci] + en dents de scie + en dernier [lieu|recours|ressort] + en désespoir de cause + en détention provisoire + en direction de l’ [est|ouest] + en direction du [nord|nord-est|nord-ouest|sud|sud-est|sud-ouest] + en état de choc ?circulatoire¿ + en état de marche + en état d’ ébriété ?avancée¿ + en excellent état + en file indienne + en fin [de|d’] [compte|journée|matinée|soirée|après-midi] + en forte [baisse|hausse] + en gage de bonne foi + en garde à vue ?prolongée¿ + en grand nombre + en grende [difficulté|majorité|partie|pompe] + en haut lieu + en l’occurrence + en lieu sûr + en ligne de [compte|mire] + en mains propres + en mauvais état + en mauvaise [posture|santé] + en même temps + en milieu [de|d’] [journée|matinée|soirée|après-midi] + en nombre suffisant + en nombre plus que suffisant + en partant de zéro + en plein [air|cœur|jour] + en pleine [gueule|figure|forme|poire|nuit|tronche] + en perte de vitesse + en peu de temps + en piteux état + en point [de|d’] [mire|orgue] + en position de [force|faiblesse] + en premier lieu + en première [instance|ligne] + en pure perte + en quantité suffisante + en quantité plus que suffisante + en quelque sorte + en queue de peloton + en rangs serrés + en rase campagne + en règle générale + en roue libre + en sens inverse + en si peu de temps + en sous-main + en tête à tête + en temps et en heure + en temps [normal|opportun|ordinaire|utile|voulu] + en termes choisis + en toile de fond + en tous les cas + en tous les sens + en tout bien tout honneur + en tout [cas|genre|lieu|sens|temps] + en tout et pour tout + en tout état de cause + en tout premier lieu + en toute bonne foi + en toute connaissance de cause + en toute [circonstance|confiance|discrétion|franchise|hâte|impartialité|impunité|innocence|légalité|liberté|logique|sécurité|simplicité] + en toutes circonstances + en un clin d’œil + en un rien de temps + en une autre occasion + en vase clos + en voie de développement + en y réfléchissant bien + en [janvier|février|mars|avril|mai|juin|juillet|août|aout|septembre|octobre|novembre|décembre] dernier + en [mon|ton|son|leur|notre|votre] âme et conscience + en [mon|ton|son|leur|notre|votre] for intérieur + en [mon|ton|son|leur|notre|votre] nom propre + en ce [moment|temps-là] + en ce qui [me|te|le|la|les|nous|vous] [concerne|concernait] + en cet instant + encore une fois + encore et [encore|toujours] + entre {mois} ?~\d{2,5}¿ et {mois} ?~\d{2,5}¿ + entre chien et loup + entre de [bonnes|mauvaises] mains + entre l’ [un|une] et l’ autre + entre les uns et les autres + entre [quatre|quatr’|quat’] [zyeux|yeux] + entre [quatre-zyeux|quatr’zyeux|quat’zyeux|quatre-yeux|quatr’yeux|quat’yeux] + envers et contre tout + épaule contre épaule + et ainsi de suite + et tutti quanti + été comme hiver + euh + face à face + @:B fois de suite + grosso modo + [hier|avant-hier] ?[matin|soir|après-midi]¿ + hors [de|d’] [contrôle|portée|atteinte] + hors d’ état de nuire + hors du commun + ici [comme|ou] ailleurs + ici et [là|maintenant] + ici même + ici-bas + il y a ?très¿ longtemps + il y a ?très¿ peu de temps + il y a quelques [secondes|minutes|heures|jours|semaines|mois|année|ans|siècles|millénaires] + il y a moins de ~\d+ [secondes|minutes|heures|jours|semaines|mois|année|ans|siècles|millénaires] + il y a ~\d+ [secondes|minutes|heures|jours|semaines|mois|année|ans|siècles|millénaires] + il y a quelque temps + il n’y a pas ?si¿ longtemps + illico presto + j’ en passe et des [meilleurs|meilleures] + jour pour jour + [jusqu’|jusqu] [alors|ici|aujourd’hui|Noël|Pâques] + [jusqu’|jusqu] au bout des ongles + [jusqu’|jusqu] au nouvel an + [jusqu’|jusqu] à aujourd’hui + [jusqu’|jusqu] à bac + [jusqu’|jusqu] à présent + [jusqu’|jusqu] à maintenant + [jusqu’|jusqu] à récemment + [jusqu’|jusqu] à [demain|hier] ?[matin|soir|après-midi]¿ + [jusqu’|jusqu] à nouvel ordre + [jusqu’|jusqu] à plus ample informé + [jusqu’|jusqu] à plus soif + [jusqu’|jusqu] à preuve du contraire + [jusqu’|jusqu] à la fin de [mes|tes|ses|nos|vos|leurs] jours + [jusqu’|jusqu] à la fin des temps + [jusqu’|jusqu] à la tombée de la nuit + [jusqu’|jusqu] à [mon|ton|son|notre|votre|leur] dernier souffle + [jusqu’|jusqu] à [mon|ton|son|notre|votre|leur] dernier souffle de vie + [jusqu’|jusqu] à ce que mort s’ensuive + [jusqu’|jusqu] à ce que [j’|il|elle|on] en sache plus + [jusqu’|jusqu] à ce que tu en saches plus + [jusqu’|jusqu] à ce que nous en sachions plus + [jusqu’|jusqu] à ce que vous en sachiez plus + [jusqu’|jusqu] à ce que [ils|elles] en sachent plus + jusque-là + la plupart du temps + la main dans la main + là-bas + là-haut + là-dedans + là-dehors + là-derrière + là-dessous + là-dessus + là-devant + là non plus + la mort dans l’ âme + le cas échéant + le moins du monde + le [moins|plus] [tôt|tard|souvent] + le [moins|plus] de [temps|monde] possible + le moment venu + les [uns|unes] des autres + les [uns|unes] [après|avec|chez|contre|de|derrière|devant|envers|malgré|pour|sans|sous|sur] les autres + l’ [un|une] [après|avec|chez|contre|de|derrière|devant|envers|malgré|pour|sans|sous|sur] l’ autre + l’ [un|une] près de l’autre + loin [de|d’] là + loin [de|d’] tout [ça|cela|ceci] + loin d’ [être|ici] + loin s’ en [faut|fallait] + maintes fois + malgré [ça|cela|ceci|tout] + manu militari + mieux [vaut|valait] tard que jamais + moins que [nécessaire|prévu] + moitié ** ?,¿ moitié ** + mot pour mot + ne [lui|leur|m’|t’|nous|vous] en déplaise + nez à nez + non loin [de|d’] [ici|là] + nulle part + ô combien + oh + ou quelque chose d’ approchant + où bon [me|te|lui|nous|vous|leur] semble + oui [ou|et] non + outre mesure + ni de près ?,¿ ni de loin + ni plus ?,¿ ni moins + ni vu ?,¿ ni connu + non [plus|seulement] + non sans raison + quant à présent + par [à-coups|ailleurs|avance|chance|conséquent|curiosité|contre|défaut|définition|endroits|essence|excellence|exemple|hasard|ici|inadvertance|là|moments|nature|principe|terre] + par acquit de conscience + par beau temps + par bien des [aspects|côtés] + par bonté de cœur + par ce biais + par certains [aspects|côtés] + par la même occasion + par la suite + par le passé + par les temps qui courent + par monts et par vaux + par temps de pluie + par tout le monde + par voie de [conséquence|mer|terre] + par voie d’exception + par @:B fois + un par un + une par une + deux par deux + trois par trois + quatre par quatre + cinq par cinq + six par six + sept par sept + huit par huit + neuf par neuf + dix par dix + onze par onze + douze par douze + treize par treize + quatorze par quatorze + quinze par quinze + seize par seize + vingt par vingt + trente par trente + quarante par quarante + cinquante par cinquante + soixante par soixante + cent par cent + mille par mille + bout par bout + étage par étage + étape par étape + fragment par fragment + morceau par morceau + niveau par niveau + pièce par pièce + par-ci ?,¿ par-là + par-devant + par-derrière + par-dessus le marché + par-dessus tout + partant de là + pas du tout + pas à pas + pas le moins du monde + pendant ce temps-là + pendant ?[bien|si|assez|très]¿ longtemps + pendant plusieurs [heures|minutes|secondes|mois|semaines|jours|années|siècles|millénaires|décennies] + pendant quelque temps + petit à petit + peu à peu + peu de temps auparavant + peu ou prou + pile poil + plein [nord|sud|ouest] + plein de fois + plus bas que terre + plus d’ une fois + plus du tout + plus jamais + plus que [nécessaire|prévu|jamais] + plus que tout au monde + plus que toute autre chose + plus [tôt|tard] que [prévu|nécessaire] + plusieurs fois + plusieurs fois de suite + pour ainsi dire + pour ce faire + pour ce que [j’|tu] en [sais|savais] + pour couronner le tout + pour de bon + pour faire bonne mesure + pour faire simple + pour la [première|seconde|dernière|~ième$] fois + pour la [première|seconde|dernière|~ième$] fois de ma vie + pour la [première|seconde|dernière|~ième$] fois de suite + pour la suite + pour le [moment|moins] + pour le meilleur et pour le pire + pour l’ [essentiel|instant|heure] + pour quelque [part|temps] + pour rien au monde + pour tout dire + pour un oui ou pour un non + pour une fois + pour y parvenir + pour ça [vaut|valait] + pour [ma|ta|sa|notre|votre|leur] [gouverne|part] + pour [mon|ton|son|notre|votre|leur] propre [compte|bien] + pour [celui|celle|ceux|celles] que [ça|cela|ceci] intéresse + pour [celui|celle|ceux|celles] et [celui|celle|ceux|celles] que [ça|cela|ceci] intéresse + pour [m’|t’|s’|nous|vous] en rendre compte + quand bien même + quand bon [me|te|lui|nous|vous|leur] [semble|semblera|semblait] + quant à [ça|cela|ceci] + que [ça|ceci|cela] [me|te|lui|leur|nous|vous] plaise ou non + que je le veuille ou non + que tu le veuilles ou non + [qu’|qu] [il|elle|on] le veuille ou non + que vous le vouliez ou non + que nous le voulions ou non + [qu’|qu] [ils|elles] le veuillent ou non + [qu’|qu] à cela ne tienne + quel [qu’|qu] en soit le [moyen|prix|danger] + quel [qu’|qu] en soit le risque ?financier¿ + quelle [qu’|qu] en soit la [cause|raison] + quelque [part|temps] + quelques fois + quelques [instants|secondes|minutes|heures|jours|semaines|mois|années|décennies|siècles|millénaires|trimestres|semestres] auparavant + quelques [instants|secondes|minutes|heures|jours|semaines|mois|années|décennies|siècles|millénaires|trimestres|semestres] plus [tard|tôt] + qui plus est + quoi [qu’|qu] il [arrive|arrivât|advienne|advînt] + quoi [qu’|qu] il en [coûte|coûtât|coute|coutât] + sans [grande|grosse] difficulté ?[apparente|aucune|financière|majeure|particulière]¿ + sans [ambages|arrêt|cesse|conteste|doute|encombre|encombres|fin|relâche|répit|trêve|vergogne] + sans aucun doute + sans autre forme de procès + sans commune mesure + sans coup férir + sans crier gare + sans difficulté ?[apparente|aucune|financière|majeure|particulière]¿ + sans dire mot + sans états d’ âme + sans foi ?,¿ ni loi + sans l’ ombre d’ un doute + sans le faire exprès + sans le vouloir + sans mot dire + sans nul doute + sans queue ni tête + sans raison apparente + sans ?grand¿ succès + sans faire de vagues + sans s’ en rendre compte + sans s’ en apercevoir + sans l’ aide de personne + sans y faire attention + sans y prendre [garde|goût|gout] + sans y [parvenir|réussir|réfléchir|songer|penser] + sans pour autant y faire attention + sans pour autant y prendre [garde|goût|gout] + sans pour autant y [parvenir|réussir|réfléchir|songer|penser] + séance tenante + selon toute vraisemblance + semble-t-il + semblait-il + sens dessus dessous + [seule|seul] à [seule|seul] + s’ il [te|vous] [plaît|plait] + si besoin est + si [bas|haut|longtemps|nécessaire|possible|soudain] + si [cela|ça|ceci] ne tenait [qu’|qu] à [moi|toi|lui|eux|elle|elles|nous|vous] + six pieds sous terre + sine die + sine qua non + soit dit en passant + soi-disant + sous aucun prétexte + sous bonne [escorte|garde] + sous coupe réglée + sous haute surveillance + stricto sensu + sur ce , + sur ce plan-là + sur le [long|moyen|court] terme + sur le qui-vive + sur la forme comme sur le fond + sur la même longueur d’ onde + sur [mon|ton|son|notre|votre|leur] [trente-et-un|31] + sur [mon|ton|son|notre|votre|leur] trente et un + tant bien que mal + tant s’ en faut + tôt ou tard + tous comptes faits + tous frais payés + tout à [fait|coup] + tout à l’ heure + tout au plus + tout aussi bien + tout bien [considéré|réfléchi] + tout compte fait + tout de [même|suite|go] + tout du long + tout [bonnement|simplement] + tout feu ?,¿ tout [flamme|flammes] + tout le temps + toutes affaires cessantes + toutes choses égales par ailleurs + toutes griffes dehors + toutes proportions gardées + trait pour trait + très [bas|haut|bien|mal] + un à un + une à une + un jour ou l’autre + un instant plus [tôt|tard] + un [millier|million|milliard] de fois + un moment plus [tôt|tard] + un peu mieux + un peu moins bien + un peu partout + un peu plus [tôt|tard] que prévu + un tant soit peu + une à une + une autre fois + une bonne fois pour toutes + une dernière fois + une fois de plus + une fois n’ est pas coutume + une fois pour toutes + urbi et orbi + vaille que vaille + ventre à terre + vers nulle part + <<- ~>> * + + +@@@@ +@@@@END_GRAPH +@@@@ + + __[i](p_plus_avant)__ plus avant(?! de | que?) <<- ~>> * -__[i](p_plus_qqch)__ plus (?:du tout|que (?:nécessaire|prévu|jamais|tout(?: au monde|e autre chose))|jamais|bas que terre|d’une fois) <<- ~>> * -__[i](p_plusieurs_fois)__ plusieurs fois(?: de suite)? <<- ~>> * -__[i](p_pour_qqch)__ pour (?:autrui|le (?:moment|moins|meilleur et pour le pire)|une fois|l’(?:essentiel|instant)|l’heure|de bon|la suite|un oui ou pour un non|ainsi dire|ce faire|quelque (?:part|temps)|tout (?:le monde|un chacun|dire)|faire (?:bonne mesure|simple)|y parvenir|couronner le tout|rien au monde|ce que (?:(?:j’|tu )en sais)|ça va(?:ut|lait)) <<- ~>> * -__[i](p_pour_pronom)__ pour (?:[mt]oi|elles?|eux|ça|cela|ceci|ceux-(?:là|ci)|celles?-(?:là|ci))(?! qui) <<- ~>> * -__[i](p_pour_xxx_fois)__ pour la (?:première|seconde|{w_2}ième|dernière) fois(?: de suite| de ma vie|) <<- ~>> * -__[i](p_pour_det_fem_qqch)__ pour (?:[mts]a|[nv]otre|leur) (?:gouverne|part) <<- ~>> * -__[i](p_pour_det_mas_qqch)__ pour (?:[mts]on|[nv]otre|leur) propre (?:compte|bien) <<- ~>> * -__[i](p_pour_xxx_que_ça_intéresse)__ pour ce(?:lles?|ux|lui) (?:et ce(?:lles?|ux|lui) |)que (?:ça|ce(?:la|ci)) intéresse <<- ~>> * -__[i](p_pour_s_en_rendre_compte)__ pour (?:[mts]’|[vn]ous )en rendre compte <<- ~>> * -__[i](p_quand_qqch)__ quand b(?:ien même|on (?:[mt]e|l(?:ui|eur)|[nv]ous) semble) <<- ~>> * -__[i](p_quant_à_pronom1)__ quant à (?:[mt]oi|lui|elles?|[nv]ous|eux)(?! qui) <<- ~>> * -__[i](p_quant_à_pronom2)__ quant à (?:ça|cela|ceci) <<- ~>> * -__[i](p_que_ça_plaise_ou_non)__ que (?:ça|ceci|cela) (?:me|te|l(?:ui|eur)|[nv]ous) plaise ou non <<- ~>> * -__[i](p_que_voulu_ou_non)__ que (?:je le veuille|tu le veuilles|vous le vouliez|nous le voulions) ou non <<- ~>> * -__[i](p_que_xxx_ou_non)__ qu (?:à cela ne tienne|(?:(?:il|elle|on) le veuille|(?:ils|elles) le veuillent) ou non) <<- ~>> * -__[i](p_quel_qu_en_soit_le_qqch)__ quel qu en soit le (?:moyen|prix|risque(?: financier|)|danger) <<- ~>> * -__[i](p_quelle_qu_en_soit_la_qqch)__ quelle qu en soit la (?:cause|raison) <<- ~>> * -__[i](p_quelque_qqch)__ quelque(?: (?:part|temps)|s fois) <<- ~>> * -__[i](p_quelques_tps_adv)__ quelques (?:instants|secondes|minutes|heures|jours|semaines|mois|années|décennies|siècles|millénaires|trimestres|semestres) (?:auparavant|plus (?:tard|tôt)) <<- ~>> * -__[i](p_qui_plus_est)__ qui plus est <<- ~>> * -__[i](p_qui_loc_tps)__ qui (ce (?:jour|matin|après-midi|soir)-là|cette (?:nuit|matinée|soirée)-là) @@4 <<- ~1>> * -__[i](p_quoi_qu_il_qqch)__ quoi qu il (?:(?:arriv|en co[ûu]t)(?:e|ât)|adv(?:ienne|înt)) <<- ~>> * -__[i](p_sans_difficulté)__ sans (?:grande|grosse) difficulté(?: apparente| aucune| financière| majeure| particulière|) <<- ~>> * -__[i](p_sans_qqch)__ sans (?:ambages|arrêt|au(?:cun doute|tre forme de procès)|cesse|commune mesure|conteste|coup férir|crier gare|difficulté(?: apparente| aucune| financière| majeure| particulière|)|dire mot|doute|encombres?|états d’âme|fin|foi,? ni loi|l’ombre d’un doute|le (?:faire exprès|vouloir)|mot dire|nul doute|queue ni tête|raison apparente|relâche|répit|(?:grand |)succès|trêve|vergogne|(?:pour autant |)y (?:prendre g(?:arde|o[ûu]t)|faire attention|parvenir|réussir|réfléchir|songer|penser)|faire de vagues|s’en (?:rendre compte|apercevoir)|l’aide de personne) <<- ~>> * -__[i](p_séance_tenante)__ séance tenante <<- ~>> * -__[i](p_selon_qqch)__ selon (?:toute vraisemblance|(?:[mt]oi|lui|elles?|eux|nous|vous)(?! qui)) <<- ~>> * -__[i](p_semble_t_il)__ sembl(?:e-t-il|ait-il) <<- ~>> * -__[i](p_sens_dessus_dessous)__ sens dessus dessous <<- ~>> * -__[i](p_seul_à_seul)__ seule?s? à seule?s? <<- ~>> * -__[i](p_stp_svp)__ s’il (?:te|vous) pla[îi]t <<- ~>> * -__[i](p_si_qqch)__ si (?:bas|besoin est|haut|longtemps|nécessaire|possible|soudain|(?:cela|ça) ne tenait qu à (?:moi|toi|lui|eux|elles?|nous|vous)) <<- ~>> * -__[i](p_six_pieds_sous_terre)__ six pieds sous terre <<- ~>> * -__[i](p_sine_loc_latine)__ sine (?:die|qua non) <<- ~>> * -__[i](p_soi_qqch)__ soi(?:t dit en passant|-disant) <<- ~>> * -__[i](p_sous_qqch)__ sous (?:aucun prétexte|bonne (?:escorte|garde)|coupe réglée|haute surveillance) <<- ~>> * -__[i](p_stricto_sensu)__ stricto sensu <<- ~>> * -__[i>(p_sur_ce)__ sur ce, <<- ~>> * -__[i](p_sur_qqch)__ sur (?:ce plan-là|le (?:(?:long|moyen|court) terme|qui-vive)|la (?:forme comme sur le fond|même longueur d’onde)|(?:leur|[mts]on|[nv]otre) (?:trente[ -]et[ -]un|31)) <<- ~>> * -__[i](p_tant_qqch)__ tant (?:bien que mal|s’en faut) <<- ~>> * -__[i](p_tôt_ou_tard)__ tôt ou tard <<- ~>> * -__[i](loc_tour_à_tour)__ - tours? [àa] tours? - <<- not re.search("(?i)^tour à tour$", \0) ->> tour à tour # Locution adverbiale invariable. Écrivez “tour à tour”.|https://fr.wiktionary.org/wiki/tour_%C3%A0_tour - <<- ~>> * -__[i](p_tous_qqch)__ tous (?:comptes faits|frais payés) <<- ~>> * -__[i](p_tout_qqch)__ tout (?:à (?:fait|coup|l’heure)|le temps|de (?:même|suite|go)|au plus|aussi bien|simplement|bonnement|compte fait|feu,? tout flammes?|bien (?:considéré|réfléchi)|du long) <<- ~>> * -__[i](p_toutes_qqch)__ toutes (?:affaires cessantes|choses égales par ailleurs|griffes dehors|proportions gardées) <<- ~>> * -__[i](p_trait_pour_trait)__ trait pour trait <<- ~>> * -__[i](p_très_adverbe)__ très (?:bas|haut|bien|mal) <<- ~>> * -__[i](p_un_à_un)__ (une?) à \1 @@0 <<- ~>> * -__[i](p_un_qqch)__ un (?:à un|jour ou l’autre|instant plus (?:tôt|tard)|milli(?:er|on|ard) de fois|moment plus (?:tôt|tard)|peu (?:mieux|moins bien|partout|plus t(?:ôt|ard) que prévu)|tant soit peu) <<- ~>> * -__[i](p_plus_tôt_tard_que)__ plus t(?:ôt|ard) que (?:prévu|nécessaire) <<- ~>> * -__[i](p_une_qqch)__ une (?:à une|autre fois|bonne fois pour toutes|dernière fois|fois(?: pour toutes| de plus| n’est pas coutume)) <<- ~>> * -__[i](p_une_fois)__ une fois <<- ~>> _ -__[i](p_urbi_et_orbi)__ urbi et orbi <<- ~>> * -__[i](p_v_divers)__ v(?:aille que vaille|entre à terre|ers nulle part) <<- ~>> * + + +__[i](p_qui_loc_tps)__ qui (ce (?:jour|matin|après-midi|soir)-là|cette (?:nuit|matinée|soirée)-là) @@4 <<- ~1>> * + + + + TEST: ils vont et viennent, toujours {{cotes a cotes}}… TEST: Nous irons {{tours à tours}} chercher du bois. TEST: Ma thèse en 180 secondes. @@ -5140,10 +6254,12 @@ # Après __[i](p_adv_longtemps)__ (?:bien|si|assez) longtemps <<- ~>> * __[i](p_plus_loc_adv)__ plus (?:près|loin|tôt|tard|ou moins|que (?:nécessaire|jamais)|d’une fois) <<- ~>> * ## Simplification partielle +__[i](p_ceux_d_entre_pronom)__ ce(?:lui|lles?|ux) (d’entre (?:[nv]ous|eux|elles)) @@$ <<- ~1>> * +__[i](p_chacun_d_entre_nous)__ chacune? (d’entre (?:[nv]ous|eux|elles)) @@$ <<- ~1>> * __[i](p_tout_au_long_de)__ (tout au long) d(?:es?|u) @@0 <<- not morph(word(-1), ":R", False, False) ~1>> au __[i](p_à_loc_de1)__ à (bonne distance|bord|cause|contre-courant|côté|court|défaut|droite|gauche|hauteur|l’(?:aff[ûu]t|arrière|autre bout|aune|avant|écart|égard|extérieur|encontre|ins(?:u|tar)|intérieur|opposé|orée|approche)|la (?:hauteur|portée|suite)|partir|portée|pro(?:ximité|pos)|quelques (?:mètres|kilomètres|lieues|pas|centaines de mètres|minutes|heures)|rebours) d(?:es?|u) @@2 <<- ~1>> * __[i](p_à_loc_de2)__ à (base|force|grand(?: renfort|s coups)|raison) de? @@2 <<- ~1>> * __[i](p_au_loc_de)__ au (bout|beau milieu|courant|cours|détriment|fin fond|grand dam|fur et à mesure|gré|l(?:ieu|ong|arge)|milieu|nez et à la barbe|plus profond|profit|s(?:ein|ortir|ujet)|vu(?: et au su|)) d(?:es?|u) @@3 <<- ~1>> * __[i](p_aux_loc_de)__ aux (abords|dépens) d(?:es?|u) @@4 <<- ~1>> * @@ -5165,10 +6281,18 @@ # Déterminant + nombre __[i](p_dét_plur_nombre_nom)__ (?:[dmts]es|nos|vos|le(?:ur|)s) (\d+(?: ou \d+|)) ({w_2}) @@w,$ <<- morphex(\2, ":[NA].*:[pi]", ":(?:V0|3p)|>(?:janvier|février|mars|avril|mai|juin|juillet|ao[ûu]t|septembre|octobre|novembre|décembre|vendémiaire|brumaire|frimaire|nivôse|pluviôse|ventôse|germinal|floréal|prairial|messidor|thermidor|fructidor)") ~1>> * + + +__[i](p_du_moins)__ du moins <<- ~>> _ +__[i](p_don_Juan)__ (don) Juan @@0 <<- ~1>> * +__[i](p_le_pour_et_le_contre)__ le pour et le contre <<- ~>> =\0.replace(" ", "_") +__[i](p_ou_bien)__ ou (bien) @@3 <<- ~1>> * +__[i](p_une_fois)__ une fois <<- ~>> _ + ## Simplifications des substantifs __[i](loc_arc_à_poulies)__ arcs? (([àa]) poulies) @@$,w <<- \2 == "a" -2>> à # Confusion : “a” est une conjugaison du verbe “avoir”. Pour la préposition, écrivez “à”. @@ -5202,11 +6326,11 @@ __[i](loc_chair_à)__ chairs? (([àa]) (?:pâté|canons?)) @@$,w <<- \2 == "a" -2>> à # Confusion : “a” est une conjugaison du verbe “avoir”. Pour la préposition, écrivez “à”. <<- ~1>> * __[i](p_chambre_de)__ chambres? (d’(?:agriculture|hôtes?)|de (?:commerce|compensation|décompression|dégrisement)) @@$ <<- ~1>> * -__[i](p_chemin_de_traverse)__ chemins? (de traverse) @@$ <<- ~1>> * +__[i](p_chemin_de_traverse)__ chemins? (de (?:traverse|fer)) @@$ <<- ~1>> * __[i](p_chili_con_carne)__ chilis? (con carne) @@$ <<- ~1>> * __[i](p_chef_d_œuvre)__ chefs?(-d’œuvre) @@$ <<- ~1>> * __[i](p_clair_comme)__ claire?s? (comme (?:de l’eau de (?:boudin|roche|source)|du (?:cristal|jus de (?:boudin|chaussettes?|chique)))) @@$ <<- ~1>> * __[i](p_commis_d_office)__ commise?s? (d’office) @@$ <<- ~1>> * __[i](p_convention)__ conventions? (récepteur|générateur) @@$ <<- ~1>> * @@ -5289,11 +6413,11 @@ __[i](p_motion_de)__ motions? (de (?:blâme|censure|défiance)) @@$ <<- ~1>> * __[i](p_noix_de)__ noix (de (?:cajou|p[ée]can|coco|lavage|muscade|veau|macadamia)) @@$ <<- ~1>> * __[i](p_nu_comme_un_ver)__ nue?s? (comme (?:un ver|des vers)) @@$ <<- ~1>> * __[i](p_numéro)__ numéro (un|deux|trois|quatre|cinq|six|sept|huit|neuf|dix(?:-sept|-huit|-neuf|)|onze|douze|treize|quatorze|quinze|seize|vingt|trente|quarante|cinquante|soixante(?:-dix|)|quatre-vingt(?:-dix|)|cent|mille|\d+) @@$ - <<- before(r"\b[lL]a +$") =>> define(\0, [">numéro :N:f:s"]) + <<- before(r"\b[lL]a +$") =>> define(\0, [">numéro/:N:f:s"]) <<- ~1>> * __[i](p_oiseau_de)__ oiseaux? (de (?:malheur|nuit|proie|mauvais augure)) @@$ <<- ~1>> * __[i](p_onde_de_choc)__ ondes? (de choc) @@$ <<- ~1>> * __[i](p_orge)__ orge (perlé|mondé|carré) @@$ <<- ~1>> * __[i](p_noire_comme)__ noire?s? (comme (?:la nuit|une nuit sans lune)) @@$ <<- ~1>> * @@ -5446,15 +6570,15 @@ ## Conditionnel __[i](p_à_xxx_pour_cent)__ à ({w_2}) pour cent @@2 <<- morph(\1, ":B", False) ~>> * __[i](p_au_moins)__ (au moins) +({w_1}) @@0,$ <<- not morph(\2, ":[AQ].*:[me]:[si]", False) ~1>> * __[i](p_au_hasard)__ au hasard <<- isEndOfNG() ~>> * __[i](p_aussi_adv_que_possible)__ aussi ({w_2}) que (?:nécessaire|possible) @@6 <<- morph(\1, ":W", False) ~>> * -__[i](p_au_sens_adj_du_terme)__ au sens (?:le (?:plus|moins) |)({w_2}) du terme @@w <<- morph(\1, ":A .*:m:s", False) ~>> * +__[i](p_au_sens_adj_du_terme)__ au sens (?:le (?:plus|moins) |)({w_2}) du terme @@w <<- morph(\1, ":A.*:m:s", False) ~>> * #__[i](p_aussi_xxx_que_ce_soit)__ aussi ({w_2}) que ce soit __[i](p_nombre_de)__ (nombre) des? @@0 <<- morph(word(-1), ":(?:R|C[sc])", False, True) ~1>> * __[i](p_à_xxx_reprises)__ à ({w_2}) reprises @@2 <<- morph(\1, ":B", False) or re.search("(?i)^(?:plusieurs|maintes)", \1) ~>> * -__[i](p_bien_entendu)__ bien entendu <<- morph(word(1), ":[NAQR]|>que? ", False, True) ~>> * +__[i](p_bien_entendu)__ bien entendu <<- morph(word(1), ":[NAQR]|>que?/", False, True) ~>> * __[i](p_comme_pronom)__ ({w_2}) (comme (?:eux|elles?|lui|ça|celui-(?:ci|là)|celles?-(?:ci|là)|ceux(?:ci|là)|l[ea] [nv]ôtre|le [mts]ien|la [mts]ienne|les (?:[nv]ôtres|sien(?:ne|)s))) @@0,$ <<- morphex(\1, ":[NAQ]", ":V0") ~2>> * __[i](p_pêle_mêle)__ ({w_2}) (pêle-mêle) @@0,$ <<- not morph(\1, ":D", False) ~2>> * __[i](p_droit_devant)__ ({w_2}) (droit) devant @@0,w <<- not morph(\1, ":D.*:[me]:[si]", False) ~2>> * @@ -5464,11 +6588,11 @@ __[i](p_du_coup)__ (du coup) ({w_1}) @@0,$ <<- not morph(\2, ":A", False) ~1>> * __[i](p_verbe_pronom_être)__ (d[eouû]\w+|cr[ouû]\w+|pens\w+|imagin\w+|estim\w+) (l(?:eur|ui)|nous|vous) être @@0,w - <<- morph(\1, ">(?:croire|devoir|estimer|imaginer|penser) ") ~2>> * + <<- morph(\1, ">(?:croire|devoir|estimer|imaginer|penser)/") ~2>> * __[i](p_en_partie)__ (en partie) ({w_2}) @@0,$ <<- morph(\1, ":(?:R|D|[123]s|X)", False) ~1>> * __[i](p_en_plus)__ en plus @@ -5554,11 +6678,11 @@ __[i](p_avoir_pronom_loc_adv)__ ({avoir})-(?:je|tu|ils?|elles?|nous|vous|on) +(besoin|bon (?:dos|pied,? bon œil)|carte blanche|confiance|conscience|crainte|faim|forme humaine|honte|partie (?:gagnée|liée)|peur|soif|voix au chapitre) @@0,$ <<- morph(\1, ":V0a", False) ~2>> * __[i](p_avoir_tous_toutes_les)__ ({avoir}) +(tou(?:te|)s les ({w_2})) +({w_2}) @@0,w,>3:$,$ - <<- morph(\1, ":V0a", False) and morph(\3, ":B", False) and morph(\4, ">besoin |:(?:Q|V1.*:Y)", False) ~2>> * + <<- morph(\1, ":V0a", False) and morph(\3, ":B", False) and morph(\4, ">besoin/|:(?:Q|V1.*:Y)", False) ~2>> * # elle aussi + adj __[i](p_elle_aussi)__ (elle aussi) +({w_3}) @@0,$ <<- morph(\2, ":A:[fe]:s", False) ~1>> * @@ -5587,19 +6711,22 @@ __[i](p_les_xxx_les_plus_adj)__ (?:[lmts]es|nos|vos|leurs) ({w_2}) (les plus) ({w_2}) @@w,w,$ <<- morphex(\1, ":[NAQ].*:[pi]", ":[123][sp]") and morph(\3, ":A.*:[pi]", False) ~2>> * __[i](p_le_plus_le_moins)__ (le (?:plus|moins)) ({w_2}) @@0,$ - <<- morph(\2, ":A", ":([me]:[si]|G)") and morph(word(-1), ">(?:avoir|être) :V", False) ~1>> * + <<- morph(\2, ":A", ":([me]:[si]|G)") and morph(word(-1), ">(?:avoir|être)/:V", False) ~1>> * __[i](p_bien_sûr)__ bien sûr(?! de) <<- ~>> * __[i](p_bien_mal_fort_adj_adv)__ (bien|mal|(?:fort|super) (?:bien|mal)|fort) +({w_2}) @@0,$ <<- morph(\2, ":[AW]") ~1>> * __[i](p_loc_adj_adv)__ - (à (?:demi|peine|peu près)|depuis peu|quelque peu|pas très|un (?:petit |)peu(?: plus| moins|)|peu|plus|moins|si) +({w_2}) @@0,$ + (à (?:demi|peine|peu près)|depuis peu|quelque peu|pas très|un (?:petit |)peu(?: plus| moins|)|peu|plus|moins) +({w_2}) @@0,$ <<- morph(\2, ":[AW]", False) ~1>> * +__[i](p_si_adj_adv)__ + (si) +({w_2}) @@0,$ + <<- morph(\2, ":[AW]", False) and not (\2 == "bien" and after("^ +que? ")) ~1>> * __[i](p_un_brin_chouïa_rien_tantinet_soupçon)__ (un (?:brin|chou[iï]a|rien|minimum|soupçon|tantinet)(?: trop|)) ({w_2}) @@0,$ <<- morphex(\2, ":A", ":G") ~1>> * __[i](p_assez_trop_adv_xxxment)__ (?:assez|trop) +(\w+ment) @@$ @@ -5718,94 +6845,94 @@ ## Simplication des locutions verbales __[i](loc_arriver)__ (arriv\w+) (([aà]) (?:échéance|point nommé)) @@0,$,w - <<- morph(\1, ">arriver ", False) >>> + <<- morph(\1, ">arriver/", False) >>> <<- \3 == "a" -3>> à # Confusion : “a” est une conjugaison du verbe “avoir”. Pour la préposition, écrivez “à”. <<- ~2>> * __[i](p_donner_sens)__ ((?:re|)donn\w+) +(sens) @@0,$ - <<- morph(\1, ">(?:re|)donner ", False) ~2>> * + <<- morph(\1, ">(?:re|)donner/", False) ~2>> * __[i](p_faire_qqch)__ (f[aiîeo]\w*) +(tous(?: deux| trois|) +|)(allusion|amende honorable|assaut|bande à part|bonne figure|chaud|confiance|compliqué|copain[- ]copain|de (?:[mts]on|leur|[nv]otre) mieux|dé(?:bat|faut)|demi-tour|envie|fausse route|figure|froid|front commun|gr(?:ise mine|and (?:bruit|cas))|h(?:alte|onte)|illusion|long feu|ma(?:chine|rche) arrière|main basse|mouche|office|p(?:art(?:ie(?: intégrante|)|)|eur|laisir|rofil bas)|rage|salle comble|scandale|sens|signe|table rase|volte-face|ce que bon (?:me|te|lui|leur|nous|vous) semble) @@0,*,$ - <<- morph(\1, ">faire ", False) ~2>> * + <<- morph(\1, ">faire/", False) ~2>> * <<- __also__ ~3>> * __[i](loc_laisser_pour_compte)__ (laiss\w+) +(pour (co[mn]p?tes?)) @@0,$,$ - <<- morph(\1, ">laisser ", False) >>> + <<- morph(\1, ">laisser/", False) >>> <<- \3 != "compte" -3>> compte # Confusion. Locution “laisser pour compte”.|https://fr.wiktionary.org/wiki/laisser_pour_compte <<- ~2>> * __[i](loc_mettre_à_qqch)__ (m(?:et|[iî][mst])\w*) +(([àa]) (?:bas|jour|niveau|plat|l’(?:écart|épreuve)|terre)) @@0,$,w - <<- morph(\1, ">mettre ", False) >>> + <<- morph(\1, ">mettre/", False) >>> <<- \3 == "a" -3>> à # Confusion : “a” est une conjugaison du verbe “avoir”. Pour la préposition, écrivez “à”. <<- ~2>> * __[i](p_mettre_qqch)__ (m(?:et|[iî][mst])\w*) +(au p(?:oint|as)|en (?:avant|bouche|demeure|garde|jeu|lumière|œuvre|place|scène|terre)) @@0,$ - <<- morph(\1, ">mettre ", False) ~2>> * + <<- morph(\1, ">mettre/", False) ~2>> * __[i](loc_mourir_qqch)__ (m[oe]\w+) +(jeûne) @@0,$ - <<- morph(\1, ">mourir ", False) -2>> =\2.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune + <<- morph(\1, ">mourir/", False) -2>> =\2.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune __[i](p_paraitre_qqch)__ (par\w+) +(jeûnes?) @@0,$ - <<- morph(\1, ">para[îi]tre ", False) -2>> =\2.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune + <<- morph(\1, ">para[îi]tre/", False) -2>> =\2.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune __[i](p_porter_qqch)__ (port\w+) +(atteinte|bonheur|caution|chance|malheur|plainte|préjudice|secours) @@0,$ - <<- morph(\1, ">porter ", False) ~2>> * + <<- morph(\1, ">porter/", False) ~2>> * __[i](loc_prendre_à_la_légère)__ (pr[eiî]\w+) +(([àa]) la légère) @@0,$,w - <<- morph(\1, ">prendre ", False) >>> + <<- morph(\1, ">prendre/", False) >>> <<- \3 == "a" -3>> à # Confusion : “a” est une conjugaison du verbe “avoir”. Pour la préposition, écrivez “à”. <<- ~2>> * __[i](p_prendre)__ (pr[eiî]\w+) +(au (?:dépourvu|sérieux)|congé|conscience|contact|de court|en charge|ombrage|pour argent comptant|par surprise|racine|soin|vie) @@0,$ - <<- morph(\1, ">prendre ", False) ~2>> * + <<- morph(\1, ">prendre/", False) ~2>> * __[i](loc_rendre_compte)__ (rend\w+) +(co[mn]tes?) @@0,$ - <<- morph(\1, ">rendre ", False) -2>> compte # Confusion probable. Locution “rendre compte”.|https://fr.wiktionary.org/wiki/rendre_compte + <<- morph(\1, ">rendre/", False) -2>> compte # Confusion probable. Locution “rendre compte”.|https://fr.wiktionary.org/wiki/rendre_compte <<- ~1>> * __[i](loc_rester_qqch)__ (rest\w+) +(lettre morte|jeûnes?) @@0,$ - <<- morph(\1, ">rester ", False) >>> - <<- morph(\2, ">jeûne ", False) -2>> =\2.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune + <<- morph(\1, ">rester/", False) >>> + <<- morph(\2, ">jeûne/", False) -2>> =\2.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune <<- __else__ ~2>> * __[i](loc_semble_qqch)__ (sembl\w+) +(jeûnes?) @@0,$ - <<- morph(\1, ">sembler ", False) -2>> =\2.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune + <<- morph(\1, ">sembler/", False) -2>> =\2.replace("û", "u") # Confusion. Le jeûne est une privation de nourriture.|https://fr.wiktionary.org/wiki/jeune __[i](p_sembler_paraitre_être)__ (sembl\w+|par[au]\w+) +(être|avoir été) +({w_2}) @@0,w,$ - <<- morph(\1, ">(?:sembler|para[îi]tre) ") and morphex(\3, ":A", ":G") ~2>> * + <<- morph(\1, ">(?:sembler|para[îi]tre)/") and morphex(\3, ":A", ":G") ~2>> * __[i](loc_suivre_de_près)__ (suiv\w+) +((?:ça +|ce(?:ci|la) +|)de (pr[èé]s?|prêts?)) @@0,$,$ - <<- morph(\1, ">suivre ", False) >>> + <<- morph(\1, ">suivre/", False) >>> <<- \3 != "près" -3>> près # Confusion : écrivez “près” pour dire “proche de quelque chose”.|https://fr.wiktionary.org/wiki/pr%C3%A8s <<- ~2>> * __[i](loc_tenir_à_distance)__ (t[eiî]\w+) +(([àa]) distance +(?:respectable +|))d(?:es?|u) @@0,*,w - <<- morph(\1, ">tenir ", False) >>> + <<- morph(\1, ">tenir/", False) >>> <<- \3 == "a" -3>> à # Confusion : “a” est une conjugaison du verbe “avoir”. Pour la préposition, écrivez “à”. <<- ~2>> * __[i](loc_tenir_compte)__ (t[eiî]\w+) +(co(?:mp?|n)tes?|au courant) @@0,$ - <<- morph(\1, ">tenir ", False) >>> - <<- morph(\2, ">co[mn]te(?:sse|) ", False) -2>> compte # Confusion. Dans la locution “tenir compte”, écrivez “compte” au singulier.|https://fr.wiktionary.org/wiki/tenir_compte + <<- morph(\1, ">tenir/", False) >>> + <<- morph(\2, ">co[mn]te(?:sse|)/", False) -2>> compte # Confusion. Dans la locution “tenir compte”, écrivez “compte” au singulier.|https://fr.wiktionary.org/wiki/tenir_compte <<- ~2>> * __[i](p_tirer_profit)__ (tir\w+) +(avantage|profit) d(?:es?|u) @@0,w - <<- morph(\1, ">tirer ", False) ~2>> * + <<- morph(\1, ">tirer/", False) ~2>> * __[i](loc_tourner_court)__ (tourn\w+) +(cour(?:re|t|s|)) @@0,$ - <<- morph(\1, ">tourner ", False) >>> + <<- morph(\1, ">tourner/", False) >>> <<- \2 != "court" -2>> court # Locution : tourner court.|https://fr.wiktionary.org/wiki/tourner_court <<- ~2>> * __[i](p_trier_sur_le_volet)__ (tri\w+) (sur le volet) @@0,$ - <<- morph(\1, ">trier ", False) ~2>> * + <<- morph(\1, ">trier/", False) ~2>> * __[i](p_venir)__ (v[eiî]\w+) ((?:on ne sait|je ne sais) (?:pas |)(?:trop |)d’où) @@0,$ - <<- morph(\1, ">venir ", False) ~2>> * + <<- morph(\1, ">venir/", False) ~2>> * TEST: ce contrat arrive {{a}} échéance. TEST: il faut tenir {{contes}} des faits au lieu de nos impressions. TEST: prendre {{a}} la légère ce test serait une erreur. TEST: on va suivre ça de {{prêt}}. @@ -5901,11 +7028,11 @@ !!!! Redondances dans la phrase __[i]/redon2(redondances_phrase)__ ({w_4})[ ,].* (\1) @@0,$ - <<- not morph(\1, ":(?:G|V0)|>même ", False) -2>> _ # Dans cette phrase, répétition de « \1 » (à gauche). + <<- not morph(\1, ":(?:G|V0)|>même/", False) -2>> _ # Dans cette phrase, répétition de « \1 » (à gauche). <<- __also__ -1>> _ # Dans cette phrase, répétition de « \1 » (à droite). TEST: __redon2__ Quelle {{imposture}}, c’est d’un ennui, c’est une {{imposture}}. TEST: __redon2__ ils sont là côte à côte. TEST: __redon2__ Tu avances petit à petit, et tu réussis. @@ -5956,15 +7083,15 @@ <<- __also__ -1>> les # Accord de nombre erroné : « \2 » est au pluriel. __[i]/gn(gn_le_accord2)__ ({w_1}) +(le) +({w_2}) @@0,w,$ <<- morph(\2, ":D", False) >>> <<- morphex(\3, ":[NAQ].*:f", ":(?:e|m|P|G|W|[123][sp]|Y)") - or ( morphex(\3, ":[NAQ].*:f", ":[me]") and morphex(\1, ":R", ">(?:e[tn]|ou) ") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False)) ) + or ( morphex(\3, ":[NAQ].*:f", ":[me]") and morphex(\1, ":R", ">(?:e[tn]|ou)/") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False)) ) -2>> =suggLesLa(\3) # Accord de genre erroné : « \3 » est féminin. <<- __also__ and hasMasForm(\3) -3>> =suggMasSing(@, True) # Accord de genre erroné : « \2 » est un déterminant masculin. <<- __else__ and morph(\3, ":[NAQ].*:p") - or ( morphex(\3, ":[NAQ].*:p", ":[si]") and morphex(\1, ":[RC]", ">(?:e[tn]|ou)") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False)) ) + or ( morphex(\3, ":[NAQ].*:p", ":[si]") and morphex(\1, ":[RC]", ">(?:e[tn]|ou)/") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False)) ) -3>> =suggMasSing(@) # Accord de nombre erroné : « \3 » devrait être au singulier. <<- __also__ -2>> les # Accord de nombre erroné : « \3 » est au pluriel. __[i]/gn(gn_le_accord3)__ ^ *(le) +({w_2}) @@*,$ <<- morphex(\2, ":[NAQ].*:f", ":(?:e|m|P|G|W|Y)") -1>> =suggLesLa(\2) # Accord de genre erroné : « \2 » est féminin. @@ -6047,15 +7174,15 @@ <<- __else__ and morph(\2, ":[NAQ].*:p") -2>> =suggFemSing(@) # Accord de nombre erroné : « \2 » devrait être au singulier. __[i]/gn(gn_la_accord2)__ ({w_1}) +(la) +({w_2}) @@0,w,$ <<- morph(\2, ":D", False) >>> <<- morphex(\3, ":[NAQ].*:m", ":(?:e|f|P|G|W|[1-3][sp]|Y)") - or ( morphex(\3, ":[NAQ].*:m", ":[fe]") and morphex(\1, ":[RC]", ">(?:e[tn]|ou) ") and not (morph(\1, ":(?:Rv|C)", False) and morph(\3, ":Y", False)) ) + or ( morphex(\3, ":[NAQ].*:m", ":[fe]") and morphex(\1, ":[RC]", ">(?:e[tn]|ou)/") and not (morph(\1, ":(?:Rv|C)", False) and morph(\3, ":Y", False)) ) -2>> le # Accord de genre erroné : « \3 » est masculin. <<- __also__ and hasFemForm(\3) -3>> =suggFemSing(@, True) # Accord de genre erroné : « \2 » est un déterminant féminin. <<- __else__ and morph(\3, ":[NAQ].*:p") - or ( morphex(\3, ":[NAQ].*:p", ":[si]") and morphex(\1, ":[RC]", ">(?:e[tn]|ou)") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False)) ) + or ( morphex(\3, ":[NAQ].*:p", ":[si]") and morphex(\1, ":[RC]", ">(?:e[tn]|ou)/") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False)) ) -3>> =suggFemSing(@) # Accord de nombre erroné : « \3 » devrait être au singulier. __[i]/gn(gn_la_accord3)__ ^ *(la) +({w_2}) @@*,$ <<- morphex(\2, ":[NAQ].*:m", ":[efPGWY]") -1>> le # Accord de genre erroné : « \2 » est masculin. <<- __also__ and hasFemForm(\2) -2>> =suggFemSing(@, True) # Accord de genre erroné : « \1 » est un déterminant féminin. @@ -6157,11 +7284,11 @@ <<- morph(\2, ":[NAQ].*:p") -1>> leurs # Accord de nombre erroné avec « \2 ». <<- __also__ -2>> =suggSing(@) # Accord de nombre erroné : « \2 » devrait être au singulier. __[i]/gn(gn_leur_accord2)__ ({w_1}) +(leur) +({w_2}) @@0,w,$ <<- morph(\3, ":[NAQ].*:p") - or ( morphex(\3, ":[NAQ].*:p", ":[si]") and morphex(\1, ":[RC]|>de ", ">(?:e[tn]|ou)") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False)) ) + or ( morphex(\3, ":[NAQ].*:p", ":[si]") and morphex(\1, ":[RC]|>de/", ">(?:e[tn]|ou)/") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False)) ) -2>> leurs # Accord de nombre erroné avec « \3 ». <<- __also__ -3>> =suggSing(@) # Accord de nombre erroné : « \3 » devrait être au singulier. __> leurs # Accord de nombre erroné avec « \1 ». @@ -6196,11 +7323,11 @@ -2>> =suggPlur(@) # Accord de nombre erroné : « \2 » devrait être au pluriel. __[i]/gn(gn_les_accord2)__ ({w_1}) +(les) +({w_2}) @@0,w,$ <<- morph(\2, ":D", False) >>> <<- ( morph(\3, ":[NAQ].*:s") - or (morphex(\3, ":[NAQ].*:s", ":[pi]|>avoir") and morphex(\1, ":[RC]", ">(?:e[tn]|ou) ") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False))) ) + or (morphex(\3, ":[NAQ].*:s", ":[pi]|>avoir") and morphex(\1, ":[RC]", ">(?:e[tn]|ou)/") and not (morph(\1, ":Rv", False) and morph(\3, ":Y", False))) ) and not (after("^ +(?:et|ou) ") and morph(word(2), ":[NAQ]", True, False)) -3>> =suggPlur(@) # Accord de nombre erroné : « \3 » devrait être au pluriel. __[i]/gn(gn_les_accord3)__ ^ *(les) +({w_2}) @@w,$ <<- (morphex(\2, ":[NAQ].*:s", ":[ipYPGW]") @@ -6485,11 +7612,11 @@ ## trouver ça/ceci/cela + adj __[i]/gn(gn_trouver_ça_adj)__ (trouv\w+) +(ça|ce(?:ci|la)) +({w_2}) @@0,w,$ - <<- morph(\1, ">trouver ", False) and morphex(\3, ":A.*:(?:f|m:p)", ":(?:G|3[sp]|M[12P])") + <<- morph(\1, ">trouver/", False) and morphex(\3, ":A.*:(?:f|m:p)", ":(?:G|3[sp]|M[12P])") -3>> =suggMasSing(@) # Trouver \2 + [adjectif] : l’adjectif s’accorde avec “\2” (au masculin singulier). TEST: ils trouvent ça de plus en plus {{idiots}} ->> idiot @@ -6622,15 +7749,15 @@ __[i]/gn(gn_2m_un_après_et_ou_de)__ (?:et +|ou +|d’)un +({w_2}) +({w_2}) @@w,$ <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[me]", ":(?:B|G|V0|f)") and morph(\2, ":[NAQ].*:f") and not apposition(\1, \2) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggMasSing(@, True) # Accord de genre erroné : « \1 » est masculin, « \2 » est féminin. <<- morphex(\1, ":[NAQ].*:[si]", ":G") and morph(\2, ":[NAQ].*:p") and not \2.startswith("seul") and not apposition(\1, \2) - and not morph(word(-1), ":[NAQB]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQB]|>(?:et|ou)/", False, False) -2>> =suggMasSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. TEST: un exercice pas très {{utiles}}. ->> utile TEST: un homme {{grands}} ->> grand TEST: un homme {{futiles}} ->> futile @@ -6651,15 +7778,15 @@ __[i]/gn(gn_2m_une_après_et_ou_de)__ (?:et +|ou +|d’)une +({w_2}) +({w_2}) @@w,$ <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[fe]", ":(?:B|G|V0|m)") and morph(\2, ":[NAQ].*:m") and not apposition(\1, \2) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggFemSing(@, True) # Accord de genre erroné : « \1 » est féminin, « \2 » est masculin. <<- \1 != "fois" and morph(\1, ":[NAQ].*:[si]", False) and morph(\2, ":[NAQ].*:p") and not \2.startswith("seul") and not apposition(\1, \2) - and not morph(word(-1), ":[NAQB]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQB]|>(?:et|ou)/", False, False) -2>> =suggFemSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. TEST: Une grande {{homme}}. TEST: une géologue {{intelligents}} TEST: Et une femme {{déterminées}} @@ -6683,15 +7810,15 @@ __[i]/gn(gn_2m_le_après_et_ou_de)__ (?:et|ou) +(le) +({w_2}) +({w_2}) @@w,w,$ <<- morph(\1, ":D", False) >>> <<- not \3.startswith("seul") and morphex(\2, ":[NAQ].*:[me]", ":(?:B|G|V0|f)") and morph(\3, ":[NAQ].*:f") - and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -3>> =suggMasSing(@, True) # Accord de genre erroné : « \2 » est masculin, « \3 » est féminin. <<- not \3.startswith("seul") and morphex(\2, ":[NAQ].*:[si]", ":G") and morphex(\3, ":[NAQ].*:p", ":[GWsi]") - and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -3>> =suggMasSing(@) # Accord de nombre erroné avec « \2 » : « \3 » devrait être au singulier. TEST: le test très {{cons}} qu’on a passé hier. TEST: c’était le chien {{perdue}} des voisins. TEST: viens vite ou le pari {{imperdables}} sera moins facile… @@ -6709,15 +7836,15 @@ -2>> =suggMasSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. __[i]/gn(gn_2m_det_mas_sing_après_et_ou_de)__ (?:et|ou|de) +(?:cet?|quel|au|ledit) +({w_2}) +({w_2}) @@w,$ <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[me]", ":(?:B|G|V0|f)") and morph(\2, ":[NAQ].*:f") - and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggMasSing(@, True) # Accord de genre erroné : « \1 » est masculin, « \2 » est féminin. <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[si]", ":G") and morphex(\2, ":[NAQ].*:p", ":[GWsi]") - and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggMasSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. TEST: cet outil {{terribles}} qu’il a dans les mains TEST: J’aimerais connaître de quel parti {{gauchistes}} on parle. @@ -6734,16 +7861,16 @@ -2>> =suggMasSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. __[i]/gn(gn_2m_mon_ton_son_après_et_ou_de)__ (?:et|ou|de) +[mts]on +({w_2}) +({w_2}) @@w,$ <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:m", ":(?:B|G|e|V0|f)") and morph(\2, ":[NAQ].*:f") - and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggMasSing(@, True) # Accord de genre erroné : « \1 » est masculin, « \2 » est féminin. <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[si]", ":G") and morphex(\2, ":[NAQ].*:p", ":[GWsi]") and not apposition(\1, \2) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggMasSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. TEST: il brandissait avec fougue son drapeau {{déchirés}} TEST: comment osez-vous médire de mon héritage {{glorieuse}} @@ -6762,15 +7889,15 @@ __[i]/gn(gn_2m_la_après_et_ou_de)__ (?:et|ou|de) +(la) +({w_2}) +({w_2}) @@w,w,$ <<- morph(\1, ":D", False) >>> <<- \2 != "fois" and not \3.startswith("seul") and morphex(\2, ":[NAQ].*:[fe]", ":(?:B|G|V0|m)") and morph(\3, ":[NAQ].*:m") - and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -3>> =suggFemSing(@, True) # Accord de genre erroné : « \2 » est féminin, « \3 » est masculin. <<- not \3.startswith("seul") and morphex(\2, ":[NAQ].*:[si]", ":G") and morphex(\3, ":[NAQ].*:p", ":[GWsi]") - and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -3>> =suggFemSing(@) # Accord de nombre erroné avec « \2 » : « \3 » devrait être au singulier. TEST: La plus grande {{cinglé}}. TEST: il imaginait de la pluie {{noir}} tombant sur une terre dévastée. @@ -6787,15 +7914,15 @@ -2>> =suggFemSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. __[i]/gn(gn_2m_det_fem_sing_après_et_ou_de)__ (?:et|ou|de) +(?:[mts]a|cette|quelle|ladite) +({w_2}) +({w_2}) @@w,$ <<- \1 != "fois" and not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[fe]", ":(?:B|G|V0|m)") and morph(\2, ":[NAQ].*:m") - and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggFemSing(@, True) # Accord de genre erroné : « \1 » est féminin, « \2 » est masculin. <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[si]", ":G") and morphex(\2, ":[NAQ].*:p", ":[GWsi]") - and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggFemSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. TEST: quelle belle {{étourdi}}, cette gamine TEST: j’en ai assez de cette ville {{stressées}} en permanence. TEST: Peut-on imaginer de plus {{beaux}} {{enfant}} ? @@ -6817,16 +7944,16 @@ (?:et|ou|de) +(leur) +({w_2}) +({w_2}) @@w,w,$ <<- morph(\1, ":D", False) >>> <<- \2 != "fois" and not \3.startswith("seul") and ((morphex(\2, ":[NAQ].*:m", ":(?:B|e|G|V0|f)") and morph(\3, ":[NAQ].*:f")) or (morphex(\2, ":[NAQ].*:f", ":(?:B|e|G|V0|m)") and morph(\3, ":[NAQ].*:m"))) and not apposition(\2, \3) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -3>> =switchGender(@, False) # Accord de genre erroné entre « \2 » et « \3 ». <<- __also__ and hasFemForm(\2) -1>> =switchGender(@, False) # Accord de genre erroné avec « \3 ». <<- not \3.startswith("seul") and morphex(\2, ":[NAQ].*:[si]", ":G") and morphex(\3, ":[NAQ].*:p", ":[GWsi]") - and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\2, \3) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -3>> =suggSing(@) # Accord de nombre erroné avec « \2 » : « \3 » devrait être au singulier. TEST: leur puissance {{perdues}} TEST: leur arbre {{élaguée}} TEST: je me souviens de leur verve {{décalé}} @@ -6847,16 +7974,16 @@ __[i]/gn(gn_2m_det_epi_sing_après_et_ou_de)__ (?:et|ou|de) +(?:chaque|quelque|[nv]otre) +({w_2}) +({w_2}) @@w,$ <<- \1 != "fois" and not \2.startswith("seul") and not re.search("(?i)quelque chose", \0) and ((morphex(\1, ":[NAQ].*:m", ":(?:B|e|G|V0|f)") and morph(\2, ":[NAQ].*:f")) or (morphex(\1, ":[NAQ].*:f", ":(?:B|e|G|V0|m)") and morph(\2, ":[NAQ].*:m"))) and not apposition(\1, \2) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =switchGender(@, False) # Accord de genre erroné entre « \1 » et « \2 ». <<- __also__ and hasFemForm(\1) -1>> =switchGender(@, False) # Accord de genre erroné avec « \2 ». <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[si]", ":G") and morphex(\2, ":[NAQ].*:p", ":[GWsi]") - and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not apposition(\1, \2) and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggSing(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au singulier. TEST: chaque élément {{terrestres}} TEST: ils viennent de chaque coin {{ignorée}} du pays. @@ -6875,11 +8002,11 @@ __[i]/gn(gn_2m_det_mas_plur_après_et_ou_de)__ (?:et|ou|de) +(?:certains|quels|lesdits) +({w_2}) +({w_2}) @@w,$ <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[me]", ":(?:B|G|V0|f)") and morph(\2, ":[NAQ].*:f") and not apposition(\1, \2) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggMasPlur(@, True) # Accord de genre erroné : « \1 » est masculin, « \2 » est féminin. <<- not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[pi]", ":G") and morph(\2, ":[NAQ].*:s") and not apposition(\1, \2) and not (after_chk1(r"^ +et +(\w[\w-]+)", ":A") or after_chk1(r"^ *, +(\w[\w-]+)", ":A.*:[si]")) and not ( before(r"(?i)\bune? de ") or (\0.startswith("de") and before(r"(?i)\bune? +$")) ) @@ -6905,11 +8032,11 @@ __[i]/gn(gn_2m_det_fem_plur_après_et_ou_de)__ (?:et|ou|de) +(?:certaines|quelles|lesdites) +({w_2}) +({w_2}) @@w,$ <<- \1 != "fois" and not \2.startswith("seul") and morphex(\1, ":[NAQ].*:[fe]", ":(?:B|G|V0|m)") and morph(\2, ":[NAQ].*:m") and not apposition(\1, \2) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =suggFemPlur(@, True) # Accord de genre erroné : « \1 » est féminin, « \2 » est masculin. <<- not \2.startswith("seul") and morph(\1, ":[NAQ].*:[pi]", False) and morph(\2, ":[NAQ].*:s") and not apposition(\1, \2) and not (after_chk1(r"^ +et +(\w[\w-]+)", ":A") or after_chk1(r"^ *, +(\w[\w-]+)", ":A.*:[si]")) and not ( before(r"(?i)\bune? de ") or (\0.startswith("de") and before(r"(?i)\bune? +$")) ) @@ -6935,11 +8062,11 @@ (?:et|ou) +(les) +({w_2}) +({w_2}) @@w,w,$ <<- morph(\1, ":D", False) >>> <<- \2 != "fois" and not \3.startswith("seul") and ((morphex(\2, ":[NAQ].*:m", ":(?:B|e|G|V0|f)") and morph(\3, ":[NAQ].*:f")) or (morphex(\2, ":[NAQ].*:f", ":(?:B|e|G|V0|m)") and morph(\3, ":[NAQ].*:m"))) and not apposition(\2, \3) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -3>> =switchGender(@, True) # Accord de genre erroné entre « \2 » et « \3 ». <<- __also__ and hasFemForm(\2) -2>> =switchGender(@, True) # Accord de genre erroné avec « \3 ». <<- \2 != "fois" and not \3.startswith("seul") and morph(\2, ":[NAQ].*:[pi]", False) and morph(\3, ":[NAQ].*:s") and not apposition(\2, \3) and not (after_chk1(r"^ +et +(\w[\w-]+)", ":A") or after_chk1(r"^ *, +(\w[\w-]+)", ":A.*:[si]")) @@ -6965,11 +8092,11 @@ __[i]/gn(gn_2m_det_epi_plur_après_et_ou_de)__ (?:et|ou|de) +(?:[cmts]es|[nv]os|leurs|quelques|plusieurs|aux|moult) +({w_2}) +({w_2}) @@w,$ <<- \1 != "fois" and not \2.startswith("seul") and ((morphex(\1, ":[NAQ].*:m", ":(?:B|e|G|V0|f)") and morph(\2, ":[NAQ].*:f")) or (morphex(\1, ":[NAQ].*:f", ":(?:B|e|G|V0|m)") and morph(\2, ":[NAQ].*:m"))) and not apposition(\1, \2) - and not morph(word(-1), ":[NAQ]|>(?:et|ou) ", False, False) + and not morph(word(-1), ":[NAQ]|>(?:et|ou)/", False, False) -2>> =switchGender(@, True) # Accord de genre erroné entre « \1 » et « \2 ». <<- __also__ and hasFemForm(\1) -1>> =switchGender(@, True) # Accord de genre erroné avec « \2 ». <<- \1 != "fois" and not \2.startswith("seul") and morph(\1, ":[NAQ].*:[pi]", False) and morph(\2, ":[NAQ].*:s") and not apposition(\1, \2) and not (after_chk1(r"^ +et +(\w[\w-]+)", ":A") or after_chk1(r"^ *, +(\w[\w-]+)", ":A.*:[si]")) @@ -6986,16 +8113,16 @@ __[i]/gn(gn_2m_des)__ des +({w_2}) +({w_2}) @@w,$ <<- \1 != "fois" and not \2.startswith("seul") and ( (morphex(\1, ":[NAQ].*:m", ":[fe]") and morph(\2, ":[NAQ].*:f")) or (morphex(\1, ":[NAQ].*:f", ":[me]") and morph(\2, ":[NAQ].*:m")) ) and not apposition(\1, \2) and not (after_chk1(r"^ +et +(\w[\w-]+)", ":A") or after_chk1(r"^ *, +(\w[\w-]+)", ":A.*:[si]")) - and morph(word(-1), ":[VRBX]|>comme ", True, True) + and morph(word(-1), ":[VRBX]|>comme/", True, True) -2>> =switchGender(@, True) # Accord de genre erroné avec « \1 ». <<- __also__ and hasFemForm(\1) -1>> =switchGender(@) # Accord de genre erroné avec « \2 ». <<- morph(\1, ":[NAQ].*:[pi]", False) and morph(\2, ":[NAQ].*:s") and not apposition(\1, \2) and not (after_chk1(r"^ +et +(\w[\w-]+)", ":A") or after_chk1(r"^ *, +(\w[\w-]+)", ":A.*:[si]")) - and (morphex(\2, ":N", ":[AQ]") or morph(word(-1), ":[VRBX]|>comme ", True, True)) + and (morphex(\2, ":N", ":[AQ]") or morph(word(-1), ":[VRBX]|>comme/", True, True)) -2>> =suggPlur(@) # Accord de nombre erroné avec « \1 » : « \2 » devrait être au pluriel. <<- checkAgreement(\1, \2) =>> =exclude(\2, ":V") TEST: faire table rase des passions {{inutile}} ->> inutiles TEST: à bonne distance des {{chiens}} {{méchante}} @@ -7260,11 +8387,11 @@ #### Locutions # à __[i]/sgpl(sgpl_à_nu)__ (m[eiî]\w+) +([aà] nu(?:es?|s)) @@0,$ - <<- morph(\1, ">(?:mettre|mise) ", False) -2>> à nu # « nu » est invariable dans cette locution. + <<- morph(\1, ">(?:mettre|mise)/", False) -2>> à nu # « nu » est invariable dans cette locution. TEST: Mettre {{à nus}} les hommes. __[i]/sgpl(sgpl_à_part_égales)__ à part? égale? <<- ->> à parts égales # Il y a plusieurs parts. @@ -7280,14 +8407,14 @@ # affaires __[i]/sgpl(sgpl_chiffre_d_affaires)__ chiffres? d’(affaire) @@$ <<- -1>> affaires # Le chiffre d’affaires. Toujours un “s” final. __[i]/sgpl(sgpl_faire_affaire_avec)__ (f[aieî]\w+) (affaires) avec @@0,w - <<- morph(\1, ">faire ", False) -2>> affaire # « Faire affaire avec ». Pas de “s”. + <<- morph(\1, ">faire/", False) -2>> affaire # « Faire affaire avec ». Pas de “s”. __[u]/sgpl(sgpl_faire_affaire_à_en)__ (f[aieî]\w+) (affaire) (?:à|en) ([A-ZÉÈÂ][\w-]+) @@0,w,$ - <<- morph(\1, ">faire ", False) and morph(\3, ":(?:N|MP)") + <<- morph(\1, ">faire/", False) and morph(\3, ":(?:N|MP)") -2>> affaires # Ajoutez un “s” à « affaire ». TEST: Quel est son chiffre d’{{affaire}} ? TEST: Allez-vous faire {{affaires}} avec ces connards ? TEST: Faire {{affaire}} à Paris. @@ -7342,20 +8469,20 @@ # coûter cher __[i]/sgpl(sgpl_coûter_cher)__ ((?:co[uû]t|pa)\w+) +(chers|chères?|chaire?s?) @@0,$ - <<- morph(\1, ">(?:co[ûu]ter|payer) ", False) + <<- morph(\1, ">(?:co[ûu]ter|payer)/", False) -2>> cher # Ici, « cher » est un adverbe, invariable. TEST: ces saloperies coûtent vraiment {{chères}} ! # donner lieu __[i]/sgpl(sgpl_donner_lieu)__ (donn\w+) +(lieux) @@0,$ - <<- morph(\1, ">donner ", False) + <<- morph(\1, ">donner/", False) -2>> lieu # « Donner lieu » : “lieu” est invariable dans cette locution verbale. TEST: ces conneries donneront {{lieux}} à une enquête approfondie. @@ -7367,11 +8494,11 @@ # ensemble __[i]/sgpl(sgpl_ensemble)__ ({w_1}) +(ensembles) @@0,$ - <<- morphex(\1, ":V.*:[123]p|>(?:tou(?:te|)s|pas|rien|guère|jamais|toujours|souvent) ", ":[DRB]") + <<- morphex(\1, ":V.*:[123]p|>(?:tou(?:te|)s|pas|rien|guère|jamais|toujours|souvent)/", ":[DRB]") -2>> ensemble # S’il s’agit bien de l’adverbe “ensemble”, il est invariable.|https://fr.wiktionary.org/wiki/ensemble TEST: Elles viendront {{ensembles}}. @@ -7398,11 +8525,11 @@ # pied __[i]/sgpl(sgpl_avoir_pied)__ ([aeop]\w*) +(?:pas |)(pieds) @@0,$ - <<- morph(\1, ">(?:avoir|perdre) ", False) -2>> pied # Pas de “s” final. + <<- morph(\1, ">(?:avoir|perdre)/", False) -2>> pied # Pas de “s” final. __[i]/sgpl(sgpl_à_pied)__ à (pieds) @@2 <<- not before(r"(?i)\b(?:lit|fauteuil|armoire|commode|guéridon|tabouret|chaise)s?\b") -1>> pied # Pas de “s” final. __[i]/sgpl(sgpl_au_pied_levé)__ @@ -7494,11 +8621,11 @@ # vacances __[i]/sgpl(sgpl_bonnes_vacances)__ bonne vacance <<- not morph(word(-1), ":D.*:f:s", False, False) ->> bonnes vacances # Au pluriel. __[i]/sgpl(sgpl_en_vacances)__ ({w1}) +en (vacance) @@0,$ - <<- morph(\1, ">(?:aller|partir) ", False) -2>> vacances # Si vous parlez des congés, « vacance » doit être au pluriel. + <<- morph(\1, ">(?:aller|partir)/", False) -2>> vacances # Si vous parlez des congés, « vacance » doit être au pluriel. TEST: Je pars en {{vacance}}. TEST: {{Bonne vacance}} ! TEST: Il nous reste un poste en vacance. TEST: Cette place est en vacance. @@ -7532,19 +8659,19 @@ !! # à / a __[i]/conf(conf_suite_à)__ suite (a) ({w1}) @@w,$ - <<- morph(\2, ":D|>[ld] ", False) and isStart() -1>> à # Confusion : “a” est une forme conjuguée du verbe “avoir”. Pour la préposition, écrivez “à”. + <<- morph(\2, ":D|>[ld]/", False) and isStart() -1>> à # Confusion : “a” est une forme conjuguée du verbe “avoir”. Pour la préposition, écrivez “à”. TEST: Suite {{a}} ces folies, nous rentrâmes chez nous. TEST: il s’avère que, suite {{a}} d’horribles complications, nous renonçâmes. __[i]/conf(conf_pronom_à_l_air)__ (?:tout|ça|ce(?:ci|la)) (à) l’air +({w_2}) @@w,$ - <<- morphex(\2, ":[AR]", ">libre ") and morph(word(-1), ":Cs", False, True) + <<- morphex(\2, ":[AR]", ">libre/") and morph(word(-1), ":Cs", False, True) -1>> a # Confusion probable : “à” est une préposition. Pour le verbe “avoir”, écrivez “a”. TEST: lorsque tout {{à}} l’air fini, c’est trompeur. TEST: Tout {{à}} l’air complètement foutu… TEST: Ça {{à}} l’air génial. @@ -7579,21 +8706,21 @@ TEST: un terrain de 3 {{âcres}}. __[i]/conf(conf_âcre)__ acres? - <<- morph(word(-1), ">(?:être|go[ûu]t|humeur|odeur|parole|parfum|remarque|reproche|réponse|saveur|senteur|sensation|vin)", False, False) + <<- morph(word(-1), ">(?:être|go[ûu]t|humeur|odeur|parole|parfum|remarque|reproche|réponse|saveur|senteur|sensation|vin)/", False, False) ->> =\0.replace("a", "â").replace("A", "Â") # Confusion probable : “acre” est une unité de surface agraire. Pour l’adjectif signifiant “irritant”, écrivez :|https://fr.wiktionary.org/wiki/%C3%A2cre TEST: Il avait ce goût {{acre}} dans la bouche qui ne passait pas. # accro / accroc __[i]/conf(conf_être_accro)__ ({etre}|dev\w+|sembl\w+|par\w+|rend\w+) +(accrocs?) @@0,$ - <<- morph(\1, ">(?:être|devenir|para[îi]tre|rendre|sembler) ", False) + <<- morph(\1, ">(?:être|devenir|para[îi]tre|rendre|sembler)/", False) -2>> =\2.replace("oc", "o") # Confusion : “accroc” signifie “déchirure”, “incident”, etc. tandis que “accro” est un terme familier qui signifie “dépendant”. __[i]/conf(conf_accro_à)__ (accrocs?) (?:[àa] (?:la (?:bouffe|cocaïne|cod[ée]ine|course|drogue|coke|meth|méthamphétamine|morphine|nicotine|nourriture|télé(?:vision|)|clope|cigarette|came|poudre|baise|musique)|cette (?:came|émission|merde|poudre|femme|meuf|gonzesse|conne|salope|garce)|ce (?:mec|keum|type|con(?:nard|)|fils de pute)|cet (?:homme|enculé|imbécile|enfoiré)|l’(?:alcool|amour|argent|ecstasy|herbe|héro(?:ïne|)|opium|ordi(?:nateur|))|Facebook|Internet|Twitter|lui|elle)|[ad]u (?:chocolat|cul|jeu|poker|sexe|shopping|smartphone|sport|sucre|tabac|téléphone|travail|LSD|crack)|aux (?:anti-?dépresseurs|bonbons|hommes|mecs|femmes|gonzesses|méd(?:icaments|ocs)|jeux|séries|sucreries)) @@0 @@ -7613,11 +8740,11 @@ __[i]/conf(conf_par_acquit_de_conscience)__ par (acquis) de conscience @@4 <<- -1>> acquit # Confusion. On écrit « par acquit de conscience ». <<- ~>> * __[i]/conf(conf_tenir_pour_acquit)__ - (t\w+) +pour (acquits?) @@0,$ <<- morph(\1, ">tenir ") -2>> acquis # Confusion. On écrit « tenir pour acquis ». + (t\w+) +pour (acquits?) @@0,$ <<- morph(\1, ">tenir/") -2>> acquis # Confusion. On écrit « tenir pour acquis ». TEST: Je le tenais pour {{acquit}}. TEST: Par {{acquis}} de conscience. @@ -7637,14 +8764,14 @@ # amende / amande __[i]/conf(conf_yeux_en_amande)__ yeux en (amendes?) @@$ <<- -1>> amande # Confusion. Une amende est une peine.|http://www.cnrtl.fr/lexicographie/amende __[i]/conf(conf_à_l_amende)__ - (m\w+) à (l’amande) @@0,$ <<- morph(\1, ">mettre ", False) -2>> l’amende # Confusion. L’amande est un fruit. + (m\w+) à (l’amande) @@0,$ <<- morph(\1, ">mettre/", False) -2>> l’amende # Confusion. L’amande est un fruit. __[i]/conf(conf_faire_amende_honorable)__ (f\w+)(?:-(?:je|tu|ils?|[nv]ous|elles?)|) +(amandes? honorables?) @@0,$ - <<- morph(\1, ">faire ", False) -2>> amende honorable # Confusion. L’amande est un fruit. + <<- morph(\1, ">faire/", False) -2>> amende honorable # Confusion. L’amande est un fruit. TEST: Avec ses beaux yeux en {{amendes}} nul ne peut lui résister. TEST: Nous avons déconné, nous avons été mis à {{l’amande}}. TEST: Ces gens-là ne feront jamais {{amande honorable}}. @@ -7680,11 +8807,11 @@ # Confusion probable. L’hospice est un centre de soins.|https://fr.wiktionary.org/wiki/auspice __[i]/conf(conf_sous_les_auspices2)__ sous (?:les \w+|d’\w+|des? \w+) +(hospices) @@$ <<- -1>> auspices # Confusion probable. L’hospice est un centre de soins.|https://fr.wiktionary.org/wiki/auspice __[i]/conf(conf_hospice1)__ - ({etre}|{aller}) +(?:à|dans) l’(auspice) @@0,$ <<- morph(\1, ">(?:être|aller) ", False) -2>> hospice + ({etre}|{aller}) +(?:à|dans) l’(auspice) @@0,$ <<- morph(\1, ">(?:être|aller)/", False) -2>> hospice # Confusion. Les auspices sont des présages, des devins ou, au sens moderne, les circonstances.|https://fr.wiktionary.org/wiki/auspice __[i]/conf(conf_hospice2)__ dans (?:un|cet|[ldc]es) +(auspices?) @@$ <<- -1>> =\1.replace("auspice", "hospice") # Confusion. Les auspices sont des présages, des devins ou, au sens moderne, les circonstances.|https://fr.wiktionary.org/wiki/auspice __[i]/conf(conf_hospice3)__ @@ -7727,15 +8854,15 @@ __[i]/conf(conf_en_rupture_de_ban)__ en ruptures? de (bancs?) @@$ <<- -1>> ban # Confusion. Locution “en rupture de ban”.|https://fr.wiktionary.org/wiki/en_rupture_de_ban __[i]/conf(conf_mettre_au_ban)__ (m[eiî]\w+) au (banc) @@0,$ - <<- morph(\1, ">mettre ", False) and not after("^ +des accusés") + <<- morph(\1, ">mettre/", False) and not after("^ +des accusés") -2>> ban # Confusion : « mettre au ban » signifie « faire déchoir ».|https://fr.wiktionary.org/wiki/mettre_au_ban __[i]/conf(conf_publier_les_bans)__ (publi\w+) [dlcmts]es (bancs) @@0,$ - <<- morph(\1, ">publi(?:er|cation) ", False) -2>> bans # Confusion.|https://fr.wikipedia.org/wiki/Publication_des_bans + <<- morph(\1, ">publi(?:er|cation)/", False) -2>> bans # Confusion.|https://fr.wikipedia.org/wiki/Publication_des_bans TEST: Convoquons le ban et l’{{arrière-banc}}. TEST: il faut publier les {{bancs}} avant qu’il ne soit trop tard. TEST: Les {{bancs}} de mariage sont prêts. TEST: des hommes en rupture de {{banc}} @@ -7812,11 +8939,11 @@ __[i]/conf(conf_nom_de_cane)__ (?:œuf|filet)s? de (cannes?) @@$ <<- -1>> cane # Confusion. La canne est un bâton ou un roseau. Pour la femelle du canard, écrivez|https://fr.wiktionary.org/wiki/canne __[i]/conf(conf_verbe_canne)__ ((?:appu|batt|frapp|l[eè]v|march)\w+) (?:avec|sur) (?:[dl]es|[mts](?:a|es)|une) (canes?) @@0,$ - <<- morph(\1, ">(?:appuyer|battre|frapper|lever|marcher) ", False) + <<- morph(\1, ">(?:appuyer|battre|frapper|lever|marcher)/", False) -2>> =\2.replace("cane", "canne") # Confusion. La cane est la femelle du canard.|https://fr.wiktionary.org/wiki/cane __[i]/conf(conf_bec_de_cane)__ becs?-de-(cannes?) @@$ <<- -1>> cane # Confusion. Le bec-de-cane se somme ainsi à cause de la ressemblance avec le bec de l’animal.|https://fr.wiktionary.org/wiki/bec-de-cane @@ -7840,11 +8967,11 @@ # chair / chère __[i]/conf(conf_faire_bonne_chère)__ (f[aioîe]\w+) +(bonnes? ch(?:ai|e)re?) @@0,$ - <<- morph(\1, ">faire ", False) + <<- morph(\1, ">faire/", False) -2>> bonne chère # Confusion. « Faire bonne chère » signifie bien manger, ripailler. TEST: ils ont fait {{bonne chaire}}. @@ -7929,16 +9056,21 @@ (c[ôo]tes?) de mailles? @@0 <<- -1>> =\1.replace("ô", "o").replace("t", "tt") # Confusion : écrivez « cotte » pour la cotte de mailles.|https://fr.wiktionary.org/wiki/cotte_de_mailles __[i]/conf(conf_avoir_la_cote)__ ({avoir}) +la (côte) @@0,$ <<- morph(\1, ":V0a", False) -2>> cote # Confusion probable : utilisez « cote » (cotation).|http://fr.wiktionary.org/wiki/cote +__[i](conf_côte_à_côte)__ + c[ôo]tt?es? [àaá] c[ôo]tt?es? + <<- not re.search("(?i)^côte à côte$", \0) ->> côte à côte # Locution adverbiale invariable. Écrivez “côte à côte”.|https://fr.wiktionary.org/wiki/c%C3%B4te_%C3%A0_c%C3%B4te + <<- ~>> * TEST: Rien ne vaut une bonne {{cote}} de bœuf. TEST: Elles ont passé une radiographie des {{cottes}}. TEST: Quelle est sa {{côte}} de popularité TEST: il a réussi à percer sa {{cote}} de mailles. TEST: Il a la {{côte}} auprès de ses collègues +TEST: ils sont {{cotte à cotte}} TEST: on a atteint la cote d’alerte. # cou / coup / coût __[i]/conf(conf_coup_de)__ @@ -7958,15 +9090,15 @@ TEST: Merci de calculer le {{coup}} de production avant d’établir une facture. TEST: Elle a un {{coût}} si gracile. __[i]/conf(conf_tordre_le_cou)__ - (tord\w*) +le (co[uû][pt]s?) @@0,$ <<- morph(\1, ">tordre ", False) -2>> cou + (tord\w*) +le (co[uû][pt]s?) @@0,$ <<- morph(\1, ">tordre/", False) -2>> cou # Confusion. Le coût indique ce que ça coûte. Un coup, c’est quelque chose qui frappe. La partie séparant la tête du corps s’écrit “cou”. __[i]/conf(conf_rendre_coup_pour_coup)__ (rend\w*) +(co[uû]t?s? pour co[uû]t?s?) @@0,$ - <<- morph(\1, ">rendre ", False) -2>> coup pour coup + <<- morph(\1, ">rendre/", False) -2>> coup pour coup # Confusion. Le coût indique ce que ça coûte. Un cou est la partie séparant la tête du corps. Pour ce qui frappe, écrivez “coup”. TEST: Je vais tordre le {{coup}} à toutes ces idées stupides, une par une. TEST: Implacable, elle a rendu {{cout pour cout}} sans se départir de son calme. @@ -7975,14 +9107,14 @@ __[i]/conf(conf_au_cours_de)__ au (court?) (?:des?|du?) @@3 <<- -1>> cours # Confusion probable. Une cour… Un cours… Adjectif : court(e). __[i]/conf(conf_en_cours)__ en cour(?! martiale?| de (?:cassation|justice)| d’assises) <<- ->> en cours # Confusion probable. Une cour… Un cours… Adjectif : court(e). __[i]/conf(conf_couper_court)__ - (coup\w+) (cours?) @@0,$ <<- morph(\1, ">couper ") -2>> court # « Couper court ». Écourter. Une cour… Un cours… Adjectif : court(e). + (coup\w+) (cours?) @@0,$ <<- morph(\1, ">couper/") -2>> court # « Couper court ». Écourter. Une cour… Un cours… Adjectif : court(e). __[i]/conf(conf_laisser_libre_cours)__ ({w1}) +libre (court?) @@0,$ - <<- morph(\1, ">(?:avoir|donner|laisser) ", False) -2>> cours # Confusion probable. Ce qui a « libre cours ».|https://fr.wiktionary.org/wiki/donner_libre_cours + <<- morph(\1, ">(?:avoir|donner|laisser)/", False) -2>> cours # Confusion probable. Ce qui a « libre cours ».|https://fr.wiktionary.org/wiki/donner_libre_cours __[i]/conf(conf_à_court_de)__ à (cours?) de? @@2 <<- -1>> court # Confusion probable : écrivez « à court de … » pour « manquer de … » __[i]/conf(conf_à_court_terme)__ à cour(?:s|ts|) termes? <<- ->> à court terme # Confusion. Une cour… Un cours… Adjectif : court(e). @@ -8012,16 +9144,16 @@ # desceller / déceler / desseller __[i]/conf(conf_erreur_problème_decelé)__ (erreur|faute|incohérence|problème|bug|bogue|faille|maladie|défaut|défaillance|perturbation|irrégularité)s? .*(des[cs]ell\w+) @@0,$ - <<- morph(\2, ">(?:desceller|desseller) ", False) + <<- morph(\2, ">(?:desceller|desseller)/", False) -2>> =\2.replace("escell", "écel").replace("essell", "écel") # Confusion probable si ce mot se rapporte à « \1 ». Desceller signifie briser un sceau, un cachet… Desseller signifie ôter une selle. Si vous voulez dire “remarquer”, “dévoiler”, “découvrir”, écrivez :|http://fr.wiktionary.org/wiki/déceler __[i]/conf(conf_deceler_qqch)__ (des[cs]ell\w+) +(?:(?:une?|l[ae]|des?|ce(?:tte|t|s|)|[mts](?:on|a|es)|[nv]os|leurs?|plusieurs|quelques|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze) +|l’)(?:(?:petite?|grande?|énorme|dangeureu(?:x|se)|formidable|forte?|lég(?:er|ère)|merveilleu(?:x|se)|nouv(?:el|elle|eaux?)|vraie?|réel(?:le|)|sévère|véritable)s? +|)(acidité|activité|allergie|anévrisme|anomalie|arnaque|appendicite|atrophie|baisse|bébé|blessure|bug|bogue|carie|cancer|cause|changement|complot|comète|concentration|corrélation|croissance|défaut|défaillance|demande|dépression|diabète|différence|diminution|effluve|épilepsie|erreur|essai|existence|grossesse|grosseur|faille|faute|fuite|fraude|grippe|handicap|hausse|hémorragie|hostilité|hypertrophie|incompatibilité|incohérence|infection|infraction|indice|infidélité|insuffisance|intrigue|irrégularité|leucémie|lésion|lueur|lumière|maladie|malformation|manœuvre|manipulation|molécule|mensonge|mutation|once|perturbation|personnalité|piste|perte|planète|exoplanète|présence|qualité|odeur|opportunité|otite|problème|surdité|talent|tendance|tentative|tumeur|utilisation|hoax|variation|vie|virus)s? @@0,$ - <<- morph(\1, ">(?:desceller|desseller) ", False) + <<- morph(\1, ">(?:desceller|desseller)/", False) -1>> =\1.replace("escell", "écel").replace("essell", "écel") # Confusion probable si ce mot se rapporte à « \2 ». Desceller signifie briser un sceau, un cachet… Desseller signifie ôter une selle.|http://fr.wiktionary.org/wiki/déceler TEST: il y a une erreur qu’on peut {{desceller}} dans ses analyses. TEST: elle a {{dessellé}} une forte hostilité dans ses propos. @@ -8028,21 +9160,21 @@ # en train / entrain __[i]/conf(conf_en_train)__ entrain - <<- morph(word(-1), ">(?:être|voyager|surprendre|venir|arriver|partir|aller) ", False, False) or before("-(?:ils?|elles?|on|je|tu|nous|vous) +$") + <<- morph(word(-1), ">(?:être|voyager|surprendre|venir|arriver|partir|aller)/", False, False) or before("-(?:ils?|elles?|on|je|tu|nous|vous) +$") ->> en train # Confusion. L’entrain est une fougue, une ardeur à accomplir quelque chose.|https://fr.wiktionary.org/wiki/entrain TEST: Vous êtes {{entrain}} de vaincre. TEST: Viennent-ils {{entrain}} ? TEST: ces idiots sont en train de tout foutre en l’air. __[i]/conf(conf_entrain)__ en train - <<- morph(word(-1), ">(?:avec|sans|quel(?:le|)|cet|votre|notre|mon|leur) ", False, False) or before(" [dlDL]’$") + <<- morph(word(-1), ">(?:avec|sans|quel(?:le|)|cet|votre|notre|mon|leur)/", False, False) or before(" [dlDL]’$") ->> entrain # Confusion. Soudez les deux mots. L’entrain est une fougue, une ardeur à accomplir quelque chose.|https://fr.wiktionary.org/wiki/entrain TEST: Avec quel {{en train}}, ils nous ont mené jusque là-haut. TEST: Son manque d’{{en train}} était contagieux. TEST: c’est l’{{en train}} de cette jeune femme qui force l’admiration de tout le monde. @@ -8050,11 +9182,11 @@ # envi / envie __[i]/conf(conf_à_l_envi)__ à l’(envie) @@4 - <<- not morph(word(-1), ">(?:abandonner|céder|résister) ", False) and not after("^ d(?:e |’)") + <<- not morph(word(-1), ">(?:abandonner|céder|résister)/", False) and not after("^ d(?:e |’)") -1>> envi # Locution adverbiale « à l’envi », signifiant « autant que possible ». TEST: Ils s’amusèrent à l’{{envie}} et oublièrent tous leurs soucis. TEST: Je résiste à l’envie de manger du chocolat. TEST: On ne s’intéresse pas à l’école ni à l’âge, mais aux compétences et à l’envie de partager. @@ -8127,11 +9259,11 @@ (mauvaise|bonne) (fois) @@0,$ <<- not ( \1 == "bonne" and before(r"(?i)\bune +$") and after("(?i)^ +pour toute") ) -2>> foi # Confusion probable.|http://fr.wiktionary.org/wiki/foi __[i]/conf(conf_faire_perdre_donner_foi)__ ((?:f[aieî]|perd|donn|[ae])\w*) (fois) @@0,$ - <<- morph(\1, ">(?:faire|perdre|donner|avoir) ", False) -2>> foi # Confusion probable.|http://fr.wiktionary.org/wiki/foi + <<- morph(\1, ">(?:faire|perdre|donner|avoir)/", False) -2>> foi # Confusion probable.|http://fr.wiktionary.org/wiki/foi TEST: C’est une personne de bonne {{fois}}. TEST: Mais il a perdu {{fois}} en l’avenir. @@ -8281,17 +9413,17 @@ TEST: elle se {{l’a}} {{réserve}} pour elle-même. __[i]/conf(conf_il_elle_on_l_a)__ (?:il|elle|on) (?:vous |nous |)(la)[ @]+({w_2}) @@*,$ - <<- morphex(\2, ":Q", ":(?:[123][sp]|V[123]......e)|>lui ") -1>> l’a # Confusion probable : “\2” est un participe passé. Il faut donc employer l’auxiliaire “avoir”. + <<- morphex(\2, ":Q", ":(?:[123][sp]|V[123]......e)|>lui/") -1>> l’a # Confusion probable : “\2” est un participe passé. Il faut donc employer l’auxiliaire “avoir”. __[i]/conf(conf_ne_l_a)__ ne (?:vous |nous |)(la)[ @]+({w_2}) @@*,$ - <<- morphex(\2, ":Q", ":(?:[123][sp]|V[123]......e)|>lui ") -1>> l’a # Confusion probable : “\2” est un participe passé. Il faut donc employer l’auxiliaire “avoir”. + <<- morphex(\2, ":Q", ":(?:[123][sp]|V[123]......e)|>lui/") -1>> l’a # Confusion probable : “\2” est un participe passé. Il faut donc employer l’auxiliaire “avoir”. __[i]/conf(conf_me_te_l_a)__ [mt]e (la)[ @]+({w_2}) @@*,$ - <<- morphex(\2, ":Q", ":(?:[123][sp]|V[123]......e)|>lui ") -1>> l’a # Confusion probable : “\2” est un participe passé. Il faut donc employer l’auxiliaire “avoir”. + <<- morphex(\2, ":Q", ":(?:[123][sp]|V[123]......e)|>lui/") -1>> l’a # Confusion probable : “\2” est un participe passé. Il faut donc employer l’auxiliaire “avoir”. TEST: il {{la}} {{donnée}}. TEST: ne {{la}} {{donné}} que contraint et forcé… TEST: celle-là, il me {{la}} {{commandée}} ? @@ -8314,11 +9446,11 @@ # lever un lièvre / soulever __[i]/conf(conf_lever_un_lièvre)__ (soul\w+) +(?:un|le) lièvre @@0 - <<- morph(\1, ">soulever ", False) -1>> =\1[3:] + <<- morph(\1, ">soulever/", False) -1>> =\1[3:] # Expression impropre. On écrit « lever un lièvre ».|http://fr.wiktionary.org/wiki/lever_le_li%C3%A8vre TEST: j’ai {{soulevé}} un lièvre, là ! @@ -8332,16 +9464,16 @@ @@0 <<- -1>> lieux # Confusion probable. Pour désigner un endroit, utilisez “lieu(x)”.|http://fr.wiktionary.org/wiki/lieu __[i]/conf(conf_être_à_xxx_lieues)__ ((?:[eêsf]|demeur|habit|trouv|situ|rest)\w+) à (?:quelques|dix|douze|quinze|seize|vingt|cent|mille|des|\d+) (lieu[sx]) @@0,$ - <<- morph(\1, ">(?:être|habiter|trouver|situer|rester|demeurer?) ", False) + <<- morph(\1, ">(?:être|habiter|trouver|situer|rester|demeurer?)/", False) -2>> lieues # Confusion probable. Pour désigner une distance, utilisez “lieues”.|http://fr.wiktionary.org/wiki/lieue __[i]/conf(conf_avoir_eu_lieu)__ ({avoir}) +(?:eue?s? +|)(lieu(?:es?|x)) @@0,$ - <<- morph(\1, ">avoir ", False) -2>> lieu # Confusion. Dans l’expression « avoir lieu », “lieu” est invariable. + <<- morph(\1, ">avoir/", False) -2>> lieu # Confusion. Dans l’expression « avoir lieu », “lieu” est invariable. TEST: qui est le responsable des {{lieues}} ? TEST: ce sont des {{lieus}} mythiques TEST: elle habitait à quelques {{lieux}} d’ici TEST: Cette réunion ayant eu {{lieue}} loin d’ici @@ -8504,11 +9636,11 @@ __[i]/conf(conf_pain_qqch)__ (pins?) (?:d’épices?|perdus?|sans glutens?) @@0 <<- -1>> =\1.replace("pin", "pain") # Confusion. Le pin est un arbre résineux à aiguilles persistantes. Pour parler la pâte de farine et d’eau cuite au four, écrivez : __[i]/conf(conf_manger_pain)__ ((?:mang|dévor|aval|englout)\w+) +(?:les?|d(?:u|es)|un|[mts](?:on|es)|leurs?|[nv]o(?:s|tre)) +(pins?) @@0,$ - <<- morph(\1, ">(?:manger|dévorer|avaler|engloutir) ") -2>> =\2.replace("pin", "pain") + <<- morph(\1, ">(?:manger|dévorer|avaler|engloutir)/") -2>> =\2.replace("pin", "pain") # Confusion probable. Le pin est un arbre résineux à aiguilles persistantes. Pour parler la pâte de farine et d’eau cuite au four, écrivez : __[i]/conf(conf_pomme_de_pin)__ pommes? de (pains?) @@$ <<- -1>> pin # Le pain est une pâte de farine et d’eau cuite au four. La pomme de pin est le fruit du pin.|https://fr.wiktionary.org/wiki/pomme_de_pin @@ -8518,11 +9650,11 @@ # pair / paire __[i]/conf(conf_aller_de_pair)__ ((?:all|v|ir)\w+) de (pair(?:es?|s)|perd?s?) @@0,$ - <<- morph(\1, ">aller ", False) -2>> pair # Confusion. On écrit « aller de pair ». + <<- morph(\1, ">aller/", False) -2>> pair # Confusion. On écrit « aller de pair ». TEST: Ils vont de {{paires}}. # pâle / pale @@ -8539,19 +9671,19 @@ TEST: Sous une lumière {{pale}}, # parti / partie __[i]/conf(conf_prendre_parti)__ - (pr\w+) +(parti(?:s|es?)) @@0,$ <<- morph(\1, ">prendre ", False) -2>> parti # Confusion. On écrit « prendre parti ». + (pr\w+) +(parti(?:s|es?)) @@0,$ <<- morph(\1, ">prendre/", False) -2>> parti # Confusion. On écrit « prendre parti ». __[i]/conf(conf_tirer_parti)__ - (tir\w+) +(parti(?:s|es?)) @@0,$ <<- morph(\1, ">tirer ", False) -2>> parti # Confusion. On écrit « tirer parti ». + (tir\w+) +(parti(?:s|es?)) @@0,$ <<- morph(\1, ">tirer/", False) -2>> parti # Confusion. On écrit « tirer parti ». __[i]/conf(conf_faire_partie)__ - (f[aieoî]\w+) +(parti(?:s|es|)) @@0,$ <<- morph(\1, ">faire ", False) -2>> partie # Confusion. On écrit « faire partie ». + (f[aieoî]\w+) +(parti(?:s|es|)) @@0,$ <<- morph(\1, ">faire/", False) -2>> partie # Confusion. On écrit « faire partie ». __[i]/conf(conf_juge_et_partie)__ juges? et partis? <<- ->> juge et partie|juges et parties # Confusion. On écrit « être juge et partie ». __[i]/conf(conf_prendre_à_partie)__ - (pr\w+) +(?:{w_2} +|)([àa] partis?) @@0,$ <<- morph(\1, ">prendre ", False) -2>> à partie # Confusion. On écrit « prendre à partie ». + (pr\w+) +(?:{w_2} +|)([àa] partis?) @@0,$ <<- morph(\1, ">prendre/", False) -2>> à partie # Confusion. On écrit « prendre à partie ». TEST: Elle prend toujours {{partie}} aux réunions. TEST: Il faut savoir tirer {{partis}} de ces atouts-là. TEST: Tu fais {{parti}} de l’élite, enfin, façon de parler. TEST: Nous sommes tous d’une manière ou d’une autre {{juge et parti}}. @@ -8696,11 +9828,11 @@ # raisonner / résonner __[i]/conf(conf_raisonner)__ (?:la|les?|[mts]e|[nv]ous) (résonn\w+) @@$ - <<- morph(\1, ">résonner ", False) -1>> =\1.replace("réso", "raiso") # Confusion probable. Vous utilisez la raison, mais vous ne « sonnez » pas. + <<- morph(\1, ">résonner/", False) -1>> =\1.replace("réso", "raiso") # Confusion probable. Vous utilisez la raison, mais vous ne « sonnez » pas. TEST: Vous {{résonnez}} comme un sot. TEST: Nous allons le {{résonner}}. @@ -8758,20 +9890,20 @@ __[i]/conf(conf_qqch_septique)__ (?:fosse|installation|choc|chirurgie|maladie|plaie|blessure|embolie|arthrite|isolement|pneumo-entérite)s? (sceptiques?) @@$ <<- -1>> =\1.replace("scep","sep") # Confusion possible. Septique = corrompu, infecté. Sceptique = ayant des doutes. __[i]/conf(conf_être_sceptique)__ ({etre}|demeur\w+) +(septiques?) @@0,$ - <<- morph(\1, ">(?:être|demeurer) ", False) -2>> =\2.replace("sep", "scep") + <<- morph(\1, ">(?:être|demeurer)/", False) -2>> =\2.replace("sep", "scep") # Confusion possible. Septique = corrompu, infecté. Sceptique = ayant des doutes. TEST: cette fosse {{sceptique}} est pleine. TEST: Je suis {{septique}} ! # s’ensuivre __[i]/conf(conf_s_ensuivre)__ - s’en (sui\w+) @@$ <<- morph(\1, ">suivre ", False) ->> s’en\1 # Verbe « s’ensuivre ». + s’en (sui\w+) @@$ <<- morph(\1, ">suivre/", False) ->> s’en\1 # Verbe « s’ensuivre ». TEST: {{S’en suivit}} une guerre de tous les instants. TEST: {{S’en suivre}}. @@ -8792,11 +9924,11 @@ -1>> soi # Confusion probable. __[i]/conf(conf_quel_que_soit2)__ quel(?:le|)s? que (soi(?:es?|)) @@$ <<- -1>> soit|soient # Confusion probable. __[i]/conf(conf_soi_même1)__ (soi[tes]s? mêmes?) @@$ - <<- morph(word(-1), ":[YQ]|>(?:avec|contre|par|pour|sur) ", False, True) -1>> soi-même # Confusion probable : moi-même, toi-même, lui-même, elle-même, soi-même, elles-mêmes, eux-mêmes. + <<- morph(word(-1), ":[YQ]|>(?:avec|contre|par|pour|sur)/", False, True) -1>> soi-même # Confusion probable : moi-même, toi-même, lui-même, elle-même, soi-même, elles-mêmes, eux-mêmes. __[i]/conf(conf_soi_même2)__ soi[tes]s?-mêmes? <<- ->> soi-même # Confusion : moi-même, toi-même, lui-même, elle-même, soi-même, elles-mêmes, eux-mêmes. TEST: chez {{soit}}, c’est presque toujours mieux. TEST: ce n’est pas la philosophie en {{soit}} qui est problématique @@ -8815,45 +9947,15 @@ <<- isStart() -1>> soit # Confusion probable : pour évoquer une option, écrivez “soit”.|https://fr.wiktionary.org/wiki/soit#Conjonction TEST: {{soi}} je vais au cinéma, {{soi}} je m’abstiens. TEST: {{soie}} j’arrive avant tout le monde. - -# sur / sûr -__[i]/conf(conf_sûr_que)__ - (sure?s?) que? @@0 - <<- -1>> =\1.replace("sur", "sûr") - # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur -__[i]/conf(conf_sûre_surs_de)__ - (sur(?:es?|s)) de? @@0 - <<- -1>> =\1.replace("sur", "sûr") - # Confusion probable : “sur” un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur -__[i]/conf(conf_sûr_de)__ - (sur) d(?:e (?:m(?:oi|es?|on|a)|t(?:oi|es?|on|a)|vous|nous|l(?:ui|es?)|s(?:oi|es?|on|a)|ce(?:ci|la|s|tte|t|)|ça)|’(?:elles?|eux)) @@0 - <<- -1>> sûr - # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur -__[i]/conf(conf_sûr_de_vinfi)__ - (sur) de (?:l(?:a |’|es? |ui |eur )|)({infi}) @@0,$ - <<- morph(\2, ":Y", False) - -1>> =\1.replace("sur", "sûr") - # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur -__[i]/conf(conf_en_lieu_sûr)__ - en lieu (sur) @@8 - <<- -1>> sûr - # Confusion probable : “sur” est une préposition ou un adjectif signifiant acide ou aigre ; utilisez “sûr” pour certain, vrai ou sans danger.|http://fr.wiktionary.org/wiki/sur - -TEST: Je suis {{sure}} qu’il ne va pas tarder à venir -TEST: {{sures}} d’elles-mêmes, elles ne s’en laissent pas conter. -TEST: {{sur}} de toi et de moi, que peut-il nous arriver, sinon le meilleur. -TEST: Il est tellement {{sur}} de la trouver. -TEST: ils sont en lieu {{sur}} et introuvables. - # tâche / tache (de chocolat / rousseur / vin / sang / café / gras / graisse / huile / etc.) __[i]/conf(conf_tache_de_qqch)__ (tâches?) d(?:e +|’)({w_2}) @@0,$ - <<- morphex(\2, ":N", ":[GMY]|>(?:fonds?|grande (?:envergure|ampleur|importance)|envergure|ampleur|importance|départ|surveillance) ") and not before("accompl|dél[éè]gu") + <<- morphex(\2, ":N", ":[GMY]|>(?:fonds?|grande (?:envergure|ampleur|importance)|envergure|ampleur|importance|départ|surveillance)/") and not before("accompl|dél[éè]gu") -1>> =\1.replace("â", "a") # Confusion probable. Une tâche est un travail à accomplir. Pour une salissure, une altération, une marque, une coloration… employez “tache”. __[i]/conf(conf_tache_adjectif)__ (tâches?) +(?:indélébile|rouge|verte|noire|bleue|jaune|grise|blanche|brune|pourpre|chocolat|mauve|fushia|violette|rose|claire|sombre)s? @@0 <<- -1>> =\1.replace("â", "a") @@ -8869,14 +9971,14 @@ # taule / tôle __[i]/conf(conf_aller_en_taule)__ ({aller}) +en (t[ôo]les?) @@0,$ - <<- morph(\1, ">aller ", False) -2>> taule # Confusion. La tôle est une plaque de métal laminé. Pour la prison, écrivez : + <<- morph(\1, ">aller/", False) -2>> taule # Confusion. La tôle est une plaque de métal laminé. Pour la prison, écrivez : __[i]/conf(conf_faire_de_la_taule)__ (f[aiî]\w+) +de la (t[ôo]les?) @@0,$ - <<- morph(\1, ">faire ", False) -2>> taule # Confusion. La tôle est une plaque de métal laminé. Pour la prison, écrivez : + <<- morph(\1, ">faire/", False) -2>> taule # Confusion. La tôle est une plaque de métal laminé. Pour la prison, écrivez : __[i]/conf(conf_tôle_qqch)__ (taules?) (?:(?:boulonné|cintré|émaillé|embouti|galvanisé|gaufré|nervuré|ondulé|perforé|soudé|translucide)e?s?|(?:d(?:e |’)|en )(?:acier|alu|aluminium|bardage|cuivre|étanchéité|fer|festonnage|inox|laiton|métal|trapèze|zinc|éverite|fibro-?ciment|plastique|polycarbonate|PVC)s?) @@0 <<- -1>> =\1.replace("au", "ô") # Confusion. La taule est la forme argotique pour évoquer la prison, le bordel ou toute forme d’habitation. TEST: Demain, il va aller en {{tôle}}. @@ -8887,11 +9989,11 @@ # tant / temps (2e partie) __[i]/conf(conf_en_tant_que)__ en (temps|tan) que? @@3 <<- -1>> tant # Confusion. Écrivez « en tant que ».|http://fr.wiktionary.org/wiki/en_tant_que __[i]/conf(conf_il_être_tant_de)__ il ({etre}) +(tant?) d(?:e |’)({infi}|ne|en|y) @@3,w,$ - <<- morph(\1, ":V0e", False) and morph(\3, ":Y|>(?:ne|en|y) ", False) + <<- morph(\1, ":V0e", False) and morph(\3, ":Y|>(?:ne|en|y)/", False) -2>> temps # Confusion. TEST: en {{tan}} que meneuse intrépide, elle a toujours fait preuve d’une grande imagination. TEST: il est bien évidemment {{tant}} d’en finir avec ça. @@ -8909,18 +10011,24 @@ # tort / tord __[i]/conf(conf_à_tort)__ à (tor[de]?s?) @@2 <<- -1>> tort # Confusion : “tord” est une conjugaison du verbe tordre. __[i]/conf(conf_avoir_tort)__ ({avoir}|donn\w+) +(tor[ed]?s?) @@0,$ - <<- morph(\1, ">(?:avoir|donner) ", False) -2>> tort # Confusion : “tord” est une conjugaison du verbe tordre. + <<- morph(\1, ">(?:avoir|donner)/", False) -2>> tort # Confusion : “tord” est une conjugaison du verbe tordre. TEST: elles seront à {{tord}} accusées. TEST: ils ont {{tords}}… TEST: ils ont {{tord}}. TEST: ils n’ont pas {{tord}}. TEST: je ne peux pas lui donner {{tord}}. + +__[i]/conf(conf_tour_à_tour)__ + tours? [àa] tours? + <<- not re.search("(?i)^tour à tour$", \0) ->> tour à tour # Locution adverbiale invariable. Écrivez “tour à tour”.|https://fr.wiktionary.org/wiki/tour_%C3%A0_tour + <<- ~>> * + # venimeux / vénéneux __[i]/conf(conf_qqch_venimeux)__ (?:serpent|araignée|scorpion|vipère|cobra|crapaud|grenouille|dendrobate|poulpe|guêpe|abeille|méduse|morsure|piqûre|dard|dent|croc|crochet)s? +(vénéneu(?:x|ses?)) @@$ <<- -1>> =\1.replace("énén", "enim") # Confusion : “vénéneux” se dit des plantes, employez “venimeux”. @@ -9078,11 +10186,11 @@ # les langues __[s]/maj(maj_langues)__ ((?:parl|cours|leçon|appr|étud|tradu|enseign|professeur|enseignant|dictionnaire|méthode)\w*) (?:le |d[eu] |l’|d’|qu |)(Afrikaans|Albanais|Allemand|Alsacien|Anglais|Arabe|Aragonais|Arménien|Asturien|Basque|Bengali|Biélorusse|Birman|Bosniaque|Breton|Bulgare|Cantonais|Catalan|Cherokee|Chinois|Corse|Cornique|Coréen|Croate|Danois|Écossais|Espagnol|Espéranto|Estonien|Féroïen|Farsi|Finnois|Flamand|Français|Frison|Galicien|Gallois|Gaulois|Géorgien|Grec|Gujarati|Hakka|Hawaïen|Hébreu|Hindi|Hollandais|Hongrois|Javanais|Ido|Indonésien|Interlingua|Islandais|Italien|Irlandais|Japonais|Kazakh|Khmer|Kurde|Ladino|Laotien|Latin|Ligurien|Limbourgeois|Lituanien|Lombard|Luxembourgeois|Macédonien|Malais|Maldivien|Malgache|Maltais|Mandarin|Maori|Marathi|Marwari|Moldave|Mongol|Napolitain|Néerlandais|Norvégien|Occitan|Ourdou|Ouzbek|Persan|Peul|Piémontais|Polonais|Portugais|Provençal|Quichua|Romanche|Roumain|Russe|Sans[ck]rit|Sarde|Serbe|Sicilien|Sindhi|Slovaque|Slovène|Soudanais|Sorabe|Suédois|Swahili|Tagalog|Tahitien|Tamoul|Tatar|Tchèque|Thaï|Turc|Ukrainien|Vénitien|Vietnamien|Volapük|Wallon|Wo?u|Yiddish|Xhosa|Xiang|Zoulou) @@0,$ - <<- morph(\1, ">(?:parler|cours|leçon|apprendre|étudier|traduire|enseigner|professeur|enseignant|dictionnaire|méthode) ", False) + <<- morph(\1, ">(?:parler|cours|leçon|apprendre|étudier|traduire|enseigner|professeur|enseignant|dictionnaire|méthode)/", False) -2>> =\2.lower() # Si vous parlez de la langue, pas de majuscule. __[s]/maj(maj_en_langue)__ (?:[Ee]n )(Afrikaans|Albanais|Allemand|Alsacien|Anglais|Arabe|Aragonais|Arménien|Asturien|Basque|Bengali|Biélorusse|Birman|Bosniaque|Breton|Bulgare|Cantonais|Catalan|Cherokee|Chinois|Cornique|Coréen|Croate|Danois|Écossais|Espagnol|Espéranto|Estonien|Féroïen|Farsi|Finnois|Flamand|Français|Frison|Galicien|Gallois|Gaulois|Géorgien|Grec|Gujarati|Hakka|Hawaïen|Hébreu|Hindi|Hollandais|Hongrois|Javanais|Ido|Indonésien|Interlingua|Islandais|Italien|Irlandais|Japonais|Kazakh|Khmer|Kurde|Ladino|Laotien|Latin|Ligurien|Limbourgeois|Lituanien|Lombard|Luxembourgeois|Macédonien|Malais|Maldivien|Malgache|Maltais|Mandarin|Maori|Marathi|Marwari|Moldave|Mongol|Napolitain|Néerlandais|Norvégien|Occitan|Ourdou|Ouzbek|Persan|Peul|Piémontais|Polonais|Portugais|Provençal|Quichua|Romanche|Roumain|Russe|Sans[ck]rit|Sarde|Serbe|Sicilien|Sindhi|Slovaque|Slovène|Soudanais|Sorabe|Suédois|Swahili|Tagalog|Tahitien|Tamoul|Tatar|Tchèque|Thaï|Turc|Ukrainien|Vénitien|Vietnamien|Volapük|Wallon|Wo?u|Yiddish|Xhosa|Xiang|Zoulou) @@3 <<- -1>> =\1.lower() # Si vous parlez de la langue, pas de majuscule. @@ -9217,11 +10325,11 @@ TEST: y {{mangée}} était un supplice __[i]/infi(infi_pour)__ pour ({w_2}(?:ée?s?|ez)) @@5 - <<- morphex(\1, ":V1", ":[NM]") and not morph(word(-1), ">(?:tenir|passer) ", False) + <<- morphex(\1, ":V1", ":[NM]") and not morph(word(-1), ">(?:tenir|passer)/", False) -1>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: pour {{mangé}} à sa faim, il faudra chasser. TEST: C’est pour {{attaqué}} la journée. @@ -9249,11 +10357,11 @@ TEST: vous {{mangé}} __[i]/infi(infi_devoir_savoir_pouvoir_interrogatif)__ (d[eouû]\w+|s[auû]\w+|p[eouû]\w+|v[eo]u\w+)-(?:ils?|elles?|on|je|tu|nous|vous) +(?:pas +|)(?:[mts](?:e +|’)|lui +|[nv]ous +|)({w_2}) @@0,$ - <<- morph(\1, ">(?:devoir|savoir|pouvoir|vouloir) ", False) and morphex(\2, ":(?:Q|A|[123][sp])", ":[GYW]") + <<- morph(\1, ">(?:devoir|savoir|pouvoir|vouloir)/", False) and morphex(\2, ":(?:Q|A|[123][sp])", ":[GYW]") -2>> =suggVerbInfi(@) # Après « \1 » , le verbe devrait être à l’infinitif. TEST: Peuvent-elles s’{{installaient}} ici ? TEST: Peut-il {{chassé}} ces intrus ? TEST: Ne veux-tu pas {{gardé}} ton boulot ? @@ -9269,41 +10377,41 @@ TEST: Est-ce que Pierre Xazzz va bien ? TEST: Qu’est-ce que rapporte réellement Dassault & Co au budget __[i]/infi(infi_commencer_finir_par)__ ((?:commen[cç]|fin[iî])\w+) +par ({w_2}(?:ée?s?|ai[st])) @@0,$ - <<- morph(\1, ">(?:commencer|finir) ", False) and morphex(\2, ":V", ":[NGM]") and not \2[0:1].isupper() + <<- morph(\1, ">(?:commencer|finir)/", False) and morphex(\2, ":V", ":[NGM]") and not \2[0:1].isupper() -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: commence par {{mangé}} le poulet. TEST: enfin la petite finit par {{pleuré}} à chaudes larmes. TEST: sa tournée, elle la finit par Rodez. __[i]/infi(infi_verbe_de)__ ((?:cess|dé[cf]|sugg[éè]r|command|essa|tent|chois|perm[eiî]t|interd)\w*) +(?:pas |plus |point |guère |jamais |peu |rien |) *(?:de +|d’)({w_2}(?:ée?s?|ez)) @@0,$ - <<- morph(\1, ">(?:cesser|décider|défendre|suggérer|commander|essayer|tenter|choisir|permettre|interdire) ", False) and analysex(\2, ":(?:Q|2p)", ":M") + <<- morph(\1, ">(?:cesser|décider|défendre|suggérer|commander|essayer|tenter|choisir|permettre|interdire)/", False) and analysex(\2, ":(?:Q|2p)", ":M") -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: cessez d’{{anesthésié}} ces gens ! ## INFINITIFS ERRONÉS __[i]/infi(infi_adjectifs_masculins_singuliers)__ ^ *(?:le|un|cet?|[mts]on|quel) (?!verbe)({w_2}) +({w_2}er) @@w,$ - <<- morphex(\1, ":N.*:m:[si]", ":G") and morphex(\2, ":Y", ">aller |:(?:M|N.*:m:s)") and isNextVerb() + <<- morphex(\1, ":N.*:m:[si]", ":G") and morphex(\2, ":Y", ">aller/|:(?:M|N.*:m:s)") and isNextVerb() -2>> =suggVerbPpas(\2, ":m:s") # Confusion probable : “\2” est à verbe à l’infinitif. Pour l’adjectif, écrivez : __[i]/infi(infi_adjectifs_féminins_singuliers)__ ^ *(?:la|une|cette|[mts]a|quelle) ({w_2}) +({w_2}er) @@w,$ - <<- morphex(\1, ":N.*:f:[si]", ":G") and morphex(\2, ":Y", ">aller |:M") and isNextVerb() + <<- morphex(\1, ":N.*:f:[si]", ":G") and morphex(\2, ":Y", ">aller/|:M") and isNextVerb() -2>> =suggVerbPpas(\2, ":f:s") # Confusion probable : “\2” est à verbe à l’infinitif. Pour l’adjectif, écrivez : __[i]/infi(infi_adjectifs_singuliers)__ ^ *(?:leur|[nv]otre) ({w_2}) +({w_2}er) @@w,$ - <<- morphex(\1, ":N.*:[si]", ":G") and morphex(\2, ":Y", ">aller |:M") and isNextVerb() + <<- morphex(\1, ":N.*:[si]", ":G") and morphex(\2, ":Y", ">aller/|:M") and isNextVerb() -2>> =suggVerbPpas(\2, ":s") # Confusion probable : “\2” est à verbe à l’infinitif. Pour l’adjectif, écrivez : TEST: ce tableau {{voler}} coûte très cher. TEST: la difficulté {{passer}} t’aidera par la suite TEST: leur compte {{épurer}} servira encore. @@ -9310,11 +10418,11 @@ TEST: Le vieux cocher avait mission __[i]/infi(infi_adjectifs_pluriels)__ ^ *(?:[lmtsc]es|[nv]os|leurs|quel(?:le|)s) ({w_1}[sxz]) +({w_2}er) @@w,$ - <<- morphex(\1, ":N.*:[pi]", ":G") and morphex(\2, ":Y", ">aller |:M") and isNextVerb() + <<- morphex(\1, ":N.*:[pi]", ":G") and morphex(\2, ":Y", ">aller/|:M") and isNextVerb() -2>> =suggVerbPpas(\2, ":p") # Confusion probable : “\2” est à verbe à l’infinitif. Pour l’adjectif, écrivez : TEST: les documents {{scanner}} ne sont pas lisibles. TEST: tes doutes {{remâcher}} deviennent difficiles à vivre. @@ -9350,11 +10458,11 @@ \w+-(?:je|tu|ils?|elles?|on|[nv]ous) (pas|point|rien|bien|ensemble) @@$ <<- ~1>> * # sembler le croire/penser/présumer/supposer/envisager/imaginer __[i](p_que_semble_le_penser)__ que +(sembl\w+) +(l(?:e (?:penser|croire|présumer|supposer)|’(?:envisager|imaginer))) @@w,$ - <<- morph(\1, ">sembler ", False) ~2>> * + <<- morph(\1, ">sembler/", False) ~2>> * ### tous / tout / toute / toutes __[i](p_tout_det_mas)__ (tout) (?:le|ce|[mts]on|leur) @@0 <<- ~1>> * __[i](p_toute_det_fem)__ (toute) (?:la|cette|[mts](?:a|on)|leur) @@0 <<- ~1>> * __[i](p_tous_det_plur)__ (tou(?:te|)s) (?:[csmlt]es|[vn]os|leurs) @@0 <<- ~1>> * @@ -9371,11 +10479,11 @@ __[i](p_en_tant_que_tel)__ en tant que tel(?:s|lles?|) <<- ~>> * # de + __[i](p_de_vinfi)__ d(?:e |’)({infi}) @@$ - <<- morphex(\1, ":V[123]_i", ">(?:devenir|rester|demeurer) ") and isNextNotCOD() ~>> * + <<- morphex(\1, ":V[123]_i", ">(?:devenir|rester|demeurer)/") and isNextNotCOD() ~>> * __[i](p_de_manière_façon_xxx_et_xxx)__ de (?:manière|façon) +(?:non +|)({w_2}) +et +(?:non +|)({w_2}) @@w,$ <<- morph(\1, ":A", False) and morphex(\2, ":A", ":[GM]") ~>> * __[i](p_de_manière_façon)__ de (?:manière|façon) +(?:non +|)({w_2}) @@$ @@ -9425,11 +10533,11 @@ ## doute que __[i](p_nul_doute_que)__ nul doute qu <<- isStart() ~>> * __[i](p_douter_que)__ - (dout\w+)( ) *que? @@0,* <<- morph(\1, ">douter ", False) and before(r"(?i)\b(?:[mts]e|[nv]ous) +$") ~2>> , + (dout\w+)( ) *que? @@0,* <<- morph(\1, ">douter/", False) and before(r"(?i)\b(?:[mts]e|[nv]ous) +$") ~2>> , ## de + __[i](p_de_nom)__ d(?:e +|’)(?!autres)({w_2}) @@$ <<- morphex(\1, ":N", ":[GY]") and isEndOfNG() ~>> * @@ -9469,11 +10577,11 @@ -2>> =suggVerbPpas(\2, ":m:p") # Erreur de numérisation ? __[i]/ocr(ocr_avoir_participes_passés)__ ({avoir}) +({w_2}es?) @@0,$ <<- morph(\1, ":V0a", False) >>> - <<- \2.endswith("e") and morphex(\2, ":V1.*:Ip.*:[13]s", ":[GM]|>envie ") + <<- \2.endswith("e") and morphex(\2, ":V1.*:Ip.*:[13]s", ":[GM]|>envie/") -2>> =suggVerbPpas(\2, ":m:s") # Erreur de numérisation ? <<- __else__ and \2.endswith("s") and morphex(\2, ":V1.*:Ip.*:2s", ":[GM]") -2>> =suggVerbPpas(\2, ":m:p") # Erreur de numérisation ? TEST: __ocr__ vous serez {{couche}} en terre. @@ -9548,11 +10656,11 @@ ## soit / soie / soi __[i]/conf(conf_aller_de_soi)__ ({aller}) +de (soi[tes]) @@0,$ - <<- morph(\1, ">aller", False) and not after(" soit ") -2>> soi # Confusion.|https://fr.wiktionary.org/wiki/aller_de_soi + <<- morph(\1, ">aller/", False) and not after(" soit ") -2>> soi # Confusion.|https://fr.wiktionary.org/wiki/aller_de_soi TEST: cela ne va pas de {{soit}}. @@ -9559,11 +10667,11 @@ !!!! Adverbes après verbe # fort __[i]/sgpl(sgpl_verbe_fort)__ ({w_2}) +(forts) @@0,$ - <<- morphex(\1, ":V", ":[AN].*:[me]:[pi]|>(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre|appara[îi]tre) .*:(?:[123]p|P|Q)|>(?:affirmer|trouver|croire|désirer|estime|préférer|penser|imaginer|voir|vouloir|aimer|adorer|souhaiter) ") + <<- morphex(\1, ":V", ":[AN].*:[me]:[pi]|>(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre|appara[îi]tre)/.*:(?:[123]p|P|Q)|>(?:affirmer|trouver|croire|désirer|estime|préférer|penser|imaginer|voir|vouloir|aimer|adorer|souhaiter)/") and not morph(word(1), ":A.*:[me]:[pi]", False) -2>> fort # Confusion probable. S’il s’agit ici de l’adverbe “fort” (équivalent de “fortement”), écrivez-le au singulier. TEST: ces emmerdeurs crient bien trop {{forts}} TEST: ces animaux paraissent forts, mais ils sont faibles. @@ -9626,11 +10734,11 @@ TEST: de me le {{facturez}} __[i]/infi(infi_faire)__ (f(?:ai|[iî]|er|on)\w+) +({w_2}(?:ée?s?|ez)) @@0,$ - <<- morph(\1, ">faire ", False) and not before(r"(?i)\b(?:en|[mtsldc]es?|[nv]ous|un) +$") and morphex(\2, ":V", ":M") + <<- morph(\1, ">faire/", False) and not before(r"(?i)\b(?:en|[mtsldc]es?|[nv]ous|un) +$") and morphex(\2, ":V", ":M") and not (re.search("(?i)^fait$", \1) and \2.endswith("é")) and not (re.search("(?i)^faits$", \1) and \2.endswith("és")) -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: elle fit peu {{mangé}} les enfants @@ -9638,11 +10746,11 @@ TEST: Tu fais {{décoloré}} tes cheveux ? __[i]/infi(infi_vouloir)__ (v[oe]u\w+) +({w_2}(?:ée?s?|ez)) @@0,$ - <<- morph(\1, ">vouloir ", False) and not before(r"(?i)\b(?:[mtsldc]es?|[nv]ous|un) +$") and morphex(\2, ":V", ":M") + <<- morph(\1, ">vouloir/", False) and not before(r"(?i)\b(?:[mtsldc]es?|[nv]ous|un) +$") and morphex(\2, ":V", ":M") and not (re.search("(?i)^vouloir$", \1) and \2.endswith("é")) and not (re.search("(?i)^vouloirs$", \1) and \2.endswith("és")) -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: je veux {{changé}} @@ -9653,11 +10761,11 @@ TEST: en voulant {{changé}} __[i]/infi(infi_me_te_se_faire)__ [mts]e (f(?:ai|[iî]|er|on)\w+) +({w_2}(?:ée?s?|ez)) @@0,$ - <<- morph(\1, ">faire ", False) and morphex(\2, ":V", ":M") + <<- morph(\1, ">faire/", False) and morphex(\2, ":V", ":M") -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: me faire constamment {{laminé}} au jeu, ça finit par me fâcher. @@ -9669,11 +10777,11 @@ TEST: Je suis fatigué de vouloir {{essayé}} d’y remédier. __[i]/infi(infi_savoir)__ (s[auû]\w+) +({w_2}(?:ée?s?|ez)) @@0,$ - <<- morph(\1, ">savoir :V", False) and morph(\2, ":V", False) and not before(r"(?i)\b(?:[mts]e|[vn]ous|les?|la|un) +$") + <<- morph(\1, ">savoir/:V", False) and morph(\2, ":V", False) and not before(r"(?i)\b(?:[mts]e|[vn]ous|les?|la|un) +$") -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: Il faut savoir {{arrêté}} les frais. TEST: un certain nombre de savoirs spécialisés @@ -9707,34 +10815,34 @@ !! !! __[i]/conj(conj_se_conf_être_avoir)__ (s’)(?:en +|y+ |)({avoir}) @@0,$ - <<- morph(\2, ">avoir ", False) >>> + <<- morph(\2, ">avoir/", False) >>> <<- morph(\2, ":3p", False) -2>> sont|étaient|seront|seraient # Confusion. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. <<- __else__ -2>> est|était|sera|serait # Confusion. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. TEST: s’en {{ait}} trop __[i]/conj(conj_je_me_conf_être_avoir)__ je m’(?:en +|y+ |)({avoir}) @@$ - <<- morph(\1, ">avoir ", False) -1>> suis|étais|serai|serais # Confusion. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. + <<- morph(\1, ">avoir/", False) -1>> suis|étais|serai|serais # Confusion. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. __[i]/conj(conj_tu_te_conf_être_avoir)__ tu t’(?:en +|y+ |)({avoir}) @@$ - <<- morph(\1, ">avoir ", False) and not morph(word(-1), ":V0", False, False) + <<- morph(\1, ">avoir/", False) and not morph(word(-1), ":V0", False, False) -1>> es|étais|seras|serais # Confusion. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. __[i]/conj(conj_nous_nous_conf_être_avoir)__ (nous) nous (?:en +|y+ |)({avoir}) @@0,$ - <<- morph(\2, ">avoir ", False) and isStart() -2>> sommes|étions|serons|serions # Confusion possible. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. + <<- morph(\2, ">avoir/", False) and isStart() -2>> sommes|étions|serons|serions # Confusion possible. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. <<- __also__ -1>> nous, # S’il ne s’agit pas d’une locution pronominale, mettez une virgule pour séparer les personnes que vous désignez du sujet. __[i]/conj(conj_vous_vous_conf_être_avoir)__ (vous) vous (?:en +|y+ |)({avoir}) @@0,$ - <<- morph(\2, ">avoir ", False) and isStart() -2>> êtes|étiez|serez|seriez # Confusion possible. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. + <<- morph(\2, ">avoir/", False) and isStart() -2>> êtes|étiez|serez|seriez # Confusion possible. Sous sa forme pronominale, un verbe s’emploie avec l’auxilaire “être”, non “avoir”. <<- __also__ -1>> vous, # S’il ne s’agit pas d’une locution pronominale, mettez une virgule pour séparer les personnes que vous désignez du sujet. TEST: je m’y {{avais}} habitué. TEST: tu t’{{avais}} donné du temps pour finir ton mémoire. TEST: Ce qu’il a tu t’a donné la nausée. @@ -9769,54 +10877,54 @@ __[i]/ppas(ppas_il_se_être_verbe)__ il +(?:ne +|)s(?:e +|’(?:y +|))(?:est?|soi[st]|étai[st]|fu(?:sses?|s|t)|serai?[st]?) +({w_3}) @@$ - <<- morphex(\1, ":Q.*:(?:f|m:p)", ":(?:G|Q.*:m:[si])|>dire ") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not before(r"\b[qQ]ue? +$")) ) + <<- morphex(\1, ":Q.*:(?:f|m:p)", ":(?:G|Q.*:m:[si])|>dire/") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not before(r"\b[qQ]ue? +$")) ) -1>> =suggVerbPpas(\1, ":m:s") # Si ce participe passé se rapporte bien à “il”, il devrait être au masculin singulier. TEST: le dédale dans lequel il se serait {{perdue}} TEST: il s’était perdu dans la forêt. __[i]/ppas(ppas_elle_se_être_verbe)__ elle +(?:ne +|)s(?:e +|’(?:y +|))(?:est?|soi[st]|étai[st]|fu(?:sses?|s|t)|serai?[st]?) +({w_3}) @@$ - <<- morphex(\1, ":Q.*:(?:m|f:p)", ":(?:G|Q.*:f:[si])|>dire ") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not morph(word(-1), ":R|>que ", False, False)) ) + <<- morphex(\1, ":Q.*:(?:m|f:p)", ":(?:G|Q.*:f:[si])|>dire/") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not morph(word(-1), ":R|>que/", False, False)) ) -1>> =suggVerbPpas(\1, ":f:s") # Si ce participe passé se rapporte bien à “elle”, il devrait être au féminin singulier. TEST: elle s’y était {{préparé}}. TEST: elle s’était trouvé un mari. __[i]/ppas(ppas_nous_nous_être_verbe)__ nous +(?:ne +|)nous +(?:y +|)(?:sommes|étions|fûmes|fussions|seri?ons) +({w_3}) @@$ - <<- morphex(\1, ":Q.*:s", ":(?:G|Q.*:[pi])|>dire ") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not morph(word(-1), ":R|>que ", False, False)) ) + <<- morphex(\1, ":Q.*:s", ":(?:G|Q.*:[pi])|>dire/") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not morph(word(-1), ":R|>que/", False, False)) ) -1>> =suggVerbPpas(\1, ":p") # Si ce participe passé se rapporte bien à “nous”, il devrait être au pluriel. TEST: Nous nous étions {{cru}} au paradis. __[i]/ppas(ppas_ils_se_être_verbe)__ ils +(?:ne +|)s(?:e +|’(?:y +|))(?:so(?:ie|)nt|étaient|fu(?:r|ss)ent|ser(?:aie|o)nt) +({w_3}) @@$ - <<- morphex(\1, ":Q.*:(?:f|m:s)", ":(?:G|Q.*:m:[pi])|>dire ") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not before(r"\b[qQ]ue? +$")) ) + <<- morphex(\1, ":Q.*:(?:f|m:s)", ":(?:G|Q.*:m:[pi])|>dire/") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not before(r"\b[qQ]ue? +$")) ) -1>> =suggVerbPpas(\1, ":m:p") # Si ce participe passé se rapporte bien à “ils”, il devrait être au masculin pluriel. TEST: ils s’y étaient {{abandonné}} avec ferveur __[i]/ppas(ppas_elles_se_être_verbe)__ elles +(?:ne +|)s(?:e +|’(?:y +|))(?:so(?:ie|)nt|étaient|fu(?:r|ss)ent|ser(?:aie|o)nt) +({w_3}) @@$ - <<- morphex(\1, ":Q.*:(?:m|f:s)", ":(?:G|Q.*:f:[pi])|>dire ") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not morph(word(-1), ":R|>que ", False, False)) ) + <<- morphex(\1, ":Q.*:(?:m|f:s)", ":(?:G|Q.*:f:[pi])|>dire/") and ( morph(\1, ":V[123]_.__p_e_") or (isRealEnd() and not morph(word(-1), ":R|>que/", False, False)) ) -1>> =suggVerbPpas(\1, ":f:p") # Si ce participe passé se rapporte bien à “elles”, il devrait être au féminin pluriel. TEST: elles ne s’y étaient pas {{donnée}}. TEST: sans fin elles se sont succédé __[i]/ppas(ppas_se_être)__ [mts](?:e +|’(?:y +|en +|))({etre}) +({w_2}) @@w,$ - <<- morph(\1, ">être ", False) >>> + <<- morph(\1, ">être/", False) >>> <<- morphex(\2, ":(?:Y|[123][sp])", ":Q") and not re.search(r"(?i)^t’(?:es|étais)", \0) -2>> =suggVerbPpas(@) # Incohérence. Après « s’être », le verbe doit être un participe passé. <<- __else__ and morph(\1, ":[123]s", False) and morph(\2, ":Q.*:p", False) and not before(r"(?i)\bque?[, ]|\bon (?:ne |)$") and not re.search(r"(?i)^t’(?:es|étais)", \0) -2>> =suggSing(@) # Le participe passé devrait être au singulier. @@ -9842,11 +10950,11 @@ !! !! __[i]/ppas(ppas_me_te_laisser_adj)__ ([mt]e|l[ae]) +(laiss\w*) +({w_3}) @@0,w,$ - <<- morph(\2, ">laisser ", False) and morphex(\3, ":[AQ].*:p", ":(?:[YG]|[AQ].*:[is])") + <<- morph(\2, ">laisser/", False) and morphex(\3, ":[AQ].*:p", ":(?:[YG]|[AQ].*:[is])") -3>> =suggSing(@) # Accord avec « \1 » : « \3 » devrait être au singulier. TEST: Elle te laisse {{épuisés}} par la tâche. TEST: Ils la laissèrent {{malades}}. TEST: Ils la laissent prendre le train. @@ -9855,11 +10963,11 @@ TEST: Il te laisse trois jours de délai. __[i]/ppas(ppas_nous_les_laisser_adj)__ (nous|les) +(laiss\w*) +({w_3}) @@0,w,$ - <<- morph(\2, ">laisser ", False) and morphex(\3, ":[AQ].*:s", ":(?:[YG]|[AQ].*:[ip])") + <<- morph(\2, ">laisser/", False) and morphex(\3, ":[AQ].*:s", ":(?:[YG]|[AQ].*:[ip])") and (\1.endswith("es") or ( \1.endswith("us") and not \2.endswith("ons") )) -3>> =suggPlur(@) # Accord avec « \1 » : « \3 » devrait être au pluriel. TEST: je les laisse {{indifférent}}. TEST: elle nous laissera {{perdu}} dans nos délires. @@ -9875,11 +10983,11 @@ !! !! __[i]/ppas(ppas_je_verbe)__ j(?:e +|’(?:y +|en +|))(?:ne +|n’|)((?:s[oue]|étai|fus|dev|re(?:dev|st)|par)\w*|a(?:ie?|vais|urais?) +été|eus(?:se|) +été) +({w_2}) @@w,$ - <<- (morph(\1, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \1.endswith(" été")) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") + <<- (morph(\1, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \1.endswith(" été")) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") -2>> =suggSing(@) # Accord avec le sujet « je » : « \2 » devrait être au singulier. TEST: j’étais {{perdus}} ->> perdu TEST: j’aurais été {{perdus}} sans toi ->> perdu TEST: je n’étais pas {{perdus}} ->> perdu @@ -9894,21 +11002,21 @@ TEST: J’y semble être {{perdus}}. __[i]/ppas(ppas_tu_verbe)__ tu +(?:ne +|n’|)((?:es|étai|fus|se[rm]|soi|dev|re(?:dev|st)|par)\w*|a(?:s|ies|vais|urai?s) +été|eus(?:ses|) +été) +({w_2}) @@w,$ - <<- (morph(\1, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \1.endswith(" été")) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") + <<- (morph(\1, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \1.endswith(" été")) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") -2>> =suggSing(@) # Accord avec le sujet « tu » : « \2 » devrait être au singulier. TEST: tu n’es pas {{petites}} TEST: tu es {{insignifiants}} TEST: tu deviens vraiment très {{forts}} à ce jeu. __[i]/ppas(ppas_il_verbe)__ (il|ce|ce qui|celui +qui|ça +qui|lui +qui|celui-(?:ci|là) +(?:qui +|)|quiconque) +(?:ne +|n’|)((?:es|étai|f[uû]|se[mr]|soi|dev|re(?:dev|st)|par)\w*|a(?:it|vait|ura(?:it|)|) +été|e[uû]t +été) +({w_2}) @@0,w,$ - <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \2.endswith(" été")) + <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \2.endswith(" été")) and (morphex(\3, ":[NAQ].*:p", ":[GWYsi]") or morphex(\3, ":[AQ].*:f", ":[GWYme]")) -3>> =suggMasSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin singulier. TEST: Il semble être {{partis}} pour toujours. ->> parti TEST: Il est {{demander}} à chacun de participer. @@ -9918,11 +11026,11 @@ TEST: c’est ça qui paraît {{stupides}} TEST: celui-là semble {{perdus}} dans ses pensées. __[i]/ppas(ppas_c_être)__ c’(?:est|était|e[uû]t +été) +({w_2}) @@$ - <<- not (morph(\1, ">seule ", False) and after("^ +que? ")) + <<- not (morph(\1, ">seule/", False) and after("^ +que? ")) and ( morphex(\1, ":[NAQ].*:p", ":[GWYsi]") or ( morphex(\1, ":[AQ].*:f", ":[GWYme]") and not morph(word(1), ":N.*:f", False, False) ) ) -1>> =suggMasSing(@) # Accord avec le sujet « c’ » : « \1 » devrait être au masculin singulier. TEST: c’est {{condescendants}}. ->> condescendant TEST: C’est {{finis}}. @@ -9937,13 +11045,13 @@ TEST: Ç’avait été {{horribles}} __[i]/ppas(ppas_ça_verbe)__ (ça|ce(?:la|ci)|celui-(?:ci|là)) +(?:ne +|n’|)((?:es|étai|f[uû]|se[mr]|soi|par|dev|re(?:dev|st))\w+|a(?:it|vait|ura(?:it|)|) +été|e[uû]t +été) +({w_2}) @@0,w,$ - <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \2.endswith(" été")) + <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \2.endswith(" été")) and ( morphex(\3, ":[NAQ].*:p", ":[GWYsi]") or ( morphex(\3, ":[AQ].*:f", ":[GWYme]") and not morph(word(1), ":N.*:f", False, False) ) ) - and not morph(word(-1), ":(?:R|V...t)|>de ", False, False) + and not morph(word(-1), ":(?:R|V...t)|>de/", False, False) -3>> =suggMasSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin singulier. TEST: ça semble {{perdus}} TEST: cela paraît {{incroyables}} TEST: Je n’arrêtais pas de me répéter que tout cela était peut-être pure imagination @@ -9951,23 +11059,23 @@ TEST: De cela a toujours été faite notre vie __[i]/ppas(ppas_lequel_verbe)__ (lequel) +(?:ne +|n’|)((?:es|étai|f[uû]|se[mr]|soi|par|dev|re(?:dev|st))\w+|a(?:it|vait|ura(?:it|)|) +été|e[uû]t +été) +({w_2}) @@0,w,$ - <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \2.endswith(" été")) + <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \2.endswith(" été")) and ( morphex(\3, ":[NAQ].*:p", ":[GWYsi]") or ( morphex(\3, ":[AQ].*:f", ":[GWYme]") and not morph(word(1), ":N.*:f", False, False) ) ) and not morph(word(-1), ":R", False, False) -3>> =suggMasSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin singulier. TEST: elle avait accompagné cet homme, lequel était {{revenue}} de l’enfer. __[i]/ppas(ppas_elle_verbe)__ (elle|celle-(?:ci|là)|laquelle) +(?:ne +|n’|)((?:es|étai|f[uû]|se[rm]|soi|dev|re(?:dev|st)|par)\w*|a(?:it|vait|ura(?:it|)|) +été|e[uû]t +été) +({w_2}) @@0,w,$ - <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \2.endswith(" été")) + <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \2.endswith(" été")) and (morphex(\3, ":[NAQ].*:p", ":[GWYsi]") or morphex(\3, ":[AQ].*:m", ":[GWYfe]")) - and not morph(word(-1), ":R|>de ", False, False) + and not morph(word(-1), ":R|>de/", False, False) -3>> =suggFemSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin singulier. TEST: elle a été {{perdu}} sans toi ->> perdue TEST: Elle semble être totalement {{ruiné}}. ->> ruinée TEST: Elle est complètement {{fol}}. ->> folle @@ -9975,31 +11083,31 @@ TEST: Elle est de plus en plus {{belles}}. ->> belle __[i]/ppas(ppas_elle_qui_verbe)__ (c?elle +qui) +(?:ne +|n’|)((?:es|étai|f[uû]|se[rm]|soi|dev|re(?:dev|st)|par)\w*|a(?:it|vait|ura(?:it|)|) +été|e[uû]t +été) +({w_2}) @@0,w,$ - <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \2.endswith(" été")) + <<- (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \2.endswith(" été")) and (morphex(\3, ":[NAQ].*:p", ":[GWYsi]") or morphex(\3, ":[AQ].*:m", ":[GWYfe]")) -3>> =suggFemSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin singulier. TEST: celle qui paraît {{dingues}} de toi __[i]/ppas(ppas_nous_verbe)__ nous +(?:ne +|n’|)((?:sommes|étions|fûmes|fussions|seri?ons|soyons|sembl|dev|re(?:dev|st)|par)\w*|a(?:vi?ons|uri?ons|yions) +été|e(?:ûme|ussion)s +été) +({w_2}) @@w,$ <<- not re.search("(?i)^légion$", \2) and not before(r"(?i)\b(?:nous|ne) +$") - and ((morph(\1, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) and morph(\1, ":1p", False)) or \1.endswith(" été")) + and ((morph(\1, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) and morph(\1, ":1p", False)) or \1.endswith(" été")) and morphex(\2, ":[NAQ].*:s", ":[GWYpi]") -2>> =suggPlur(@) # Accord avec le sujet « nous » : « \2 » devrait être au pluriel. TEST: nous paraissons {{faible}} TEST: Nous paraissons avoir été complètement {{prise}} de panique. ->> prises __[i]/ppas(ppas_ils_verbe)__ (ils|c?eux +qui|ceux-ci|ceux-là|lesquels) +(?:ne +|n’|)((?:sont|étaient|fu[rs]|se[rm]|soient|dev|re(?:dev|st)|par)\w*|ont +été|a(?:ient|vaient|ur(?:ont|aient)) +été|eu(?:r|ss)ent +été) +({w_2}) @@0,w,$ - <<- not re.search("(?i)^légion$", \3) and (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \2.endswith(" été")) + <<- not re.search("(?i)^légion$", \3) and (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \2.endswith(" été")) and (morphex(\3, ":[NAQ].*:s", ":[GWYpi]") or morphex(\3, ":[AQ].*:f", ":[GWYme]")) and not before("(?i)ce que? +$") and (not re.search("^(?:ceux-(?:ci|là)|lesquels)$", \1) or not morph(word(-1), ":R", False, False)) -3>> =suggMasPlur(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin pluriel. TEST: ils sont {{parti}}. ->> partis @@ -10014,11 +11122,11 @@ TEST: ils sont très loin d’être {{idiot}}. __[i]/ppas(ppas_elles_verbe)__ (elles|c?elles +qui|celles-(?:ci|là)|lesquelles) +(?:ne +|n’|)((?:sont|étai|fu[rs]|se[rm]|soi|dev|re(?:dev|st)|par)\w*|ont +été|a(?:ient|vaient|ur(?:ont|aient)) +été|eu(?:r|ss)ent +été) +({w_2}) @@0,w,$ - <<- not re.search("(?i)^légion$", \3) and (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre) ", False) or \2.endswith(" été")) + <<- not re.search("(?i)^légion$", \3) and (morph(\2, ">(?:être|sembler|devenir|re(?:ster|devenir)|para[îi]tre)/", False) or \2.endswith(" été")) and (morphex(\3, ":[NAQ].*:s", ":[GWYpi]") or morphex(\3, ":[AQ].*:m", ":[GWYfe]")) and (not re.search("(?i)^(?:elles|celles-(?:ci|là)|lesquelles)$", \1) or not morph(word(-1), ":R", False, False)) -3>> =suggFemPlur(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin pluriel. TEST: elles n’ont tout de même pas été {{attaqué}} ->> attaquées @@ -10058,11 +11166,11 @@ __[i](p_risque_d_être)__ risqu\w+ +(d’)être @@* <<- ~1>> * __[i]/ppas(ppas_je_verbe_être)__ j(?:e|’(?:y|en)) +(?:ne +|n’|)((?:p[aeouûr]|s(?:embl|ouhait)|cr[ouû]|d[eouûéiî]|estim|i(?:magin|r)|v(?:[eo]u|a)|a(?:ffirm|im|dor|ll)|risqu)\w*) +(?:être|avoir été) +({w_2}) @@w,$ - <<- morph(\1, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- morph(\1, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") -2>> =suggSing(@) # Accord avec le sujet « je » : « \2 » devrait être au singulier. TEST: Je ne peux pas être {{méchants}}. TEST: j’aurais vraiment été {{tentés}} @@ -10071,11 +11179,11 @@ TEST: je voudrais bien être dans ses souliers __[i]/ppas(ppas_tu_verbe_être)__ tu +(?:ne +|n’|)((?:p[aeouûr]|s(?:embl|ouhait)|cr[ouû]|d[eouûéiî]|estim|i(?:magin|r)|v(?:[eo]u|a)|a(?:ffirm|im|dor|ll)|risqu)\w*) +(?:être|avoir été) +({w_2}) @@w,$ - <<- morph(\1, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- morph(\1, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") -2>> =suggSing(@) # Accord avec le sujet « tu » : « \2 » devrait être au singulier. TEST: tu ne crois pas être {{meilleurs}}. TEST: tu ne crois pas avoir été {{découvertes}} @@ -10082,11 +11190,11 @@ TEST: tu vas être {{payées}} __[i]/ppas(ppas_il_verbe_être)__ (il|ce|ce qui|celui +qui|ça +qui|lui +qui|celui-(?:ci|là) +qui|quiconque) +(?:ne +|n’|)((?:p[aeouûr]|s(?:embl|ouhait)|cr[ouû]|d[eouûéiî]|estim|i(?:magin|r)|v(?:[eo]u|a)|a(?:ffirm|im|dor|ll)|risqu)\w*) +(?:être|avoir été) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and (morphex(\3, ":[NAQ].*:p", ":[GWYsi]") or morphex(\3, ":[AQ].*:f", ":[GWYme]")) -3>> =suggMasSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin singulier. TEST: Il peut être {{observée}}. TEST: celui-là pensait être {{perdue}} @@ -10095,11 +11203,11 @@ TEST: lui qui ne pensait jamais être {{reconnus}}. __[i]/ppas(ppas_ça_verbe_être)__ (ça|ce(?:la|ci)|celui-(?:ci|là)|lequel) +(?:ne +|n’|)((?:p[aeouûr]|s(?:embl|ouhait)|cr[ouû]|d[eouûéiî]|estim|i(?:magin|r)|v(?:[eo]u|a)|a(?:ffirm|im|dor|ll)|risqu)\w*) +(?:être|avoir été) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and (morphex(\3, ":[NAQ].*:p", ":[MWYsi]") or morphex(\3, ":[AQ].*:f", ":[GWYme]")) and not morph(word(-1), ":R", False, False) -3>> =suggMasSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin singulier. TEST: ça ne semble pas avoir été {{conçus}} pour ça. @@ -10106,11 +11214,11 @@ TEST: lequel allait être {{renvoyée}} de l’établissement. __[i]/ppas(ppas_elle_verbe_être)__ (elle|celle-(?:ci|là)|laquelle) +(?:ne +|n’|)((?:p[aeouûr]|s(?:embl|ouhait)|cr[ouû]|d[eouûéiî]|estim|i(?:magin|r)|v(?:[eo]u|a)|a(?:ffirm|im|dor|ll)|risqu)\w*) +(?:être|avoir été) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and (morphex(\3, ":[NAQ].*:p", ":[GWYsi]") or morphex(\3, ":[AQ].*:m", ":[GWYfe]")) and not morph(word(-1), ":R", False, False) -3>> =suggFemSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin singulier. TEST: elle ne croit pas être {{trompé}} @@ -10117,40 +11225,40 @@ TEST: ici, elle ne risque pas d’être {{attaquées}} __[i]/ppas(ppas_elle_qui_verbe_être)__ (c?elle +qui) +(?:ne +|n’|)((?:p[aeouûr]|s(?:embl|ouhait)|cr[ouû]|d[eouûéiî]|estim|i(?:magin|r)|v(?:[eo]u|a)|a(?:ffirm|im|dor|ll)|risqu)\w*) +(?:être|avoir été) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and (morphex(\3, ":[NAQ].*:p", ":[MWYsi]") or morphex(\3, ":[AQ].*:m", ":[GWYfe]")) -3>> =suggFemSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin singulier. TEST: celle qui pense être {{découvert}} __[i]/ppas(ppas_nous_verbe_être)__ (?(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- not re.search("(?i)^légion$", \2) and morph(\1, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and morph(\1, ":1p", False) and morphex(\2, ":[NAQ].*:s", ":[GWYpi]") -2>> =suggPlur(@) # Accord avec le sujet « nous » : « \2 » devrait être au pluriel. TEST: nous pensons être {{désiré}} TEST: nous ne devons pas être {{instruit}} __[i]/ppas(ppas_ils_verbe_être)__ (ils|c?eux +qui|ceux-(?:ci|là)|lesquels) +(?:ne +|n’|)((?:p[aeouûr]|s(?:embl|ouhait)|cr[ouû]|d[eouûéiî]|estim|i(?:magin|r)|v(?:[eo]u|a)|a(?:ffirm|im|dor|ll)|risqu)\w*) +(?:être|avoir été) +({w_2}) @@0,w,$ - <<- not re.search("(?i)^légion$", \3) and morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- not re.search("(?i)^légion$", \3) and morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and (morphex(\3, ":[NAQ].*:s", ":[GWYpi]") or morphex(\3, ":[AQ].*:f", ":[GWYme]")) and (not re.search("^(?:ceux-(?:ci|là)|lesquels)$", \1) or not morph(word(-1), ":R", False, False)) -3>> =suggMasPlur(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin pluriel. TEST: ils croient être {{perdu}} __[i]/ppas(ppas_elles_verbe_être)__ (elles|c?elles +qui|celles-(?:ci|là)|lesquelles) +(?:ne +|n’|)((?:p[aeouûr]|s(?:embl|ouhait)|cr[ouû]|d[eouûéiî]|estim|i(?:magin|r)|v(?:[eo]u|a)|a(?:ffirm|im|dor|ll)|risqu)\w*) +(?:être|avoir été) +({w_2}) @@0,w,$ - <<- not re.search("(?i)^légion$", \3) and morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller) ", False) + <<- not re.search("(?i)^légion$", \3) and morph(\2, ">(?:sembler|para[îi]tre|pouvoir|penser|préférer|croire|d(?:evoir|éclarer|ésirer|étester|ire)|vouloir|affirmer|aimer|adorer|souhaiter|estimer|imaginer|risquer|aller)/", False) and (morphex(\3, ":[NAQ].*:s", ":[GWYpi]") or morphex(\3, ":[AQ].*:m", ":[GWYfe]")) and (not re.search("^(?:elles|celles-(?:ci|là)|lesquelles)$", \1) or not morph(word(-1), ":R", False, False)) -3>> =suggFemPlur(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin pluriel. TEST: elles veulent être {{différente}} @@ -10236,19 +11344,19 @@ TEST: — {{Déçue}}, il s’en est allé. __[i]/ppas(ppas_adj_accord_elle)__ ^ *({w_2}[éuitsx]),? elle @@* - <<- morphex(\1, ":A.*:[mp]", ":(?:G|E|M1|W|f:[si])|>(?:désoler|pire) ") + <<- morphex(\1, ":A.*:[mp]", ":(?:G|E|M1|W|f:[si])|>(?:désoler|pire)/") -1>> =suggFemSing(@) # Si cet adjectif se réfère au pronom « elle », l’adjectif devrait être au féminin singulier. TEST: — {{Déçu}}, elle s’en est allée. __[i]/ppas(ppas_adj_accord_ils)__ ^ *({w_2}[eiuéts]),? ils @@* - <<- morphex(\1, ":A.*:[fs]", ":(?:G|E|M1|W|m:[pi])|>(?:désoler|pire) ") + <<- morphex(\1, ":A.*:[fs]", ":(?:G|E|M1|W|m:[pi])|>(?:désoler|pire)/") -1>> =suggMasPlur(@) # Si cet adjectif se réfère au pronom « ils », l’adjectif devrait être au masculin pluriel. TEST: Très vite, ils sont partis TEST: Une fois terminé, ils sont revenus. TEST: Vraiment {{soucieuse}}, ils sont. @@ -10256,11 +11364,11 @@ TEST: Pire, ils piétinent parfois les droits humains. __[i]/ppas(ppas_adj_accord_elles)__ ^ *({w_2}[eiuétsx]),? elles @@* - <<- morphex(\1, ":A.*:[ms]", ":(?:G|E|M1|W|f:[pi])|>(?:désoler|pire) ") + <<- morphex(\1, ":A.*:[ms]", ":(?:G|E|M1|W|f:[pi])|>(?:désoler|pire)/") -1>> =suggFemPlur(@) # Si cet adjectif se réfère au pronom « elles », l’adjectif devrait être au féminin pluriel. TEST: Absolument {{heureux}}, elles exultèrent de joie. @@ -10289,27 +11397,27 @@ (?:es|étais|fus(?:ses|)|serai?s)-tu +({w_2}) @@$ <<- morphex(\1, ":(?:[123][sp]|Y|[NAQ].*:p)", ":[GWsi]") -1>> =suggSing(@) # Accord avec le sujet « tu » : « \1 » devrait être au singulier. __[i]/ppas(ppas_inversion_être_il_ce)__ (?:est|était|f[uû]t|sera(?:-t|it))-(il|ce) +({w_2}) @@*,$ - <<- morphex(\2, ":(?:[123][sp]|Y|[NAQ].*:[pf])", ":(?:G|W|[me]:[si])|question ") and not (\1 == "ce" and morph(\2, ":Y", False)) + <<- morphex(\2, ":(?:[123][sp]|Y|[NAQ].*:[pf])", ":(?:G|W|[me]:[si])|question/") and not (\1 == "ce" and morph(\2, ":Y", False)) -2>> =suggMasSing(@) # Accord avec le sujet « il » : « \2 » devrait être au masculin singulier. __[i]/ppas(ppas_inversion_être_elle)__ (?:est|était|f[uû]t|sera(?:-t|it))-elle +({w_2}) @@$ <<- morphex(\1, ":(?:[123][sp]|Y|[NAQ].*:[pm])", ":(?:G|W|[fe]:[si])") -1>> =suggFemSing(@) # Accord avec le sujet « elle » : « \1 » devrait être au féminin singulier. __[i]/ppas(ppas_inversion_être_nous)__ (?:sommes|étions|fûmes|fussions|seri?ons)-nous +({w_2}) @@$ - <<- morphex(\1, ":(?:[123][sp]|Y|[NAQ].*:s)", ":[GWpi]|>dire ") + <<- morphex(\1, ":(?:[123][sp]|Y|[NAQ].*:s)", ":[GWpi]|>dire/") -1>> =suggPlur(@) # Accord avec le sujet « nous » : « \1 » devrait être au pluriel. __[i]/ppas(ppas_inversion_être_ils)__ (?:sont|étaient|fu(?:r|ss)ent|ser(?:o|aie)nt)-ils +({w_2}) @@$ - <<- not re.search("(?i)^légion$", \1) and (morphex(\1, ":(?:[123][sp]|Y|[NAQ].*:s)", ":[GWpi]|>dire ") or morphex(\1, ":(?:[123][sp]|[AQ].*:f)", ":[GWme]|>dire ")) + <<- not re.search("(?i)^légion$", \1) and (morphex(\1, ":(?:[123][sp]|Y|[NAQ].*:s)", ":[GWpi]|>dire/") or morphex(\1, ":(?:[123][sp]|[AQ].*:f)", ":[GWme]|>dire/")) -1>> =suggMasPlur(@) # Accord avec « ils » : « \1 » devrait être au masculin pluriel. __[i]/ppas(ppas_inversion_être_elles)__ (?:sont|étaient|fu(?:r|ss)ent|ser(?:o|aie)nt)-elles +({w_2}) @@$ - <<- not re.search("(?i)^légion$", \1) and (morphex(\1, ":(?:[123][sp]|Y|[NAQ].*:s)", ":[GWpi]|>dire ") or morphex(\1, ":(?:[123][sp]|[AQ].*:m)", ":[GWfe]|>dire ")) + <<- not re.search("(?i)^légion$", \1) and (morphex(\1, ":(?:[123][sp]|Y|[NAQ].*:s)", ":[GWpi]|>dire/") or morphex(\1, ":(?:[123][sp]|[AQ].*:m)", ":[GWfe]|>dire/")) -1>> =suggFemPlur(@) # Accord avec « elles » : « \1 » devrait être au féminin pluriel. TEST: serais-je {{fâchés}} contre vous ? TEST: Est-elle {{arriver}} ? TEST: Sont-elles {{arriver}} ? @@ -10352,37 +11460,37 @@ !! !! __[i]/ppas(ppas_je_me_verbe)__ je +(?:ne +|)me +((?:s[eauû]|montr|pens|rév|v[oiîe])\w+) +({w_2}) @@w,$ - <<- morph(\1, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir) ", False) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") + <<- morph(\1, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir)/", False) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") -2>> =suggSing(@) # Accord avec le sujet « je » : « \2 » devrait être au singulier. TEST: je me savais {{implacables}} avec eux __[i]/ppas(ppas_tu_te_verbe)__ tu +(?:ne +|)te +((?:s[eauû]|montr|pens|rév|v[oiîe])\w+) +({w_2}) @@w,$ - <<- morph(\1, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir) ", False) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") + <<- morph(\1, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir)/", False) and morphex(\2, ":[NAQ].*:p", ":[GWYsi]") -2>> =suggSing(@) # Accord avec le sujet « je » : « \2 » devrait être au singulier. TEST: quand tu te montres {{infaillibles}} __[i]/ppas(ppas_il_se_verbe)__ (il|ce|ce qui|celui +qui|ça +qui|lui +qui|celui-(?:ci|là)|quiconque|lequel) +(?:ne +|)se +((?:s[eauû]|montr|pens|rév|v[oiîe])\w+) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir) ", False) + <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir)/", False) and (morphex(\3, ":[NAQ].*:p", ":[GWsi]") or morphex(\3, ":[NAQ].*:f", ":[GWYme]")) and (not re.search("^(?:celui-(?:ci|là)|lequel)$", \1) or not morph(word(-1), ":R", False, False)) -3>> =suggMasSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin singulier. TEST: lequel se veut {{imbattables}} ? __[i]/ppas(ppas_elle_se_verbe)__ (elle|celle-(?:ci|là)|laquelle) +(?:ne +|)se +((?:s[eauû]|montr|pens|rév|v[oiîe])\w+) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir) ", False) + <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir)/", False) and (morphex(\3, ":[NAQ].*:p", ":[GWsi]") or morphex(\3, ":[NAQ].*:m", ":[GWYfe]")) and not morph(word(-1), ":R", False, False) -3>> =suggFemSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin singulier. TEST: Elle se sait plus {{fortes}} qu’eux tous. @@ -10389,84 +11497,84 @@ TEST: elle se vit {{abandonné}} __[i]/ppas(ppas_elle_qui_se_verbe)__ (c?elle +qui) +(?:ne +|)se +((?:s[eauû]|montr|pens|rév|v[oiîe])\w+) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir) ", False) + <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir)/", False) and (morphex(\3, ":[NAQ].*:p", ":[GWsi]") or morphex(\3, ":[NAQ].*:m", ":[GWYfe]")) -3>> =suggFemSing(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin singulier. TEST: à celle qui se révélera {{attentif}} à tous ces problèmes. __[i]/ppas(ppas_nous_nous_verbe)__ nous +(?:ne +|)nous +((?:s[eauû]|montr|pens|rév|v[oiîe])\w*ons) +({w_2}) @@w,$ - <<- morph(\1, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir) ", False) and morphex(\2, ":[NAQ].*:s", ":[GWpi]") + <<- morph(\1, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir)/", False) and morphex(\2, ":[NAQ].*:s", ":[GWpi]") -2>> =suggPlur(@) # Accord avec le sujet « nous » : « \2 » devrait être au pluriel. TEST: nous nous pensions {{invincible}} jusqu’au jour où tout a basculé. __[i]/ppas(ppas_ils_se_verbe)__ (ils|c?eux +qui|ceux-ci|ceux-là|lesquels) +(?:ne +|)se +((?:s[eauû]|montr|pens|rév|v[oiîe])\w+) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir) ", False) + <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir)/", False) and (morphex(\3, ":[NAQ].*:s", ":[GWpi]") or morphex(\3, ":[NAQ].*:f", ":[GWYme]")) and (not re.search("^(?:ceux-(?:ci|là)|lesquels)$", \1) or not morph(word(-1), ":R", False, False)) -3>> =suggMasPlur(@) # Accord avec le sujet « \1 » : « \3 » devrait être au masculin pluriel. TEST: ils se montrent {{exigeantes}} __[i]/ppas(ppas_elles_se_verbe)__ (elles|c?elles +qui|celles-(?:ci|là)|lesquelles) +(?:ne +|)se +((?:s[eauû]|montr|pens|rév|v[oiîe])\w+) +({w_2}) @@0,w,$ - <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir) ", False) + <<- morph(\2, ">(?:montrer|penser|révéler|savoir|sentir|voir|vouloir)/", False) and (morphex(\3, ":[NAQ].*:s", ":[GWpi]") or morphex(\3, ":[NAQ].*:m", ":[GWYfe]")) and (not re.search("^(?:elles|celles-(?:ci|là)|lesquelles)$", \1) or not morph(word(-1), ":R", False, False)) -3>> =suggFemPlur(@) # Accord avec le sujet « \1 » : « \3 » devrait être au féminin pluriel. TEST: elles se sentent {{perdu}} __[i]/ppas(ppas_le_verbe_pensée)__ le ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}[esx]) @@w,$ - <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", False) and morphex(\2, ":[AQ].*:(?:[me]:p|f)", ":(?:G|Y|[AQ].*:m:[is])") + <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà)/", False) and morphex(\2, ":[AQ].*:(?:[me]:p|f)", ":(?:G|Y|[AQ].*:m:[is])") and not (morph(\1, ":Y", False) and morph(\2, ":3s", False)) -2>> =suggMasSing(@) # Accord avec le COD “le” : « \2 » doit être au masculin singulier. __[i]/ppas(ppas_la_verbe_pensée)__ la ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}[uiéesx]) @@w,$ - <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", False) and morphex(\2, ":[AQ].*:(?:[fe]:p|m)", ":(?:G|Y|[AQ]:f:[is])") + <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà)/", False) and morphex(\2, ":[AQ].*:(?:[fe]:p|m)", ":(?:G|Y|[AQ]:f:[is])") and not (morph(\1, ":Y", False) and morph(\2, ":3s", False)) -2>> =suggFemSing(@) # Accord avec le COD “la” : « \2 » doit être au féminin singulier. __[i]/ppas(ppas_les_verbe_pensée)__ les ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}) @@w,$ - <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", False) and morphex(\2, ":[AQ].*:s", ":(?:G|Y|[AQ].*:[ip])") + <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà)/", False) and morphex(\2, ":[AQ].*:s", ":(?:G|Y|[AQ].*:[ip])") and not (morph(\1, ":Y", False) and morph(\2, ":3s", False)) -2>> =suggPlur(@) # Accord avec le COD “les” : « \2 » doit être au pluriel. __[i]/ppas(ppas_me_te_verbe_pensée)__ ([mt]e) ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}[sx]) @@0,w,$ - <<- morph(\2, ">(?:trouver|considérer|croire|rendre|voilà) ", False) and morphex(\3, ":[AQ].*:p", ":(?:G|Y|[AQ].*:[is])") + <<- morph(\2, ">(?:trouver|considérer|croire|rendre|voilà)/", False) and morphex(\3, ":[AQ].*:p", ":(?:G|Y|[AQ].*:[is])") and not (morph(\1, ":Y", False) and morph(\2, ":3s", False)) -3>> =suggSing(@) # Accord avec le pronom “\1” : « \3 » doit être au singulier. __[i]/ppas(ppas_se_verbe_pensée)__ se ((?:trouv|consid[éè]r|cr[ouû]|rend)\w*) +({w_3}) @@w,$ - <<- morph(\1, ">(?:trouver|considérer|croire|rendre) .*:3s", False) and morphex(\2, ":[AQ].*:p", ":(?:G|Y|[AQ].*:[is])") + <<- morph(\1, ">(?:trouver|considérer|croire|rendre)/.*:3s", False) and morphex(\2, ":[AQ].*:p", ":(?:G|Y|[AQ].*:[is])") and not (morph(\1, ":Y", False) and morph(\2, ":3s", False)) -2>> =suggSing(@) # Accord avec le pronom “se” (le verbe étant au singulier) : « \2 » doit être au singulier. - <<- __else__ and morph(\1, ">(?:trouver|considérer|croire|rendre) .*:3p", False) and morphex(\2, ":[AQ].*:s", ":(?:G|Y|[AQ].*:[ip])") + <<- __else__ and morph(\1, ">(?:trouver|considérer|croire|rendre)/.*:3p", False) and morphex(\2, ":[AQ].*:s", ":(?:G|Y|[AQ].*:[ip])") and not (morph(\1, ":Y", False) and morph(\2, ":3s", False)) -2>> =suggPlur(@) # Accord avec le pronom “se” (le verbe étant au pluriel) : « \2 » doit être au pluriel. __[i]/ppas(ppas_nous_verbe_pensée)__ nous ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}) @@w,$ - <<- ( morphex(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", ":1p") - or (morph(\1, ">(?:trouver|considérer|croire) .*:1p", False) and before(r"\bn(?:ous|e) +$")) ) + <<- ( morphex(\1, ">(?:trouver|considérer|croire|rendre|voilà)/", ":1p") + or (morph(\1, ">(?:trouver|considérer|croire)/.*:1p", False) and before(r"\bn(?:ous|e) +$")) ) and morphex(\2, ":[AQ].*:s", ":(?:G|Y|[AQ].*:[ip])") and not (morph(\1, ":Y", False) and morph(\2, ":3s", False)) -2>> =suggPlur(@) # Accord avec le pronom “nous” : « \2 » doit être au pluriel. #__[i]/ppas(ppas_vous_verbe)__ # vous ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}) @@w,$ -# <<- ( morphex(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", ":2p") -# or (morph(\1, ">(?:trouver|considérer|croire) .*:2p", False) and before(r"\b(?:vous|ne) +$")) ) +# <<- ( morphex(\1, ">(?:trouver|considérer|croire|rendre|voilà)/", ":2p") +# or (morph(\1, ">(?:trouver|considérer|croire)/.*:2p", False) and before(r"\b(?:vous|ne) +$")) ) # and morphex(\2, ":[AQ].*:s", ":(?:G|[AQ].*:[ip])") # -2>> =suggPlur(@) # Accord avec le pronom “vous” : « \2 » doit être au pluriel. TEST: ces hommes le rendent {{dingues}} TEST: Il me considère {{stupides}} @@ -10498,15 +11606,15 @@ #__[i]/conj__ fait(s|e|es) ({w1}) <<- morph(\2, ":V") and not morph(\2, ":Y") # ->> fait \1 # Le participe passé de faire reste au masculin singulier s’il est suivi par un verbe à l’infinitif. __[i](p_les_avoir_fait_vinfi)__ - les ({avoir}) +(fait) +(?:[mts](?:e +|’)|)({infi}) @@w,w,$ <<- morph(\1, ">avoir ", False) and morph(\3, ":Y", False) ~2>> _ + les ({avoir}) +(fait) +(?:[mts](?:e +|’)|)({infi}) @@w,w,$ <<- morph(\1, ">avoir/", False) and morph(\3, ":Y", False) ~2>> _ __[i]/ppas(ppas_pronom_avoir)__ (?:j’|je |tu |ils? |elles? |on |et )(?:ne +|n’|l(?:ui|eur) +|)({avoir}) +({w_2}) @@w,$ - <<- not re.search("(?i)^(?:barre|confiance|cours|envie|peine|prise|crainte|cure|affaire|hâte|force|recours)$", \2) and morph(word(-1), ">(?:comme|et|lorsque?|mais|o[uù]|puisque?|qu(?:oique?|i|and)|si(?:non|)) ", False, True) + <<- not re.search("(?i)^(?:barre|confiance|cours|envie|peine|prise|crainte|cure|affaire|hâte|force|recours)$", \2) and morph(word(-1), ">(?:comme|et|lorsque?|mais|o[uù]|puisque?|qu(?:oique?|i|and)|si(?:non|))/", False, True) and morph(\1, ":V0a", False) and not \2.isupper() and morphex(\2, ":(?:[123][sp]|Q.*:[fp])", ":(?:G|W|Q.*:m:[si])") -2>> =suggMasSing(@) # Ce verbe devrait être un participe passé au masculin singulier.|http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais TEST: ils leur avaient {{donnés}} du fil à retordre. @@ -10518,11 +11626,11 @@ __[i]/ppas(ppas_nous_vous_avoir)__ ([nv]ous) +(?:ne +|n’|l(?:ui|eur) +|)({avoir}) +({w_2}) @@0,w,$ <<- morph(\1, ":Os", False) - and not re.search("(?i)^(?:barre|confiance|cours|envie|peine|prise|crainte|cure|affaire|hâte|force|recours)$", \3) and morph(word(-1), ">(?:comme|et|lorsque?|mais|o[uù]|puisque?|qu(?:oique?|i|and)|si(?:non|)) ", False, True) + and not re.search("(?i)^(?:barre|confiance|cours|envie|peine|prise|crainte|cure|affaire|hâte|force|recours)$", \3) and morph(word(-1), ">(?:comme|et|lorsque?|mais|o[uù]|puisque?|qu(?:oique?|i|and)|si(?:non|))/", False, True) and morph(\2, ":V0a", False) and not \3.isupper() and morphex(\3, ":(?:[123][sp]|Q.*:[fp])", ":(?:G|W|Q.*:m:[si])") -3>> =suggMasSing(@) # Ce verbe devrait être un participe passé au masculin singulier.|http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais TEST: Nous avons {{donne}} tout notre potentiel. @@ -10530,11 +11638,11 @@ TEST: D’un côté, le modèle occidental, […], nous a libérés de […] __[i]/ppas(ppas_det_nom_avoir)__ (l(?:’|es? |a |eurs )|ce(?:s|tte|t|rtaine?s|) |des |quelques |[mts](?:es|on|a) |[nv]o(?:s|tre) ) *({w_2}) +(?:ne +|n’|l(?:ui|eur) +|)({avoir}) +({w_2}) @@0,w,w,$ - <<- not re.search("(?i)^(?:barre|confiance|cours|envie|peine|prise|crainte|cure|affaire|hâte|force|recours)$", \4) and morph(word(-1), ">(?:comme|et|lorsque?|mais|o[uù]|puisque?|qu(?:oique?|i|and)|si(?:non|)) ", False, True) + <<- not re.search("(?i)^(?:barre|confiance|cours|envie|peine|prise|crainte|cure|affaire|hâte|force|recours)$", \4) and morph(word(-1), ">(?:comme|et|lorsque?|mais|o[uù]|puisque?|qu(?:oique?|i|and)|si(?:non|))/", False, True) and not morph(\2, ":G", False) and morph(\3, ":V0a", False) and not \4.isupper() and morphex(\4, ":(?:[123][sp]|Q.*:[fp])", ":(?:G|W|Q.*:m:[si])") and not (\3 == "avions" and morph(\4, ":3[sp]", False)) -4>> =suggMasSing(@) # Ce verbe devrait être un participe passé au masculin singulier.|http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais @@ -10581,11 +11689,11 @@ TEST: ces livres m’avaient {{ennuyés}} au-delà du dicible. __[i]/ppas(ppas_qui_avoir)__ qui +(?:n’|l(?:ui|eur) |ne l(?:ui|eur) |ne +|)({avoir}) +({w_2}[es]) @@w,$ - <<- morph(\1, ">avoir ", False) and morphex(\2, ":Q.*:(?:f|m:p)", ":m:[si]") + <<- morph(\1, ">avoir/", False) and morphex(\2, ":Q.*:(?:f|m:p)", ":m:[si]") -2>> =suggMasSing(@) # Le participe passé devrait être au masculin singulier.|http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais TEST: des hommes, des femmes, des enfants qui ne leur avaient {{faits}} que du bien. @@ -10629,11 +11737,11 @@ ## avoir avec participe passé __[i]/ppas(ppas_m_t_l_avoir)__ [lmt]’(?:en +|y +|)({avoir}) +({w_3}) @@2,$ - <<- morph(\1, ">avoir ", False) and morphex(\2, ":(?:Y|[123][sp])", ":[QGWMX]") + <<- morph(\1, ">avoir/", False) and morphex(\2, ":(?:Y|[123][sp])", ":[QGWMX]") and not re.search(r"(?i)^t’as +envie", \0) -2>> =suggVerbPpas(@, ":m:s") # Confusion : employez un participe passé. TEST: m’avoir {{terminer}}. TEST: il m’a {{souffler}} la bonne réponse. @@ -10872,11 +11980,11 @@ <<- morph(\2, ":(?:[123][sp]|P)", False) =>> select(\2,":(?:[123][sp]|P)") <<- ~1>> * __> select(\2,":(?:[123][sp]|P)") - <<- not morph(\1, ":X|>rien ", False) ~1>> * + <<- not morph(\1, ":X|>rien/", False) ~1>> * __> select(\2,":(?:[123][sp]|P)") <<- ~1>> * __> select(\2,":(?:[123][sp]|P)") <<- ~1>> * __(p_premier_ne_pro_per_obj5)__ ^( *n’(?:en |y |))({w_2}) @@0,$ <<- morph(\2, ":(?:[123][sp]|P)", False) =>> select(\2,":(?:[123][sp]|P)") - <<- not morph(\1, ":X|>rien ", False) ~1>> * + <<- not morph(\1, ":X|>rien/", False) ~1>> * __(p_premier_ne_pro_per_obj6)__ ^( *ne l’)({w_2}) @@0,$ <<- morph(\2, ":(?:[123][sp]|P)", False) =>> select(\2,":(?:[123][sp]|P)") <<- ~1>> * __(p_premier_ne_pro_per_obj7)__ ^( *ne) ({w_2}) @@0,$ <<- morph(\2, ":(?:[123][sp]|P)", False) =>> select(\2,":(?:[123][sp]|P)") - <<- not morph(\2, ":X|>rien ", False) ~1>> * + <<- not morph(\2, ":X|>rien/", False) ~1>> * TEST: Ne rien céder. TEST: Ne pas manger. TEST: Ne manquer de rien. TEST: Ne jamais miser sur ces tocards. @@ -10950,19 +12058,20 @@ # verbes du 2ᵉ et du 3ᵉ groupe en -t __[i]/imp(imp_vgroupe2_vgroupe3_t)__ ^ *(\w+t)(?![- ](?:je|tu|[nv]ous|ils?|elles?|on|t-ils?|t-elles?)) @@$ <<- morphex(\1, ":V[23].*:Ip.*:3s", ":[GNA]|>(?:devoir|suffire)") and analyse(\1[:-1]+"s", ":E:2s", False) - and not (re.search("(?i)^vient$", \1) and after("^ +(?:l[ea]|se |s’)")) + and not (re.search("(?i)^vient$", \1) and after("^ +(?:l[ea]|[sd]e |[sd]’)")) and not (re.search("(?i)^dit$", \1) and after("^ +[A-ZÉÈÂÎ]")) -1>> =\1[:-1]+"s" # S’il s’agit d’un impératif, la terminaison est “is”, non “it”. TEST: {{Finit}} ton assiette. TEST: Ne {{pourrit}} pas l’ambiance. TEST: Suffit de s’en servir. TEST: Et ne doit pas être rejeté dans les limbes. TEST: Vient s’ajouter à ce contexte la perception, partagée par beaucoup, du caractère fortement menaçant de l’environnement économique et géopolitique. +TEST: À son bord vient d’embarquer un nouvel équipage # verbes du 3ᵉ groupe en -d __[i]/imp(imp_vgroupe3_d)__ ^ *(\w+d)(?![- ](?:je|tu|[nv]ous|ils?|elles?|on|t-ils?|t-elles?)) @@$ @@ -11084,11 +12193,11 @@ TEST: deux fois par an, souligne le Dr Assouline __[i]/imp(imp_laisser_le_la_les_infi)__ ((laiss\w+) l(?:es|a)) +({w_2}) @@0,0,$ - <<- morph(\2, ">laisser .*:E", False) and morphex(\3, ":(?:Y|X|Oo)", ":[NAB]") + <<- morph(\2, ">laisser/.*:E", False) and morphex(\3, ":(?:Y|X|Oo)", ":[NAB]") -1>> =\1.replace(" ", "-") # S’il s’agit d’un impératif, mettez un trait d’union.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=4206 TEST: {{Laisse les}} entrer… TEST: {{Laissez la}} venir… @@ -11097,11 +12206,11 @@ TEST: le coût humain de la guerre qu’il a laissé les submerger. __guerre ", False, False)) ->> \1’en + <<- not (\0.endswith("t-en") and before(r"(?i)\bva$") and morph(word(1), ">guerre/", False, False)) ->> \1’en # « \1e » est ici abrégé, c’est une forme élidée. Il faut mettre une apostrophe et non un trait d’union. TEST: donne{{-m-en}} encore @@ -11281,11 +12390,11 @@ <<- morph(\2, ":(?:[123][sp]|P|Y)", False) =>> select(\2, ":(?:[123][sp]|P|Y)") <<- not morph(\2, ":2s", False) or before(r"(?i)\b(?:je|tu|on|ils?|elles?|nous) +$") ~1>> * __[i](p_pro_per_obj30)__ (t’)({w_2}) @@0,$ <<- morph(\2, ":(?:[123][sp]|P|Y)", False) =>> select(\2, ":(?:[123][sp]|P|Y)") - <<- not morph(\2, ":2s|>(ils?|elles?|on) ", False) or before(r"(?i)\b(?:je|tu|on|ils?|elles?|nous) +$") ~1>> * + <<- not morph(\2, ":2s|>(ils?|elles?|on)/", False) or before(r"(?i)\b(?:je|tu|on|ils?|elles?|nous) +$") ~1>> * __[i>(p_pro_per_obj31)__ (ne +[mtsl]’)({w_1}) @@0,$ <<- morph(\2, ":(?:[123][sp]|P|Y)", False) =>> select(\2, ":(?:[123][sp]|P|Y)") <<- ~1>> * __[i>(p_pro_per_obj32)__ @@ -11351,11 +12460,11 @@ #### CONFUSION veillez/veuillez __[i]/conf(conf_veillez2)__ (veuillez) +à +(ne|{infi}) @@0,$ - <<- isStart() and morph(\2, ":Y|>ne ", False) -1>> veillez # Confusion probable : “veuillez” est une forme conjuguée du verbe “vouloir”.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=1939 + <<- isStart() and morph(\2, ":Y|>ne/", False) -1>> veillez # Confusion probable : “veuillez” est une forme conjuguée du verbe “vouloir”.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=1939 TEST: {{Veuillez}} à ne pas tomber dans ce piège. TEST: Et {{veuillez}} surtout à ouvrir grand les yeux. TEST: {{Veuillez}}, s’il vous plaît, à prendre vos médicaments. TEST: Veuillez à nouveau faire attention à ce problème. @@ -11363,11 +12472,11 @@ TEST: Veillez à bien fermer les fenêtres. __[i]/conf(conf_veuillez)__ (veillez) +(ne|{infi}) @@0,$ - <<- isStart() and morph(\2, ":Y|>ne ", False) -1>> veuillez + <<- isStart() and morph(\2, ":Y|>ne/", False) -1>> veuillez # Confusion probable : “veiller” signifie “prendre garde” ou “être vigilant”. Pour inviter à faire quelque chose, écrivez “veuillez”.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=1939 TEST: {{Veillez}} excuser mon retard. TEST: {{Veillez}} me contacter. TEST: {{Veillez}} me le faire savoir. @@ -11405,11 +12514,11 @@ TEST: en train de {{mangez}} __[i]/infi(infi_verbe)__ ((?:aim|all|v|ir|désir|esp[éè]r|p(?:[eou]|réf[éè]r))\w*) +({w_2}(?:ée?s?|ez)) @@0,$ - <<- morphex(\1, ">(?:aimer|aller|désirer|devoir|espérer|pouvoir|préférer|souhaiter|venir) ", ":[GN]") and morphex(\2, ":V", ":M") + <<- morphex(\1, ">(?:aimer|aller|désirer|devoir|espérer|pouvoir|préférer|souhaiter|venir)/", ":[GN]") and morphex(\2, ":V", ":M") -2>> =suggVerbInfi(@) # S’il s’agit d’une action à accomplir, le verbe devrait être à l’infinitif. TEST: elle préférait {{mangée}} seule. TEST: Il venait, comme d’habitude, {{discuté}} avec son ami. TEST: Ces types-là venaient {{mangé}} chez moi tous les dimanches. @@ -11419,11 +12528,11 @@ TEST: Cette affaire ne va rien {{arrangé}}. __[i]/infi(infi_devoir)__ (d[eouû]\w+) +({w_2}(?:ée?s?|ez)) @@0,$ - <<- morph(\1, ">devoir ", False) and morphex(\2, ":V", ":M") and not morph(word(-1), ":D", False) + <<- morph(\1, ">devoir/", False) and morphex(\2, ":V", ":M") and not morph(word(-1), ":D", False) -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: il devait {{utilisé}} son temps à bon escient. TEST: tu dois {{mangé}} @@ -11436,11 +12545,11 @@ TEST: faut-il {{pensé}} à ces choses-là encore et encore ? __[i]/infi(infi_mieux_valoir)__ mieux (?:ne |)(va\w+) +({w_2}(?:ée?s?|ez)) @@w,$ - <<- morph(\1, ">valoir ", False) and morphex(\2, ":(?:Q|2p)", ":[GM]") + <<- morph(\1, ">valoir/", False) and morphex(\2, ":(?:Q|2p)", ":[GM]") -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif. TEST: Mieux vaut {{consacré}} son temps à des occupations utiles. @@ -11453,11 +12562,11 @@ TEST: je vais à Rodez. __[i]/infi(infi_avoir_beau)__ ({avoir}) beau ({w_2}(?:ée?s?|ez|ai[ts]?)) @@0,$ - <<- morph(\1, ">avoir ", False) and morphex(\2, ":V1", ":N") + <<- morph(\1, ">avoir/", False) and morphex(\2, ":V1", ":N") -2>> =suggVerbInfi(@) # Le verbe devrait être à l’infinitif.|http://fr.wiktionary.org/wiki/avoir_beau TEST: Ils ont beau {{consacré}} le plus clair de leur temps à ce projet, ça n’avance guère. @@ -11491,11 +12600,11 @@ !! ## 1sg __[i]/conj(conj_j)__ j’({w_1}) @@2 - <<- morphex(\1, ":V", ":1s|>(?:en|y) ") >>> + <<- morphex(\1, ":V", ":1s|>(?:en|y)/") >>> <<- \1 == "est" or \1 == "es" -1>> ai|aie|suis # Conjugaison erronée. Confusion probable entre “être” et “avoir”. Accord avec « \1 ». Le verbe devrait être à la 1ʳᵉ personne du singulier. <<- __else__ -1>> =suggVerb(@, ":1s") # Conjugaison erronée. Accord avec « je ». Le verbe devrait être à la 1ʳᵉ personne du singulier. __[i]/conj(conj_je)__ (je) +({w_1}) @@0,$ <<- morphex(\2, ":V", ":(?:1s|G)") and not (morph(\2, ":[PQ]", False) and morph(word(-1), ":V0.*:1s", False, False)) >>> @@ -11601,11 +12710,11 @@ TEST: celui qui {{pensent}} mal de toute chose __[i]/conj(conj_ça)__ (ça|chacune?|l’une?|ce(?:ci|la|lui-(?:ci|là)|lle-(?:ci|là))|n`importe quo?i|quelqu(?:’une?|e chose)) +(?:qui +|)({w_1}) @@0,$ - <<- morphex(\2, ":V", ":(?:3s|P|Q|G|3p!)") and not morph(word(-1), ":[VR]|>de ", False, False) + <<- morphex(\2, ":V", ":(?:3s|P|Q|G|3p!)") and not morph(word(-1), ":[VR]|>de/", False, False) -2>> =suggVerb(@, ":3s") # Conjugaison erronée. Accord avec « \1 ». Le verbe devrait être à la 3ᵉ personne du singulier. TEST: chacun {{fais}} comme il peut TEST: quelqu’un {{sauras}} #TEST: quelqu’une se {{montrent}} désagréable # Fuck you, JavaScript (wait for negative lookbehind assertions) @@ -11905,11 +13014,11 @@ # L’accord par syllepse est obligatoire après /la plupart/, ainsi qu’après /nombre/ et /quantité/ employés sans déterminant. L’accord se fait avec le « pseudo-complément ». __[i]/conj(conj_beaucoup_d_aucuns_la_plupart)__ (beaucoup|d’aucuns|la plupart) +({w_2}) @@0,$ - <<- morphex(\2, ":V", ":(?:3p|P|Q|G)") and not morph(word(-1), ":[VR]|>de ", False, False) + <<- morphex(\2, ":V", ":(?:3p|P|Q|G)") and not morph(word(-1), ":[VR]|>de/", False, False) -2>> =suggVerb(@, ":3p") # Conjugaison erronée. Accord avec « \1 ». Le verbe devrait être à la 3ᵉ personne du pluriel. __[i]/conj(conj_beaucoup_d_aucuns_la_plupart_qui)__ (beaucoup|d’aucuns|la plupart) +qui +({w_2}) @@0,$ <<- morphex(\2, ":V", ":(?:3p|P|Q|G)") and not morph(word(-1), ":[VR]", False, False) @@ -12023,11 +13132,11 @@ TEST: L’un comme l’autre devaient y renoncer. __[i]/conj(conj_des_nom1)__ ^ *des +({w_2}) +({w_2}) @@w,$ - <<- morph(\1, ":[NAQ].*:[pi]", False) and morphex(\2, ":V", ":(?:[13]p|P|G|Q|A.*:[pi])") and morph(word(1), ":(?:R|D.*:p)|>au ", False, True) >>> + <<- morph(\1, ":[NAQ].*:[pi]", False) and morphex(\2, ":V", ":(?:[13]p|P|G|Q|A.*:[pi])") and morph(word(1), ":(?:R|D.*:p)|>au/", False, True) >>> <<- not morph(\2, ":[NA]", False) -2>> =suggVerb(@, ":3p") # Conjugaison erronée. Accord avec « des \1… ». Le verbe devrait être à la 3ᵉ personne du pluriel. <<- __else__ and not checkAgreement(\1, \2) -2>> =suggVerb(@, ":3p", suggPlur) # Conjugaison erronée. Accord avec « des \1… ». Le verbe devrait être à la 3ᵉ personne du pluriel. __[i]/conj(conj_des_nom_qui)__ ^ *des +({w_2}) +qui +({w_2}) @@w,$ <<- morph(\1, ":[NAQ].*:[pi]", False) and morphex(\2, ":V", ":(?:[13]p|P|G)") @@ -12210,11 +13319,11 @@ ({w_1}s) tu @@0 <<- morphex(\1, ":V.*:2s", ":[GNW]") and not before(r"(?i)\b(?:je|tu) +$") and morphex(word(1), ":", ":2s", True) ->> \1-tu # Forme interrogative ? Mettez un trait d’union. __[i]/inte(inte_union_il_on)__ ({w_2}[td]) (?:il|on) @@0 - <<- morphex(\1, ":V.*:3s", ":[GNW]") and not before(r"(?i)\b(?:ce|il|elle|on) +$") and morphex(word(1), ":", ":3s|>y ", True) + <<- morphex(\1, ":V.*:3s", ":[GNW]") and not before(r"(?i)\b(?:ce|il|elle|on) +$") and morphex(word(1), ":", ":3s|>y/", True) ->> =\0.replace(" ", "-") # Forme interrogative ? Mettez un trait d’union. __[i]/inte(inte_union_elle)__ (?> \1-elle # Forme interrogative ? Mettez un trait d’union. @@ -12222,11 +13331,11 @@ ({w_2}ons) nous @@0 <<- morphex(\1, ":V.*:1p", ":[GNW]") and not morph(word(-1), ":Os", False, False) and morphex(word(1), ":", ":(?:Y|1p)", True) ->> \1-nous # Forme interrogative ? Mettez un trait d’union. __[i]/inte(inte_union_vous)__ ({w_2}e[zs]) vous @@0 - <<- morphex(\1, ":V.*:2p", ":[GNW]|>vouloir .*:E:2p") and not morph(word(-1), ":Os", False, False) and morphex(word(1), ":", ":(?:Y|2p)", True) + <<- morphex(\1, ":V.*:2p", ":[GNW]|>vouloir/.*:E:2p") and not morph(word(-1), ":Os", False, False) and morphex(word(1), ":", ":(?:Y|2p)", True) ->> \1-vous # Forme interrogative ? Mettez un trait d’union. __[i]/inte(inte_union_ils_elles)__ (?> =\0.replace(" ", "-") # Forme interrogative ? Mettez un trait d’union. @@ -12294,20 +13403,20 @@ __[i]/inte(inte_nous)__ ({w1})-nous @@0 <<- morphex(\1, ":V", ":(?:1p|E:2[sp])") -1>> =suggVerb(@, ":1p") # Forme interrogative ou impérative incorrecte. - <<- morphex(\1, ":", ":V|>chez ") -1>> =suggSimil(\1, ":1p", False) # Forme interrogative ou impérative incorrecte. + <<- morphex(\1, ":", ":V|>chez/") -1>> =suggSimil(\1, ":1p", False) # Forme interrogative ou impérative incorrecte. TEST: {{Prendront}}-nous ->> Prendrons TEST: {{Attendront}}-nous le train ->> Attendrons __[i]/inte(inte_vous)__ ({w1})-vous @@0 <<- morphex(\1, ":V", ":2p") -1>> =suggVerb(@, ":2p") # Forme interrogative ou impérative incorrecte. - <<- not morph(\1, ":V|>chez ", False) -1>> =suggSimil(\1, ":2p", False) # Forme interrogative ou impérative incorrecte. + <<- not morph(\1, ":V|>chez/", False) -1>> =suggSimil(\1, ":2p", False) # Forme interrogative ou impérative incorrecte. TEST: {{Attaquait}}-vous ->> Attaquiez TEST: Elle a de nombreux rendez-vous ce matin. TEST: êtes-vous là ? @@ -12326,46 +13435,58 @@ !!!! Verbe auxiliaire __[i]/conf(conf_avoir_sujet_participe_passé)__ ({avoir})-(?:je|tu|ils?|elles?|on) +({ppas}) @@0,$ - <<- morph(\1, ">avoir ", False) and morph(\2, ":V.......e_.*:Q", False) -1>> _ # Incohérence. La forme verbale “\2” ne peut pas être utilisé avec l’auxiliaire “avoir”, seulement avec l’auxiliaire “être”. + <<- morph(\1, ">avoir/", False) and morph(\2, ":V.......e_.*:Q", False) -1>> _ # Incohérence. La forme verbale “\2” ne peut pas être utilisé avec l’auxiliaire “avoir”, seulement avec l’auxiliaire “être”. __[i]/conf(conf_sujet_avoir_participe_passé)__ (?:j’|je |tu |ils? |elles? |on ) *({avoir}) +({ppas}) @@*,$ - <<- morph(\1, ">avoir ", False) and morph(\2, ":V.......e_.*:Q", False) -1>> _ # Incohérence. La forme verbale “\2” ne peut pas être utilisé avec l’auxiliaire “avoir”, seulement avec l’auxiliaire “être”. + <<- morph(\1, ">avoir/", False) and morph(\2, ":V.......e_.*:Q", False) -1>> _ # Incohérence. La forme verbale “\2” ne peut pas être utilisé avec l’auxiliaire “avoir”, seulement avec l’auxiliaire “être”. TEST: {{Ait}}-il arrivé à ses fins ? TEST: je n’{{avais}} pas parti avec eux. TEST: Avais-je partie liée avec lui ? TEST: il {{avait}} parti. + + + +@@@@ +@@@@ +@@@@ +@@@@ +@@@@GRAPH: last_graph +@@@@ +@@@@ +@@@@ +@@@@ + !! !! !!!! Modes verbaux !! !! # conditionnel / futur -__[i]/vmode(vmode_j_aimerais_vinfi)__ - j(?:e +|’)(aimerai|préf[éè]rerai|apprécierai|voudrai|souhaiterai|désirerai|adorerai) +({w_1}) @@w,$ - <<- morphex(\2, ":[YX]|>(?:y|ne|que?) ", ":R") and isStart() -1>> \1s # Si vous exprimez un souhait, utilisez le conditionnel et non le futur. +__vmode_j_aimerais_vinfi__ + [|,] [je|j’] [aimerai|préférerai|préfèrerai|apprécierai|voudrai|souhaiterai|désirerai|adorerai] @:[YX]|>(?:y|ne|que?)/¬:R + <<- /vmode/ -3>> \1s # Si vous exprimez un souhait, utilisez le conditionnel et non le futur. TEST: J’{{aimerai}} savoir ce dont il retourne. TEST: dans tous les cas j’{{aimerai}} ne rien savoir TEST: Je {{voudrai}} qu’il soit déjà là. TEST: J’aimerai ces cours-là autant que les autres. TEST: J’aimerai la danse et la musique, puisque vous l’exigez. TEST: Je sais que j’aimerai ça, tout comme lui. -__[i]/vmode(vmode_j_aurais_aimé_que_avoir_être)__ - j’(aurai) +(?:aimé|souhaité|préféré|voulu|apprécié|désiré|adoré) +(que?|ne|{infi}) @@2,$ - <<- morph(\2, ":Y|>(?:ne|que?) ", False) - -1>> aurais|eusse # Pour un souhait passé, utilisez le conditionnel passé et non le futur antérieur. Exemple pour le futur antérieur : « quand j’aurai fini… » +__vmode_j_aurais_aimé_que_vinfi__ + j’ aurai [aimé|souhaité|préféré|voulu|apprécié|désiré|adoré] [que|qu’|qu|ne|n’|@:Y] + <<- /vmode/ -2>> aurais|eusse # Pour un souhait passé, utilisez le conditionnel passé et non le futur antérieur. Exemple pour le futur antérieur : « quand j’aurai fini… » TEST: J’{{aurai}} aimé nous offrir ce magnifique cadeau. TEST: j’{{aurai}} voulu être un artiste. TEST: j’{{aurai}} préféré ne pas avoir à l’entendre. TEST: j’{{aurai}} préféré l’entendre un autre jour. @@ -12374,41 +13495,39 @@ TEST: Quand j’aurai fini ce boulot, je ne sais pas ce que je ferai. TEST: Quand j’aurai soif et faim, je m’arrêterai. # Si suivi du conditionnel ou du subjonctif -__[i]/vmode(vmode_si_sujet1)__ - si +({w1}) +({w_2}) @@w,$ - <<- morph(\1, ":(?:Os|M)", False) and morphex(\2, ":[SK]", ":(?:G|V0|I)") and isStart() - -2>> _ # Ce verbe ne devrait être ni au conditionnel, ni au subjonctif. -__[i]/vmode(vmode_si_sujet2)__ - (?:si [jt]’|s’ils? +)({w_2}) @@$ - <<- morphex(\1, ":[SK]", ":(?:G|V0|I)") and isStart() - -1>> _ # Ce verbe ne devrait être ni au conditionnel, ni au subjonctif. +__vmode_si_sujet__ + [|,] si [j’|J’|t’|T’] @:[SK]¬:(?:G|V0|I) + [|,] si @:(?:Os|M) @:[SK]¬:(?:G|V0|I) + [|,] s’ [il|ils] @:[SK]¬:(?:G|V0|I) + <<- /vmode/ -4>> _ # Ce verbe ne devrait être ni au conditionnel, ni au subjonctif. TEST: Si Pierre {{avancerait}} sa voiture de quelques mètres, ça nous permettrait de passer. TEST: s’ils ne {{mangeraient}} pas tous les jours, ils seraient moins gros. +TEST: Si j’{{irais}} le faire # Dès que + indicatif -__[i]/vmode(vmode_dès_que)__ - dès +que? +({w_2}) +({w_2}) @@w,$ - <<- morph(\1, ":(?:Os|M)", False) and morphex(\2, ":S", ":[IG]") -2>> =suggVerbMode(@, ":I", \1) # Ce verbe ne devrait pas être au subjonctif. -# <<- morph(\1, ":(?:Os|M)", False) and morph(\2, ":K", False) -2>> =suggVerbMode(@, ":If", \1) # Ce verbe ne devrait pas être au conditionnel. +__vmode_dès_que__ + dès [que|qu’|qu] @:(?:Os|M) @:S¬:[IG] + <<- /vmode/ -4>> =suggVerbMode(\4, ":I", \3) # Ce verbe ne devrait pas être au subjonctif. +# <<- morph(\1, ":(?:Os|M)", False) and morph(\2, ":K", False) -2>> =suggVerbMode(@, ":If", \1) # Ce verbe ne devrait pas être au conditionnel. #TEST: dès que je le {{verrais}} TEST: dès qu’il le {{voie}} TEST: donnant à entendre qu’il avait l’intention de violer Laura dès qu’il en aurait l’occasion # verbe que + subjonctif -__[i]/vmode(vmode_qqch_que_subjonctif1)__ - (afin|avant|pour|quoi|(?:perm|fa|v[oe]|ordonn|exig|désir|dout|suff|préf[éè]r)\w+) +que? +({w_2}) +({w_2}) @@0,w,$ - <<- morph(\1, ">(?:afin|avant|pour|quoi|permettre|falloir|vouloir|ordonner|exiger|désirer|douter|préférer|suffire) ", False) - and morph(\2, ":(?:Os|M)", False) and morphex(\3, ":I", ":[GYS]") - and not (morph(\1, ">douter ", False) and morph(\3, ":(?:If|K)", False)) - -3>> =suggVerbMode(@, ":S", \2) # Après « \1 que », ce verbe devrait être au subjonctif. +__vmode_qqch_que_subjonctif1__ + [afin|avant|pour|quoi|>permettre|>falloir|>vouloir|>ordonner|>exiger|>désirer|>préférer|>suffire] [que|qu’|qu] @:(?:Os|M) @:I¬:[GYS] + <<- /vmode/ -4>> =suggVerbMode(\4, ":S", \3) # Après « \1 que », ce verbe devrait être au subjonctif. + + >douter [que|qu’|qu] @:(?:Os|M) @:I¬:(?:[GYSK]|If) + <<- /vmode/ morph(\1, ":V", ":N") -4>> =suggVerbMode(\4, ":S", \3) # Après « \1 que », ce verbe devrait être au subjonctif. TEST: Il suffit qu’il {{court}} plus TEST: Je veux qu’il {{finit}} son repas. TEST: quoi qu’il en {{conclut}} TEST: Je ne veux pas que tu {{es}} des ennuis @@ -12417,76 +13536,123 @@ TEST: Je ne doute pas qu’ils réussiront leur mission. TEST: Je me doutais bien qu’Apple pourrait marcher TEST: il ne fait aucun doute qu’Amazon le sait. TEST: quoi que nous autres hommes ayons pu faire + +__vmode_qqch_que_subjonctif2__ + à condition [que|qu’|qu] @:(?:Os|M) @:I¬:[GYS] + pour peu [que|qu’|qu] @:(?:Os|M) @:I¬:[GYS] + il peut [que|qu’|qu] @:(?:Os|M) @:I¬:[GYS] + <<- /vmode/ -5>> =suggVerbMode(\5, ":S", \4) # Ce verbe devrait être au subjonctif. + +TEST: à condition qu’il {{finit}} son boulot. +TEST: pour peu qu’il {{prend}} son devoir sérieux… +TEST: il se peut que nous {{avons}} tort. + # Bien que + subjonctif -__[i]/vmode(vmode_bien_que_subjonctif)__ - bien ?que? ({w_2}) +({w_2}) @@w,$ - <<- morph(\1, ":(?:Os|M)", False) and morphex(\2, ":V.*:I", ":(?:[GSK]|If)|>(?:hériter|recevoir|donner|offrir) ") and isStart() - and not ( morph(\2, ":V0a", False) and morph(word(1), ">(?:hériter|recevoir|donner|offrir) ", False) ) - and not before0(r"(?i)\bsi ") - -2>> =suggVerbMode(@, ":S", \1) # Après « bien que », le verbe s’emploie au subjonctif. +__vmode_bien_que_subjonctif__ + [|,] bien [que|qu’|qu] @:(?:Os|M) @:I¬:(?:[GSK]|If|V0a)|>(?:hériter|recevoir|donner|offrir)/ + <<- /vmode/ -5>> =suggVerbMode(\5, ":S", \1) # Après « bien que », le verbe s’emploie au subjonctif. + + [|,] bien [que|qu’|qu] @:(?:Os|M) >avoir @:[QYG]¬>(?:hériter|recevoir|donner|offrir)/ + <<- /vmode/ morph(\5, ":I", ":S") -5>> =suggVerbMode(\5, ":S", \1) # Après « bien que », le verbe s’emploie au subjonctif. TEST: Il ne le savait pas, bien qu’il en {{avait}} entendu parler. TEST: Bien que je {{prends}} mon mal en patience. TEST: C’est un joli bien. Bien qu’il a hérité de son oncle, notez bien. TEST: Bien qu’il avait donné à ses enfants. TEST: si bien que je me suis toujours demandée si cela ne m’avait pas un peu bousillé les yeux - # Malgré que + subjonctif # «Malgré que» peut être utilisé délibérément pour un parler populaire qui ignore le subjonctif. # --> pas de règle de contrôle sur ce point. -__[i]/vmode(vmode_qqch_que_subjonctif2)__ - (?:à condition|pour peu|il +peut) +que? +({w1}) +({w_2}) @@w,$ - <<- morph(\1, ":(?:Os|M)", False) and morphex(\2, ":", ":[GYS]") -2>> =suggVerbMode(@, ":S", \1) # Ce verbe devrait être au subjonctif. - -TEST: à condition qu’il {{finit}} son boulot. -TEST: pour peu qu’il {{prend}} son devoir sérieux… -TEST: il se peut que nous {{avons}} tort. - # indicatif nécessaire -__[i]/vmode(vmode_sujet_indicatif)__ - ^ *(je|j’(?:en|y)|tu|ils?|elles?|on|nous|vous) +({w_2}) @@*,$ - <<- morphex(\2, ":S", ":[GIK]") and not re.search("^e(?:usse|û[mt]es|ût)", \2) - -2>> =suggVerbMode(@, ":I", \1) # Ce verbe ne devrait pas être au subjonctif. -__[i]/vmode(vmode_j_indicatif)__ - ^ *j’({w_2}) @@$ - <<- morphex(\1, ":S", ":[GIK]") and \1 != "eusse" -1>> =suggVerbMode(@, ":I", "je") # Ce verbe ne devrait pas être au subjonctif. +__vmode_sujet_indicatif__ + [je|tu|il|ils|elle|elles|on|nous|vous] @:S¬:[GIK]|V0a.*:Sq + <<- /vmode/ -3>> =suggVerbMode(\3, ":I", \2) # Ce verbe ne devrait pas être au subjonctif. + + j’ @:S¬:[GIK]|V0a.*:Sq:1s + <<- /vmode/ -3>> =suggVerbMode(\3, ":I", "je") # Ce verbe ne devrait pas être au subjonctif. + + j’ [en|y] @:S¬:[GIK]|V0a.*:Sq + <<- /vmode/ -4>> =suggVerbMode(\4, ":I", "je") # Ce verbe ne devrait pas être au subjonctif. TEST: Il {{ait}} parti. TEST: Il en {{conclue}} qu’il a eu raison. TEST: j’en {{aie}} marre TEST: j’{{aie}} faim # Après que + indicatif -__[i]/vmode(vmode_après_que_indicatif)__ - après que? ({w_2}) +({w_2}) @@w,$ - <<- morph(\1, ":(?:Os|M)", False) and (morphex(\2, ":V.*:S", ":[GI]") or morph(\2, ":V0e.*:S", False)) - -2>> =suggVerbMode(@, ":I", \1) - # Après « après que », le verbe ne s’emploie pas au subjonctif mais à l’indicatif, si l’action s’est déroulée de façon certaine. +__vmode_après_que_indicatif__ + après [que|qu’|qu] @:(?:Os|M) @:V.*:S¬:[GI] + après [que|qu’|qu] @:(?:Os|M) @:V0e.*:S + <<- /vmode/ -4>> =suggVerbMode(\4, ":I", \3) # Après « après que », le verbe ne s’emploie pas au subjonctif mais à l’indicatif, si l’action s’est déroulée de façon certaine. TEST: Après qu’il {{ait}} allé TEST: Après que Paul {{ait}} mangé son repas. TEST: Après qu’il {{soit}} parti, il plut. # Quand/lorsque + indicatif -__[i]/vmode(vmode_quand_lorsque_indicatif)__ - (?:quand|lorsque?) ({w_2}) +({w_2}) @@w,$ - <<- morph(\1, ":(?:Os|M)", False) and (morphex(\2, ":V.*:S", ":[GI]") or morph(\2, ":V0e.*:S", False)) - -2>> =suggVerbMode(@, ":I", \1) - # Après « quand » ou « lorsque », le verbe ne s’emploie pas au subjonctif mais à l’indicatif. +__vmode_quand_lorsque_indicatif__ + [quand|lorsque|lorsqu’|lorsqu] @:(?:Os|M) @:V.*:S¬:[GI] + [quand|lorsque|lorsqu’|lorsqu] @:(?:Os|M) @:V0e.*:S + <<- /vmode/ -3>> =suggVerbMode(\3, ":I", \2) # Après « quand » ou « lorsque », le verbe ne s’emploie pas au subjonctif mais à l’indicatif. TEST: quand elle {{rencontrât}} son créateur TEST: lorsqu’il y {{eût}} du grabuge, nous montâmes tous sur le pont. + +@@@@ +@@@@END_GRAPH +@@@@ + + + +@@@@ +@@@@ +@@@@ +@@@@ +@@@@GRAPH: test +@@@@ +@@@@ +@@@@ +@@@@ + +__code_legacy__ + legacy code + code legacy + <<- -1:2>> code hérité|code reliquat # \1 \2. Anglicisme superflu. + +TEST: c’est du {{legacy code}}. +TEST: ce {{code legacy}} est un cauchemar + + +__être_en_xxxx__ + [>être|>rester|>demeurer] an [désaccord|accord] + <<- -2>> en # Confusion. Un an = une année. Pour la préposition, écrivez “en”. + +TEST: Je suis {{an}} désaccord avec lui. + + +__faire_plaisir__ + >faire plaisirs + <<- -2>> plaisir # Faire plaisir : dans cette locution, “plaisir” doit être au singulier. + <<- ~2>> * + +TEST: Ça me fait {{plaisirs}}. + + + +@@@@ +@@@@END_GRAPH +@@@@ !! !! !! @@ -16555,13 +17721,11 @@ TEST: Acaste, TEST: Clitandre, marquis TEST: Basque, valet de Célimène, TEST: Un garde de la maréchaussée de France, TEST: Dubois, valet d’Alceste. - TEST: La scène se passe à Paris, dans la maison de Célimène. - TEST: ACTE I TEST: SCÈNE PREMIÈRE. Philinte, Alceste. TEST: PHILINTE. Qu’est-ce donc ? Qu’avez-vous ? TEST: ALCESTE, assis. Laissez-moi, je vous prie. TEST: PHILINTE. Mais encor, dites-moi, quelle bizarrerie… Index: grammalecte-cli.py ================================================================== --- grammalecte-cli.py +++ grammalecte-cli.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 + +""" +Grammalecte CLI (command line interface) +""" import sys import os.path import argparse import json @@ -71,10 +75,11 @@ sText, lLineSet = txt.createParagraphWithLines(lLine) yield iParagraph, sText, lLineSet def output (sText, hDst=None): + "write in the console or in a file if not null" if not hDst: echo(sText, end="") else: hDst.write(sText) @@ -92,10 +97,11 @@ print("# Error: file <" + spf + "> not found.") return None def main (): + "launch the CLI (command line interface)" xParser = argparse.ArgumentParser() xParser.add_argument("-f", "--file", help="parse file (UTF-8 required!) [on Windows, -f is similar to -ff]", type=str) xParser.add_argument("-ff", "--file_to_file", help="parse file (UTF-8 required!) and create a result file (*.res.txt)", type=str) xParser.add_argument("-owe", "--only_when_errors", help="display results only when there are errors", action="store_true") xParser.add_argument("-j", "--json", help="generate list of errors in JSON (only with option --file or --file_to_file)", action="store_true") @@ -232,14 +238,14 @@ elif sText.startswith("/++ "): for sRule in sText[3:].strip().split(): oGrammarChecker.gce.reactivateRule(sRule) echo("done") elif sText == "/debug" or sText == "/d": - xArgs.debug = not(xArgs.debug) + xArgs.debug = not xArgs.debug echo("debug mode on" if xArgs.debug else "debug mode off") elif sText == "/textformatter" or sText == "/tf": - xArgs.textformatter = not(xArgs.textformatter) + xArgs.textformatter = not xArgs.textformatter echo("textformatter on" if xArgs.debug else "textformatter off") elif sText == "/help" or sText == "/h": echo(_HELP) elif sText == "/lopt" or sText == "/lo": oGrammarChecker.gce.displayOptions("fr") @@ -253,16 +259,16 @@ # reload (todo) pass else: for sParagraph in txt.getParagraph(sText): if xArgs.textformatter: - sText = oTextFormatter.formatText(sText) - sRes = oGrammarChecker.generateParagraph(sText, bEmptyIfNoErrors=xArgs.only_when_errors, nWidth=xArgs.width, bDebug=xArgs.debug) + sText = oTextFormatter.formatText(sParagraph) + sRes = oGrammarChecker.generateParagraph(sParagraph, bEmptyIfNoErrors=xArgs.only_when_errors, nWidth=xArgs.width, bDebug=xArgs.debug) if sRes: echo("\n" + sRes) else: echo("\nNo error found.") sText = _getText(sInputText) if __name__ == '__main__': main() Index: grammalecte-server.py ================================================================== --- grammalecte-server.py +++ grammalecte-server.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 -import sys -import os.path -import argparse +""" +GRAMMALECTE SERVER +""" + import json import traceback import configparser import time @@ -19,11 +20,11 @@ - +

Grammalecte · Serveur

INFORMATIONS

@@ -49,11 +50,11 @@

Remise à zéro de ses options

[adresse_serveur]:8080/reset_options/fr (POST)

TEST

- +

Analyse

Texte à analyser :

@@ -91,10 +92,11 @@ I'm doomed, but you are not. You can get out of here. """ def getServerOptions (): + "load server options in , returns server options as dictionary" xConfig = configparser.SafeConfigParser() try: xConfig.read("grammalecte-server-options._global.ini") dOpt = xConfig._sections['options'] except: @@ -101,11 +103,12 @@ echo("Options file [grammalecte-server-options._global.ini] not found or not readable") exit() return dOpt -def getConfigOptions (sLang): +def getLangConfigOptions (sLang): + "load options for language , returns grammar checker options as dictionary" xConfig = configparser.SafeConfigParser() try: xConfig.read("grammalecte-server-options." + sLang + ".ini") except: echo("Options file [grammalecte-server-options." + sLang + ".ini] not found or not readable") @@ -118,10 +121,11 @@ exit() return dGCOpt def genUserId (): + "generator: create a user id" i = 0 while True: yield str(i) i += 1 @@ -135,11 +139,11 @@ oTextFormatter = oGrammarChecker.getTextFormatter() gce = oGrammarChecker.getGCEngine() echo("Grammalecte v{}".format(gce.version)) dServerOptions = getServerOptions() - dGCOptions = getConfigOptions("fr") + dGCOptions = getLangConfigOptions("fr") if dGCOptions: gce.setOptions(dGCOptions) dServerGCOptions = gce.getOptions() echo("Grammar options:\n" + " | ".join([ k + ": " + str(v) for k, v in sorted(dServerGCOptions.items()) ])) dUser = {} @@ -148,25 +152,28 @@ app = Bottle() # GET @app.route("/") def mainPage (): + "show main page" if dServerOptions.get("testpage", False) == "True": return HOMEPAGE #return template("main", {}) return SADLIFEOFAMACHINE @app.route("/get_options/fr") def listOptions (): + "show language options as JSON string" sUserId = request.cookies.user_id dOptions = dUser[sUserId]["gc_options"] if sUserId and sUserId in dUser else dServerGCOptions return '{ "values": ' + json.dumps(dOptions) + ', "labels": ' + json.dumps(gce.getOptionsLabels("fr"), ensure_ascii=False) + ' }' # POST @app.route("/gc_text/fr", method="POST") def gcText (): + "parse text sent via POST, show result as a JSON string" #if len(lang) != 2 or lang != "fr": # abort(404, "No grammar checker available for lang “" + str(lang) + "”") bComma = False dOptions = None sError = "" @@ -195,10 +202,11 @@ sJSON += "\n]}\n" return sJSON @app.route("/set_options/fr", method="POST") def setOptions (): + "change options for user_id, returns options as a JSON string" if request.forms.options: sUserId = request.cookies.user_id if request.cookies.user_id else next(userGenerator) dOptions = dUser[sUserId]["gc_options"] if sUserId in dUser else dict(dServerGCOptions) try: dOptions.update(json.loads(request.forms.options)) @@ -210,16 +218,18 @@ return '{"error": "options not registered"}' return '{"error": "no options received"}' @app.route("/reset_options/fr", method="POST") def resetOptions (): + "erase options stored for user_id" if request.cookies.user_id and request.cookies.user_id in dUser: del dUser[request.cookies.user_id] return "done" @app.route("/format_text/fr", method="POST") def formatText (): + "returns text modified via the text formatter" return oTextFormatter.formatText(request.forms.text) #@app.route('/static/') #def server_static (filepath): # return static_file(filepath, root='./views/static') @@ -234,19 +244,19 @@ nNowMinusNHours = int(time.time()) - (int(request.forms.hours) * 60 * 60) for nUserId, dValue in dUser.items(): if dValue["time"] < nNowMinusNHours: del dUser[nUserId] return "done" - else: - return "no" + return "no" except: traceback.print_exc() return "error" # ERROR @app.error(404) def error404 (error): + "show error when error 404" return 'Error 404.
' + str(error) run(app, \ host=dServerOptions.get('host', 'localhost'), \ port=int(dServerOptions.get('port', 8080))) Index: graphspell-js/ibdawg.js ================================================================== --- graphspell-js/ibdawg.js +++ graphspell-js/ibdawg.js @@ -512,11 +512,11 @@ let iAddr2 = this._convBytesToInteger(this.byDic.slice(iEndArcAddr, iEndArcAddr+this.nBytesNodeAddress)); let nRawArc2 = 0; while (!(nRawArc2 & this._lastArcMask)) { let iEndArcAddr2 = iAddr2 + this.nBytesArc; nRawArc2 = this._convBytesToInteger(this.byDic.slice(iAddr2, iEndArcAddr2)); - l.push(sStem + " " + this.lArcVal[nRawArc2 & this._arcMask]); + l.push(sStem + "/" + this.lArcVal[nRawArc2 & this._arcMask]); iAddr2 = iEndArcAddr2+this.nBytesNodeAddress; } } iAddr = iEndArcAddr + this.nBytesNodeAddress; } Index: graphspell-js/spellchecker.js ================================================================== --- graphspell-js/spellchecker.js +++ graphspell-js/spellchecker.js @@ -41,10 +41,14 @@ this.oPersonalDic = this._loadDictionary(personalDic, sPath); this.bExtendedDic = Boolean(this.oExtendedDic); this.bCommunityDic = Boolean(this.oCommunityDic); this.bPersonalDic = Boolean(this.oPersonalDic); this.oTokenizer = null; + // storage + this.bStorage = false; + this._dMorphologies = new Map(); // key: flexion, value: list of morphologies + this._dLemmas = new Map(); // key: flexion, value: list of lemmas } _loadDictionary (dictionary, sPath="", bNecessary=false) { // returns an IBDAWG object if (!dictionary) { @@ -132,10 +136,26 @@ deactivatePersonalDictionary () { this.bPersonalDic = false; } + + // Storage + + activateStorage () { + this.bStorage = true; + } + + deactivateStorage () { + this.bStorage = false; + } + + clearStorage () { + this._dLemmas.clear(); + this._dMorphologies.clear(); + } + // parse text functions parseParagraph (sText) { if (!this.oTokenizer) { @@ -203,21 +223,40 @@ return false; } getMorph (sWord) { // retrieves morphologies list, different casing allowed - let lResult = this.oMainDic.getMorph(sWord); + if (this.bStorage && this._dMorphologies.has(sWord)) { + return this._dMorphologies.get(sWord); + } + let lMorph = this.oMainDic.getMorph(sWord); if (this.bExtendedDic) { - lResult.push(...this.oExtendedDic.getMorph(sWord)); + lMorph.push(...this.oExtendedDic.getMorph(sWord)); } if (this.bCommunityDic) { - lResult.push(...this.oCommunityDic.getMorph(sWord)); + lMorph.push(...this.oCommunityDic.getMorph(sWord)); } if (this.bPersonalDic) { - lResult.push(...this.oPersonalDic.getMorph(sWord)); + lMorph.push(...this.oPersonalDic.getMorph(sWord)); + } + if (this.bStorage) { + this._dMorphologies.set(sWord, lMorph); + this._dLemmas.set(sWord, Array.from(new Set(this.getMorph(sWord).map((sMorph) => { return sMorph.slice(1, sMorph.indexOf("/")); })))); + //console.log(sWord, this._dLemmas.get(sWord)); + } + return lMorph; + } + + getLemma (sWord) { + // retrieves lemmas + if (this.bStorage) { + if (!this._dLemmas.has(sWord)) { + this.getMorph(sWord); + } + return this._dLemmas.get(sWord); } - return lResult; + return Array.from(new Set(this.getMorph(sWord).map((sMorph) => { return sMorph.slice(1, sMorph.indexOf("/")); }))); } * suggest (sWord, nSuggLimit=10) { // generator: returns 1, 2 or 3 lists of suggestions yield this.oMainDic.suggest(sWord, nSuggLimit); Index: graphspell-js/tokenizer.js ================================================================== --- graphspell-js/tokenizer.js +++ graphspell-js/tokenizer.js @@ -16,37 +16,39 @@ "default": [ [/^[   \t]+/, 'SPACE'], [/^\/(?:~|bin|boot|dev|etc|home|lib|mnt|opt|root|sbin|tmp|usr|var|Bureau|Documents|Images|Musique|Public|Téléchargements|Vidéos)(?:\/[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_.()-]+)*/, 'FOLDERUNIX'], [/^[a-zA-Z]:\\(?:Program Files(?: \(x86\)|)|[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st.()]+)(?:\\[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_.()-]+)*/, 'FOLDERWIN'], - [/^[,.;:!?…«»“”‘’"(){}\[\]/·–—]+/, 'SEPARATOR'], + [/^[,.;:!?…«»“”‘’"(){}\[\]·–—]/, 'SEPARATOR'], [/^[A-Z][.][A-Z][.](?:[A-Z][.])*/, 'ACRONYM'], [/^(?:https?:\/\/|www[.]|[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_-]+[@.][a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_-]{2,}[@.])[a-zA-Z0-9][a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_.\/?&!%=+*"'@$#-]+/, 'LINK'], [/^[#@][a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_-]+/, 'TAG'], [/^<[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+.*?>|<\/[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+ *>/, 'HTML'], [/^\[\/?[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+\]/, 'PSEUDOHTML'], [/^&\w+;(?:\w+;|)/, 'HTMLENTITY'], [/^\d\d?h\d\d\b/, 'HOUR'], [/^-?\d+(?:[.,]\d+|)/, 'NUM'], + [/^[%‰+=*/<>⩾⩽-]/, 'SIGN'], [/^[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+(?:[’'`-][a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+)*/, 'WORD'] ], "fr": [ [/^[   \t]+/, 'SPACE'], [/^\/(?:~|bin|boot|dev|etc|home|lib|mnt|opt|root|sbin|tmp|usr|var|Bureau|Documents|Images|Musique|Public|Téléchargements|Vidéos)(?:\/[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_.()-]+)*/, 'FOLDERUNIX'], [/^[a-zA-Z]:\\(?:Program Files(?: \(x86\)|)|[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st.()]+)(?:\\[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_.()-]+)*/, 'FOLDERWIN'], - [/^[,.;:!?…«»“”‘’"(){}\[\]/·–—]+/, 'SEPARATOR'], + [/^[,.;:!?…«»“”‘’"(){}\[\]·–—]/, 'SEPARATOR'], [/^[A-Z][.][A-Z][.](?:[A-Z][.])*/, 'ACRONYM'], [/^(?:https?:\/\/|www[.]|[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_-]+[@.][a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_-]{2,}[@.])[a-zA-Z0-9][a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_.\/?&!%=+*"'@$#-]+/, 'LINK'], [/^[#@][a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st_-]+/, 'TAG'], [/^<[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+.*?>|<\/[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+ *>/, 'HTML'], [/^\[\/?[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+\]/, 'PSEUDOHTML'], [/^&\w+;(?:\w+;|)/, 'HTMLENTITY'], [/^(?:l|d|n|m|t|s|j|c|ç|lorsqu|puisqu|jusqu|quoiqu|qu)['’`]/i, 'ELPFX'], [/^\d\d?[hm]\d\d\b/, 'HOUR'], - [/^\d+(?:er|nd|e|de|ième|ème|eme)s?\b/, 'ORDINAL'], + [/^\d+(?:ers?|nds?|es?|des?|ièmes?|èmes?|emes?|ᵉʳˢ?|ⁿᵈˢ?|ᵉˢ?|ᵈᵉˢ?)\b/, 'ORDINAL'], [/^-?\d+(?:[.,]\d+|)/, 'NUM'], + [/^[%‰+=*/<>⩾⩽-]/, 'SIGN'], [/^[a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+(?:[’'`-][a-zA-Zà-öÀ-Ö0-9ø-ÿØ-ßĀ-ʯfi-st]+)*/, 'WORD'] ] }; @@ -60,36 +62,32 @@ this.aRules = aTkzPatterns[this.sLang]; } * genTokens (sText) { let m; - let i = 0; + let iNext = 0; while (sText) { - let nCut = 1; + let iCut = 1; + let iToken = 0; for (let [zRegex, sType] of this.aRules) { try { if ((m = zRegex.exec(sText)) !== null) { - if (sType == 'SEPARATOR') { - for (let c of m[0]) { - yield { "sType": sType, "sValue": c, "nStart": i, "nEnd": i + m[0].length } - } - } else { - yield { "sType": sType, "sValue": m[0], "nStart": i, "nEnd": i + m[0].length } - } - nCut = m[0].length; + iToken += 1; + yield { "i": iToken, "sType": sType, "sValue": m[0], "nStart": iNext, "nEnd": iNext + m[0].length } + iCut = m[0].length; break; } } catch (e) { helpers.logerror(e); } } - i += nCut; - sText = sText.slice(nCut); + iNext += iCut; + sText = sText.slice(iCut); } } } if (typeof(exports) !== 'undefined') { exports.Tokenizer = Tokenizer; } Index: graphspell/__init__.py ================================================================== --- graphspell/__init__.py +++ graphspell/__init__.py @@ -1,2 +1,11 @@ + +""" +SPELLCHECKER +using a Direct Acyclic Word Graph +with a transducer to retrieve +- lemma of words +- morphologies +with a spell suggestion mechanism +""" from .spellchecker import * Index: graphspell/char_player.py ================================================================== --- graphspell/char_player.py +++ graphspell/char_player.py @@ -1,7 +1,9 @@ -# list of similar chars -# useful for suggestion mechanism +""" +List of similar chars +useful for suggestion mechanism +""" import re import unicodedata @@ -8,10 +10,11 @@ _xTransCharsForSpelling = str.maketrans({ 'ſ': 's', 'ffi': 'ffi', 'ffl': 'ffl', 'ff': 'ff', 'ſt': 'ft', 'fi': 'fi', 'fl': 'fl', 'st': 'st' }) def spellingNormalization (sWord): + "nomalization NFC and removing ligatures" return unicodedata.normalize("NFC", sWord.translate(_xTransCharsForSpelling)) _xTransCharsForSimplification = str.maketrans({ 'à': 'a', 'é': 'e', 'î': 'i', 'ô': 'o', 'û': 'u', 'ÿ': 'i', "y": "i", @@ -19,11 +22,11 @@ 'ä': 'a', 'ê': 'e', 'í': 'i', 'ó': 'o', 'ü': 'u', 'ý': 'i', 'á': 'a', 'ë': 'e', 'ì': 'i', 'ò': 'o', 'ú': 'u', 'ỳ': 'i', 'ā': 'a', 'ē': 'e', 'ī': 'i', 'ō': 'o', 'ū': 'u', 'ȳ': 'i', 'ç': 'c', 'ñ': 'n', 'k': 'q', 'w': 'v', 'œ': 'oe', 'æ': 'ae', - 'ſ': 's', 'ffi': 'ffi', 'ffl': 'ffl', 'ff': 'ff', 'ſt': 'ft', 'fi': 'fi', 'fl': 'fl', 'st': 'st', + 'ſ': 's', 'ffi': 'ffi', 'ffl': 'ffl', 'ff': 'ff', 'ſt': 'ft', 'fi': 'fi', 'fl': 'fl', 'st': 'st', }) def simplifyWord (sWord): "word simplication before calculating distance between words" sWord = sWord.lower().translate(_xTransCharsForSimplification) @@ -92,11 +95,11 @@ "f": "fF", "F": "Ff", "g": "gGjJĵĴ", "G": "GgJjĴĵ", - + "h": "hH", "H": "Hh", "i": "iIîÎïÏyYíÍìÌīĪÿŸ", "I": "IiÎîÏïYyÍíÌìĪīŸÿ", @@ -237,10 +240,11 @@ "Z": ("SS", "ZH"), } def get1toXReplacement (cPrev, cCur, cNext): + "return tuple of replacements for " if cCur in aConsonant and (cPrev in aConsonant or cNext in aConsonant): return () return d1toX.get(cCur, ()) Index: graphspell/dawg.py ================================================================== --- graphspell/dawg.py +++ graphspell/dawg.py @@ -1,15 +1,16 @@ #!python3 -# FSA DICTIONARY BUILDER -# -# by Olivier R. -# License: MPL 2 -# -# This tool encodes lexicon into an indexable binary dictionary -# Input files MUST be encoded in UTF-8. - +""" +FSA DICTIONARY BUILDER + +by Olivier R. +License: MPL 2 + +This tool encodes lexicon into an indexable binary dictionary +Input files MUST be encoded in UTF-8. +""" import sys import os import collections import json @@ -21,10 +22,11 @@ from .progressbar import ProgressBar def readFile (spf): + "generator: read file and return for each line a list of elements separated by a tabulation." print(" < Read lexicon: " + spf) if os.path.isfile(spf): with open(spf, "r", encoding="utf-8") as hSrc: for sLine in hSrc: sLine = sLine.strip() @@ -97,23 +99,23 @@ nTag += 1 dTagOccur[sTag] = dTagOccur.get(sTag, 0) + 1 aEntry.add((sFlex, dAff[sAff], dTag[sTag])) if not aEntry: raise ValueError("# Error. Empty lexicon") - + # Preparing DAWG print(" > Preparing list of words") print(" Filter: " + (sSelectFilterRegex or "[None]")) lVal = lChar + lAff + lTag lWord = [ [dChar[c] for c in sFlex] + [iAff+nChar] + [iTag+nChar+nAff] for sFlex, iAff, iTag in aEntry ] aEntry = None - + # Dictionary of arc values occurrency, to sort arcs of each node dValOccur = dict( [ (dChar[c], dCharOccur[c]) for c in dChar ] \ + [ (dAff[aff]+nChar, dAffOccur[aff]) for aff in dAff ] \ + [ (dTag[tag]+nChar+nAff, dTagOccur[tag]) for tag in dTag ] ) - + self.sFileName = src if type(src) is str else "[None]" self.sLangCode = sLangCode self.sLangName = sLangName self.sDicName = sDicName self.nEntry = len(lWord) @@ -132,15 +134,15 @@ self.nArcVal = len(lVal) self.nTag = self.nArcVal - self.nChar - nAff self.cStemming = cStemming if cStemming == "A": self.funcStemming = st.changeWordWithAffixCode - elif cStemming == "S": + elif cStemming == "S": self.funcStemming = st.changeWordWithSuffixCode else: self.funcStemming = st.noStemming - + # build lWord.sort() oProgBar = ProgressBar(0, len(lWord)) for aEntry in lWord: self.insert(aEntry) @@ -147,20 +149,21 @@ oProgBar.increment(1) oProgBar.done() self.finish() self.countNodes() self.countArcs() - self.sortNodes() # version 2 and 3 + self.sortNodes() # version 2 and 3 self.sortNodeArcs(dValOccur) #self.sortNodeArcs2 (self.oRoot, "") self.displayInfo() # BUILD DAWG def insert (self, aEntry): + "insert a new entry (insertion must be made in alphabetical order)." if aEntry < self.aPreviousEntry: sys.exit("# Error: Words must be inserted in alphabetical order.") - + # find common prefix between word and previous word nCommonPrefix = 0 for i in range(min(len(aEntry), len(self.aPreviousEntry))): if aEntry[i] != self.aPreviousEntry[i]: break @@ -179,11 +182,11 @@ iChar = nCommonPrefix for c in aEntry[nCommonPrefix:]: oNextNode = DawgNode() oNode.arcs[c] = oNextNode self.lUncheckedNodes.append((oNode, c, oNextNode)) - if iChar == (len(aEntry) - 2): + if iChar == (len(aEntry) - 2): oNode.final = True iChar += 1 oNode = oNextNode oNode.final = True self.aPreviousEntry = aEntry @@ -203,54 +206,61 @@ # add the state to the minimized nodes. self.lMinimizedNodes[oChildNode] = oChildNode self.lUncheckedNodes.pop() def countNodes (self): + "count the number of nodes of the whole word graph" self.nNode = len(self.lMinimizedNodes) def countArcs (self): + "count the number of arcs in the whole word graph" self.nArc = 0 for oNode in self.lMinimizedNodes: self.nArc += len(oNode.arcs) - + def sortNodeArcs (self, dValOccur): + "sort arcs of each node according to " print(" > Sort node arcs") self.oRoot.sortArcs(dValOccur) for oNode in self.lMinimizedNodes: oNode.sortArcs(dValOccur) - + def sortNodeArcs2 (self, oNode, cPrevious=""): + "sort arcs of each node depending on the previous char" # recursive function dCharOccur = getCharOrderAfterChar(cPrevious) if dCharOccur: oNode.sortArcs2(dCharOccur, self.lArcVal) for nArcVal, oNextNode in oNode.arcs.items(): self.sortNodeArcs2(oNextNode, self.lArcVal[nArcVal]) def sortNodes (self): + "sort nodes" print(" > Sort nodes") for oNode in self.oRoot.arcs.values(): self._parseNodes(oNode) - + def _parseNodes (self, oNode): # Warning: recursive method if oNode.pos > 0: return oNode.setPos() self.lSortedNodes.append(oNode) for oNextNode in oNode.arcs.values(): - self._parseNodes(oNextNode) - + self._parseNodes(oNextNode) + def lookup (self, sWord): + "return True if is within the word graph (debugging)" oNode = self.oRoot for c in sWord: if self.dChar.get(c, '') not in oNode.arcs: return False oNode = oNode.arcs[self.dChar[c]] return oNode.final def morph (self, sWord): + "return a string of the morphologies of (debugging)" oNode = self.oRoot for c in sWord: if self.dChar.get(c, '') not in oNode.arcs: return '' oNode = oNode.arcs[self.dChar[c]] @@ -265,10 +275,11 @@ s += "]" return s return '' def displayInfo (self): + "display informations about the word graph" print(" * {:<12} {:>16,}".format("Entries:", self.nEntry)) print(" * {:<12} {:>16,}".format("Characters:", self.nChar)) print(" * {:<12} {:>16,}".format("Affixes:", self.nAff)) print(" * {:<12} {:>16,}".format("Tags:", self.nTag)) print(" * {:<12} {:>16,}".format("Arc values:", self.nArcVal)) @@ -275,10 +286,11 @@ print(" * {:<12} {:>16,}".format("Nodes:", self.nNode)) print(" * {:<12} {:>16,}".format("Arcs:", self.nArc)) print(" * {:<12} {:>16}".format("Stemming:", self.cStemming + "FX")) def getArcStats (self): + "return a string with statistics about nodes and arcs" d = {} for oNode in self.lMinimizedNodes: n = len(oNode.arcs) d[n] = d.get(n, 0) + 1 s = " * Nodes:\n" @@ -285,10 +297,11 @@ for n in d: s = s + " {:>9} nodes have {:>3} arcs\n".format(d[n], n) return s def writeInfo (self, sPathFile): + "write informations in file " print(" > Write informations") with open(sPathFile, 'w', encoding='utf-8', newline="\n") as hDst: hDst.write(self.getArcStats()) hDst.write("\n * Values:\n") for i, s in enumerate(self.lArcVal): @@ -394,10 +407,11 @@ if self.lSortedNodes[i].size != nSize: self.lSortedNodes[i].size = nSize bEnd = False def getBinaryAsJSON (self, nCompressionMethod=1, bBinaryDictAsHexString=True): + "return a JSON string containing all necessary data of the dictionary (compressed as a binary string)" self._calculateBinary(nCompressionMethod) byDic = b"" if nCompressionMethod == 1: byDic = self.oRoot.convToBytes1(self.nBytesArc, self.nBytesNodeAddress) for oNode in self.lMinimizedNodes: @@ -436,10 +450,11 @@ # https://github.com/mozilla/addons-linter/issues/1361 "sByDic": byDic.hex() if bBinaryDictAsHexString else [ e for e in byDic ] } def writeAsJSObject (self, spfDst, nCompressionMethod, bInJSModule=False, bBinaryDictAsHexString=True): + "write a file (JSON or JS module) with all the necessary data" if not spfDst.endswith(".json"): spfDst += "."+str(nCompressionMethod)+".json" with open(spfDst, "w", encoding="utf-8", newline="\n") as hDst: if bInJSModule: hDst.write('// JavaScript\n// Generated data (do not edit)\n\n"use strict";\n\nconst dictionary = ') @@ -447,17 +462,19 @@ if bInJSModule: hDst.write(";\n\nexports.dictionary = dictionary;\n") def writeBinary (self, sPathFile, nCompressionMethod, bDebug=False): """ + Save as a binary file. + Format of the binary indexable dictionary: Each section is separated with 4 bytes of \0 - + - Section Header: /grammalecte-fsa/[compression method] * compression method is an ASCII string - + - Section Informations: /[lang code] /[lang name] /[dictionary name] /[date creation] @@ -472,14 +489,14 @@ /[stemming code] * "S" means stems are generated by /suffix_code/, "A" means they are generated by /affix_code/ See defineSuffixCode() and defineAffixCode() for details. "N" means no stemming - + - Section Values: * a list of strings encoded in binary from utf-8, each value separated with a tabulation - + - Section Word Graph (nodes / arcs) * A list of nodes which are a list of arcs with an address of the next node. See DawgNode.convToBytes() for details. """ self._calculateBinary(nCompressionMethod) @@ -520,30 +537,32 @@ def _writeNodes (self, sPathFile, nCompressionMethod): "for debugging only" print(" > Write nodes") with open(sPathFile+".nodes."+str(nCompressionMethod)+".txt", 'w', encoding='utf-8', newline="\n") as hDst: if nCompressionMethod == 1: - hDst.write(self.oRoot.getTxtRepr1(self.nBytesArc, self.nBytesNodeAddress, self.lArcVal)+"\n") + hDst.write(self.oRoot.getTxtRepr1(self.nBytesArc, self.lArcVal)+"\n") #hDst.write( ''.join( [ "%02X " % z for z in self.oRoot.convToBytes1(self.nBytesArc, self.nBytesNodeAddress) ] ).strip() ) for oNode in self.lMinimizedNodes: - hDst.write(oNode.getTxtRepr1(self.nBytesArc, self.nBytesNodeAddress, self.lArcVal)+"\n") + hDst.write(oNode.getTxtRepr1(self.nBytesArc, self.lArcVal)+"\n") if nCompressionMethod == 2: - hDst.write(self.oRoot.getTxtRepr2(self.nBytesArc, self.nBytesNodeAddress, self.lArcVal)+"\n") + hDst.write(self.oRoot.getTxtRepr2(self.nBytesArc, self.lArcVal)+"\n") for oNode in self.lSortedNodes: - hDst.write(oNode.getTxtRepr2(self.nBytesArc, self.nBytesNodeAddress, self.lArcVal)+"\n") + hDst.write(oNode.getTxtRepr2(self.nBytesArc, self.lArcVal)+"\n") if nCompressionMethod == 3: - hDst.write(self.oRoot.getTxtRepr3(self.nBytesArc, self.nBytesNodeAddress, self.nBytesOffset, self.lArcVal)+"\n") + hDst.write(self.oRoot.getTxtRepr3(self.nBytesArc, self.nBytesOffset, self.lArcVal)+"\n") #hDst.write( ''.join( [ "%02X " % z for z in self.oRoot.convToBytes3(self.nBytesArc, self.nBytesNodeAddress, self.nBytesOffset) ] ).strip() ) for oNode in self.lSortedNodes: - hDst.write(oNode.getTxtRepr3(self.nBytesArc, self.nBytesNodeAddress, self.nBytesOffset, self.lArcVal)+"\n") + hDst.write(oNode.getTxtRepr3(self.nBytesArc, self.nBytesOffset, self.lArcVal)+"\n") class DawgNode: + """Node of the word graph""" + NextId = 0 NextPos = 1 # (version 2) - + def __init__ (self): self.i = DawgNode.NextId DawgNode.NextId += 1 self.final = False self.arcs = {} # key: arc value; value: a node @@ -551,19 +570,21 @@ self.pos = 0 # position in the binary dictionary (version 2) self.size = 0 # size of node in bytes (version 3) @classmethod def resetNextId (cls): + "set NextId to 0 " cls.NextId = 0 def setPos (self): # version 2 + "define a position for node (version 2)" self.pos = DawgNode.NextPos DawgNode.NextPos += 1 def __str__ (self): # Caution! this function is used for hashing and comparison! - sFinalChar = "1" if self.final else "0"; + sFinalChar = "1" if self.final else "0" l = [sFinalChar] for (key, node) in self.arcs.items(): l.append(str(key)) l.append(str(node.i)) return "_".join(l) @@ -576,36 +597,40 @@ # Used as a key in a python dictionary. # Nodes are equivalent if they have identical arcs, and each identical arc leads to identical states. return self.__str__() == other.__str__() def sortArcs (self, dValOccur): + "sort arcs of node according to " self.arcs = collections.OrderedDict(sorted(self.arcs.items(), key=lambda t: dValOccur.get(t[0], 0), reverse=True)) def sortArcs2 (self, dValOccur, lArcVal): + "sort arcs of each node depending on the previous char" self.arcs = collections.OrderedDict(sorted(self.arcs.items(), key=lambda t: dValOccur.get(lArcVal[t[0]], 0), reverse=True)) # VERSION 1 ===================================================================================================== def convToBytes1 (self, nBytesArc, nBytesNodeAddress): """ + Convert to bytes (method 1). + Node scheme: - Arc length is defined by nBytesArc - Address length is defined by nBytesNodeAddress - + | Arc | Address of next node | | | | - /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ + ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ + ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ + ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ [...] - /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ + ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ + ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ + ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ^ ^ - | | - | | - | \___ if 1, last arc of this node - \_____ if 1, this node is final (only on the first arc) + ┃ ┃ + ┃ ┃ + ┃ ┗━━━ if 1, last arc of this node + ┗━━━━━ if 1, this node is final (only on the first arc) """ nArc = len(self.arcs) nFinalNodeMask = 1 << ((nBytesArc*8)-1) nFinalArcMask = 1 << ((nBytesArc*8)-2) if len(self.arcs) == 0: @@ -621,12 +646,13 @@ if i == nArc: val = val | nFinalArcMask by += val.to_bytes(nBytesArc, byteorder='big') by += self.arcs[arc].addr.to_bytes(nBytesNodeAddress, byteorder='big') return by - - def getTxtRepr1 (self, nBytesArc, nBytesNodeAddress, lVal): + + def getTxtRepr1 (self, nBytesArc, lVal): + "return representation as string of node (method 1)" nArc = len(self.arcs) nFinalNodeMask = 1 << ((nBytesArc*8)-1) nFinalArcMask = 1 << ((nBytesArc*8)-2) s = "i{:_>10} -- #{:_>10}\n".format(self.i, self.addr) if len(self.arcs) == 0: @@ -642,28 +668,30 @@ return s # VERSION 2 ===================================================================================================== def convToBytes2 (self, nBytesArc, nBytesNodeAddress): """ + Convert to bytes (method 2). + Node scheme: - Arc length is defined by nBytesArc - Address length is defined by nBytesNodeAddress - + | Arc | Address of next node | | | | - /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ + ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ + ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ + ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ [...] - /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ + ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ + ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ + ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ^ ^ ^ - | | | - | | \_ if 1, caution, no address: next node is the following node - | \___ if 1, last arc of this node - \_____ if 1, this node is final (only on the first arc) + ┃ ┃ ┃ + ┃ ┃ ┗━━ if 1, caution, no address: next node is the following node + ┃ ┗━━━━ if 1, last arc of this node + ┗━━━━━━ if 1, this node is final (only on the first arc) """ nArc = len(self.arcs) nFinalNodeMask = 1 << ((nBytesArc*8)-1) nFinalArcMask = 1 << ((nBytesArc*8)-2) nNextNodeMask = 1 << ((nBytesArc*8)-3) @@ -684,12 +712,13 @@ by += val.to_bytes(nBytesArc, byteorder='big') else: by += val.to_bytes(nBytesArc, byteorder='big') by += self.arcs[arc].addr.to_bytes(nBytesNodeAddress, byteorder='big') return by - - def getTxtRepr2 (self, nBytesArc, nBytesNodeAddress, lVal): + + def getTxtRepr2 (self, nBytesArc, lVal): + "return representation as string of node (method 2)" nArc = len(self.arcs) nFinalNodeMask = 1 << ((nBytesArc*8)-1) nFinalArcMask = 1 << ((nBytesArc*8)-2) nNextNodeMask = 1 << ((nBytesArc*8)-3) s = "i{:_>10} -- #{:_>10}\n".format(self.i, self.addr) @@ -702,41 +731,43 @@ val = val | nFinalNodeMask if i == nArc: val = val | nFinalArcMask if (self.pos + 1) == self.arcs[arc].pos and self.i != 0: val = val | nNextNodeMask - s += " {:<20} {:0>16}\n".format(lVal[arc], bin(val)[2:], "") + s += " {:<20} {:0>16}\n".format(lVal[arc], bin(val)[2:]) else: s += " {:<20} {:0>16} i{:_>10} #{:_>10}\n".format(lVal[arc], bin(val)[2:], self.arcs[arc].i, self.arcs[arc].addr) return s # VERSION 3 ===================================================================================================== def convToBytes3 (self, nBytesArc, nBytesNodeAddress, nBytesOffset): """ + Convert to bytes (method 3). + Node scheme: - Arc length is defined by nBytesArc - Address length is defined by nBytesNodeAddress - Offset length is defined by nBytesOffset - + | Arc | Address of next node or offset to next node | | | | - /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ - |1|0|0| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ + ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ + ┃1┃0┃0┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ + ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ [...] - /---------------\ /---------------\ /---------------\ - |0|0|1| | | | | | | | | | | | | | | | | | | | | | | | Offsets are shorter than addresses - \---------------/ \---------------/ \---------------/ - /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ /---------------\ - |0|1|0| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ \---------------/ + ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ + ┃0┃0┃1┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ Offsets are shorter than addresses + ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ + ┃0┃1┃0┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ + ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ^ ^ ^ - | | | - | | \_ if 1, offset instead of address of next node - | \___ if 1, last arc of this node - \_____ if 1, this node is final (only on the first arc) + ┃ ┃ ┃ + ┃ ┃ ┗━━ if 1, offset instead of address of next node + ┃ ┗━━━━ if 1, last arc of this node + ┗━━━━━━ if 1, this node is final (only on the first arc) """ nArc = len(self.arcs) nFinalNodeMask = 1 << ((nBytesArc*8)-1) nFinalArcMask = 1 << ((nBytesArc*8)-2) nNextNodeMask = 1 << ((nBytesArc*8)-3) @@ -759,12 +790,13 @@ by += (self.arcs[arc].addr-self.addr).to_bytes(nBytesOffset, byteorder='big') else: by += val.to_bytes(nBytesArc, byteorder='big') by += self.arcs[arc].addr.to_bytes(nBytesNodeAddress, byteorder='big') return by - - def getTxtRepr3 (self, nBytesArc, nBytesNodeAddress, nBytesOffset, lVal): + + def getTxtRepr3 (self, nBytesArc, nBytesOffset, lVal): + "return representation as string of node (method 3)" nArc = len(self.arcs) nFinalNodeMask = 1 << ((nBytesArc*8)-1) nFinalArcMask = 1 << ((nBytesArc*8)-2) nNextNodeMask = 1 << ((nBytesArc*8)-3) nMaxOffset = (2 ** (nBytesOffset * 8)) - 1 @@ -794,20 +826,23 @@ "": {} } def addWordToCharDict (sWord): + "for each character of , count how many times it appears after the previous character, and store result in a <_dCharOrder>" cPrevious = "" for cChar in sWord: if cPrevious not in _dCharOrder: _dCharOrder[cPrevious] = {} _dCharOrder[cPrevious][cChar] = _dCharOrder[cPrevious].get(cChar, 0) + 1 cPrevious = cChar def getCharOrderAfterChar (cChar): + "return a dictionary of chars with number of times it appears after character " return _dCharOrder.get(cChar, None) def displayCharOrder (): + "display how many times each character appear after another one" for key, value in _dCharOrder.items(): print("[" + key + "]: ", ", ".join([ c+":"+str(n) for c, n in sorted(value.items(), key=lambda t: t[1], reverse=True) ])) Index: graphspell/echo.py ================================================================== --- graphspell/echo.py +++ graphspell/echo.py @@ -1,9 +1,13 @@ #!python3 -# The most boring yet indispensable function: print! +""" +The most boring yet indispensable function: print! +Because you can print on Windows console without being sure the script won’t crash… +Windows console don’t accept many characters. +""" import sys _CHARMAP = str.maketrans({ 'œ': 'ö', 'Œ': 'Ö', 'ʳ': "r", 'ᵉ': "e", '…': "_", \ @@ -22,8 +26,8 @@ if sys.platform != "win32": print(obj, sep=sep, end=end, file=file, flush=flush) return True try: print(str(obj).translate(_CHARMAP), sep=sep, end=end, file=file, flush=flush) - except: + except Exception: print(str(obj).encode('ascii', 'replace').decode('ascii', 'replace'), sep=sep, end=end, file=file, flush=flush) return True ADDED graphspell/fr.py Index: graphspell/fr.py ================================================================== --- /dev/null +++ graphspell/fr.py @@ -0,0 +1,46 @@ +""" +Default suggestion for French language +""" + +dSugg = { + "bcp": "beaucoup", + "ca": "ça", + "cad": "c’est-à-dire", + "cb": "combien|CB", + "cdlt": "cordialement", + "construirent": "construire|construisirent|construisent|construiront", + "càd": "c’est-à-dire", + "dc": "de|donc", + "email": "courriel|e-mail|émail", + "emails": "courriels|e-mails", + "Etes-vous": "Êtes-vous", + "Etiez-vous": "Étiez-vous", + "Etions-nous": "Étions-nous", + "parce-que": "parce que", + "pcq": "parce que", + "pd": "pendant", + "pdq": "pendant que", + "pdt": "pendant", + "pdtq": "pendant que", + "pk": "pourquoi", + "pq": "pourquoi|PQ", + "prq": "presque", + "prsq": "presque", + "qcq": "quiconque", + "qq": "quelque", + "qqch": "quelque chose", + "qqn": "quelqu’un", + "qqne": "quelqu’une", + "qqs": "quelques", + "qqunes": "quelques-unes", + "qquns": "quelques-uns", + "tdq": "tandis que", + "tj": "toujours", + "tjs": "toujours", + "tq": "tant que|tandis que", + "ts": "tous", + "tt": "tant|tout", + "tte": "toute", + "ttes": "toutes", + "y’a": "y a" +} Index: graphspell/ibdawg.py ================================================================== --- graphspell/ibdawg.py +++ graphspell/ibdawg.py @@ -1,8 +1,13 @@ #!python3 -import os +""" +INDEXABLE BINARY DIRECT ACYCLIC WORD GRAPH +Implementation of a spellchecker as a transducer (storing transformation code to get lemma and morphologies) +and a spell suggestion mechanim +""" + import traceback import pkgutil import re from functools import wraps import time @@ -19,10 +24,11 @@ def timethis (func): "decorator for the execution time" @wraps(func) def wrapper (*args, **kwargs): + "something to prevent pylint whining" fStart = time.time() result = func(*args, **kwargs) fEnd = time.time() print(func.__name__, fEnd - fStart) return result @@ -56,11 +62,11 @@ self.aSugg.add(sSugg) if nDist < self.nMinDist: self.nMinDist = nDist self.nDistLimit = min(self.nDistLimit, self.nMinDist+2) - def getSuggestions (self, nSuggLimit=10, nDistLimit=-1): + def getSuggestions (self, nSuggLimit=10): "return a list of suggestions" if self.dSugg[0]: # we sort the better results with the original word self.dSugg[0].sort(key=lambda sSugg: st.distanceDamerauLevenshtein(self.sWord, sSugg)) lRes = self.dSugg.pop(0) @@ -75,10 +81,11 @@ elif self.sWord[0:1].isupper(): lRes = list(map(lambda sSugg: sSugg[0:1].upper()+sSugg[1:], lRes)) # dont’ use <.istitle> return lRes[:nSuggLimit] def reset (self): + "clear data" self.aSugg.clear() self.dSugg.clear() class IBDAWG: @@ -147,11 +154,11 @@ raise ValueError("# Error. Unknown dictionary version: {}".format(self.by[17:18])) try: header, info, values, bdic = self.by.split(b"\0\0\0\0", 3) except Exception: raise Exception - + self.nCompressionMethod = int(self.by[17:18].decode("utf-8")) self.sHeader = header.decode("utf-8") self.lArcVal = values.decode("utf-8").split("\t") self.nArcVal = len(self.lArcVal) self.byDic = bdic @@ -182,10 +189,11 @@ self.__dict__.update(oJSON) self.byDic = binascii.unhexlify(self.sByDic) self.dCharVal = { v: k for k, v in self.dChar.items() } def getInfo (self): + "return string about the IBDAWG" return " Language: {0.sLangName} Lang code: {0.sLangCode} Dictionary name: {0.sDicName}" \ " Compression method: {0.nCompressionMethod:>2} Date: {0.sDate} Stemming: {0.cStemming}FX\n" \ " Arcs values: {0.nArcVal:>10,} = {0.nChar:>5,} characters, {0.nAff:>6,} affixes, {0.nTag:>6,} tags\n" \ " Dictionary: {0.nEntry:>12,} entries, {0.nNode:>11,} nodes, {0.nArc:>11,} arcs\n" \ " Address size: {0.nBytesNodeAddress:>1} bytes, Arc size: {0.nBytesArc:>1} bytes\n".format(self) @@ -194,35 +202,35 @@ "write IBDAWG as a JavaScript object in a JavaScript module" with open(spfDest, "w", encoding="utf-8", newline="\n") as hDst: if bInJSModule: hDst.write('// JavaScript\n// Generated data (do not edit)\n\n"use strict";\n\nconst dictionary = ') hDst.write(json.dumps({ - "sHeader": "/grammalecte-fsa/", - "sLangCode": self.sLangCode, - "sLangName": self.sLangName, - "sDicName": self.sDicName, - "sFileName": self.sFileName, - "sDate": self.sDate, - "nEntry": self.nEntry, - "nChar": self.nChar, - "nAff": self.nAff, - "nTag": self.nTag, - "cStemming": self.cStemming, - "dChar": self.dChar, - "nNode": self.nNode, - "nArc": self.nArc, - "nArcVal": self.nArcVal, - "lArcVal": self.lArcVal, - "nCompressionMethod": self.nCompressionMethod, - "nBytesArc": self.nBytesArc, - "nBytesNodeAddress": self.nBytesNodeAddress, - "nBytesOffset": self.nBytesOffset, - # JavaScript is a pile of shit, so Mozilla’s JS parser don’t like file bigger than 4 Mb! - # So, if necessary, we use an hexadecimal string, that we will convert later in Firefox’s extension. - # https://github.com/mozilla/addons-linter/issues/1361 - "sByDic": self.byDic.hex() if bBinaryDictAsHexString else [ e for e in self.byDic ] - }, ensure_ascii=False)) + "sHeader": "/grammalecte-fsa/", + "sLangCode": self.sLangCode, + "sLangName": self.sLangName, + "sDicName": self.sDicName, + "sFileName": self.sFileName, + "sDate": self.sDate, + "nEntry": self.nEntry, + "nChar": self.nChar, + "nAff": self.nAff, + "nTag": self.nTag, + "cStemming": self.cStemming, + "dChar": self.dChar, + "nNode": self.nNode, + "nArc": self.nArc, + "nArcVal": self.nArcVal, + "lArcVal": self.lArcVal, + "nCompressionMethod": self.nCompressionMethod, + "nBytesArc": self.nBytesArc, + "nBytesNodeAddress": self.nBytesNodeAddress, + "nBytesOffset": self.nBytesOffset, + # JavaScript is a pile of shit, so Mozilla’s JS parser don’t like file bigger than 4 Mb! + # So, if necessary, we use an hexadecimal string, that we will convert later in Firefox’s extension. + # https://github.com/mozilla/addons-linter/issues/1361 + "sByDic": self.byDic.hex() if bBinaryDictAsHexString else [ e for e in self.byDic ] + }, ensure_ascii=False)) if bInJSModule: hDst.write(";\n\nexports.dictionary = dictionary;\n") def isValidToken (self, sToken): "checks if is valid (if there is hyphens in , is split, each part is checked)" @@ -265,11 +273,11 @@ iAddr = 0 for c in sWord: if c not in self.dChar: return False iAddr = self._lookupArcNode(self.dChar[c], iAddr) - if iAddr == None: + if iAddr is None: return False return bool(int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask) def getMorph (self, sWord): "retrieves morphologies list, different casing allowed" @@ -344,17 +352,17 @@ self._suggest(oSuggResult, "", nMaxSwitch, nMaxDel, nMaxHardRepl, nMaxJump, nDist, nDeep+1, iAddr, sNewWord, True) # remove last char and go on for sRepl in cp.dFinal1.get(sRemain, ()): self._suggest(oSuggResult, sRepl, nMaxSwitch, nMaxDel, nMaxHardRepl, nMaxJump, nDist, nDeep+1, iAddr, sNewWord, True) #@timethis - def suggest2 (self, sWord, nMaxSugg=10): + def suggest2 (self, sWord, nSuggLimit=10): "returns a set of suggestions for " sWord = cp.spellingNormalization(sWord) sPfx, sWord, sSfx = cp.cut(sWord) oSuggResult = SuggResult(sWord) self._suggest2(oSuggResult) - aSugg = oSuggResult.getSuggestions() + aSugg = oSuggResult.getSuggestions(nSuggLimit) if sSfx or sPfx: # we add what we removed return list(map(lambda sSug: sPfx + sSug + sSfx, aSugg)) return aSugg @@ -407,21 +415,21 @@ "show the path taken by in the graph" sWord = cp.spellingNormalization(sWord) c1 = sWord[0:1] if sWord else " " iPos = -1 n = 0 - print(c1 + ": ", end="") + echo(c1 + ": ", end="") for c2, jAddr in self._getCharArcs(iAddr): - print(c2, end="") + echo(c2, end="") if c2 == sWord[0:1]: iNextNodeAddr = jAddr iPos = n n += 1 if not sWord: return if iPos >= 0: - print("\n "+ " " * iPos + "|") + echo("\n " + " " * iPos + "|") self.drawPath(sWord[1:], iNextNodeAddr) def getSimilarEntries (self, sWord, nSuggLimit=10): "return a list of tuples (similar word, stem, morphology)" if not sWord: @@ -469,29 +477,29 @@ iAddr = 0 for c in sWord: if c not in self.dChar: return [] iAddr = self._lookupArcNode(self.dChar[c], iAddr) - if iAddr == None: + if iAddr is None: return [] - if (int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask): + if int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask: l = [] nRawArc = 0 while not (nRawArc & self._lastArcMask): iEndArcAddr = iAddr + self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') nArc = nRawArc & self._arcMask if nArc > self.nChar: - # This value is not a char, this is a stemming code + # This value is not a char, this is a stemming code sStem = ">" + self.funcStemming(sWord, self.lArcVal[nArc]) # Now , we go to the next node and retrieve all following arcs values, all of them are tags iAddr2 = int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big') nRawArc2 = 0 while not (nRawArc2 & self._lastArcMask): iEndArcAddr2 = iAddr2 + self.nBytesArc nRawArc2 = int.from_bytes(self.byDic[iAddr2:iEndArcAddr2], byteorder='big') - l.append(sStem + " " + self.lArcVal[nRawArc2 & self._arcMask]) + l.append(sStem + "/" + self.lArcVal[nRawArc2 & self._arcMask]) iAddr2 = iEndArcAddr2+self.nBytesNodeAddress iAddr = iEndArcAddr+self.nBytesNodeAddress return l return [] @@ -500,21 +508,21 @@ iAddr = 0 for c in sWord: if c not in self.dChar: return [] iAddr = self._lookupArcNode(self.dChar[c], iAddr) - if iAddr == None: + if iAddr is None: return [] - if (int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask): + if int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask: l = [] nRawArc = 0 while not (nRawArc & self._lastArcMask): iEndArcAddr = iAddr + self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') nArc = nRawArc & self._arcMask if nArc > self.nChar: - # This value is not a char, this is a stemming code + # This value is not a char, this is a stemming code l.append(self.funcStemming(sWord, self.lArcVal[nArc])) iAddr = iEndArcAddr+self.nBytesNodeAddress return l return [] @@ -522,33 +530,33 @@ "looks if is an arc at the node at , if yes, returns address of next node else None" while True: iEndArcAddr = iAddr+self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') if nVal == (nRawArc & self._arcMask): - # the value we are looking for + # the value we are looking for # we return the address of the next node return int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big') else: # value not found - if (nRawArc & self._lastArcMask): + if nRawArc & self._lastArcMask: return None iAddr = iEndArcAddr+self.nBytesNodeAddress def _getArcs1 (self, iAddr): "generator: return all arcs at as tuples of (nVal, iAddr)" while True: iEndArcAddr = iAddr+self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') - yield (nRawArc & self._arcMask, int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big')) - if (nRawArc & self._lastArcMask): + yield nRawArc & self._arcMask, int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big') + if nRawArc & self._lastArcMask: break iAddr = iEndArcAddr+self.nBytesNodeAddress def _writeNodes1 (self, spfDest): "for debugging only" print(" > Write binary nodes") - with codecs.open(spfDest, 'w', 'utf-8', newline="\n") as hDst: + with open(spfDest, 'w', 'utf-8', newline="\n") as hDst: iAddr = 0 hDst.write("i{:_>10} -- #{:_>10}\n".format("0", iAddr)) while iAddr < len(self.byDic): iEndArcAddr = iAddr+self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') @@ -567,21 +575,21 @@ iAddr = 0 for c in sWord: if c not in self.dChar: return [] iAddr = self._lookupArcNode(self.dChar[c], iAddr) - if iAddr == None: + if iAddr is None: return [] - if (int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask): + if int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask: l = [] nRawArc = 0 while not (nRawArc & self._lastArcMask): iEndArcAddr = iAddr + self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') nArc = nRawArc & self._arcMask if nArc > self.nChar: - # This value is not a char, this is a stemming code + # This value is not a char, this is a stemming code sStem = ">" + self.funcStemming(sWord, self.lArcVal[nArc]) # Now , we go to the next node and retrieve all following arcs values, all of them are tags if not (nRawArc & self._addrBitMask): iAddr2 = int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big') else: @@ -592,11 +600,11 @@ iAddr2 += self.nBytesArc + self.nBytesNodeAddress nRawArc2 = 0 while not (nRawArc2 & self._lastArcMask): iEndArcAddr2 = iAddr2 + self.nBytesArc nRawArc2 = int.from_bytes(self.byDic[iAddr2:iEndArcAddr2], byteorder='big') - l.append(sStem + " " + self.lArcVal[nRawArc2 & self._arcMask]) + l.append(sStem + "/" + self.lArcVal[nRawArc2 & self._arcMask]) iAddr2 = iEndArcAddr2+self.nBytesNodeAddress if not (nRawArc2 & self._addrBitMask) else iEndArcAddr2 iAddr = iEndArcAddr+self.nBytesNodeAddress if not (nRawArc & self._addrBitMask) else iEndArcAddr return l return [] @@ -605,21 +613,21 @@ iAddr = 0 for c in sWord: if c not in self.dChar: return [] iAddr = self._lookupArcNode(self.dChar[c], iAddr) - if iAddr == None: + if iAddr is None: return [] - if (int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask): + if int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask: l = [] nRawArc = 0 while not (nRawArc & self._lastArcMask): iEndArcAddr = iAddr + self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') nArc = nRawArc & self._arcMask if nArc > self.nChar: - # This value is not a char, this is a stemming code + # This value is not a char, this is a stemming code l.append(self.funcStemming(sWord, self.lArcVal[nArc])) # Now , we go to the next node if not (nRawArc & self._addrBitMask): iAddr2 = int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big') else: @@ -636,11 +644,11 @@ "looks if is an arc at the node at , if yes, returns address of next node else None" while True: iEndArcAddr = iAddr+self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') if nVal == (nRawArc & self._arcMask): - # the value we are looking for + # the value we are looking for if not (nRawArc & self._addrBitMask): # we return the address of the next node return int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big') else: # we go to the end of the node @@ -649,18 +657,18 @@ nRawArc = int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') iAddr += self.nBytesArc + self.nBytesNodeAddress if not (nRawArc & self._addrBitMask) else self.nBytesArc return iAddr else: # value not found - if (nRawArc & self._lastArcMask): + if nRawArc & self._lastArcMask: return None iAddr = iEndArcAddr+self.nBytesNodeAddress if not (nRawArc & self._addrBitMask) else iEndArcAddr def _writeNodes2 (self, spfDest): "for debugging only" print(" > Write binary nodes") - with codecs.open(spfDest, 'w', 'utf-8', newline="\n") as hDst: + with open(spfDest, 'w', 'utf-8', newline="\n") as hDst: iAddr = 0 hDst.write("i{:_>10} -- #{:_>10}\n".format("0", iAddr)) while iAddr < len(self.byDic): iEndArcAddr = iAddr+self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') @@ -670,11 +678,11 @@ hDst.write(" {:<20} {:0>16} i{:>10} #{:_>10}\n".format(self.lArcVal[nArc], bin(nRawArc)[2:], "?", iNextNodeAddr)) iAddr = iEndArcAddr+self.nBytesNodeAddress else: hDst.write(" {:<20} {:0>16}\n".format(self.lArcVal[nArc], bin(nRawArc)[2:])) iAddr = iEndArcAddr - if (nRawArc & self._lastArcMask): + if nRawArc & self._lastArcMask: hDst.write("\ni{:_>10} -- #{:_>10}\n".format("?", iAddr)) hDst.close() # VERSION 3 def _morph3 (self, sWord): @@ -682,22 +690,22 @@ iAddr = 0 for c in sWord: if c not in self.dChar: return [] iAddr = self._lookupArcNode(self.dChar[c], iAddr) - if iAddr == None: + if iAddr is None: return [] - if (int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask): + if int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask: l = [] nRawArc = 0 iAddrNode = iAddr while not (nRawArc & self._lastArcMask): iEndArcAddr = iAddr + self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') nArc = nRawArc & self._arcMask if nArc > self.nChar: - # This value is not a char, this is a stemming code + # This value is not a char, this is a stemming code sStem = ">" + self.funcStemming(sWord, self.lArcVal[nArc]) # Now , we go to the next node and retrieve all following arcs values, all of them are tags if not (nRawArc & self._addrBitMask): iAddr2 = int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big') else: @@ -704,11 +712,11 @@ iAddr2 = iAddrNode + int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesOffset], byteorder='big') nRawArc2 = 0 while not (nRawArc2 & self._lastArcMask): iEndArcAddr2 = iAddr2 + self.nBytesArc nRawArc2 = int.from_bytes(self.byDic[iAddr2:iEndArcAddr2], byteorder='big') - l.append(sStem + " " + self.lArcVal[nRawArc2 & self._arcMask]) + l.append(sStem + "/" + self.lArcVal[nRawArc2 & self._arcMask]) iAddr2 = iEndArcAddr2+self.nBytesNodeAddress if not (nRawArc2 & self._addrBitMask) else iEndArcAddr2+self.nBytesOffset iAddr = iEndArcAddr+self.nBytesNodeAddress if not (nRawArc & self._addrBitMask) else iEndArcAddr+self.nBytesOffset return l return [] @@ -717,22 +725,22 @@ iAddr = 0 for c in sWord: if c not in self.dChar: return [] iAddr = self._lookupArcNode(self.dChar[c], iAddr) - if iAddr == None: + if iAddr is None: return [] - if (int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask): + if int.from_bytes(self.byDic[iAddr:iAddr+self.nBytesArc], byteorder='big') & self._finalNodeMask: l = [] nRawArc = 0 - iAddrNode = iAddr + #iAddrNode = iAddr while not (nRawArc & self._lastArcMask): iEndArcAddr = iAddr + self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') nArc = nRawArc & self._arcMask if nArc > self.nChar: - # This value is not a char, this is a stemming code + # This value is not a char, this is a stemming code l.append(self.funcStemming(sWord, self.lArcVal[nArc])) iAddr = iEndArcAddr+self.nBytesNodeAddress if not (nRawArc & self._addrBitMask) else iEndArcAddr+self.nBytesOffset return l return [] @@ -741,25 +749,25 @@ iAddrNode = iAddr while True: iEndArcAddr = iAddr+self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') if nVal == (nRawArc & self._arcMask): - # the value we are looking for + # the value we are looking for if not (nRawArc & self._addrBitMask): return int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesNodeAddress], byteorder='big') else: return iAddrNode + int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesOffset], byteorder='big') else: # value not found - if (nRawArc & self._lastArcMask): + if nRawArc & self._lastArcMask: return None iAddr = iEndArcAddr+self.nBytesNodeAddress if not (nRawArc & self._addrBitMask) else iEndArcAddr+self.nBytesOffset def _writeNodes3 (self, spfDest): "for debugging only" print(" > Write binary nodes") - with codecs.open(spfDest, 'w', 'utf-8', newline="\n") as hDst: + with open(spfDest, 'w', 'utf-8', newline="\n") as hDst: iAddr = 0 hDst.write("i{:_>10} -- #{:_>10}\n".format("0", iAddr)) while iAddr < len(self.byDic): iEndArcAddr = iAddr+self.nBytesArc nRawArc = int.from_bytes(self.byDic[iAddr:iEndArcAddr], byteorder='big') @@ -770,8 +778,8 @@ iAddr = iEndArcAddr+self.nBytesNodeAddress else: iNextNodeAddr = int.from_bytes(self.byDic[iEndArcAddr:iEndArcAddr+self.nBytesOffset], byteorder='big') hDst.write(" {:<20} {:0>16} i{:>10} +{:_>10}\n".format(self.lArcVal[nArc], bin(nRawArc)[2:], "?", iNextNodeAddr)) iAddr = iEndArcAddr+self.nBytesOffset - if (nRawArc & self._lastArcMask): + if nRawArc & self._lastArcMask: hDst.write("\ni{:_>10} -- #{:_>10}\n".format("?", iAddr)) hDst.close() Index: graphspell/keyboard_chars_proximity.py ================================================================== --- graphspell/keyboard_chars_proximity.py +++ graphspell/keyboard_chars_proximity.py @@ -1,13 +1,18 @@ -# Keyboard chars proximity + +""" +Keyboard chars proximity +""" def getKeyboardMap (sKeyboard): + "return keyboard map as a dictionary of chars" return _dKeyboardMap.get(sKeyboard.lower(), {}) def getKeyboardList (): + "return list of keyboards available" return _dKeyboardMap.keys() _dKeyboardMap = { # keyboards by alphabetical order Index: graphspell/progressbar.py ================================================================== --- graphspell/progressbar.py +++ graphspell/progressbar.py @@ -1,14 +1,17 @@ -# Textual progressbar +""" +Textual progressbar +""" + # by Olivier R. # License: MPL 2 import time class ProgressBar: "Textual progressbar" - + def __init__ (self, nMin=0, nMax=100, nWidth=78): "initiate with minimum nMin to maximum nMax" self.nMin = nMin self.nMax = nMax self.nSpan = nMax - nMin @@ -17,19 +20,19 @@ self.nCurVal = nMin self.startTime = time.time() self._update() def _update (self): - fDone = ((self.nCurVal - self.nMin) / self.nSpan) + fDone = (self.nCurVal - self.nMin) / self.nSpan nAdvance = int(fDone * self.nWidth) - if (nAdvance > self.nAdvance): + if nAdvance > self.nAdvance: self.nAdvance = nAdvance print("\r[ {}{} {}% ] ".format('>'*nAdvance, ' '*(self.nWidth-nAdvance), round(fDone*100)), end="") def increment (self, n=1): "increment value by n (1 by default)" self.nCurVal += n self._update() - + def done (self): "to call when it’s finished" print("\r[ task done in {:.1f} s ] ".format(time.time() - self.startTime)) Index: graphspell/spellchecker.py ================================================================== --- graphspell/spellchecker.py +++ graphspell/spellchecker.py @@ -1,16 +1,17 @@ -# Spellchecker -# Wrapper for the IBDAWG class. -# Useful to check several dictionaries at once. - -# To avoid iterating over a pile of dictionaries, it is assumed that 3 are enough: -# - the main dictionary, bundled with the package -# - the extended dictionary -# - the community dictionary, added by an organization -# - the personal dictionary, created by the user for its own convenience - - +""" +Spellchecker. +Useful to check several dictionaries at once. + +To avoid iterating over a pile of dictionaries, it is assumed that 3 are enough: +- the main dictionary, bundled with the package +- the extended dictionary +- the community dictionary, added by an organization +- the personal dictionary, created by the user for its own convenience +""" + +import importlib import traceback from . import ibdawg from . import tokenizer @@ -20,10 +21,11 @@ "en": "en.bdic" } class SpellChecker (): + "SpellChecker: wrapper for the IBDAWG class" def __init__ (self, sLangCode, sfMainDic="", sfExtendedDic="", sfCommunityDic="", sfPersonalDic=""): "returns True if the main dictionary is loaded" self.sLangCode = sLangCode if not sfMainDic: @@ -34,10 +36,17 @@ self.oPersonalDic = self._loadDictionary(sfPersonalDic) self.bExtendedDic = bool(self.oExtendedDic) self.bCommunityDic = bool(self.oCommunityDic) self.bPersonalDic = bool(self.oPersonalDic) self.oTokenizer = None + # Default suggestions + self.dDefaultSugg = None + self.loadSuggestions(sLangCode) + # storage + self.bStorage = False + self._dMorphologies = {} # key: flexion, value: list of morphologies + self._dLemmas = {} # key: flexion, value: list of lemmas def _loadDictionary (self, source, bNecessary=False): "returns an IBDAWG object" if not source: return None @@ -48,23 +57,24 @@ raise Exception(str(e), "Error: <" + str(source) + "> not loaded.") print("Error: <" + str(source) + "> not loaded.") traceback.print_exc() return None - def loadTokenizer (self): + def _loadTokenizer (self): self.oTokenizer = tokenizer.Tokenizer(self.sLangCode) def getTokenizer (self): + "load and return the tokenizer object" if not self.oTokenizer: - self.loadTokenizer() + self._loadTokenizer() return self.oTokenizer def setMainDictionary (self, source): "returns True if the dictionary is loaded" self.oMainDic = self._loadDictionary(source, True) return bool(self.oMainDic) - + def setExtendedDictionary (self, source, bActivate=True): "returns True if the dictionary is loaded" self.oExtendedDic = self._loadDictionary(source) self.bExtendedDic = False if not bActivate else bool(self.oExtendedDic) return bool(self.oExtendedDic) @@ -80,33 +90,68 @@ self.oPersonalDic = self._loadDictionary(source) self.bPersonalDic = False if not bActivate else bool(self.oPersonalDic) return bool(self.oPersonalDic) def activateExtendedDictionary (self): + "activate extended dictionary (if available)" self.bExtendedDic = bool(self.oExtendedDic) def activateCommunityDictionary (self): + "activate community dictionary (if available)" self.bCommunityDic = bool(self.oCommunityDic) def activatePersonalDictionary (self): + "activate personal dictionary (if available)" self.bPersonalDic = bool(self.oPersonalDic) def deactivateExtendedDictionary (self): + "deactivate extended dictionary" self.bExtendedDic = False def deactivateCommunityDictionary (self): + "deactivate community dictionary" self.bCommunityDic = False def deactivatePersonalDictionary (self): + "deactivate personal dictionary" self.bPersonalDic = False + + # Default suggestions + + def loadSuggestions (self, sLangCode): + "load default suggestion module for " + try: + suggest = importlib.import_module("."+sLangCode, "graphspell") + except ImportError: + print("No suggestion module for language <"+sLangCode+">") + return + self.dDefaultSugg = suggest.dSugg + + + # Storage + + def activateStorage (self): + "store all lemmas and morphologies retrieved from the word graph" + self.bStorage = True + + def deactivateStorage (self): + "stop storing all lemmas and morphologies retrieved from the word graph" + self.bStorage = False + + def clearStorage (self): + "clear all stored data" + self._dLemmas.clear() + self._dMorphologies.clear() + # parse text functions def parseParagraph (self, sText, bSpellSugg=False): + "return a list of tokens where token value doesn’t exist in the word graph" if not self.oTokenizer: - self.loadTokenizer() + self._loadTokenizer() aSpellErrs = [] for dToken in self.oTokenizer.genTokens(sText): if dToken['sType'] == "WORD" and not self.isValidToken(dToken['sValue']): if bSpellSugg: dToken['aSuggestions'] = [] @@ -114,12 +159,14 @@ dToken['aSuggestions'].extend(lSugg) aSpellErrs.append(dToken) return aSpellErrs def countWordsOccurrences (self, sText, bByLemma=False, bOnlyUnknownWords=False, dWord={}): + """count word occurrences. + can be used to cumulate count from several texts.""" if not self.oTokenizer: - self.loadTokenizer() + self._loadTokenizer() for dToken in self.oTokenizer.genTokens(sText): if dToken['sType'] == "WORD": if bOnlyUnknownWords: if not self.isValidToken(dToken['sValue']): dWord[dToken['sValue']] = dWord.get(dToken['sValue'], 0) + 1 @@ -149,11 +196,11 @@ "checks if sWord is valid (different casing tested if the first letter is a capital)" if self.oMainDic.isValid(sWord): return True if self.bExtendedDic and self.oExtendedDic.isValid(sWord): return True - if self.bCommunityDic and self.oCommunityDic.isValid(sToken): + if self.bCommunityDic and self.oCommunityDic.isValid(sWord): return True if self.bPersonalDic and self.oPersonalDic.isValid(sWord): return True return False @@ -161,33 +208,52 @@ "checks if sWord is in dictionary as is (strict verification)" if self.oMainDic.lookup(sWord): return True if self.bExtendedDic and self.oExtendedDic.lookup(sWord): return True - if self.bCommunityDic and self.oCommunityDic.lookup(sToken): + if self.bCommunityDic and self.oCommunityDic.lookup(sWord): return True if self.bPersonalDic and self.oPersonalDic.lookup(sWord): return True return False def getMorph (self, sWord): "retrieves morphologies list, different casing allowed" - lResult = self.oMainDic.getMorph(sWord) + if self.bStorage and sWord in self._dMorphologies: + return self._dMorphologies[sWord] + lMorph = self.oMainDic.getMorph(sWord) if self.bExtendedDic: - lResult.extend(self.oExtendedDic.getMorph(sWord)) + lMorph.extend(self.oExtendedDic.getMorph(sWord)) if self.bCommunityDic: - lResult.extend(self.oCommunityDic.getMorph(sWord)) + lMorph.extend(self.oCommunityDic.getMorph(sWord)) if self.bPersonalDic: - lResult.extend(self.oPersonalDic.getMorph(sWord)) - return lResult + lMorph.extend(self.oPersonalDic.getMorph(sWord)) + if self.bStorage: + self._dMorphologies[sWord] = lMorph + self._dLemmas[sWord] = set([ s[1:s.find("/")] for s in lMorph ]) + return lMorph def getLemma (self, sWord): - return set([ s[1:s.find(" ")] for s in self.getMorph(sWord) ]) + "retrieves lemmas" + if self.bStorage: + if sWord not in self._dLemmas: + self.getMorph(sWord) + return self._dLemmas[sWord] + return set([ s[1:s.find("/")] for s in self.getMorph(sWord) ]) def suggest (self, sWord, nSuggLimit=10): "generator: returns 1, 2 or 3 lists of suggestions" - yield self.oMainDic.suggest(sWord, nSuggLimit) + if self.dDefaultSugg: + if sWord in self.dDefaultSugg: + yield self.dDefaultSugg[sWord].split("|") + elif sWord.istitle() and sWord.lower() in self.dDefaultSugg: + lRes = self.dDefaultSugg[sWord.lower()].split("|") + yield list(map(lambda sSugg: sSugg[0:1].upper()+sSugg[1:], lRes)) + else: + yield self.oMainDic.suggest(sWord, nSuggLimit) + else: + yield self.oMainDic.suggest(sWord, nSuggLimit) if self.bExtendedDic: yield self.oExtendedDic.suggest(sWord, nSuggLimit) if self.bCommunityDic: yield self.oCommunityDic.suggest(sWord, nSuggLimit) if self.bPersonalDic: @@ -202,10 +268,11 @@ yield from self.oCommunityDic.select(sFlexPattern, sTagsPattern) if self.bPersonalDic: yield from self.oPersonalDic.select(sFlexPattern, sTagsPattern) def drawPath (self, sWord): + "draw the path taken by within the word graph: display matching nodes and their arcs" self.oMainDic.drawPath(sWord) if self.bExtendedDic: print("-----") self.oExtendedDic.drawPath(sWord) if self.bCommunityDic: Index: graphspell/str_transform.py ================================================================== --- graphspell/str_transform.py +++ graphspell/str_transform.py @@ -1,25 +1,32 @@ #!python3 +""" +Operations on strings: +- calculate distance between two strings +- transform strings with transformation codes +""" + #### DISTANCE CALCULATIONS def longestCommonSubstring (s1, s2): + "longest common substring" # http://en.wikipedia.org/wiki/Longest_common_substring_problem # http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring - M = [ [0]*(1+len(s2)) for i in range(1+len(s1)) ] - longest, x_longest = 0, 0 + lMatrix = [ [0]*(1+len(s2)) for i in range(1+len(s1)) ] + nLongest, nLongestX = 0, 0 for x in range(1, 1+len(s1)): for y in range(1, 1+len(s2)): if s1[x-1] == s2[y-1]: - M[x][y] = M[x-1][y-1] + 1 - if M[x][y] > longest: - longest = M[x][y] - x_longest = x + lMatrix[x][y] = lMatrix[x-1][y-1] + 1 + if lMatrix[x][y] > nLongest: + nLongest = lMatrix[x][y] + nLongestX = x else: - M[x][y] = 0 - return s1[x_longest-longest : x_longest] + lMatrix[x][y] = 0 + return s1[nLongestX-nLongest : nLongestX] def distanceDamerauLevenshtein (s1, s2): "distance of Damerau-Levenshtein between and " # https://fr.wikipedia.org/wiki/Distance_de_Damerau-Levenshtein @@ -54,11 +61,11 @@ i1, i2 = 0, 0 # Cursors for each string nLargestCS = 0 # Largest common substring nLocalCS = 0 # Local common substring nTrans = 0 # Number of transpositions ('ab' vs 'ba') lOffset = [] # Offset pair array, for computing the transpositions - + while i1 < nLen1 and i2 < nLen2: if s1[i1] == s2[i2]: nLocalCS += 1 # Check if current match is a transposition bTrans = False @@ -103,10 +110,11 @@ nLargestCS += nLocalCS return round(max(nLen1, nLen2) - nLargestCS + nTrans) def showDistance (s1, s2): + "display Damerau-Levenshtein distance and Sift4 distance between and " print("Damerau-Levenshtein: " + s1 + "/" + s2 + " = " + distanceDamerauLevenshtein(s1, s2)) print("Sift4:" + s1 + "/" + s2 + " = " + distanceSift4(s1, s2)) @@ -114,23 +122,27 @@ #### STEMMING OPERATIONS ## No stemming def noStemming (sFlex, sStem): + "return " return sStem -def rebuildWord (sFlex, cmd1, cmd2): - if cmd1 == "_": +def rebuildWord (sFlex, sCode1, sCode2): + """ Change with codes (each inserts a char at a defined possition). + + """ + if sCode1 == "_": + return sFlex + n, c = sCode1.split(":") + sFlex = sFlex[:n] + c + sFlex[n:] + if sCode2 == "_": return sFlex - n, c = cmd1.split(":") - s = s[:n] + c + s[n:] - if cmd2 == "_": - return s - n, c = cmd2.split(":") - return s[:n] + c + s[n:] - - + n, c = sCode2.split(":") + return sFlex[:n] + c + sFlex[n:] + + ## Define affixes for stemming # Note: 48 is the ASCII code for "0" @@ -150,14 +162,15 @@ jSfx = 0 for i in range(min(len(sFlex), len(sStem))): if sFlex[i] != sStem[i]: break jSfx += 1 - return chr(len(sFlex)-jSfx+48) + sStem[jSfx:] + return chr(len(sFlex)-jSfx+48) + sStem[jSfx:] def changeWordWithSuffixCode (sWord, sSfxCode): + "apply transformation code on and return the result string" if sSfxCode == "0": return sWord return sWord[:-(ord(sSfxCode[0])-48)] + sSfxCode[1:] if sSfxCode[0] != '0' else sWord + sSfxCode[1:] @@ -167,11 +180,11 @@ """ Returns a string defining how to get stem from flexion. Examples: "0" if stem = flexion "stem" if no common substring "n(pfx)/m(sfx)" with n and m: chars with numeric meaning, "0" = 0, "1" = 1, ... ":" = 10, etc. (See ASCII table.) Says how many letters to strip from flexion. - pfx [optional]: string to add before the flexion + pfx [optional]: string to add before the flexion sfx [optional]: string to add after the flexion """ if sFlex == sStem: return "0" # is stem a substring of flexion? @@ -189,13 +202,13 @@ return chr(n+48) + sPfx + "/" + chr(m+48) + sSfx return sStem def changeWordWithAffixCode (sWord, sAffCode): + "apply transformation code on and return the result string" if sAffCode == "0": return sWord if '/' not in sAffCode: return sAffCode sPfxCode, sSfxCode = sAffCode.split('/') - sWord = sPfxCode[1:] + sWord[(ord(sPfxCode[0])-48):] + sWord = sPfxCode[1:] + sWord[(ord(sPfxCode[0])-48):] return sWord[:-(ord(sSfxCode[0])-48)] + sSfxCode[1:] if sSfxCode[0] != '0' else sWord + sSfxCode[1:] - Index: graphspell/tokenizer.py ================================================================== --- graphspell/tokenizer.py +++ graphspell/tokenizer.py @@ -1,49 +1,62 @@ -# Very simple tokenizer +""" +Very simple tokenizer +using regular expressions +""" import re _PATTERNS = { "default": ( r'(?P/(?:bin|boot|dev|etc|home|lib|mnt|opt|root|sbin|tmp|usr|var|Bureau|Documents|Images|Musique|Public|Téléchargements|Vidéos)(?:/[\w.()-]+)*)', r'(?P[a-zA-Z]:\\(?:Program Files(?: [(]x86[)]|)|[\w.()]+)(?:\\[\w.()-]+)*)', - r'(?P[.,?!:;…«»“”"()/·]+)', + r'(?P[][,.;:!?…«»“”‘’"(){}·–—])', r'(?P[A-Z][.][A-Z][.](?:[A-Z][.])*)', r'(?P(?:https?://|www[.]|\w+[@.]\w\w+[@.])\w[\w./?&!%=+*"\'@$#-]+)', r'(?P[#@][\w-]+)', r'(?P<\w+.*?>|)', r'(?P\[/?\w+\])', r'(?P\d\d?h\d\d\b)', r'(?P-?\d+(?:[.,]\d+))', + r'(?P[%‰+=*/<>⩾⩽-])', r"(?P\w+(?:[’'`-]\w+)*)" ), "fr": ( r'(?P/(?:bin|boot|dev|etc|home|lib|mnt|opt|root|sbin|tmp|usr|var|Bureau|Documents|Images|Musique|Public|Téléchargements|Vidéos)(?:/[\w.()-]+)*)', r'(?P[a-zA-Z]:\\(?:Program Files(?: [(]x86[)]|)|[\w.()]+)(?:\\[\w.()-]+)*)', - r'(?P[.,?!:;…«»“”"()/·]+)', + r'(?P[][,.;:!?…«»“”‘’"(){}·–—])', r'(?P[A-Z][.][A-Z][.](?:[A-Z][.])*)', r'(?P(?:https?://|www[.]|\w+[@.]\w\w+[@.])\w[\w./?&!%=+*"\'@$#-]+)', r'(?P[#@][\w-]+)', r'(?P<\w+.*?>|)', r'(?P\[/?\w+\])', r"(?P(?:l|d|n|m|t|s|j|c|ç|lorsqu|puisqu|jusqu|quoiqu|qu)['’`])", - r'(?P\d+(?:er|nd|e|de|ième|ème|eme)\b)', + r'(?P\d+(?:ers?|nds?|es?|des?|ièmes?|èmes?|emes?|ᵉʳˢ?|ⁿᵈˢ?|ᵉˢ?|ᵈᵉˢ?)\b)', r'(?P\d\d?h\d\d\b)', r'(?P-?\d+(?:[.,]\d+|))', + r'(?P[%‰+=*/<>⩾⩽-])', r"(?P\w+(?:[’'`-]\w+)*)" ) } class Tokenizer: + "Tokenizer: transforms a text in a list of tokens" def __init__ (self, sLang): self.sLang = sLang if sLang not in _PATTERNS: self.sLang = "default" self.zToken = re.compile( "(?i)" + '|'.join(sRegex for sRegex in _PATTERNS[sLang]) ) - def genTokens (self, sText): - for m in self.zToken.finditer(sText): - yield { "sType": m.lastgroup, "sValue": m.group(), "nStart": m.start(), "nEnd": m.end() } + def genTokens (self, sText, bStartEndToken=False): + "generator: tokenize " + i = 0 + if bStartEndToken: + yield { "i": 0, "sType": "INFO", "sValue": "", "nStart": 0, "nEnd": 0 } + for i, m in enumerate(self.zToken.finditer(sText), 1): + yield { "i": i, "sType": m.lastgroup, "sValue": m.group(), "nStart": m.start(), "nEnd": m.end() } + if bStartEndToken: + iEnd = len(sText) + yield { "i": i+1, "sType": "INFO", "sValue": "", "nStart": iEnd, "nEnd": iEnd } Index: make.py ================================================================== --- make.py +++ make.py @@ -1,11 +1,14 @@ #!/usr/bin/env python3 # coding: UTF-8 + +""" +Grammalecte builder +""" import sys import os -import subprocess import re import zipfile import traceback import configparser import datetime @@ -15,40 +18,43 @@ import json import platform from distutils import dir_util, file_util -import dialog_bundled +#import dialog_bundled import compile_rules import helpers import lex_build sWarningMessage = "The content of this folder is generated by code and replaced at each build.\n" def getConfig (sLang): + "load config.ini in at gc_lang/, returns xConfigParser object" xConfig = configparser.SafeConfigParser() xConfig.optionxform = str try: - xConfig.read("gc_lang/" + sLang + "/config.ini", encoding="utf-8") - except: + xConfig.read_file(open("gc_lang/" + sLang + "/config.ini", "r", encoding="utf-8")) + except FileNotFoundError: print("# Error. Can’t read config file [" + sLang + "]") exit() return xConfig def createOptionsLabelProperties (dOptLbl): + "create content for .properties files (LibreOffice)" sContent = "" for sOpt, tLabel in dOptLbl.items(): sContent += sOpt + "=" + tLabel[0] + "\n" if tLabel[1]: sContent += "hlp_" + sOpt + "=" + tLabel[1] + "\n" return sContent def createDialogOptionsXDL (dVars): + "create bundled dialog options file .xdl (LibreOffice)" sFixedline = '\n' sCheckbox = '\n' iTabIndex = 1 nPosY = 5 nWidth = 240 @@ -133,17 +139,17 @@ if bInstall: print("> installation in Writer") if dVars.get('unopkg', False): cmd = '"'+os.path.abspath(dVars.get('unopkg')+'" add -f '+spfZip) print(cmd) - #subprocess.run(cmd) os.system(cmd) else: print("# Error: path and filename of unopkg not set in config.ini") def createServerOptions (sLang, dOptData): + "create file options for Grammalecte server" with open("grammalecte-server-options."+sLang+".ini", "w", encoding="utf-8", newline="\n") as hDst: hDst.write("# Server options. Lang: " + sLang + "\n\n[gc_options]\n") for sSection, lOpt in dOptData["lStructOpt"]: hDst.write("\n########## " + dOptData["dOptLabel"][sLang].get(sSection, sSection + "[no label found]")[0] + " ##########\n") for lLineOpt in lOpt: @@ -164,10 +170,11 @@ hZip.write(spf) hZip.writestr("setup.py", helpers.fileFile("gc_lang/fr/setup.py", dVars)) def copyGrammalectePyPackageInZipFile (hZip, spLangPack, sAddPath=""): + "copy Grammalecte Python package in zip file" for sf in os.listdir("grammalecte"): if not os.path.isdir("grammalecte/"+sf): hZip.write("grammalecte/"+sf, sAddPath+"grammalecte/"+sf) for sf in os.listdir("grammalecte/graphspell"): if not os.path.isdir("grammalecte/graphspell/"+sf): @@ -179,10 +186,11 @@ if not os.path.isdir(spLangPack+"/"+sf): hZip.write(spLangPack+"/"+sf, sAddPath+spLangPack+"/"+sf) def create (sLang, xConfig, bInstallOXT, bJavaScript): + "make Grammalecte for project " oNow = datetime.datetime.now() print("============== MAKE GRAMMALECTE [{0}] at {1.hour:>2} h {1.minute:>2} min {1.second:>2} s ==============".format(sLang, oNow)) #### READ CONFIGURATION print("> read configuration...") @@ -228,10 +236,11 @@ # TEST FILES with open("grammalecte/"+sLang+"/gc_test.txt", "w", encoding="utf-8", newline="\n") as hDstPy: hDstPy.write("# TESTS FOR LANG [" + sLang + "]\n\n") hDstPy.write(dVars['gctests']) + hDstPy.write("\n") createOXT(spLang, dVars, xConfig._sections['oxt'], spLangPack, bInstallOXT) createServerOptions(sLang, dVars) createPackageZip(sLang, dVars, spLangPack) @@ -250,11 +259,11 @@ # options data struct dVars["dOptJavaScript"] = json.dumps(list(dVars["dOptJavaScript"].items())) dVars["dOptFirefox"] = json.dumps(list(dVars["dOptFirefox"].items())) dVars["dOptThunderbird"] = json.dumps(list(dVars["dOptThunderbird"].items())) - + # create folder spLangPack = "grammalecte-js/"+sLang helpers.createCleanFolder(spLangPack) # create files @@ -273,20 +282,21 @@ helpers.copyAndFileTemplate(spLang+"/modules-js/"+sf, spLangPack+"/"+sf, dVars) print(sf, end=", ") print() try: - build_module = importlib.import_module("gc_lang."+sLang+".build") + buildjs = importlib.import_module("gc_lang."+sLang+".build") except ImportError: print("# No complementary builder in folder gc_lang/"+sLang) else: - build_module.build(sLang, dVars, spLangPack) + buildjs.build(sLang, dVars, spLangPack) return dVars['version'] def copyGraphspellCore (bJavaScript=False): + "copy Graphspell package in Grammalecte package" helpers.createCleanFolder("grammalecte/graphspell") dir_util.mkpath("grammalecte/graphspell/_dictionaries") for sf in os.listdir("graphspell"): if not os.path.isdir("graphspell/"+sf): file_util.copy_file("graphspell/"+sf, "grammalecte/graphspell") @@ -301,10 +311,11 @@ file_util.copy_file("graphspell-js/"+sf, "grammalecte-js/graphspell") helpers.copyAndFileTemplate("graphspell-js/"+sf, "grammalecte-js/graphspell/"+sf, dVars) def copyGraphspellDictionaries (dVars, bJavaScript=False, bExtendedDict=False, bCommunityDict=False, bPersonalDict=False): + "copy requested Graphspell dictionaries in Grammalecte package" dVars["dic_main_filename_py"] = "" dVars["dic_main_filename_js"] = "" dVars["dic_extended_filename_py"] = "" dVars["dic_extended_filename_js"] = "" dVars["dic_community_filename_py"] = "" @@ -333,16 +344,17 @@ dVars['dic_main_filename_py'] = dVars['dic_default_filename_py'] + ".bdic" dVars['dic_main_filename_js'] = dVars['dic_default_filename_js'] + ".json" def buildDictionary (dVars, sType, bJavaScript=False): + "build binary dictionary for Graphspell from lexicons" if sType == "main": spfLexSrc = dVars['lexicon_src'] - l_sfDictDst = dVars['dic_filenames'].split(",") - l_sDicName = dVars['dic_name'].split(",") - l_sFilter = dVars['dic_filter'].split(",") - for sfDictDst, sDicName, sFilter in zip(l_sfDictDst, l_sDicName, l_sFilter): + lSfDictDst = dVars['dic_filenames'].split(",") + lDicName = dVars['dic_name'].split(",") + lFilter = dVars['dic_filter'].split(",") + for sfDictDst, sDicName, sFilter in zip(lSfDictDst, lDicName, lFilter): lex_build.build(spfLexSrc, dVars['lang'], dVars['lang_name'], sfDictDst, bJavaScript, sDicName, sFilter, dVars['stemming_method'], int(dVars['fsa_method'])) else: if sType == "extended": spfLexSrc = dVars['lexicon_extended_src'] sfDictDst = dVars['dic_extended_filename'] @@ -358,10 +370,11 @@ lex_build.build(spfLexSrc, dVars['lang'], dVars['lang_name'], sfDictDst, bJavaScript, sDicName, "", dVars['stemming_method'], int(dVars['fsa_method'])) def main (): + "build Grammalecte with requested options" print("Python: " + sys.version) xParser = argparse.ArgumentParser() xParser.add_argument("lang", type=str, nargs='+', help="lang project to generate (name of folder in /lang)") xParser.add_argument("-b", "--build_data", help="launch build_data.py (part 1 and 2)", action="store_true") xParser.add_argument("-bb", "--build_data_before", help="launch build_data.py (only part 1: before dictionary building)", action="store_true") @@ -403,29 +416,29 @@ xArgs.add_community_dictionary = False if not dVars["lexicon_personal_src"]: xArgs.add_personal_dictionary = False # build data - build_data_module = None + databuild = None if xArgs.build_data_before or xArgs.build_data_after: # lang data try: - build_data_module = importlib.import_module("gc_lang."+sLang+".build_data") + databuild = importlib.import_module("gc_lang."+sLang+".build_data") except ImportError: print("# Error. Couldn’t import file build_data.py in folder gc_lang/"+sLang) - if build_data_module and xArgs.build_data_before: - build_data_module.before('gc_lang/'+sLang, dVars, xArgs.javascript) + if databuild and xArgs.build_data_before: + databuild.before('gc_lang/'+sLang, dVars, xArgs.javascript) if xArgs.dict: buildDictionary(dVars, "main", xArgs.javascript) if xArgs.add_extended_dictionary: buildDictionary(dVars, "extended", xArgs.javascript) if xArgs.add_community_dictionary: buildDictionary(dVars, "community", xArgs.javascript) if xArgs.add_personal_dictionary: buildDictionary(dVars, "personal", xArgs.javascript) - if build_data_module and xArgs.build_data_after: - build_data_module.after('gc_lang/'+sLang, dVars, xArgs.javascript) + if databuild and xArgs.build_data_after: + databuild.after('gc_lang/'+sLang, dVars, xArgs.javascript) # copy dictionaries from Graphspell copyGraphspellDictionaries(dVars, xArgs.javascript, xArgs.add_extended_dictionary, xArgs.add_community_dictionary, xArgs.add_personal_dictionary) # make @@ -446,16 +459,15 @@ unittest.TextTestRunner().run(xTestSuite) if xArgs.perf or xArgs.perf_memo: hDst = open("./gc_lang/"+sLang+"/perf_memo.txt", "a", encoding="utf-8", newline="\n") if xArgs.perf_memo else None tests.perf(sVersion, hDst) - # Firefox - if False: - # obsolete - with helpers.cd("_build/xpi/"+sLang): - spfFirefox = dVars['win_fx_dev_path'] if platform.system() == "Windows" else dVars['linux_fx_dev_path'] - os.system('jpm run -b "' + spfFirefox + '"') + # Firefox (obsolete) + #if False: + # with helpers.cd("_build/xpi/"+sLang): + # spfFirefox = dVars['win_fx_dev_path'] if platform.system() == "Windows" else dVars['linux_fx_dev_path'] + # os.system('jpm run -b "' + spfFirefox + '"') if xArgs.web_ext or xArgs.firefox: with helpers.cd("_build/webext/"+sLang): if xArgs.lint_web_ext: os.system(r'web-ext lint -o text') Index: misc/grammalecte.sublime-syntax ================================================================== --- misc/grammalecte.sublime-syntax +++ misc/grammalecte.sublime-syntax @@ -24,10 +24,23 @@ # Bookmarks - match: '^!!.*|^\[\+\+\].*' scope: bookmark + # Bookmarks + - match: '^GRAPH_NAME:.*' + scope: bookmark + + # Graph + - match: '^@@@@GRAPH: *(\w+) *' + scope: graphline + captures: + 1: string.graphname + + - match: '^@@@@(?:END_GRAPH *| *)' + scope: graphline + # Keywords are if, else. # Note that blackslashes don't need to be escaped within single quoted # strings in YAML. When using single quoted strings, only single quotes # need to be escaped: this is done by using two single quotes next to each # other. @@ -35,11 +48,11 @@ scope: keyword.python - match: '\b(?:True|False|None)\b' scope: constant.language - - match: '\b(?:spell|morph|morphex|stem|textarea0?\w*|before0?\w*|after0?\w*|word|option|define|select|exclude|analysex?|apposition|is[A-Z]\w+|rewriteSubject|checkD\w+|getD\w+|has[A-Z]\w+|sugg[A-Z]\w+|switch[A-Z]\w+|ceOrCet|formatN\w+|mbUnit)\b' + - match: '\b(?:spell|morph|morphex|stem|textarea0?\w*|before0?\w*|after0?\w*|word|option|define|select|exclude|analysex?|tag_|apposition|is[A-Z]\w+|rewriteSubject|checkD\w+|getD\w+|has[A-Z]\w+|sugg[A-Z]\w+|switch[A-Z]\w+|ceOrCet|formatN\w+|mbUnit)\b' scope: entity.name.function - match: '\b(?:replace|endswith|startswith|search|upper|lower|capitalize|strip|rstrip|is(?:upper|lower|digit|title))\b' scope: support.function @@ -47,19 +60,31 @@ scope: support.function.debug - match: '\bre\b' scope: support.class - # Rule options + # Regex rule option - match: '^__[\[<]([isu])[\]>](/\w+|)(\(\w+\)|)(![0-9]|)__|' scope: rule.options captures: 1: rule.casing 2: rule.optionname 3: rule.rulename 4: rule.priority + # Graph rules option + - match: '^__(\w+)(![0-9]|)__' + scope: rule.options + captures: + 1: rule.rulename2 + 2: rule.priority + + - match: '/(\w+)/' + scope: rule.options + captures: + 1: rule.optionname + # Definitions and options - match: '^OPT(?:GROUP|LANG|PRIORITY)/|^OPTSOFTWARE:' scope: options.command - match: '^OPT(?:LABEL|)/' @@ -84,20 +109,48 @@ scope: keyword.action - match: '__also__' scope: keyword.condition.green - match: '__else__' scope: keyword.condition.red - - match: '-(\d*)>>' + - match: '-(\d*(?::\d+|))>>' scope: keyword.error captures: 1: keyword.error.group - - match: '~(\d*)>>' + - match: '~(\d*(?::\d+|))>>' scope: keyword.textprocessor captures: 1: keyword.textprocessor.group - match: '=>>' scope: keyword.disambiguator + - match: '/(\d*)>>' + scope: keyword.tag + captures: + 1: keyword.tag.group + + + # Tokens + - match: '(>)\w+' + scope: string.lemma + captures: + 1: keyword.valid + + - match: '(~)(?!(?:\d+(?::\d+|)|)>>)[^\s]+' + scope: string.regex + captures: + 1: keyword.valid + + - match: '(@)([^@][^\s¬]+)' + scope: string.morph + captures: + 1: keyword.valid + 2: string.morph.pattern + + - match: '(¬)(\S+)' + scope: string.morph + captures: + 1: keyword.invalid + 2: string.morph.antipattern # Escaped chars - match: '\\(?:\d+|w|d|b|n|s|t)' scope: constant.character.escape @@ -108,11 +161,11 @@ # Example errors - match: '{{.+?}}' scope: message.error # special chars - - match: '[@=*^?!:+<>]' + - match: '[@=*^?¿!:+<>~]' scope: keyword.other - match: '\(\?(?:[:=!]|#A0F0FF background #0050A0 + + name + Graphline + scope + graphline + settings + + foreground + hsl(0, 100%, 80%) + background + hsl(0, 100%, 20% + fontStyle + bold + + name String scope string @@ -233,10 +248,40 @@ #602020 fontStyle bold + + name + Keyword tag + scope + keyword.tag + settings + + foreground + #FF70FF + background + #602060 + fontStyle + bold + + + + name + Keyword tag group + scope + keyword.tag.group + settings + + foreground + #F0B0F0 + background + #602060 + fontStyle + bold + + name Keyword textprocessor scope keyword.textprocessor @@ -291,10 +336,41 @@ foreground #A0A0A0 + + name + Keyword Valid + scope + keyword.valid + settings + + fontStyle + bold + foreground + hsl(150, 100%, 80%) + background + hsl(150, 100%, 20%) + + + + name + Keyword Invalid + scope + keyword.invalid + settings + + fontStyle + bold + foreground + hsl(0, 100%, 80%) + background + hsl(0, 100%, 20%) + + + name Rule options scope rule.options @@ -344,10 +420,21 @@ italic foreground #A0A0A0 + + name + Rule name + scope + rule.rulename2 + settings + + foreground + #F0D080 + + name Rule priority scope rule.priority @@ -356,10 +443,63 @@ foreground #F06060 + + name + String lemma + scope + string.lemma + settings + + foreground + hsl(210, 100%, 80%) + background + hsl(210, 100%, 15%) + + + + name + String regex + scope + string.regex + settings + + foreground + hsl(60, 100%, 80%) + background + hsl(60, 100%, 10%) + + + + name + String morph pattern + scope + string.morph.pattern + settings + + foreground + hsl(150, 80%, 90%) + background + hsl(150, 80%, 10%) + + + + name + String morph antipattern + scope + string.morph.antipattern + settings + + foreground + hsl(0, 80%, 90%) + background + hsl(0, 80%, 10%) + + + name JavaScript Dollar scope