Index: compile_rules.py
==================================================================
--- compile_rules.py
+++ compile_rules.py
@@ -110,13 +110,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’t 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
@@ -343,12 +351,13 @@
 
 
 def _calcRulesStats (lRules):
     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):
     print("  {:>18} {:>18} {:>18} {:>18}".format("DISAMBIGUATOR", "TEXT PROCESSOR", "GRAMMAR CHECKING", "REGEX"))
@@ -436,47 +445,57 @@
     global dDEF
     lLine = []
     lRuleLine = []
     lTest = []
     lOpt = []
-    zBookmark = re.compile("^!!+")
-    zGraphLink = re.compile(r"^@@@@GRAPHLINK>(\w+)@@@@")
 
     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())
+            # rules graph call
+            m = re.match(r"@@@@GRAPH: *(\w+)@@@@", sLine.strip())
             if m:
                 #lRuleLine.append(["@GRAPHLINK", m.group(1)])
-                printBookmark(1, "@GRAPHLINK: " + m.group(1), i)
+                printBookmark(1, "@GRAPH: " + m.group(1), i)
+                lRuleLine.append([i, "@@@@"+m.group(1)])
         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:"):
-            lTest.append("{:<8}".format(i) + "  " + sLine[5:].strip())
+            # test
+            lTest.append("r{:<7}".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):
+            # empty line
             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)
         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:
@@ -542,14 +561,14 @@
 
     print("Unnamed rules: " + str(nRULEWITHOUTNAME))
 
     d = { "callables": sPyCallables,
           "callablesJS": sJSCallables,
-          "gctests": sGCTests,
-          "gctestsJS": sGCTestsJS,
+          "regex_gctests": sGCTests,
+          "regex_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

ADDED   compile_rules_graph.py
Index: compile_rules_graph.py
==================================================================
--- /dev/null
+++ compile_rules_graph.py
@@ -0,0 +1,370 @@
+# Create a Direct Acyclic Rule Graph (DARG)
+
+import re
+import traceback
+import json
+import darg
+
+
+dDEF = {}
+dACTIONS = {}
+lFUNCTIONS = []
+
+
+def prepareFunction (s):
+    s = s.replace("__also__", "bCondMemo")
+    s = s.replace("__else__", "not bCondMemo")
+    s = re.sub(r"(select|exclude|define)[(][\\](\d+)", 'g_\\1(lToken[\\2+nTokenOffset]', s)
+    s = re.sub(r"(morph|displayInfo)[(]\\(\d+)", 'g_\\1(lToken[\\2+nTokenOffset]', s)
+    s = re.sub(r"token\(\s*(\d)", 'nextToken(\\1', s)                                       # token(n)
+    s = re.sub(r"token\(\s*-(\d)", 'prevToken(\\1', s)                                      # token(-n)
+    s = re.sub(r"before\(\s*", 'look(s[:m.start()], ', s)                                   # before(s)
+    s = re.sub(r"after\(\s*", 'look(s[m.end():], ', s)                                      # after(s)
+    s = re.sub(r"textarea\(\s*", 'look(s, ', s)                                             # textarea(s)
+    s = re.sub(r"before_chk1\(\s*", 'look_chk1(dDA, s[:m.start()], 0, ', s)                 # before_chk1(s)
+    s = re.sub(r"after_chk1\(\s*", 'look_chk1(dDA, s[m.end():], m.end(), ', s)              # after_chk1(s)
+    s = re.sub(r"textarea_chk1\(\s*", 'look_chk1(dDA, s, 0, ', s)                           # textarea_chk1(s)
+    s = re.sub(r"\bspell *[(]", '_oSpellChecker.isValid(', s)
+    s = re.sub(r"[\\](\d+)", 'lToken[\\1]', s)
+    return s
+
+
+def genTokenLines (sTokenLine):
+    "tokenize a string and return a list of lines of tokens"
+    lToken = sTokenLine.split()
+    lTokenLines = None
+    for i, sToken in enumerate(lToken):
+        if sToken.startswith("{") and sToken.endswith("}") and sToken in dDEF:
+            lToken[i] = dDEF[sToken]
+        if ( (sToken.startswith("[") and sToken.endswith("]")) or (sToken.startswith("([") and sToken.endswith("])")) ):
+            bSelectedGroup = sToken.startswith("(") and sToken.endswith(")")
+            if bSelectedGroup:
+                sToken = sToken[1:-1]
+            # multiple token
+            if not lTokenLines:
+                lTokenLines = [ [s]  for s  in sToken[1:-1].split("|") ]
+            else:
+                lNewTemp = []
+                for aRule in lTokenLines:
+                    lElem = sToken[1:-1].split("|")
+                    sElem1 = lElem.pop(0)
+                    if bSelectedGroup:
+                        sElem1 = "(" + sElem1 + ")"
+                    for sElem in lElem:
+                        if bSelectedGroup:
+                            sElem = "(" + sElem + ")"
+                        aNew = list(aRule)
+                        aNew.append(sElem)
+                        lNewTemp.append(aNew)
+                    aRule.append(sElem1)
+                lTokenLines.extend(lNewTemp)
+        else:
+            # simple token
+            if not lTokenLines:
+                lTokenLines = [[sToken]]
+            else:
+                for aRule in lTokenLines:
+                    aRule.append(sToken)
+    for aRule in lTokenLines:
+        yield aRule
+
+
+def createRule (iLine, sRuleName, sTokenLine, sActions, nPriority):
+    # print(iLine, "//", sRuleName, "//", sTokenLine, "//", sActions, "//", nPriority)
+    for lToken in genTokenLines(sTokenLine):
+        # 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 nAction, sAction in enumerate(sActions.split(" <<- ")):
+            if sAction.strip():
+                sActionId = sRuleName + "_a" + str(nAction)
+                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 (s, dPos):
+    for i in range(len(dPos), 0, -1):
+        s = s.replace("\\"+str(i), "\\"+str(dPos[i]))
+    return s
+
+
+def createAction (sIdAction, sAction, nPriority, nToken, dPos):
+    m = re.search("(?P<action>[-~=])(?P<start>\\d+|)(?P<end>:\\d+|)>> ", sAction)
+    if not m:
+        print(" # Error. No action found at: ", sIdAction)
+        print("   ==", sAction, "==")
+        return None
+    # Condition
+    sCondition = sAction[:m.start()].strip()
+    if sCondition:
+        sCondition = prepareFunction(sCondition)
+        sCondition = changeReferenceToken(sCondition, dPos)    
+        lFUNCTIONS.append(("g_c_"+sIdAction, sCondition))
+        sCondition = "g_c_"+sIdAction
+    else:
+        sCondition = ""
+    # Action
+    cAction = m.group("action")
+    sAction = sAction[m.end():].strip()
+    sAction = changeReferenceToken(sAction, dPos)
+    if not m.group("start"):
+        iStartAction = 1
+        iEndAction = nToken
+    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: " + sIdAction)
+
+    if cAction == "-":
+        ## error
+        iMsg = sAction.find(" # ")
+        if iMsg == -1:
+            sMsg = "# Error. Error message not found."
+            sURL = ""
+            print(sMsg + " Action id: " + sIdAction)
+        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()
+            if sMsg[0:1] == "=":
+                sMsg = prepareFunction(sMsg[1:])
+                lFUNCTIONS.append(("g_m_"+sIdAction, sMsg))
+                for x in re.finditer("group[(](\\d+)[)]", sMsg):
+                    if int(x.group(1)) > nToken:
+                        print("# Error in token index in message at line " + sIdAction + " ("+str(nToken)+" tokens only)")
+                sMsg = "=g_m_"+sIdAction
+            else:
+                for x in re.finditer(r"\\(\d+)", sMsg):
+                    if int(x.group(1)) > nToken:
+                        print("# Error in token index in message at line " + sIdAction + " ("+str(nToken)+" tokens only)")
+                if re.search("[.]\\w+[(]", sMsg):
+                    print("# Error in message at line " + sIdAction + ":  This message looks like code. Line should begin with =")
+            
+    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)
+        for x in re.finditer("group[(](\\d+)[)]", sAction):
+            if int(x.group(1)) > nToken:
+                print("# Error in token index in replacement at line " + sIdAction + " ("+str(nToken)+" tokens only)")
+    else:
+        for x in re.finditer(r"\\(\d+)", sAction):
+            if int(x.group(1)) > nToken:
+                print("# Error in token index in replacement at line " + sIdAction + " ("+str(nToken)+" tokens only)")
+        if re.search("[.]\\w+[(]|sugg\\w+[(]", sAction):
+            print("# Error in action at line " + sIdAction + ":  This action looks like code. Line should begin with =")
+
+    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(("g_s_"+sIdAction, sAction[1:]))
+            sAction = "=g_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, iStartAction, iEndAction, nPriority, 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(("g_p_"+sIdAction, sAction[1:]))
+            sAction = "=g_p_"+sIdAction
+        elif sAction.startswith('"') and sAction.endswith('"'):
+            sAction = sAction[1:-1]
+        return [sCondition, cAction, sAction, iStartAction, iEndAction]
+    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(("g_d_"+sIdAction, sAction))
+        sAction = "g_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 make (spLang, sLang, bJavaScript):
+    "compile rules, returns a dictionary of values"
+    # for clarity purpose, don’t create any file here
+
+    print("> read graph rules file...")
+    try:
+        lRules = open(spLang + "/rules_graph.grx", 'r', encoding="utf-8").readlines()
+    except:
+        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
+    lTest = []
+    lTokenLine = []
+    sActions = ""
+    nPriority = 4
+    dAllGraph = {}
+    sGraphName = ""
+
+    for i, sLine in enumerate(lRules, 1):
+        sLine = sLine.rstrip()
+        if "\t" in sLine:
+            # tabulation not allowed
+            print("Error. Tabulation at line: ", i)
+            exit()
+        if sLine.startswith('#END'):
+            # arbitrary end
+            printBookmark(0, "BREAK BY #END", i)
+            break
+        elif sLine.startswith("#"):
+            # comments
+            pass
+        elif sLine.startswith("GRAPH_NAME: "):
+            # Graph name
+            m = re.match("GRAPH_NAME: +([a-zA-Z_][a-zA-Z_0-9]*)+", 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 in", sLine.strip())
+                exit()
+        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("g{:<7}".format(i) + "  " + sLine[5:].strip())
+        elif sLine.startswith("TODO:"):
+            # todo
+            pass
+        elif sLine.startswith("!!"):
+            # bookmarks
+            m = re.search("^!!+", sLine)
+            nExMk = len(m.group(0))
+            if sLine[nExMk:].strip():
+                printBookmark(nExMk-2, sLine[nExMk:].strip(), i)
+        elif sLine.startswith("__") and sLine.endswith("__"):
+            # new rule group
+            m = re.match("__(\\w+)(!\\d|)__", sLine)
+            if m:
+                sRuleName = m.group(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.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, sActions, nPriority))
+            lTokenLine.clear()
+            sActions = ""
+            sRuleName = ""
+            nPriority = 4
+        elif sLine.startswith(("        ")):
+            # actions
+            sActions += " " + sLine.strip()
+        else:
+            lTokenLine.append([i, sLine.strip()])
+
+    # tests
+    print("  list tests...")
+    sGCTests = "\n".join(lTest)
+    sGCTestsJS = '{ "aData2": ' + json.dumps(lTest, ensure_ascii=False) + " }\n"
+
+    # processing rules
+    print("  preparing rules...")
+    for sGraphName, lRuleLine in dAllGraph.items():
+        lPreparedRule = []
+        for i, sRuleGroup, sTokenLine, sActions, nPriority in lRuleLine:
+            for lRule in createRule(i, sRuleGroup, sTokenLine, sActions, nPriority):
+                lPreparedRule.append(lRule)
+        # Show rules
+        for e in lPreparedRule:
+            print(e)
+        # Graph creation
+        oDARG = darg.DARG(lPreparedRule, sLang)
+        dAllGraph[sGraphName] = oDARG.createGraph()
+
+    # 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:
+        if sFuncName.startswith("g_c_"): # condition
+            sParams = "lToken, nTokenOffset, sCountry, bCondMemo"
+        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"
+
+    for sActionName, aAction in dACTIONS.items():
+        print(sActionName, aAction)
+
+    # Result
+    d = {
+        "graph_callables": sPyCallables,
+        "graph_gctests": sGCTests,
+        "rules_graphs": dAllGraph,
+        "rules_actions": dACTIONS
+    }
+
+    return d
+
+

