Grammalecte  Check-in [fb8de11c89]

Overview
Comment:[core][fr] tests suggestions for JS, suggestions update
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk | fr | core
Files: files | file ages | folders
SHA3-256: fb8de11c89bac99c911f9fa9836a2fa9cf37280f24d01f1e692e91776a03c0fa
User & Date: olr on 2021-01-21 18:04:41
Other Links: manifest | tags
Context
2021-01-21
21:12
[build] nnbsp in py2js() also check-in: b217bc2731 user: olr tags: trunk, build
18:04
[core][fr] tests suggestions for JS, suggestions update check-in: fb8de11c89 user: olr tags: trunk, fr, core
10:30
[fr] ajustements (tests) check-in: 8e7a1e20da user: olr tags: trunk, fr
Changes

Modified gc_core/js/tests.js from [5a6c382af8] to [9717be7ff0].

19
20
21
22
23
24
25
26

27
28
29
30
31
32
33
34
35

36



37
38

39
40
41
42
43
44
45
19
20
21
22
23
24
25

26
27
28
29
30
31
32
33
34

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49







-
+








-
+

+
+
+


+







        this.spfTests = spfTests;
        this._aRuleTested = new Set();
    }

    * testParse (bDebug=false) {
        const t0 = Date.now();
        let sURL;
        if(typeof(process) !== 'undefined') {
        if (typeof(process) !== 'undefined') {
            sURL = (this.spfTests !== "") ? this.spfTests : "./"+this.gce.lang+"/tests_data.json";
        } else {
            sURL = (this.spfTests !== "") ? this.spfTests : "resource://grammalecte/"+this.gce.lang+"/tests_data.json";
        }
        const aData = JSON.parse(helpers.loadFile(sURL)).aData;
        let nInvalid = 0;
        let nTotal = 0;
        let sErrorText;
        let sSugg;
        let sExpectedSuggs;
        let sExpectedErrors;
        let nTestWithExpectedError = 0;
        let nTestWithExpectedErrorAndSugg = 0;
        let nUnexpectedErrors = 0;
        let sTextToCheck;
        let sFoundErrors;
        let sFoundSuggs;
        let sListErr;
        let sLineNum;
        let i = 1;
        let sUntestedRules = "";
        let bShowUntested = false;
        let zOption = /^__([a-zA-Z0-9]+)__ /;
        let sOption;
53
54
55
56
57
58
59
60

61
62

63
64

65
66



67
68

69
70
71
72
73
74
75
76
77










78
79
80
81
82
83
84














85










































86
87
88
89
90

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
57
58
59
60
61
62
63

64


65
66
67
68
69
70
71
72
73
74

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

162
















163
164
165
166
167
168
169







-
+
-
-
+


+


+
+
+

-
+









+
+
+
+
+
+
+
+
+
+







+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







                    sOption = false;
                    m = zOption.exec(sLine);
                    if (m) {
                        sLine = sLine.slice(sLine.indexOf(" ")+1);
                        sOption = m[1];
                    }
                    if (sLine.includes("->>")) {
                        [sErrorText, sSugg] = sLine.split("->>");
                        [sErrorText, sExpectedSuggs] = this._splitTestLine(sLine);
                        sErrorText = sErrorText.trim();
                        sSugg = sSugg.trim();
                        nTestWithExpectedErrorAndSugg += 1
                    } else {
                        sErrorText = sLine.trim();
                        sExpectedSuggs = "";
                    }
                    sExpectedErrors = this._getExpectedErrors(sErrorText);
                    if (sExpectedErrors.trim() != "") {
                        nTestWithExpectedError += 1;
                    }
                    sTextToCheck = sErrorText.replace(/\{\{/g, "").replace(/\}\}/g, "");
                    [sFoundErrors, sListErr] = this._getFoundErrors(sTextToCheck, bDebug, sOption);
                    [sFoundErrors, sListErr, sFoundSuggs] = this._getFoundErrors(sTextToCheck, bDebug, sOption);
                    if (sExpectedErrors !== sFoundErrors) {
                        yield "\n" + i.toString() +
                              "\n# Line num: " + sLineNum +
                              "\n> to check: " + sTextToCheck +
                              "\n  expected: " + sExpectedErrors +
                              "\n  found:    " + sFoundErrors +
                              "\n  errors:   \n" + sListErr;
                        nInvalid = nInvalid + 1;
                    }
                    else if (sExpectedSuggs) {
                        if (!this._checkSuggestions(sExpectedSuggs, sFoundSuggs)) {
                            yield  "\n# Line num: " + sLineNum +
                                   "\n> to check: " + sTextToCheck +
                                   "\n  expected: " + sExpectedSuggs +
                                   "\n  found:    " + sFoundSuggs +
                                   "\n  errors:   \n" + sListErr;
                            nUnexpectedErrors += 1;
                        }
                    }
                    nTotal = nTotal + 1;
                }
                i = i + 1;
                if (i % 1000 === 0) {
                    yield i.toString();
                }
            }
            yield "Tests with expected errors: " + nTestWithExpectedError + " and suggestions: " + nTestWithExpectedErrorAndSugg + " > " + nTestWithExpectedErrorAndSugg/nTestWithExpectedError*100 + " %";
            if (nUnexpectedErrors) {
                yield "Unexpected errors: " + nUnexpectedErrors;
            }
            yield* this._showUntestedRules()
        }
        catch (e) {
            console.error(e);
        }
        const t1 = Date.now();
        yield "Tests parse finished in " + ((t1-t0)/1000).toString()
            + " s\nTotal errors: " + nInvalid.toString() + " / " + nTotal.toString();
    }

            bShowUntested = true;
    * _showUntestedRules () {
        let i = 0;
        for (let [sOpt, sLineId, sRuleId] of this.gce.listRules()) {
            if (sOpt !== "@@@@" && !this._aRuleTested.has(sLineId) && !/^[0-9]+[sp]$|^[pd]_/.test(sRuleId)) {
                sUntestedRules += sLineId + "/" + sRuleId + ", ";
                i += 1;
            }
        }
        if (i > 0) {
            yield sUntestedRules + "\n[" + i.toString() + " untested rules]";
        }
    }

    _splitTestLine (sLine) {
        let [sText, sSugg] = sLine.split("->>");
        sSugg = sSugg.trim().replace(/ /g, " ");
        if (sSugg.startsWith('"') && sSugg.endsWith('"')) {
            sSugg = sSugg.slice(1,-1);
        }
        return [sText.trim(), sSugg];
    }

    _getFoundErrors (sLine, bDebug, sOption) {
        try {
            let aErrs = [];
            if (sOption) {
                this.gce.setOption(sOption, true);
                aErrs = this.gce.parse(sLine, "FR", bDebug);
                this.gce.setOption(sOption, false);
            } else {
                aErrs = this.gce.parse(sLine, "FR", bDebug);
            }
            let sRes = " ".repeat(sLine.length);
            let sListErr = "";
            let lAllSugg = [];
            for (let oErr of aErrs.sort( (a,b) => a["nStart"] - b["nStart"] )) {
                sRes = sRes.slice(0, oErr["nStart"]) + "~".repeat(oErr["nEnd"] - oErr["nStart"]) + sRes.slice(oErr["nEnd"]);
                sListErr += "    * {" + oErr['sLineId'] + " / " + oErr['sRuleId'] + "}  at  " + oErr['nStart'] + ":" + oErr['nEnd'] + "\n";
                lAllSugg.push(oErr["aSuggestions"].join("|"));
                this._aRuleTested.add(oErr["sLineId"]);
            }
            return [sRes, sListErr, lAllSugg.join("|||")];
        }
        catch (e) {
            console.error(e);
        }

        return [" ".repeat(sLine.length), "", ""];
        if (bShowUntested) {
            i = 0;
            for (let [sOpt, sLineId, sRuleId] of this.gce.listRules()) {
                if (sOpt !== "@@@@" && !this._aRuleTested.has(sLineId) && !/^[0-9]+[sp]$|^[pd]_/.test(sRuleId)) {
                    sUntestedRules += sLineId + "/" + sRuleId + ", ";
                    i += 1;
                }
            }
            if (i > 0) {
                yield sUntestedRules + "\n[" + i.toString() + " untested rules]";
            }
        }

        const t1 = Date.now();
        yield "Tests parse finished in " + ((t1-t0)/1000).toString()
            + " s\nTotal errors: " + nInvalid.toString() + " / " + nTotal.toString();
    }

    _getExpectedErrors (sLine) {
        try {
            let sRes = " ".repeat(sLine.length);
            let z = /\{\{.+?\}\}/g;
            let m;
128
129
130
131
132
133
134
135
136
137
138




139
140
141
142

143
144
145
146
147




148
149
150
151
152
153



154
155
156
157

158
159
160
161
162
163
164
165
184
185
186
187
188
189
190




191
192
193
194




195





196
197
198
199






200
201
202


203

204
205

206
207
208
209
210
211







-
-
-
-
+
+
+
+
-
-
-
-
+
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
-
-

-
+

-






        }
        catch (e) {
            console.error(e);
        }
        return " ".repeat(sLine.length);
    }

    _getFoundErrors (sLine, bDebug, sOption) {
        try {
            let aErrs = [];
            if (sOption) {
    _checkSuggestions (sAllExceptedSuggs, sAllFoundSuggs) {
        let lAllExpectedSuggs = sAllExceptedSuggs.split("|||");
        let lAllFoundSuggs = sAllFoundSuggs.split("|||");
        if (lAllExpectedSuggs.length != lAllFoundSuggs.length) {
                this.gce.setOption(sOption, true);
                aErrs = this.gce.parse(sLine, "FR", bDebug);
                this.gce.setOption(sOption, false);
            } else {
            return false;
                aErrs = this.gce.parse(sLine, "FR", bDebug);
            }
            let sRes = " ".repeat(sLine.length);
            let sListErr = "";
            for (let dErr of aErrs) {
        }
        for (let i = 0;  i < lAllExpectedSuggs.length;  i++) {
            let aExpectedSuggs = new Set(lAllExpectedSuggs[i].split("|"));
            let aFoundSuggs = new Set(lAllFoundSuggs[i].split("|"));
                sRes = sRes.slice(0, dErr["nStart"]) + "~".repeat(dErr["nEnd"] - dErr["nStart"]) + sRes.slice(dErr["nEnd"]);
                sListErr += "    * {" + dErr['sLineId'] + " / " + dErr['sRuleId'] + "}  at  " + dErr['nStart'] + ":" + dErr['nEnd'] + "\n";
                this._aRuleTested.add(dErr["sLineId"]);
            }
            return [sRes, sListErr];
        }
            if (aExpectedSuggs.size !== aFoundSuggs.size || ![...aExpectedSuggs].every(value => aFoundSuggs.has(value))) {
                return false;
            }
        catch (e) {
            console.error(e);
        }
        return [" ".repeat(sLine.length), ""];
        return true;
    }

}


if (typeof(exports) !== 'undefined') {
    exports.TestGrammarChecking = TestGrammarChecking;
}

Modified gc_core/py/lang_core/tests_core.py from [f2f8ff9a94] to [335b223b65].

108
109
110
111
112
113
114
115

116
117
118



119
120
121
122
123
124
125
108
109
110
111
112
113
114

115
116
117

118
119
120
121
122
123
124
125
126
127







-
+


-
+
+
+







                    if not self._checkSuggestions(sExceptedSuggs, sFoundSuggs):
                        print("\n# Line num: " + sLineNum + \
                              "\n> to check: " + _fuckBackslashUTF8(sTextToCheck) + \
                              "\n  expected: " + sExceptedSuggs + \
                              "\n  found:    " + sFoundSuggs + \
                              "\n  errors:   \n" + sListErr)
                        nUnexpectedErrors += 1
            print("Tests with expected errors:", nTestWithExpectedError, " and suggestions:", nTestWithExpectedErrorAndSugg, ":{:.4}%".format(nTestWithExpectedErrorAndSugg/nTestWithExpectedError*100))
            print("Tests with expected errors:", nTestWithExpectedError, " and suggestions:", nTestWithExpectedErrorAndSugg, "> {:.4} %".format(nTestWithExpectedErrorAndSugg/nTestWithExpectedError*100))
            if nUnexpectedErrors:
                print("Unexpected errors:", nUnexpectedErrors)
        # untested rules
            self._showUntestedRules()

    def _showUntestedRules (self):
        aUntestedRules = set()
        for _, sOpt, sLineId, sRuleId in gc_engine.listRules():
            sRuleId = sRuleId.rstrip("0123456789")
            if sOpt != "@@@@" and sRuleId not in self._aTestedRules and not re.search("^[0-9]+[sp]$|^[pd]_", sRuleId):
                aUntestedRules.add(f"{sLineId}/{sRuleId}")
        if aUntestedRules:
            print()
165
166
167
168
169
170
171
172
173
174



175
176
177
178
179
180
181
167
168
169
170
171
172
173



174
175
176
177
178
179
180
181
182
183







-
-
-
+
+
+







        sRes = " " * len(sLine)
        for i, m in enumerate(self._zError.finditer(sLine)):
            nStart = m.start() - (4 * i)
            nEnd = m.end() - (4 * (i+1))
            sRes = sRes[:nStart] + "~" * (nEnd - nStart) + sRes[nEnd:-4]
        return sRes

    def _checkSuggestions (self, sExceptedSuggs, sFoundSuggs):
        lAllExpectedSuggs = sExceptedSuggs.split("|||")
        lAllFoundSuggs = sFoundSuggs.split("|||")
    def _checkSuggestions (self, sAllExceptedSuggs, sAllFoundSuggs):
        lAllExpectedSuggs = sAllExceptedSuggs.split("|||")
        lAllFoundSuggs = sAllFoundSuggs.split("|||")
        if len(lAllExpectedSuggs) != len(lAllFoundSuggs):
            return False
        for sExceptedSuggs, sFoundSuggs in zip(lAllExpectedSuggs, lAllFoundSuggs):
            if set(sExceptedSuggs.split("|")) != set(sFoundSuggs.split("|")):
                return False
        return True

Modified gc_lang/fr/modules-js/gce_suggestions.js from [916bb2d5db] to [807177b0cf].

77
78
79
80
81
82
83
84

85
86
87
88


89
90
91

92
93
94
95
96
97
98
77
78
79
80
81
82
83

84
85
86


87
88
89
90

91
92
93
94
95
96
97
98







-
+


-
-
+
+


-
+







        }
        return Array.from(aSugg).join("|");
    }
    return "";
}

