1 | // should now be persistable |
2 | sclass RuleEngine2 { |
3 | sclass SimplifyWithRule extends Rule { |
4 | transient O _function; // function name or F1. can return S or LS |
5 | bool isSplitRule; // splitter rather than simplifier |
6 | bool callOnTree; // function takes GazelleTree rather than S |
7 | |
8 | O function() { |
9 | if (_function == null) { |
10 | gazelle_parseInstruction(this); |
11 | assertNotNull(_function); |
12 | } |
13 | ret _function; |
14 | } |
15 | } |
16 | |
17 | abstract sclass QuickCheck { |
18 | abstract bool check(S input); |
19 | } |
20 | |
21 | sclass CicQuickCheck extends QuickCheck { |
22 | S string; |
23 | |
24 | bool check(S input) { ret cic(input, string); } |
25 | } |
26 | |
27 | sclass Rule { |
28 | S globalID; |
29 | LS in; |
30 | S out; |
31 | LS comments; |
32 | Set<S> vars; |
33 | L<GRuleLine> insWithType; |
34 | LS outTypes; |
35 | transient L inputMassagers; // func(S input) -> S for every in |
36 | transient L checkerFunctions; // func(S input) -> SS for every in |
37 | transient L matchers; // func(LS tokC, LS tokI, RuleEngine2_MatchedRule) -> SS for every in |
38 | S text; // original text |
39 | QuickCheck applicabilityQuickCheck; |
40 | S purpose = ""; |
41 | Double qualityCutOff; |
42 | bool avoidRecentlyUsedMappings; |
43 | |
44 | // (optional) massage the variable map after matching |
45 | // L<func(SS varMap, LS tokCondition, LS tokInput, RuleEngine2_MatchedRule matched) -> SS newVarMap> |
46 | L mapMassagers; |
47 | |
48 | S asText() { |
49 | if (l(in) > 1 && eq(last(in), out)) |
50 | ret join(" \n+ ", dropLast(in)) + " \n+=> " + out; |
51 | ret join(" \n+ ", in) + " \n=> " + out; |
52 | } |
53 | |
54 | void addMapMassager(O massager) { |
55 | if (massager != null) mapMassagers = addToOrCreateList(mapMassagers, massager); |
56 | } |
57 | |
58 | void setInputMassager(int i, O massager) { |
59 | inputMassagers = listSetWithCreate(inputMassagers, i, massager); |
60 | } |
61 | |
62 | void setMatcher(int i, O matcher) { |
63 | matchers = listSetWithCreate(matchers, i, matcher); |
64 | } |
65 | |
66 | void setOutType(int i, S type) { |
67 | outTypes = listSetWithCreate(outTypes, i, type); |
68 | } |
69 | |
70 | S outType() { ret first(outTypes); } |
71 | |
72 | void parseGeneralComments() { |
73 | purpose = gazelle_purposeFromComments(comments); |
74 | pcall { |
75 | S s = matchAny_firstGroup("quality cutoff = *", comments); |
76 | if (s != null) qualityCutOff = parseDouble(s); |
77 | } |
78 | avoidRecentlyUsedMappings = cic(comments, "avoid recently used mapping"); |
79 | } |
80 | |
81 | toString { |
82 | ret globalID + ": " + text; |
83 | } |
84 | } // end of class Rule |
85 | |
86 | int minScore = 50; // minimal percentage score for rule matching |
87 | int maxLevel = 100, maxOutLines = 10000; |
88 | new L<Rule> rules; |
89 | bool printMappings, printGroupedData, printRejectedVars; |
90 | Set<S> facts = ciSet(); |
91 | transient F2<S, S, O> callFunctionOnString = func(S function, S s) -> O { callAndMake(function, s) }; |
92 | bool hasHelpers; |
93 | |
94 | *() {} |
95 | |
96 | // probably not used anymore |
97 | *(LPair<S> rulesWithComments) { |
98 | for (PairS p : unnull(rulesWithComments)) |
99 | addRule(p.a, splitAtDoubleArrow_pair(p.a), p.b, null); |
100 | } |
101 | |
102 | void copyRulesFrom(RuleEngine2 engine) { |
103 | if (engine == null) ret; |
104 | rules.addAll(engine.rules); |
105 | hasHelpers = engine.hasHelpers; |
106 | } |
107 | |
108 | void addRules2(L<T3S> rulesWithCommentsAndID) { |
109 | new Matches m; |
110 | for (T3<S> p : unnull(rulesWithCommentsAndID)) try { |
111 | PairS p2 = splitAtDoubleArrow_pair(p.a); |
112 | if (p2 != null) |
113 | continue with addRule(p.a, p2, p.b, p.c); |
114 | |
115 | new SimplifyWithRule r; |
116 | r.globalID = p.c; |
117 | r.text = p.a; |
118 | r.comments = lines(p.b); |
119 | r.parseGeneralComments(); |
120 | S s = p.a; |
121 | gazelle_parseInstruction(r); |
122 | if (r._function == null) continue; |
123 | rules.add(r); |
124 | } catch e { |
125 | printStackTrace("Exception parsing rule " + p.c, e); |
126 | } |
127 | } |
128 | |
129 | void addRule(S text, PairS rule, S comments, S globalID) { |
130 | if (rule == null) ret; |
131 | rule = ai_rule_pair_expandPlusArrow(rule); |
132 | LS conditions = tok_splitAtPlusAtBeginningOfLine(rule.a); |
133 | new Rule r; |
134 | r.globalID = globalID; |
135 | r.text = text; |
136 | r.comments = lines(comments); |
137 | r.parseGeneralComments(); |
138 | r.in = conditions; |
139 | r.insWithType = map(conditions, func(S s) -> GRuleLine { GRuleLine(s, "standard") }); |
140 | |
141 | // input massagers for "<anything>" |
142 | |
143 | for (int i = 0; i < l(r.in); i++) { |
144 | S s = r.in.get(i); |
145 | continue unless endsWith(s, "<anything>"); |
146 | if (eq(s, "<anything>")) |
147 | r.checkerFunctions = listSetWithCreate(r.checkerFunctions, i, func(S s) -> SS { nempty(s) ? litmap() : null }); |
148 | else if (jmatch("* says: <anything>", javaTokWithBrackets(s))) { |
149 | //print("Made input massager"); |
150 | r.setInputMassager(i, func(S s) -> S { new Matches m; ret jMatchStart("* says:", javaTokWithBrackets(s), m) ? m.get(0) + " says: <anything>" : s; }); |
151 | } |
152 | } |
153 | |
154 | for (Matches m : getJMatches_all("expand *", r.comments)) |
155 | if (isQuoted(m.get(0))) { |
156 | fS var = m.unq(0); |
157 | for (int i = 0; i < l(r.in); i++) |
158 | r.setMatcher(i, new O { SS get(LS tokC, LS tokI, RuleEngine2_MatchedRule matched) { |
159 | if (eqic(nextToLast(tokC), var)) |
160 | tokI = tok_groupLastTokensToMatchPattern(tokI, tokC); |
161 | ret zipCodeTokensToCIMap_strict_withoutEquals(tokC, tokI); |
162 | }}); |
163 | } |
164 | |
165 | // "in = dialog" (probably not used anymore) |
166 | |
167 | if (jmatchAny(ll("in = dialog", "fact + event => replacement fact"), r.comments)) |
168 | for (int i = 0; i < l(r.insWithType); i++) |
169 | r.insWithType.get(i).type = "dialog-" + (l(r.insWithType)-i); |
170 | |
171 | new Matches m; |
172 | for (S s : r.comments) { |
173 | if (jMatchStart("in * =", s, m) && isInteger($1) |
174 | && (startsWith($2, 'statement) || eq($2, 'condition))) |
175 | set(get(r.insWithType, parseInt($1)-1), type := $2); |
176 | else if (jMatchStart("out * =", s, m) && isInteger($1)) |
177 | r.setOutType(parseInt($1)-1, $2); |
178 | else if (jMatchStart("out = ", s, m)) |
179 | r.setOutType(0, m.rest()); |
180 | else if (jMatchStart("in * :", s, m) && isInteger($1)) { |
181 | int i = parseInt($1)-1; |
182 | S comment = m.rest(); |
183 | GRuleLine line = get(r.insWithType, i); |
184 | if (line != null) { |
185 | line.comments = addToOrCreateList(line.comments, comment); |
186 | if (match("tokenize with *", comment, m)) |
187 | line.tokenizer = $1; |
188 | } |
189 | } |
190 | } |
191 | |
192 | r.out = rule.b; |
193 | r.vars = ai_wordsInBothSidesOfPair_uncurly(rule); |
194 | rules.add(r); |
195 | } |
196 | |
197 | /* |
198 | LS addFacts(Collection<S> facts) { |
199 | ret addAllAndReturnNew(this.facts, facts); |
200 | } |
201 | |
202 | void addFact(S fact) { facts.add(fact); } |
203 | |
204 | void addAndInterpretFacts(Collection<S> facts) { |
205 | addFacts(recursiveInterpretations(addFacts(facts))); |
206 | } |
207 | |
208 | void processInput(S input) { |
209 | print("\nInput: " + tok_dropCurlyBrackets(input)); |
210 | temp tempIndent(); |
211 | for (S in : interpretations(input)) { |
212 | print("\nInterpretation: " + formatForUser(in)); |
213 | Set<S> out = litciset(); |
214 | interpretRecursively(in, 5, out); |
215 | } |
216 | } |
217 | |
218 | Set<S> recursiveInterpretations(S in) { |
219 | ret recursiveInterpretations(ll(in)); |
220 | } |
221 | |
222 | Set<S> recursiveInterpretations(Iterable<S> in) { |
223 | Set<S> inSet = asCISet(in); |
224 | Set<S> out = litciset(); |
225 | new LinkedHashSet<S> queue; |
226 | addAll(queue, inSet); |
227 | while (l(out) < maxOutLines && nempty(queue)) { |
228 | LS intp = interpretations(popFirst(queue)); |
229 | for (S i : intp) |
230 | if (!inSet.contains(i) && !out.contains(i)) { |
231 | out.add(i); |
232 | queue.add(i); |
233 | if (l(out) >= maxOutLines) break; |
234 | } |
235 | } |
236 | ret out; |
237 | } |
238 | |
239 | void interpretRecursively(S input, int level, Set<S> out) { |
240 | if (level <= 0) ret; |
241 | LS interpretations = interpretations(input); |
242 | temp tempIndent(); |
243 | for (S in : addAllAndReturnNew(out, interpretations)) { |
244 | print(" => " + formatForUser(in)); |
245 | for (S s : lithashset(in, ai_superSimpleVerbCorrector(in))) |
246 | interpretRecursively(s, level-1, out); |
247 | } |
248 | } |
249 | |
250 | LS defaultParse(S s) { |
251 | ret codeTokens_lazy_uncurly(javaTokNPunctuationWithBrackets_cached(s)); |
252 | } |
253 | |
254 | LS interpretations(S input) { |
255 | LS tokI = defaultParse(input); |
256 | //print("Raw parse: " + tokI); |
257 | new LS interpretations; |
258 | |
259 | for (Rule rule : rules) { |
260 | continue unless l(rule.in) == 1; |
261 | LS tokR = defaultParse(first(rule.in)); |
262 | final SS map = ciMapWithoutKeysEqualToValues(zipTwoListsToCIMap_strict(tokR, tokI)); |
263 | if (map == null) continue; |
264 | |
265 | // Found matching rule |
266 | |
267 | int score = l(tokR)-l(map); |
268 | L<S> nonVars = withoutDollarVars(listMinusSet(keys(map), rule.vars)); |
269 | if (printRejectedVars && nempty(nonVars)) print("Rejected vars: " + nonVars); |
270 | if (nempty(nonVars)) score = 0; |
271 | int percentScore = ratioToIntPercent(score, l(tokR)); |
272 | if (printMappings) print(" " + percentScore + "% " + reverseMapCI_joinDuplicatesWithPlus(map) + " | " + rule.in); |
273 | if (percentScore < minScore) continue; |
274 | |
275 | // Make consequence |
276 | |
277 | S c = rule.out; |
278 | c = join(mapCodeTokens(javaTok(c), func(S s) -> S { getOrKeep_tryUncurlied(map, s) })); |
279 | c = join(mapCodeTokens(javaTokWithBrackets(c), func(S s) -> S { getOrKeep_tryUncurlied(map, s) })); |
280 | //c = javaTokWithBrackets_recursiveTransform(func(S s) -> S { getOrKeep(map, s) }, c); |
281 | |
282 | interpretations.add(c); |
283 | } |
284 | |
285 | ret interpretations; |
286 | } |
287 | |
288 | S formatForUser(S s) { |
289 | ret tok_dropCurlyBrackets(s); |
290 | }*/ |
291 | |
292 | LPair<LS, S> rulesAsPairs() { |
293 | ret map(rules, func(Rule r) -> Pair<LS, S> { pair(r.in, r.out) }); |
294 | } |
295 | |
296 | L<SimplifyWithRule> splitterRules() { |
297 | ret (L) [Rule r : rules | r instanceof SimplifyWithRule && ((SimplifyWithRule) r).isSplitRule]; |
298 | } |
299 | |
300 | void dropRulesWhere(IF1<Rule, Bool> pred) { |
301 | rules = antiFilter(pred, rules); |
302 | } |
303 | |
304 | void deleteRule(S ruleID) { |
305 | dropRulesWhere(r -> eq(r.globalID, ruleID)); |
306 | } |
307 | |
308 | Rule getRule(S ruleID) { |
309 | ret firstWhere(rules, globalID := ruleID); |
310 | } |
311 | } |
Began life as a copy of #1021346
download show line numbers debug dex old transpilations
Travelled to 8 computer(s): bhatertpkbcr, cfunsshuasjs, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, whxojlpjdney
No comments. add comment
Snippet ID: | #1021389 |
Snippet name: | RuleEngine2 - with multiple conditions per rule & facts |
Eternal ID of this version: | #1021389/123 |
Text MD5: | e2d7beca60770ca7cfa04b2b46a91d1f |
Author: | stefan |
Category: | javax / a.i. |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-03-29 15:46:15 |
Source code size: | 10204 bytes / 311 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 626 / 1259 |
Version history: | 122 change(s) |
Referenced in: | [show references] |