Index: compile_rules_js_convert.py
==================================================================
--- compile_rules_js_convert.py
+++ compile_rules_js_convert.py
@@ -117,10 +117,13 @@
     return (sRegex, lNegLookBeforeRegex)
 
 
 def pyRuleToJS (lRule, dJSREGEXES, sWORDLIMITLEFT):
     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
@@ -132,25 +135,31 @@
 
 
 def writeRulesToJSArray (lRules):
     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):
     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,185 @@
+#!python3
+
+# RULE GRAPH BUILDER
+#
+# by Olivier R.
+# License: MPL 2
+
+
+import json
+import time
+import traceback
+
+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):
+        if aRule < self.aPreviousRule:
+            sys.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):
+        self.nNode = len(self.lMinimizedNodes)
+
+    def countArcs (self):
+        self.nArc = 0
+        for oNode in self.lMinimizedNodes:
+            self.nArc += len(oNode.dArcs)
+
+    def displayInfo (self):
+        print(" * {:<12} {:>16,}".format("Rules:", self.nRule))
+        print(" * {:<12} {:>16,}".format("Nodes:", self.nNode))
+        print(" * {:<12} {:>16,}".format("Arcs:", self.nArc))
+
+    def createGraph (self):
+        dGraph = { 0: self.oRoot.getNodeAsDict() }
+        print(0, "\t", self.oRoot.getNodeAsDict())
+        for oNode in self.lMinimizedNodes:
+            sHashId = oNode.__hash__() 
+            if sHashId not in dGraph:
+                dGraph[sHashId] = oNode.getNodeAsDict()
+                print(sHashId, "\t", dGraph[sHashId])
+            else:
+                print("Error. Double node… same id: ", sHashId)
+                print(str(oNode.getNodeAsDict()))
+        return dGraph
+
+
+
+class Node:
+    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):
+        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 = {}
+        dRules = {}
+        dLemmas = {}
+        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:
+                dLemmas[sArc[1:]] = oNode.__hash__()
+            elif sArc.startswith("##"):
+                dRules[sArc[1:]] = oNode.__hash__()
+            else:
+                dNode[sArc] = oNode.__hash__()
+        if dReValue:
+            dNode["<re_value>"] = dReValue
+        if dReMorph:
+            dNode["<re_morph>"] = dReMorph
+        if dLemmas:
+            dNode["<lemmas>"] = dLemmas
+        if dRules:
+            dNode["<rules>"] = dRules
+        #if self.bFinal:
+        #    dNode["<final>"] = 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/lang_core/gc_engine.py
==================================================================
--- gc_core/py/lang_core/gc_engine.py
+++ gc_core/py/lang_core/gc_engine.py
@@ -10,10 +10,23 @@
 
 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", \
             "ignoreRule", "resetIgnoreRules", "reactivateRule", "listRules", "displayRules" ]
@@ -31,30 +44,80 @@
 _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"):
+    global _oSpellChecker
+    global _sAppContext
+    global _dOptions
+    global _oTokenizer
+    global _createRegexError
+    global _createTokenError
+    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)<Grammalecte>"
 
 
 #### Parsing
 
 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
+    sRealText = sText
     dDA = {}        # Disambiguisator. Key = position; value = list of morphologies
     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, dDA, dPriority, sCountry, dOpt, bShowRuleId, bDebug, bContext)
         if sNew:
             sText = sNew
     except:
         raise
 
@@ -71,31 +134,46 @@
     # 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, dDA, dPriority, sCountry, dOpt, bShowRuleId, bDebug, bContext)
                 aErrors.update(errs)
             except:
                 raise
     return aErrors.values() # this is a view (iterable)
 
+
+_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 _proofread (s, sx, nOffset, bParagraph, dDA, dPriority, sCountry, dOptions, bDebug, bContext):
+def _proofread (oSentence, s, sx, nOffset, bParagraph, dDA, dPriority, sCountry, dOptions, bShowRuleId, bDebug, bContext):
     dErrs = {}
     bChange = False
-    bIdRule = option('idrule')
-
     for sOption, lRuleGroup in _getRules(bParagraph):
-        if not sOption or dOptions.get(sOption, False):
+        if sOption == "@@@@":
+            # graph rules
+            for sGraphName, sLineId in lRuleGroup:
+                if bDebug:
+                    print(sGraphName, sLineId)
+                bChange, errs = oSentence.parse(dAllGraph[sGraphName], dPriority, sCountry, dOptions, bShowRuleId, bDebug, bContext)
+                dErrs.update(errs)
+                if bChange:
+                    oSentence.rewrite()
+                    if bDebug:
+                        print("~", oSentence.sSentence)
+        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:
@@ -105,11 +183,11 @@
                                 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)