function joinVerbAndSuffix (sFlex, sSfx) {
    if (/^-[tT]-/.test(sSfx) && /[tdTD]$/.test(sFlex)) {
    if (/^-t-/i.test(sSfx) && /[td]$/i.test(sFlex)) {
        return sFlex + sSfx.slice(2);
    }
    if (/[eacEAC]$/.test(sFlex)) {
        if (/-(?:en|y)$/i.test(sSfx)) {
    if (/[eac]$/i.test(sFlex)) {
        if (/^-(?:en|y)$/i.test(sSfx)) {
            return sFlex + "s" + sSfx;
        }
        if (/-(?:ie?l|elle|on)$/i.test(sSfx)) {
        if (/^-(?:ie?l|elle|on)$/i.test(sSfx)) {
            return sFlex + "-t" + sSfx;
        }
    }
    return sFlex + sSfx;
}

function suggVerbPpas (sFlex, sWhat=null) {
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174

175
176
177
178
179
180
181
160
161
162
163
164
165
166

167
168
169
170
171
172
173

174
175
176
177
178
179
180
181







-
+






-
+








function suggVerbFrom (sStem, sFlex, sWho="") {
    "conjugate <sStem> according to <sFlex> (and eventually <sWho>)"
    let aSugg = new Set();
    for (let sMorph of gc_engine.oSpellChecker.getMorph(sFlex)) {
        let lTenses = [ ...sMorph.matchAll(/:(?:Y|I[pqsf]|S[pq]|K|P|Q)/g) ];
        if (sWho) {
            for (let sTense of lTenses) {
            for (let [sTense, ] of lTenses) {
                if (conj.hasConj(sStem, sTense, sWho)) {
                    aSugg.add(conj.getConj(sStem, sTense, sWho));
                }
            }
        }
        else {
            for (let sTense of lTenses) {
            for (let [sTense, ] of lTenses) {
                for (let sWho of [ ...sMorph.matchAll(/:[123][sp]/g) ]) {
                    if (conj.hasConj(sStem, sTense, sWho)) {
                        aSugg.add(conj.getConj(sStem, sTense, sWho));
                    }
                }
            }
        }
221
222
223
224
225
226
227
228
229
230
231
232
233
234

235
236

237
238
239
240
241
242
243
221
222
223
224
225
226
227


228
229
230
231

232
233

234
235
236
237
238
239
240
241







-
-




-
+

-
+







}


const _dQuiEst = new Map ([
    ["je", ":1s"], ["j’", ":1s"], ["tu", ":2s"], ["il", ":3s"], ["on", ":3s"], ["elle", ":3s"], ["iel", ":3s"],
    ["nous", ":1p"], ["vous", ":2p"], ["ils", ":3p"], ["elles", ":3p"], ["iels", ":3p"]
]);
const _lIndicatif = [":Ip", ":Iq", ":Is", ":If"];
const _lSubjonctif = [":Sp", ":Sq"];

function suggVerbMode (sFlex, cMode, sSuj) {
    let lMode;
    if (cMode == ":I") {
        lMode = _lIndicatif;
        lMode = [":Ip", ":Iq", ":Is", ":If"];
    } else if (cMode == ":S") {
        lMode = _lSubjonctif;
        lMode = [":Sp", ":Sq"];
    } else if (cMode.startsWith(":I") || cMode.startsWith(":S")) {
        lMode = [cMode];
    } else {
        return "";
    }
    let sWho = _dQuiEst.gl_get(sSuj.toLowerCase(), ":3s");
    let aSugg = new Set();
255
256
257
258
259
260
261
262

263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
253
254
255
256
257
258
259

260
261












262
263
264
265
266
267
268







-
+

-
-
-
-
-
-
-
-
-
-
-
-







        return Array.from(aSugg).join("|");
    }
    return "";
}

//// Nouns and adjectives

function suggPlur (sFlex, sWordToAgree=null, bSelfSugg=false) {
function suggPlur (sFlex, bSelfSugg=false) {
    // returns plural forms assuming sFlex is singular
    if (sWordToAgree) {
        let lMorph = gc_engine.oSpellChecker.getMorph(sWordToAgree);
        if (lMorph.length === 0) {
            return "";
        }
        let sGender = cregex.getGender(lMorph);
        if (sGender == ":m") {
            return suggMasPlur(sFlex);
        } else if (sGender == ":f") {
            return suggFemPlur(sFlex);
        }
    }
    let aSugg = new Set();
    if (sFlex.endsWith("l")) {
        if (sFlex.endsWith("al") && sFlex.length > 2 && gc_engine.oSpellChecker.isValid(sFlex.slice(0,-1)+"ux")) {
            aSugg.add(sFlex.slice(0,-1)+"ux");
        }
        if (sFlex.endsWith("ail") && sFlex.length > 3 && gc_engine.oSpellChecker.isValid(sFlex.slice(0,-2)+"ux")) {
            aSugg.add(sFlex.slice(0,-2)+"ux");
295
296
297
298
299
300
301
302

303
304
305

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321

322
323
324
325
326
327
328
281
282
283
284
285
286
287

288
289
290

291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306

307
308
309
310
311
312
313
314







-
+


-
+















-
+







            aSugg.add(sFlex+"s");
        }
        if (gc_engine.oSpellChecker.isValid(sFlex+"x")) {
            aSugg.add(sFlex+"x");
        }
    } else {
        if (gc_engine.oSpellChecker.isValid(sFlex+"S")) {
            aSugg.add(sFlex+"s");
            aSugg.add(sFlex+"S");
        }
        if (gc_engine.oSpellChecker.isValid(sFlex+"X")) {
            aSugg.add(sFlex+"x");
            aSugg.add(sFlex+"X");
        }
    }
    if (mfsp.hasMiscPlural(sFlex)) {
        mfsp.getMiscPlural(sFlex).forEach(function(x) { aSugg.add(x); });
    }
    if (aSugg.size == 0 && bSelfSugg && (sFlex.endsWith("s") || sFlex.endsWith("x") || sFlex.endsWith("S") || sFlex.endsWith("X"))) {
        aSugg.add(sFlex);
    }
    aSugg.delete("");
    if (aSugg.size > 0) {
        return Array.from(aSugg).join("|");
    }
    return "";
}

function suggSing (sFlex, bSelfSugg=false) {
function suggSing (sFlex, bSelfSugg=true) {
    // returns singular forms assuming sFlex is plural
    let aSugg = new Set();
    if (sFlex.endsWith("ux")) {
        if (gc_engine.oSpellChecker.isValid(sFlex.slice(0,-2)+"l")) {
            aSugg.add(sFlex.slice(0,-2)+"l");
        }
        if (gc_engine.oSpellChecker.isValid(sFlex.slice(0,-2)+"il")) {
393
394
395
396
397
398
399
400

401
402
403
404
405
406
407
379
380
381
382
383
384
385

386
387
388
389
390
391
392
393







-
+







        if (!sMorph.includes(":V")) {
            // not a verb
            if (sMorph.includes(":m") || sMorph.includes(":e")) {
                aSugg.add(suggPlur(sFlex));
            } else {
                let sStem = cregex.getLemmaOfMorph(sMorph);
                if (mfsp.isMasForm(sStem)) {
                    aSugg.add(suggPlur(sStem, null, true));
                    aSugg.add(suggPlur(sStem, true));
                }
            }
        } else {
            // a verb
            let sVerb = cregex.getLemmaOfMorph(sMorph);
            if (conj.hasConj(sVerb, ":PQ", ":Q2")) {
                aSugg.add(conj.getConj(sVerb, ":PQ", ":Q2"));

Modified gc_lang/fr/modules/gce_suggestions.py from [af90e8e3f3] to [8bef0dbc3f].

166
167
168
169
170
171
172
173
174
175
176
177
178
179

180
181

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230

231
232

233
234
235
236
237
238
239
166
167
168
169
170
171
172


173
174
175
176

177
178

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198

199
200









201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

219
220

221
222
223
224
225
226
227
228







-
-




-
+

-
+



















-
+

-
-
-
-
-
-
-
-
-


















-
+

-
+







def suggVerbInfi (sFlex):
    "returns infinitive forms of <sFlex>"
    return "|".join([ sStem  for sStem in _oSpellChecker.getLemma(sFlex)  if conj.isVerb(sStem) ])


_dQuiEst = { "je": ":1s", "j’": ":1s", "tu": ":2s", "il": ":3s", "on": ":3s", "elle": ":3s", "iel": ":3s", \
             "nous": ":1p", "vous": ":2p", "ils": ":3p", "elles": ":3p", "iels": ":3p" }
_lIndicatif = [":Ip", ":Iq", ":Is", ":If"]
_lSubjonctif = [":Sp", ":Sq"]

def suggVerbMode (sFlex, cMode, sSuj):
    "returns other conjugations of <sFlex> acconding to <cMode> and <sSuj>"
    if cMode == ":I":
        lMode = _lIndicatif
        lMode = [":Ip", ":Iq", ":Is", ":If"]
    elif cMode == ":S":
        lMode = _lSubjonctif
        lMode = [":Sp", ":Sq"]
    elif cMode.startswith((":I", ":S")):
        lMode = [cMode]
    else:
        return ""
    sWho = _dQuiEst.get(sSuj.lower(), ":3s")
    aSugg = set()
    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))
    if aSugg:
        return "|".join(aSugg)
    return ""


## Nouns and adjectives

def suggPlur (sFlex, sWordToAgree=None, bSelfSugg=False):
def suggPlur (sFlex, bSelfSugg=False):
    "returns plural forms assuming sFlex is singular"
    if sWordToAgree:
        lMorph = _oSpellChecker.getMorph(sFlex)
        if not lMorph:
            return ""
        sGender = cr.getGender(lMorph)
        if sGender == ":m":
            return suggMasPlur(sFlex)
        if sGender == ":f":
            return suggFemPlur(sFlex)
    aSugg = set()
    if sFlex.endswith("l"):
        if sFlex.endswith("al") and len(sFlex) > 2 and _oSpellChecker.isValid(sFlex[:-1]+"ux"):
            aSugg.add(sFlex[:-1]+"ux")
        if sFlex.endswith("ail") and len(sFlex) > 3 and _oSpellChecker.isValid(sFlex[:-2]+"ux"):
            aSugg.add(sFlex[:-2]+"ux")
    if sFlex.endswith("L"):
        if sFlex.endswith("AL") and len(sFlex) > 2 and _oSpellChecker.isValid(sFlex[:-1]+"UX"):
            aSugg.add(sFlex[:-1]+"UX")
        if sFlex.endswith("AIL") and len(sFlex) > 3 and _oSpellChecker.isValid(sFlex[:-2]+"UX"):
            aSugg.add(sFlex[:-2]+"UX")
    if sFlex[-1:].islower():
        if _oSpellChecker.isValid(sFlex+"s"):
            aSugg.add(sFlex+"s")
        if _oSpellChecker.isValid(sFlex+"x"):
            aSugg.add(sFlex+"x")
    else:
        if _oSpellChecker.isValid(sFlex+"S"):
            aSugg.add(sFlex+"s")
            aSugg.add(sFlex+"S")
        if _oSpellChecker.isValid(sFlex+"X"):
            aSugg.add(sFlex+"x")
            aSugg.add(sFlex+"X")
    if mfsp.hasMiscPlural(sFlex):
        aSugg.update(mfsp.getMiscPlural(sFlex))
    if not aSugg and bSelfSugg and sFlex.endswith(("s", "x", "S", "X")):
        aSugg.add(sFlex)
    aSugg.discard("")
    if aSugg:
        return "|".join(aSugg)
298
299
300
301
302
303
304
305

306
307
308
309
310
311
312
287
288
289
290
291
292
293

294
295
296
297
298
299
300
301







-
+







        if not ":V" in sMorph:
            # not a verb
            if ":m" in sMorph or ":e" in sMorph:
                aSugg.add(suggPlur(sFlex))
            else:
                sStem = cr.getLemmaOfMorph(sMorph)
                if mfsp.isMasForm(sStem):
                    aSugg.add(suggPlur(sStem, None, True))
                    aSugg.add(suggPlur(sStem, True))
        else:
            # a verb
            sVerb = cr.getLemmaOfMorph(sMorph)
            if conj.hasConj(sVerb, ":PQ", ":Q2"):
                aSugg.add(conj.getConj(sVerb, ":PQ", ":Q2"))
            elif conj.hasConj(sVerb, ":PQ", ":Q1"):
                sSugg = conj.getConj(sVerb, ":PQ", ":Q1")

Modified gc_lang/fr/rules.grx from [aeba1ed251] to [6130f02351].

2577
2578
2579
2580
2581
2582
2583
2584

2585
2586
2587
2588
2589
2590
2591
2577
2578
2579
2580
2581
2582
2583

2584
2585
2586
2587
2588
2589
2590
2591







-
+







TEST: {{Prend-lui}} le pouls.                                                   ->> Prends-lui|Prenons-lui|Prenez-lui
TEST: {{apport-lui}}.                                                           ->>
TEST: {{Expliques-leur}} comment faire.                                         ->> Explique-leur|Expliquons-leur|Expliquez-leur
TEST: {{fou-leur}} la paix                                                      ->> fous-leur
TEST: {{explique-leurs}} de quoi il est question.                               ->> explique-leur
TEST: {{calcul-leurs}} ça.                                                      ->> calcul-leur
TEST: {{aller-y}}                                                               ->> allez-y|vas-y|allons-y
TEST: {{penser-en}}                                                           ->> pensez-en|dépenses-en|dépensons-en
TEST: {{expliquer-en}}                                                          ->> expliquez-en|expliques-en|expliquons-en
TEST: {{appuis-en}}                                                             ->> appuies-en
TEST: {{appuis-y}}                                                              ->> appuies-y
TEST: {{demande-en}}                                                            ->> demandes-en
TEST: {{demande-y}} comment faire                                               ->> demandes-y
TEST: c’est mon chez-moi
TEST: c’est ton chez-toi
TEST: penses-y
6883
6884
6885
6886
6887
6888
6889
6890

6891
6892
6893
6894
6895
6896
6897
6883
6884
6885
6886
6887
6888
6889

6890
6891
6892
6893
6894
6895
6896
6897







-
+







    avenir devant [moi|toi|soi|lui|elle|nous|vous|eux|elles]
        <<- /pleo/ morph(<1, ":A.*:[me]:[si]") ->> avenir                                   && Pléonasme.

    >coup de foudre [immédiat+s|instantané+ses|soudain]
        <<- /pleo/ ->> \1 \2 \3                                                             && Pléonasme.

    [paire+s] de >jumeau
        <<- /pleo/ ->> =suggPlur(\-1, "", True)                                             && Pléonasme.
        <<- /pleo/ ->> =suggPlur(\-1, True)                                                 && Pléonasme.

    >panacée >universel
        <<- /pleo/ ->> \1|remède universel                                                  && Pléonasme.

    >secousse [>séismique|>sismique]
        <<- /pleo/ ->> secousse tellurique|secousses telluriques|tremblement de terre       && Pléonasme.