+                                            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
@@ -132,11 +210,11 @@
     if bChange:
         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,17 +236,14 @@
         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"
@@ -177,11 +252,11 @@
     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 +269,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,28 +291,26 @@
         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 <sRepl> in <sSentence> at <iGroup> 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):
     _aIgnoredRules.add(sRuleId)
 
@@ -261,45 +330,21 @@
             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):
     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):
     if sOpt in _dOptions:
         _dOptions[sOpt] = bVal
 
@@ -334,51 +379,17 @@
 
 
 def getSpellChecker ():
     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)<Grammalecte>"
-
 
 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 _dOptions.get(sOpt, False)
 
@@ -386,33 +397,25 @@
 def displayInfo (dDA, 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]]))
+    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):
     "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 = dDA[tWord[0]]  if tWord[0] in dDA  else _oSpellChecker.getMorph(tWord[1])
     if not lMorph:
         return False
     p = re.compile(sPattern)
     if bStrict:
         return all(p.search(s)  for s in lMorph)
@@ -421,13 +424,13 @@
 
 def morphex (dDA, 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 = dDA[tWord[0]]  if tWord[0] in dDA  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):
         return False
     # search sPattern
@@ -435,46 +438,41 @@
     return any(p.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):
-        return False
-    if not _dAnalyses[sWord]:
+    lMorph = _oSpellChecker.getMorph(sWord)
+    if not lMorph:
         return False
     p = 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(p.search(s)  for s in lMorph)
+    return any(p.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]):
+    if any(np.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] ]
+    return any(p.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
@@ -534,52 +532,408 @@
 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")))
+    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):
+            dDA[nPos] = lSelect
     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")))
+    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):
+            dDA[nPos] = lSelect
     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
 
 
 #### GRAMMAR CHECKER PLUGINS
 
 ${plugins}
 
 
+
+#### TOKEN SENTENCE CHECKER
+
+class TokenSentence:
+
+    def __init__ (self, sSentence, sSentence0, nOffset):
+        self.sSentence = sSentence
+        self.sSentence0 = sSentence0
+        self.nOffset = nOffset
+        self.lToken = list(_oTokenizer.genTokens(sSentence, True))
+        self.createError = self._createWriterError  if _bWriterError  else self._createDictError
+
+    def _getNextMatchingNodes (self, dToken, dGraph, dNode):
+        "generator: return nodes where <dToken> “values” match <dNode> arcs"
+        # token value
+        if dToken["sValue"] in dNode:
+            #print("value found: ", dToken["sValue"])
+            yield dGraph[dNode[dToken["sValue"]]]
+        # token lemmas
+        if "<lemmas>" in dNode:
+            for sLemma in _oSpellChecker.getLemma(dToken["sValue"]):
+                if sLemma in dNode["<lemmas>"]:
+                    #print("lemma found: ", sLemma)
+                    yield dGraph[dNode["<lemmas>"][sLemma]]
+        # universal arc
+        if "*" in dNode:
+            #print("generic arc")
+            yield dGraph[dNode["*"]]
+        # regex value arcs
+        if "<re_value>" in dNode:
+            for sRegex in dNode["<re_value>"]:
+                if re.search(sRegex, dToken["sValue"]):
+                    #print("value regex matching: ", sRegex)
+                    yield dGraph[dNode["<re_value>"][sRegex]]
+        # regex morph arcs
+        if "<re_morph>" in dNode:
+            for sRegex in dNode["<re_morph>"]:
+                if "¬" not in sRegex:
+                    # no anti-pattern
+                    if any(re.search(sRegex, sMorph)  for sMorph in _oSpellChecker.getMorph(dToken["sValue"])):
+                        yield dGraph[dNode["<re_morph>"][sRegex]]
+                else:
+                    # there is an anti-pattern
+                    sPattern, sNegPattern = sRegex.split("¬", 1)
+                    if sNegPattern == "*":
+                        # all morphologies must match with <sPattern>
+                        if all(re.search(sPattern, sMorph)  for sMorph in _oSpellChecker.getMorph(dToken["sValue"])):
+                            yield dGraph[dNode["<re_morph>"][sRegex]]
+                    else:
+                        if sNegPattern and any(re.search(sNegPattern, sMorph)  for sMorph in _oSpellChecker.getMorph(dToken["sValue"])):
+                            continue
+                        if any(re.search(sPattern, sMorph)  for sMorph in _oSpellChecker.getMorph(dToken["sValue"])):
+                            yield dGraph[dNode["<re_morph>"][sRegex]]
+
+    def parse (self, dGraph, dPriority, sCountry="${country_default}", dOptions=None, bShowRuleId=False, bDebug=False, bContext=False):
+        dErr = {}
+        dPriority = {}  # Key = position; value = priority
+        dOpt = _dOptions  if not dOptions  else dOptions
+        lPointer = []
+        bChange = False
+        for dToken in self.lToken:
+            # check arcs for each existing pointer
+            lNextPointer = []
+            for dPointer in lPointer:
+                for dNode in self._getNextMatchingNodes(dToken, dGraph, dPointer["dNode"]):
+                    lNextPointer.append({"iToken": dPointer["iToken"], "dNode": dNode})
+            lPointer = lNextPointer
+            # check arcs of first nodes
+            for dNode in self._getNextMatchingNodes(dToken, dGraph, dGraph[0]):
+                lPointer.append({"iToken": dToken["i"], "dNode": dNode})
+            # check if there is rules to check for each pointer
+            for dPointer in lPointer:
+                if "<rules>" in dPointer["dNode"]:
+                    bHasChanged, errs = self._executeActions(dGraph, dPointer["dNode"]["<rules>"], dPointer["iToken"]-1, dPriority, dOpt, sCountry, bShowRuleId, bDebug, bContext)
+                    dErr.update(errs)
+                    if bHasChanged:
+                        bChange = True
+        return (bChange, dErr)
+
+    def _executeActions (self, dGraph, dNode, nTokenOffset, dPriority, dOpt, sCountry, bShowRuleId, bDebug, bContext):
+        "execute actions found in the DARG"
+        dErrs = {}
+        bChange = False
+        for sLineId, nextNodeKey in dNode.items():
+            for sRuleId in dGraph[nextNodeKey]:
+                bCondMemo = None
+                sFuncCond, cActionType, sWhat, *eAct = dRule[sRuleId]
+                # action in lActions: [ condition, action type, replacement/suggestion/action[, iTokenStart, iTokenEnd[, nPriority, message, URL]] ]
+                try:
+                    bCondMemo = not sFuncCond or globals()[sFuncCond](self.lToken, nTokenOffset, sCountry, bCondMemo)
+                    if bCondMemo:
+                        if cActionType == "-":
+                            # grammar error
+                            nTokenErrorStart = nTokenOffset + eAct[0]
+                            nTokenErrorEnd = nTokenOffset + eAct[1]
+                            nErrorStart = self.nOffset + self.lToken[nTokenErrorStart]["nStart"]
+                            nErrorEnd = self.nOffset + self.lToken[nTokenErrorEnd]["nEnd"]
+                            if nErrorStart not in dErrs or eAct[2] > dPriority[nErrorStart]:
+                                dErrs[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, dErrs[nErrorStart])
+                        elif cActionType == "~":
+                            # text processor
+                            self._tagAndPrepareTokenForRewriting(sWhat, nTokenOffset + eAct[0], nTokenOffset + eAct[1])
+                            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
+                        else:
+                            print("# error: unknown action at " + sLineId)
+                    elif cActionType == ">":
+                        if bDebug:
+                            print(">!", sRuleId)
+                        break
+                except Exception as e:
+                    raise Exception(str(e), sLineId)
+        return bChange, dErrs
+
+    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)
+            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:
+            p = PropertyValue()
+            p.Name = "FullCommentURL"
+            p.Value = sURL
+            xErr.aProperties = (p,)
+        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)
+            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):
+        "text processor: rewrite tokens between <nTokenRewriteStart> and <nTokenRewriteEnd> position"
+        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
+        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):
+        "rewrite the sentence, modify tokens, purge the token list"
+        lNewToken = []
+        for i, dToken in enumerate(self.lToken):
+            if "bToRemove" in dToken:
+                # remove useless token
+                self.sSentence = self.sSentence[:self.nOffset+dToken["nStart"]] + " " * (dToken["nEnd"] - dToken["nStart"]) + self.sSentence[self.nOffset+dToken["nEnd"]:]
+                #print("removed:", dToken["sValue"])
+            else:
+                lNewToken.append(dToken)
+                if "sNewValue" in dToken:
+                    # rewrite token and sentence
+                    #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[:self.nOffset+dToken["nStart"]] + sNewRepl + self.sSentence[self.nOffset+dToken["nEnd"]:]
+                    del dToken["sNewValue"]
+        self.lToken.clear()
+        self.lToken = lNewToken
+
+
+
+#### Analyse tokens
+
+def g_morph (dToken, sPattern, sNegPattern=""):
+    "analyse a token, return True if <sNegPattern> not in morphologies and <sPattern> 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 <sNegPattern> not in morphologies and <sPattern> 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)
+
+
+
+#### Disambiguator
+
+def g_select (dToken, sPattern, lDefault=None):
+    "select morphologies for <dToken> according to <sPattern>, 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 <dToken> according to <sPattern>, 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 <dToken>, always return True"
+    dToken["lMorph"] = lMorph
+    #print("DA:", dToken["sValue"], lMorph)
+    return True
+
+
+#### CALLABLES FOR REGEX RULES (generated code)
+
 ${callables}
+
+
+#### CALLABLES FOR GRAPH RULES (generated code)
+
+${graph_callables}

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,5 @@
+# generated code, do not edit
+
+dAllGraph = ${rules_graphs}
+
+dRule = ${rules_actions}

ADDED   gc_core/py/lang_core/gc_sentence.py
Index: gc_core/py/lang_core/gc_sentence.py
==================================================================
--- /dev/null
+++ gc_core/py/lang_core/gc_sentence.py
@@ -0,0 +1,237 @@
+# Sentence checker
+
+from ..graphspell.tokenizer import Tokenizer
+from .gc_rules_graph import dGraph
+
+
+oTokenizer = Tokenizer("${lang}")
+
+
+class TokenSentence:
+
+    def __init__ (self, sSentence, sSentence0, nOffset):
+        self.sSentence = sSentence
+        self.sSentence0 = sSentence0
+        self.nOffset = nOffset
+        self.lToken = list(oTokenizer.genTokens())
+
+    def parse (self):
+        dErr = {}
+        lPointer = []
+        for dToken in self.lToken:
+            for i, dPointer in enumerate(lPointer):
+                bValid = False
+                for dNode in self._getNextMatchingNodes(dToken, dPointer["dNode"]):
+                    dPointer["nOffset"] = dToken["i"]
+                    dPointer["dNode"] = dNode
+                    bValid = True
+                if not bValid:
+                    del lPointer[i]
+            for dNode in self._getNextMatchingNodes(dToken, dGraph):
+                lPointer.append({"nOffset": 0, "dNode": dNode})
+            for dPointer in lPointer:
+                if "<rules>" in dPointer["dNode"]:
+                    for dNode in dGraph[dPointer["dNode"]["<rules>"]]:
+                        dErr = self._executeActions(dNode, nOffset)
+        return dErr
+
+    def _getNextMatchingNodes (self, dToken, dNode):
+        # token value
+        if dToken["sValue"] in dNode:
+            yield dGraph[dNode[dToken["sValue"]]]
+        # token lemmas
+        for sLemma in dToken["lLemma"]:
+            if sLemma in dNode:
+                yield dGraph[dNode[sLemma]]
+        # universal arc
+        if "*" in dNode:
+            yield dGraph[dNode["*"]]
+        # regex arcs
+        if "~" in dNode:
+            for sRegex in dNode["~"]:
+                for sMorph in dToken["lMorph"]:
+                    if re.search(sRegex, sMorph):
+                        yield dGraph[dNode["~"][sRegex]]
+
+    def _executeActions (self, dNode, nOffset):
+        for sLineId, nextNodeKey in dNode.items():
+            for sArc in dGraph[nextNodeKey]:
+                bCondMemo = None
+                sFuncCond, cActionType, sWhat, *eAct = dRule[sArc]
+                # action in lActions: [ condition, action type, replacement/suggestion/action[, iGroupStart, iGroupEnd[, message, URL]] ]
+                try:
+                    bCondMemo = not sFuncCond or globals()[sFuncCond](self, sCountry, bCondMemo)
+                    if bCondMemo:
+                        if cActionType == "-":
+                            # grammar error
+                            nErrorStart = nSentenceOffset + m.start(eAct[0])
+                            nErrorEnd = nSentenceOffset + m.start(eAct[1])
+                            if nErrorStart not in dErrs or nPriority > dPriority[nErrorStart]:
+                                dErrs[nErrorStart] = _createError(self, sWhat, nErrorStart, nErrorEnd, sLineId, bUppercase, eAct[2], eAct[3], bIdRule, sOption, bContext)
+                                dPriority[nErrorStart] = nPriority
+                        elif cActionType == "~":
+                            # text processor
+                            self._rewrite(sWhat, nErrorStart, nErrorEnd)
+                        elif cActionType == "@":
+                            # jump
+                            self._jump(sWhat)
+                        elif cActionType == "=":
+                            # disambiguation
+                            globals()[sWhat](self.lToken)
+                        elif cActionType == ">":
+                            # we do nothing, this test is just a condition to apply all following actions
+                            pass
+                        else:
+                            print("# error: unknown action at " + sLineId)
+                    elif cActionType == ">":
+                        break
+                except Exception as e:
+                    raise Exception(str(e), "# " + sLineId + " # " + sRuleId)
+
+    def _createWriterError (self):
+        d = {}
+        return d
+
+    def _createDictError (self):
+        d = {}
+        return d
+
+    def _rewrite (self, sWhat, nErrorStart, nErrorEnd):
+        "text processor: rewrite tokens between <nErrorStart> and <nErrorEnd> position"
+        lTokenValue = sWhat.split("|")
+        if len(lTokenValue) != (nErrorEnd - nErrorStart + 1):
+            print("Error. Text processor: number of replacements != number of tokens.")
+            return
+        for i, sValue in zip(range(nErrorStart, nErrorEnd+1), lTokenValue):
+            self.lToken[i]["sValue"] = sValue
+
+    def _jump (self, sWhat):
+        try:
+            nFrom, nTo = sWhat.split(">")
+            self.lToken[int(nFrom)]["iJump"] = int(nTo)
+        except:
+            print("# Error. Jump failed: ", sWhat)
+            traceback.print_exc()
+            return
+
+
+#### Analyse tokens
+
+def g_morph (dToken, sPattern, bStrict=True):
+    "analyse a token, return True if <sPattern> in morphologies"
+    if "lMorph" in dToken:
+        lMorph = dToken["lMorph"]
+    else:
+        if dToken["sValue"] not in _dAnalyses and not _storeMorphFromFSA(dToken["sValue"]):
+            return False
+        if not _dAnalyses[dToken["sValue"]]:
+            return False
+        lMorph = _dAnalyses[dToken["sValue"]]
+    zPattern = re.compile(sPattern)
+    if bStrict:
+        return all(zPattern.search(sMorph)  for sMorph in lMorph)
+    return any(zPattern.search(sMorph)  for sMorph in lMorph)
+
+def g_morphex (dToken, sPattern, sNegPattern):
+    "analyse a token, return True if <sNegPattern> not in morphologies and <sPattern> in morphologies"
+    if "lMorph" in dToken:
+        lMorph = dToken["lMorph"]
+    else:
+        if dToken["sValue"] not in _dAnalyses and not _storeMorphFromFSA(dToken["sValue"]):
+            return False
+        if not _dAnalyses[dToken["sValue"]]:
+            return False
+        lMorph = _dAnalyses[dToken["sValue"]]
+    # check negative condition
+    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, bStrict=True):
+    "analyse a token, return True if <sPattern> in morphologies (disambiguation off)"
+    if dToken["sValue"] not in _dAnalyses and not _storeMorphFromFSA(dToken["sValue"]):
+        return False
+    if not _dAnalyses[dToken["sValue"]]:
+        return False
+    zPattern = re.compile(sPattern)
+    if bStrict:
+        return all(zPattern.search(sMorph)  for sMorph in _dAnalyses[dToken["sValue"]])
+    return any(zPattern.search(sMorph)  for sMorph in _dAnalyses[dToken["sValue"]])
+
+
+def g_analysex (dToken, sPattern, sNegPattern):
+    "analyse a token, return True if <sNegPattern> not in morphologies and <sPattern> in morphologies (disambiguation off)"
+    if dToken["sValue"] not in _dAnalyses and not _storeMorphFromFSA(dToken["sValue"]):
+        return False
+    if not _dAnalyses[dToken["sValue"]]:
+        return False
+    # check negative condition
+    zNegPattern = re.compile(sNegPattern)
+    if any(zNegPattern.search(sMorph)  for sMorph in _dAnalyses[dToken["sValue"]]):
+        return False
+    # search sPattern
+    zPattern = re.compile(sPattern)
+    return any(zPattern.search(sMorph)  for sMorph in _dAnalyses[dToken["sValue"]])
+
+
+#### Go outside the rule scope
+
+def g_nextToken (i):
+    pass
+
+def g_prevToken (i):
+    pass
+
+def g_look ():
+    pass
+
+def g_lookAndCheck ():
+    pass
+
+
+#### Disambiguator
+
+def g_select (dToken, sPattern, lDefault=None):
+    "select morphologies for <dToken> according to <sPattern>, always return True"
+    if dToken["sValue"] not in _dAnalyses and not _storeMorphFromFSA(dToken["sValue"]):
+        return True
+    if len(_dAnalyses[dToken["sValue"]]) == 1:
+        return True
+    lMorph = dToken["lMorph"] or _dAnalyses[dToken["sValue"]]
+    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
+    return True
+
+
+def g_exclude (dToken, sPattern, lDefault=None):
+    "select morphologies for <dToken> according to <sPattern>, always return True"
+    if dToken["sValue"] not in _dAnalyses and not _storeMorphFromFSA(dToken["sValue"]):
+        return True
+    if len(_dAnalyses[dToken["sValue"]]) == 1:
+        return True
+    lMorph = dToken["lMorph"] or _dAnalyses[dToken["sValue"]]
+    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
+    return True
+
+
+def g_define (dToken, lMorph):
+    "set morphologies of <dToken>, always return True"
+    dToken["lMorph"] = lMorph
+    return True
+
+
+#### CALLABLES (generated code)
+
+${graph_callables}

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
@@ -56,11 +56,11 @@
 
 
 def getSimil (sWord, sMorph, bSubst=False):
     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

Index: gc_lang/fr/modules/gce_analyseur.py
==================================================================
--- gc_lang/fr/modules/gce_analyseur.py
+++ gc_lang/fr/modules/gce_analyseur.py
@@ -15,63 +15,58 @@
         return "nous"
     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 s2 == "elle" or s2 == "elles":    
+        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)
+    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)
+    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,15 +77,14 @@
         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)
+    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)
 
 

Index: gc_lang/fr/modules/gce_suggestions.py
==================================================================
--- gc_lang/fr/modules/gce_suggestions.py
+++ gc_lang/fr/modules/gce_suggestions.py
@@ -7,17 +7,17 @@
 
 ## Verbs
 
 def suggVerb (sFlex, sWho, funcSugg2=None):
     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")
@@ -40,11 +40,11 @@
     return ""
 
 
 def suggVerbPpas (sFlex, sWhat=None):
     aSugg = set()
-    for sStem in stem(sFlex):
+    for sStem in _oSpellChecker.getLemma(sFlex):
         tTags = conj._getTags(sStem)
         if tTags:
             if not sWhat:
                 aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q1"))
                 aSugg.add(conj._getConjWithTags(sStem, tTags, ":PQ", ":Q2"))
@@ -83,21 +83,21 @@
     return ""
 
 
 def suggVerbTense (sFlex, sTense, sWho):
     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):
     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,11 +108,11 @@
         return "|".join(aSugg)
     return ""
 
 
 def suggVerbInfi (sFlex):
-    return "|".join([ sStem  for sStem in stem(sFlex)  if conj.isVerb(sStem) ])
+    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"]
@@ -131,11 +131,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 +147,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 +192,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 +219,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 +249,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 +274,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 +298,32 @@
         return "|".join(aSugg)
     return ""
 
 
 def hasFemForm (sFlex):
-    for sStem in stem(sFlex):
+    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):
+    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
     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 +334,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 +351,12 @@
         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
     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:
@@ -373,13 +368,12 @@
     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 ""
@@ -392,12 +386,11 @@
         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, []) ):
+    if any( ":p" in sMorph  for sMorph in _oSpellChecker.getMorph(sWord) ):
         return "les|la"
     return "la"
 
 
 _zBinary = re.compile("^[01]+$")

Index: gc_lang/fr/rules.grx
==================================================================
--- gc_lang/fr/rules.grx
+++ gc_lang/fr/rules.grx
@@ -682,11 +682,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
@@ -1232,11 +1232,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.
@@ -1751,11 +1751,11 @@
 
 
 # est-ce … ?
 __[i]/tu(tu_est_ce)__
     (?<![cCdDlL][’'])(est ce) ({w_2})  @@0,$
-    <<- morphex(\2, ":", ":N.*:[me]:[si]|>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}} ?
@@ -1890,11 +1890,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 "
 
@@ -1933,11 +1933,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.
@@ -1956,11 +1956,11 @@
 
 !!!! A / À: accentuation la préposition en début de phrase                                          
 
 __<s]/typo(typo_À_début_phrase1)__
     ^ *(A) (?!t[’-](?:ils?|elles?|on))({w_2})  @@*,$
-    <<- morphex(\2, ":[GNAY]", ":(?:Q|3s)|>(?: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.
 __<s>/typo(typo_À_début_phrase2)__
     ^ *(A) [ldnms]’  @@*  <<- -1>> À                                                                # S’il s’agit de la préposition « à », il faut accentuer la majuscule.
 __<s>/typo(typo_À_début_phrase3)__
@@ -1994,15 +1994,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)__
@@ -2044,11 +2044,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
@@ -2196,11 +2196,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
@@ -2459,11 +2459,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.
@@ -2687,11 +2687,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
@@ -2772,13 +2772,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.
@@ -2785,13 +2785,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.
@@ -2824,11 +2824,11 @@
 
 
 # avoir été
 __[i]/bs(bs_avoir_été_chez)__
     (?<!l’)({avoir}) été chez  @@0
-    <<- not re.search("(?i)^avoir$", \1) and morph(\1, ">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.
 
@@ -2841,11 +2841,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}}
@@ -2921,49 +2921,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.
@@ -2970,19 +2970,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
 
@@ -3053,11 +3053,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
@@ -3064,11 +3064,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
@@ -3273,19 +3273,19 @@
 TEST: plus d’une sont parties aussi vite qu’elles étaient venues
 
 
 __[i]/conf(conf_il_on_pas_verbe)__
     (?<!t’)(?:il|on) (?:l’|l(?:es?|a|eur|ui) +|[nv]ous +|)({w_2}) @@$
-    <<- morphex(\1, ":", ":(?:[123][sp]|O[onw]|X)|ou ") and morphex(word(-1), ":", ":3s", True)
+    <<- morphex(\1, ":", ":(?:[123][sp]|O[onw]|X)|>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)__
     (?<!t’)ils (?:l’|l(?:es?|a|eur|ui) +|[nv]ous +|)({w_2}) @@$
-    <<- morphex(\1, ":", ":(?:[123][sp]|O[onw]|X)|ou ") and morphex(word(-1), ":", ":3p", True)
+    <<- morphex(\1, ":", ":(?:[123][sp]|O[onw]|X)|>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}}
 
@@ -3331,11 +3331,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.
@@ -3385,11 +3385,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
@@ -3407,11 +3407,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 :
@@ -3433,11 +3433,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 :
@@ -3570,11 +3570,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.
@@ -3581,11 +3581,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.
 
 
@@ -3630,11 +3630,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 :
 
@@ -3653,11 +3653,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
@@ -3674,21 +3674,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.
@@ -3733,11 +3733,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
@@ -3772,11 +3772,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}}.
@@ -3837,15 +3837,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) ") and not morph(word(1), ">(?:financi[eè]re?|pécuni(?:er|aire))s? ", False, False)
+    <<- morphex(\1, ":V", ":Q|>(?:profiter|bénéficier)/") and not morph(word(1), ">(?:financi[eè]re?|pécuni(?:er|aire))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))s? ", False, False)
+    <<- not morph(\1, ">(?:profiter|bénéficier)/", False) and not morph(word(1), ">(?:financi[eè]re?|pécuni(?:er|aire))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”…
 
@@ -3921,11 +3921,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.
@@ -3956,15 +3956,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.
@@ -4019,11 +4019,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 ?
@@ -4071,11 +4071,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.
 
@@ -4205,11 +4205,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.
@@ -4224,14 +4224,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.
@@ -4240,11 +4240,11 @@
 
 
 # quand / quant / qu’en
 __[i]/conf(conf_quant_à)__
     (?<![dD]e )(quand) (?:à|aux?)  @@0
-    <<- not morph(word(-1), ">(?: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)__
@@ -4281,12 +4281,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) -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) -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) >>>
     <<- \1.endswith("e") -1>> qu’elle                                                               # Confusion. Le sujet “elle” doit être séparée de la conjonction “que”. 3
     <<- __else__ and \1.endswith("s") -1>> qu’elles                                                 # Confusion. Le sujet “elles” doit être séparée de la conjonction “que”. 4
@@ -4342,12 +4342,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”.
@@ -4423,11 +4423,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
 
@@ -4440,17 +4440,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.
@@ -4482,11 +4482,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.
@@ -4495,27 +4495,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}}.
@@ -4700,41 +4700,41 @@
 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)
+    <<- 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)
+    <<- 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.
 
 TEST: {{tous}} ces {{idiotes}}
 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)
+    <<- 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)
+    <<- 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)
+    <<- 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)
+    <<- morph(\1, ":[NAQ].*:[ms]") and morph(word(-1), ":R|>de/", False, True)
     -1>> =suggFemPlur(@, True)                                                                      # “\1” devrait être au féminin pluriel.
 
 TEST: Tout {{hommes}}
 TEST: De tous {{âge}} !
 TEST: avec toutes {{femme}}                                   ->> femmes
@@ -4793,11 +4793,11 @@
     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)
+    <<- 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é}}
 TEST: ne rien {{finit}}
 TEST: ne jamais plus s’y {{frottait}}
@@ -5273,11 +5273,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>> *
@@ -5420,15 +5420,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>> *
@@ -5438,11 +5438,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
@@ -5528,11 +5528,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>> *
@@ -5561,11 +5561,11 @@
 __[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>> *
@@ -5692,94 +5692,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[mnp]tes?))  @@0,$,$
-    <<- morph(\1, ">laisser ", False) >>>
+    <<- morph(\1, ">laisser/", False) >>>
     <<- \2 != "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}}.
@@ -5873,11 +5873,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.
@@ -5928,15 +5928,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.
@@ -6019,15 +6019,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.
@@ -6129,11 +6129,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.
 __<i]/gn(gn_leur_accord3)__
     ^ *(leur) +({w_2})  @@*,$
     <<- morphex(\2, ":[NAQ].*:p", ":[siGW]") -1>> leurs                                             # Accord de nombre erroné avec « \1 ».
@@ -6168,11 +6168,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]")
@@ -6457,11 +6457,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
 
 
@@ -6594,15 +6594,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
@@ -6623,15 +6623,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}}
@@ -6655,15 +6655,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…
@@ -6681,15 +6681,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.
 
@@ -6706,16 +6706,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}}
 
@@ -6734,15 +6734,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.
 
@@ -6759,15 +6759,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}} ?
@@ -6789,16 +6789,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é}}
@@ -6819,16 +6819,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.
 
@@ -6847,11 +6847,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? +$")) )
@@ -6877,11 +6877,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? +$")) )
@@ -6907,11 +6907,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]"))
@@ -6937,11 +6937,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]"))
@@ -6958,16 +6958,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}}
@@ -7231,11 +7231,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.
@@ -7251,14 +7251,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.
@@ -7313,20 +7313,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.
 
 
@@ -7338,11 +7338,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}}.
 
 
@@ -7369,11 +7369,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é)__
@@ -7465,11 +7465,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.
@@ -7503,19 +7503,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.
@@ -7550,21 +7550,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
@@ -7584,11 +7584,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.
 
 
@@ -7608,14 +7608,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}}.
 
@@ -7651,11 +7651,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)__
@@ -7698,15 +7698,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}}
@@ -7782,11 +7782,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
@@ -7810,11 +7810,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}}.
 
 
@@ -7928,15 +7928,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.
 
@@ -7945,14 +7945,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).
 
@@ -7982,16 +7982,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.
@@ -7998,21 +7998,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.
@@ -8020,11 +8020,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.
@@ -8097,11 +8097,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.
 
 
@@ -8251,17 +8251,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}} ?
 
@@ -8284,11 +8284,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à !
 
 
@@ -8302,16 +8302,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
@@ -8474,11 +8474,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
 
@@ -8488,11 +8488,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
@@ -8509,19 +8509,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}}.
@@ -8666,11 +8666,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}}.
 
 
@@ -8728,20 +8728,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}}.
 
 
@@ -8762,11 +8762,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
@@ -8819,11 +8819,11 @@
 
 
 # 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")
@@ -8839,14 +8839,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}}.
@@ -8857,11 +8857,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.
 
@@ -8879,11 +8879,11 @@
 # 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}}.
@@ -9048,11 +9048,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.
@@ -9187,11 +9187,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.
 
@@ -9219,11 +9219,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 ?
@@ -9239,41 +9239,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.
@@ -9280,11 +9280,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.
 
@@ -9320,11 +9320,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>> *
@@ -9341,11 +9341,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})  @@$
@@ -9395,11 +9395,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() ~>> *
@@ -9439,11 +9439,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.
@@ -9518,11 +9518,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}}.
 
 
 
@@ -9529,11 +9529,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.
@@ -9596,11 +9596,11 @@
 TEST: de me le {{facturez}}
 
 
 __[i]/infi(infi_faire_vouloir)__
     ((?:fai|f[iî]|fer|fon|v[oe]u)\w+) +({w_2}(?:ée?s?|ez))  @@0,$
-    <<- morph(\1, ">(?:faire|vouloir) ", False) and not before(r"(?i)\b(?:en|[mtsld]es?|[nv]ous|un) +$") and morphex(\2, ":V", ":M")
+    <<- morph(\1, ">(?:faire|vouloir)/", False) and not before(r"(?i)\b(?:en|[mtsld]es?|[nv]ous|un) +$") and morphex(\2, ":V", ":M")
         and not (re.search("(?i)^(?:fait|vouloir)$", \1) and \2.endswith("é"))
         and not (re.search("(?i)^(?:fait|vouloir)s$", \1) and \2.endswith("és"))
     -2>> =suggVerbInfi(@)                                                                           # Le verbe devrait être à l’infinitif.
 
 TEST: Tu fais {{décoloré}} tes cheveux ?
@@ -9613,11 +9613,11 @@
 TEST: fait pourtant avéré et corroboré par le même sondage.
 
 
 __[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.
 
 
@@ -9629,11 +9629,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
 
@@ -9667,34 +9667,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.
@@ -9729,54 +9729,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.
@@ -9802,11 +9802,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.
@@ -9815,11 +9815,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.
@@ -9835,11 +9835,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
@@ -9854,21 +9854,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.
@@ -9878,11 +9878,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}}.
@@ -9897,13 +9897,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
@@ -9911,23 +9911,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
@@ -9935,31 +9935,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
@@ -9974,11 +9974,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
@@ -10018,11 +10018,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}}
@@ -10031,11 +10031,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}}
@@ -10042,11 +10042,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}}
@@ -10055,11 +10055,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.
@@ -10066,11 +10066,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é}}
@@ -10077,40 +10077,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)__
     (?<![nN][oO][uU][sS] )nous +(?: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,$
-    <<- 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)
+    <<- 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}}
@@ -10196,19 +10196,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.
@@ -10216,11 +10216,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.
 
 
@@ -10249,27 +10249,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}} ?
@@ -10312,37 +10312,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.
@@ -10349,84 +10349,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}}
@@ -10458,15 +10458,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.
@@ -10478,11 +10478,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.
@@ -10490,11 +10490,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
 
@@ -10541,11 +10541,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.
 
@@ -10589,11 +10589,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.
@@ -10832,11 +10832,11 @@
     <<- morph(\2, ":(?:[123][sp]|P)", False) =>> select(\2,":(?:[123][sp]|P)")
     <<- ~1>> *
 __<i](p_premier_ne_pro_per_obj2)__
     ^( *ne (?:[mt]’|l(?:ui|eur) )en) ({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>> *
 __<i](p_premier_ne_pro_per_obj3)__
     ^( *ne (?:[mt]e|[nv]ous) (?:les?|la|en)) ({w_2})  @@0,$
     <<- morph(\2, ":(?:[123][sp]|P)", False) =>> select(\2,":(?:[123][sp]|P)")
     <<- ~1>> *
 __<i](p_premier_ne_pro_per_obj4)__
@@ -10844,19 +10844,19 @@
     <<- morph(\2, ":(?:[123][sp]|P)", False) =>> select(\2,":(?:[123][sp]|P)")
     <<- ~1>> *
 __<i>(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>> *
 __<i>(p_premier_ne_pro_per_obj6)__
     ^( *ne l’)({w_2})  @@0,$
     <<- morph(\2, ":(?:[123][sp]|P)", False) =>> select(\2,":(?:[123][sp]|P)")
     <<- ~1>> *
 __<i>(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.
@@ -11044,11 +11044,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 ", False) and morphex(\3, ":(?:Y|X|Oo)", ":[NAB]")
+    <<- morph(\2, ">laisser/", 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…
@@ -11056,11 +11056,11 @@
 TEST: Laissez la peste leur pourrir la vie encore quelque temps.
 
 
 __<i]/imp(imp_apostrophe_m_t_en)__
     ([ -][mt])-en @@0
-    <<- not (\0.endswith("t-en") and before(r"(?i)\bva$") and morph(word(1), ">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
 
 
@@ -11240,11 +11240,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)__
@@ -11310,11 +11310,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.
@@ -11322,11 +11322,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.
@@ -11364,11 +11364,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.
@@ -11378,11 +11378,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é}}
 
@@ -11395,11 +11395,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.
 
 
@@ -11412,11 +11412,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.
 
 
@@ -11450,11 +11450,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)) >>>
@@ -11560,11 +11560,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)
@@ -11864,11 +11864,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)
@@ -11982,11 +11982,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)")
@@ -12169,11 +12169,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)__
     (?<![cC]’)({w_2}[td]) elle  @@0
     <<- morphex(\1, ":V.*:3s", ":[GNW]") and not before(r"(?i)\b(?:ce|il|elle|on) +$") and morphex(word(1), ":", ":3s", True)
     ->> \1-elle                                                                                     # Forme interrogative ? Mettez un trait d’union.
@@ -12181,11 +12181,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)__
     (?<![cC]’)({w_1}nt) (?:ils|elles)  @@0
     <<- morphex(\1, ":V.*:3p", ":[GNW]") and not before(r"(?i)\b(?:ce|ils|elles) +$") and morphex(word(1), ":", ":3p", True)
     ->> =\0.replace(" ", "-")                                                                       # Forme interrogative ? Mettez un trait d’union.
@@ -12253,20 +12253,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à ?
 
@@ -12285,14 +12285,14 @@
 
 !!!! 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.
@@ -12307,11 +12307,11 @@
 
 # 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.
+    <<- morphex(\2, ":[YX]|>(?:y|ne|que?)/", ":R") and isStart() -1>> \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.
@@ -12319,11 +12319,11 @@
 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)
+    <<- 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… »
 
 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.
@@ -12360,13 +12360,13 @@
 
 
 # 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)
+    <<- 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))
+        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.
 
 TEST: Il suffit qu’il {{court}} plus
 TEST: Je veux qu’il {{finit}} son repas.
 TEST: quoi qu’il en {{conclut}}
@@ -12380,12 +12380,12 @@
 
 
 # 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) )
+    <<- 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.
 
 TEST: Il ne le savait pas, bien qu’il en {{avait}} entendu parler.
 TEST: Bien que je {{prends}} mon mal en patience.
@@ -12442,10 +12442,14 @@
     # 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.
 
+
+
+@@@@GRAPH: test_graph@@@@
+
 
 
 !!
 !!
 !!
@@ -16514,13 +16518,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…

ADDED   gc_lang/fr/rules_graph.grx
Index: gc_lang/fr/rules_graph.grx
==================================================================
--- /dev/null
+++ gc_lang/fr/rules_graph.grx
@@ -0,0 +1,114 @@
+#
+#   RÈGLES DE GRAMMAIRE FRANÇAISE POUR GRAMMALECTE
+#   par Olivier R.
+#
+#   Copyright © 2011-2017.
+#
+#   This file is part of Grammalecte.
+#
+#   Grammalecte is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   Grammalecte is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with Grammalecte.  If not, see <http://www.gnu.org/licenses/>
+#
+
+# RÈGLES POUR LE  GRAPHE DE TOKENS
+
+# DOCUMENTATION
+# Expressions régulières en Python : http://docs.python.org/library/re.html
+
+# [++] : séparateur des règles pour le paragraphe et des règles pour la phrase.
+
+# Types d’action:
+#   ->> erreur
+#   ~>> préprocesseur de texte
+#   =>> désambiguïsateur
+
+
+# Fin d’interprétation du fichier avec une ligne commençant par #END
+
+# ERREURS COURANTES
+# http://fr.wikipedia.org/wiki/Wikip%C3%A9dia:Fautes_d%27orthographe/Courantes
+
+
+GRAPH_NAME: test_graph                                                                              
+
+__da1__
+    ne >donner
+        <<- =>> select(\2, ":V")
+
+TEST: je ne donne rien.
+
+
+__da2__
+    je >pousser
+        <<- =>> exclude(\2, ":N")
+
+TEST: je pousse
+
+
+__da3__
+    il >venir
+        <<- =>> define(\2, [":VVV"])
+
+TEST: il vient
+
+
+__pp__
+    >avoir  marre  [d’|des|du|de]
+        <<- ~>> *
+
+TEST: J’en ai marre de ces gens-là.
+
+
+__pp2__
+    il ne pense qu’ à sa gueule
+        <<- ~4:7>> que|Z|a|perdu
+
+TEST: il ne pense qu’à sa gueule.
+
+
+__avoir_confiance_en__
+    >avoir confiance dans [moi|toi|soi|lui|elle|nous|vous|eux|elles]
+        <<-  -3>> en                                                                                # Avoir confiance en quelqu’un ou quelque chose.\3 \1 \2 \3|http://grammalecte.net
+
+TEST: Elle avait confiance {{dans}} lui.
+
+
+__code_legacy__
+    legacy code
+    code legacy
+        <<- -1:2>> code hérité|code reliquat|\1-\2|\2-\1                                            # \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.
+
+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}}.
+
+
+__test__
+    je  ~co[mn]putes?  [que|qu’]  @(?::Os|:M)¬:X  @:I
+        <<- morph(\4, ":Os|:M", ":X") -5>> \1|\5                                                    # SUBJONCTIF.
+
+TEST: je conpute qu’Isabelle {{est}} partie.

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,11 +16,11 @@
     "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'],
@@ -32,11 +32,11 @@
     "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'],
@@ -60,36 +60,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;
 }

ADDED   graphspell/fr.py
Index: graphspell/fr.py
==================================================================
--- /dev/null
+++ graphspell/fr.py
@@ -0,0 +1,44 @@
+# 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
@@ -487,11 +487,11 @@
                     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 []
 
@@ -592,11 +592,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 []
 
@@ -704,11 +704,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 []
 

Index: graphspell/spellchecker.py
==================================================================
--- graphspell/spellchecker.py
+++ graphspell/spellchecker.py
@@ -6,11 +6,11 @@
 # - 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
 
@@ -34,10 +34,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
@@ -97,10 +104,34 @@
         self.bCommunityDic = False
 
     def deactivatePersonalDictionary (self):
         self.bPersonalDic = False
 
+
+    # Default suggestions
+
+    def loadSuggestions (self, sLangCode):
+        try:
+            suggest_module = importlib.import_module("."+sLangCode, "graphspell")
+        except:
+            print("No suggestion module for language <"+sLangCode+">")
+            return
+        self.dDefaultSugg = suggest_module.dSugg
+
+
+    # Storage
+
+    def activateStorage (self):
+        self.bStorage = True
+
+    def deactivateStorage (self):
+        self.bStorage = False
+
+    def clearStorage (self):
+        self._dLemmas.clear()
+        self._dMorphologies.clear()
+
 
     # parse text functions
 
     def parseParagraph (self, sText, bSpellSugg=False):
         if not self.oTokenizer:
@@ -169,25 +200,44 @@
             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:

Index: graphspell/tokenizer.py
==================================================================
--- graphspell/tokenizer.py
+++ graphspell/tokenizer.py
@@ -5,11 +5,11 @@
 _PATTERNS = {
     "default":
         (
             r'(?P<FOLDERUNIX>/(?: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<FOLDERWIN>[a-zA-Z]:\\(?:Program Files(?: [(]x86[)]|)|[\w.()]+)(?:\\[\w.()-]+)*)',
-            r'(?P<PUNC>[.,?!:;…«»“”"()/·]+)',
+            r'(?P<PUNC>[][,.;:!?…«»“”‘’"(){}/·–—])',
             r'(?P<ACRONYM>[A-Z][.][A-Z][.](?:[A-Z][.])*)',
             r'(?P<LINK>(?:https?://|www[.]|\w+[@.]\w\w+[@.])\w[\w./?&!%=+*"\'@$#-]+)',
             r'(?P<HASHTAG>[#@][\w-]+)',
             r'(?P<HTML><\w+.*?>|</\w+ *>)',
             r'(?P<PSEUDOHTML>\[/?\w+\])',
@@ -19,11 +19,11 @@
         ),
     "fr":
         (
             r'(?P<FOLDERUNIX>/(?: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<FOLDERWIN>[a-zA-Z]:\\(?:Program Files(?: [(]x86[)]|)|[\w.()]+)(?:\\[\w.()-]+)*)',
-            r'(?P<PUNC>[.,?!:;…«»“”"()/·]+)',
+            r'(?P<PUNC>[][,.;:!?…«»“”‘’"(){}/·–—])',
             r'(?P<ACRONYM>[A-Z][.][A-Z][.](?:[A-Z][.])*)',
             r'(?P<LINK>(?:https?://|www[.]|\w+[@.]\w\w+[@.])\w[\w./?&!%=+*"\'@$#-]+)',
             r'(?P<HASHTAG>[#@][\w-]+)',
             r'(?P<HTML><\w+.*?>|</\w+ *>)',
             r'(?P<PSEUDOHTML>\[/?\w+\])',
@@ -42,8 +42,13 @@
         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):
+        if bStartEndToken:
+            yield { "i": 0, "sType": "INFO", "sValue": "<start>", "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": "<end>", "nStart": iEnd, "nEnd": iEnd }

Index: make.py
==================================================================
--- make.py
+++ make.py
@@ -17,10 +17,11 @@
 
 from distutils import dir_util, file_util
 
 import dialog_bundled
 import compile_rules
+import compile_rules_graph
 import helpers
 import lex_build
 
 
 sWarningMessage = "The content of this folder is generated by code and replaced at each build.\n"
@@ -191,12 +192,15 @@
     dVars = xConfig._sections['args']
     dVars['locales'] = dVars["locales"].replace("_", "-")
     dVars['loc'] = str(dict([ [s, [s[0:2], s[3:5], ""]] for s in dVars["locales"].split(" ") ]))
 
     ## COMPILE RULES
-    dResult = compile_rules.make(spLang, dVars['lang'], bJavaScript)
-    dVars.update(dResult)
+    dResultRegex = compile_rules.make(spLang, dVars['lang'], bJavaScript)
+    dVars.update(dResultRegex)
+
+    dResultGraph = compile_rules_graph.make(spLang, dVars['lang'], bJavaScript)
+    dVars.update(dResultGraph)
 
     ## READ GRAMMAR CHECKER PLUGINS
     print("PYTHON:")
     print("+ Plugins: ", end="")
     sCodePlugins = ""
@@ -227,11 +231,15 @@
     print()
 
     # 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("# REGEX RULES\n\n")
+        hDstPy.write(dVars['regex_gctests'])
+        hDstPy.write("\n\n\n# GRAPH RULES\n\n")
+        hDstPy.write(dVars['graph_gctests'])
+        hDstPy.write("\n")
 
     createOXT(spLang, dVars, xConfig._sections['oxt'], spLangPack, bInstallOXT)
 
     createServerOptions(sLang, dVars)
     createPackageZip(sLang, dVars, spLangPack)

Index: misc/grammalecte.sublime-syntax
==================================================================
--- misc/grammalecte.sublime-syntax
+++ misc/grammalecte.sublime-syntax
@@ -24,10 +24,14 @@
 
     # Bookmarks
     - match: '^!!.*|^\[\+\+\].*'
       scope: bookmark
 
+    # Bookmarks
+    - match: '^GRAPH_NAME:.*'
+      scope: bookmark
+
     # 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 +39,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|g_morph|stem|textarea0?\w*|before0?\w*|after0?\w*|word|option|define|select|exclude|g_define|g_select|g_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'
       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,18 +51,26 @@
       scope: support.function.debug
 
     - match: '\bre\b'
       scope: support.class
 
-    # Rule options
+    # Regex rule option
     - match: '^__[\[<]([isu])[\]>](/\w+|)(\(\w+\)|)(![0-9]|)__|</?js>'
       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
+
 
     # Definitions and options
     - match: '^OPT(?:GROUP|LANG|PRIORITY)/|^OPTSOFTWARE:'
       scope: options.command
 
@@ -84,21 +96,44 @@
       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
 
+    # 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
 
     # URL
@@ -108,11 +143,11 @@
     # Example errors
     - match: '{{.+?}}'
       scope: message.error
 
     # special chars
-    - match: '[@=*^?!:+<>]'
+    - match: '[@=*^?!:+<>~]'
       scope: keyword.other
 
     - match: '\(\?(?:[:=!]|<!)|[(|)]'
       scope: keyword.parenthesis
 

Index: misc/grammalecte.tmTheme
==================================================================
--- misc/grammalecte.tmTheme
+++ misc/grammalecte.tmTheme
@@ -291,10 +291,41 @@
 				<key>foreground</key>
 				<string>#A0A0A0</string>
 			</dict>
 		</dict>
 
+		<dict>
+			<key>name</key>
+			<string>Keyword Valid</string>
+			<key>scope</key>
+			<string>keyword.valid</string>
+			<key>settings</key>
+			<dict>
+				<key>fontStyle</key>
+				<string>bold</string>
+				<key>foreground</key>
+				<string>hsl(150, 100%, 80%)</string>
+				<key>background</key>
+				<string>hsl(150, 100%, 20%)</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>Keyword Invalid</string>
+			<key>scope</key>
+			<string>keyword.invalid</string>
+			<key>settings</key>
+			<dict>
+				<key>fontStyle</key>
+				<string>bold</string>
+				<key>foreground</key>
+				<string>hsl(0, 100%, 80%)</string>
+				<key>background</key>
+				<string>hsl(0, 100%, 20%)</string>
+			</dict>
+		</dict>
+
 		<dict>
 			<key>name</key>
 			<string>Rule options</string>
 			<key>scope</key>
 			<string>rule.options</string>
@@ -344,10 +375,21 @@
 				<string>italic</string>
 				<key>foreground</key>
 				<string>#A0A0A0</string>
 			</dict>
 		</dict>
+		<dict>
+			<key>name</key>
+			<string>Rule name</string>
+			<key>scope</key>
+			<string>rule.rulename2</string>
+			<key>settings</key>
+			<dict>
+				<key>foreground</key>
+				<string>#F0D080</string>
+			</dict>
+		</dict>
 		<dict>
 			<key>name</key>
 			<string>Rule priority</string>
 			<key>scope</key>
 			<string>rule.priority</string>
@@ -356,10 +398,63 @@
 				<key>foreground</key>
 				<string>#F06060</string>
 			</dict>
 		</dict>
 		
+		<dict>
+			<key>name</key>
+			<string>String lemma</string>
+			<key>scope</key>
+			<string>string.lemma</string>
+			<key>settings</key>
+			<dict>
+				<key>foreground</key>
+				<string>hsl(210, 100%, 80%)</string>
+				<key>background</key>
+				<string>hsl(210, 100%, 15%)</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>String regex</string>
+			<key>scope</key>
+			<string>string.regex</string>
+			<key>settings</key>
+			<dict>
+				<key>foreground</key>
+				<string>hsl(60, 100%, 80%)</string>
+				<key>background</key>
+				<string>hsl(60, 100%, 10%)</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>String morph pattern</string>
+			<key>scope</key>
+			<string>string.morph.pattern</string>
+			<key>settings</key>
+			<dict>
+				<key>foreground</key>
+				<string>hsl(150, 80%, 90%)</string>
+				<key>background</key>
+				<string>hsl(150, 80%, 10%)</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>String morph antipattern</string>
+			<key>scope</key>
+			<string>string.morph.antipattern</string>
+			<key>settings</key>
+			<dict>
+				<key>foreground</key>
+				<string>hsl(0, 80%, 90%)</string>
+				<key>background</key>
+				<string>hsl(0, 80%, 10%)</string>
+			</dict>
+		</dict>
+
 
 		<dict>
 			<key>name</key>
 			<string>JavaScript Dollar</string>
 			<key>scope</key>