// should now be persistable sclass RuleEngine2 { sclass SimplifyWithRule extends Rule { transient O _function; // function name or F1. can return S or LS bool isSplitRule; // splitter rather than simplifier bool callOnTree; // function takes GazelleTree rather than S O function() { if (_function == null) { gazelle_parseInstruction(this); assertNotNull(_function); } ret _function; } } abstract sclass QuickCheck { abstract bool check(S input); } sclass CicQuickCheck extends QuickCheck { S string; bool check(S input) { ret cic(input, string); } } sclass Rule { S globalID; LS in; S out; LS comments; Set<S> vars; L<GRuleLine> insWithType; LS outTypes; transient L inputMassagers; // func(S input) -> S for every in transient L checkerFunctions; // func(S input) -> SS for every in transient L matchers; // func(LS tokC, LS tokI, RuleEngine2_MatchedRule) -> SS for every in S text; // original text QuickCheck applicabilityQuickCheck; S purpose = ""; Double qualityCutOff; bool avoidRecentlyUsedMappings; // (optional) massage the variable map after matching // L<func(SS varMap, LS tokCondition, LS tokInput, RuleEngine2_MatchedRule matched) -> SS newVarMap> L mapMassagers; S asText() { if (l(in) > 1 && eq(last(in), out)) ret join(" \n+ ", dropLast(in)) + " \n+=> " + out; ret join(" \n+ ", in) + " \n=> " + out; } void addMapMassager(O massager) { if (massager != null) mapMassagers = addToOrCreateList(mapMassagers, massager); } void setInputMassager(int i, O massager) { inputMassagers = listSetWithCreate(inputMassagers, i, massager); } void setMatcher(int i, O matcher) { matchers = listSetWithCreate(matchers, i, matcher); } void setOutType(int i, S type) { outTypes = listSetWithCreate(outTypes, i, type); } S outType() { ret first(outTypes); } void parseGeneralComments() { purpose = gazelle_purposeFromComments(comments); pcall { S s = matchAny_firstGroup("quality cutoff = *", comments); if (s != null) qualityCutOff = parseDouble(s); } avoidRecentlyUsedMappings = cic(comments, "avoid recently used mapping"); } toString { ret globalID + ": " + text; } } // end of class Rule int minScore = 50; // minimal percentage score for rule matching int maxLevel = 100, maxOutLines = 10000; new L<Rule> rules; bool printMappings, printGroupedData, printRejectedVars; Set<S> facts = ciSet(); transient F2<S, S, O> callFunctionOnString = func(S function, S s) -> O { callAndMake(function, s) }; bool hasHelpers; *() {} // probably not used anymore *(LPair<S> rulesWithComments) { for (PairS p : unnull(rulesWithComments)) addRule(p.a, splitAtDoubleArrow_pair(p.a), p.b, null); } void copyRulesFrom(RuleEngine2 engine) { if (engine == null) ret; rules.addAll(engine.rules); hasHelpers = engine.hasHelpers; } void addRules2(L<T3S> rulesWithCommentsAndID) { new Matches m; for (T3<S> p : unnull(rulesWithCommentsAndID)) try { PairS p2 = splitAtDoubleArrow_pair(p.a); if (p2 != null) continue with addRule(p.a, p2, p.b, p.c); new SimplifyWithRule r; r.globalID = p.c; r.text = p.a; r.comments = lines(p.b); r.parseGeneralComments(); S s = p.a; gazelle_parseInstruction(r); if (r._function == null) continue; rules.add(r); } catch e { printStackTrace("Exception parsing rule " + p.c, e); } } void addRule(S text, PairS rule, S comments, S globalID) { if (rule == null) ret; rule = ai_rule_pair_expandPlusArrow(rule); LS conditions = tok_splitAtPlusAtBeginningOfLine(rule.a); new Rule r; r.globalID = globalID; r.text = text; r.comments = lines(comments); r.parseGeneralComments(); r.in = conditions; r.insWithType = map(conditions, func(S s) -> GRuleLine { GRuleLine(s, "standard") }); // input massagers for "<anything>" for (int i = 0; i < l(r.in); i++) { S s = r.in.get(i); continue unless endsWith(s, "<anything>"); if (eq(s, "<anything>")) r.checkerFunctions = listSetWithCreate(r.checkerFunctions, i, func(S s) -> SS { nempty(s) ? litmap() : null }); else if (jmatch("* says: <anything>", javaTokWithBrackets(s))) { //print("Made input massager"); r.setInputMassager(i, func(S s) -> S { new Matches m; ret jMatchStart("* says:", javaTokWithBrackets(s), m) ? m.get(0) + " says: <anything>" : s; }); } } for (Matches m : getJMatches_all("expand *", r.comments)) if (isQuoted(m.get(0))) { fS var = m.unq(0); for (int i = 0; i < l(r.in); i++) r.setMatcher(i, new O { SS get(LS tokC, LS tokI, RuleEngine2_MatchedRule matched) { if (eqic(nextToLast(tokC), var)) tokI = tok_groupLastTokensToMatchPattern(tokI, tokC); ret zipCodeTokensToCIMap_strict_withoutEquals(tokC, tokI); }}); } // "in = dialog" (probably not used anymore) if (jmatchAny(ll("in = dialog", "fact + event => replacement fact"), r.comments)) for (int i = 0; i < l(r.insWithType); i++) r.insWithType.get(i).type = "dialog-" + (l(r.insWithType)-i); new Matches m; for (S s : r.comments) { if (jMatchStart("in * =", s, m) && isInteger($1) && (startsWith($2, 'statement) || eq($2, 'condition))) set(get(r.insWithType, parseInt($1)-1), type := $2); else if (jMatchStart("out * =", s, m) && isInteger($1)) r.setOutType(parseInt($1)-1, $2); else if (jMatchStart("out = ", s, m)) r.setOutType(0, m.rest()); else if (jMatchStart("in * :", s, m) && isInteger($1)) { int i = parseInt($1)-1; S comment = m.rest(); GRuleLine line = get(r.insWithType, i); if (line != null) { line.comments = addToOrCreateList(line.comments, comment); if (match("tokenize with *", comment, m)) line.tokenizer = $1; } } } r.out = rule.b; r.vars = ai_wordsInBothSidesOfPair_uncurly(rule); rules.add(r); } /* LS addFacts(Collection<S> facts) { ret addAllAndReturnNew(this.facts, facts); } void addFact(S fact) { facts.add(fact); } void addAndInterpretFacts(Collection<S> facts) { addFacts(recursiveInterpretations(addFacts(facts))); } void processInput(S input) { print("\nInput: " + tok_dropCurlyBrackets(input)); temp tempIndent(); for (S in : interpretations(input)) { print("\nInterpretation: " + formatForUser(in)); Set<S> out = litciset(); interpretRecursively(in, 5, out); } } Set<S> recursiveInterpretations(S in) { ret recursiveInterpretations(ll(in)); } Set<S> recursiveInterpretations(Iterable<S> in) { Set<S> inSet = asCISet(in); Set<S> out = litciset(); new LinkedHashSet<S> queue; addAll(queue, inSet); while (l(out) < maxOutLines && nempty(queue)) { LS intp = interpretations(popFirst(queue)); for (S i : intp) if (!inSet.contains(i) && !out.contains(i)) { out.add(i); queue.add(i); if (l(out) >= maxOutLines) break; } } ret out; } void interpretRecursively(S input, int level, Set<S> out) { if (level <= 0) ret; LS interpretations = interpretations(input); temp tempIndent(); for (S in : addAllAndReturnNew(out, interpretations)) { print(" => " + formatForUser(in)); for (S s : lithashset(in, ai_superSimpleVerbCorrector(in))) interpretRecursively(s, level-1, out); } } LS defaultParse(S s) { ret codeTokens_lazy_uncurly(javaTokNPunctuationWithBrackets_cached(s)); } LS interpretations(S input) { LS tokI = defaultParse(input); //print("Raw parse: " + tokI); new LS interpretations; for (Rule rule : rules) { continue unless l(rule.in) == 1; LS tokR = defaultParse(first(rule.in)); final SS map = ciMapWithoutKeysEqualToValues(zipTwoListsToCIMap_strict(tokR, tokI)); if (map == null) continue; // Found matching rule int score = l(tokR)-l(map); L<S> nonVars = withoutDollarVars(listMinusSet(keys(map), rule.vars)); if (printRejectedVars && nempty(nonVars)) print("Rejected vars: " + nonVars); if (nempty(nonVars)) score = 0; int percentScore = ratioToIntPercent(score, l(tokR)); if (printMappings) print(" " + percentScore + "% " + reverseMapCI_joinDuplicatesWithPlus(map) + " | " + rule.in); if (percentScore < minScore) continue; // Make consequence S c = rule.out; c = join(mapCodeTokens(javaTok(c), func(S s) -> S { getOrKeep_tryUncurlied(map, s) })); c = join(mapCodeTokens(javaTokWithBrackets(c), func(S s) -> S { getOrKeep_tryUncurlied(map, s) })); //c = javaTokWithBrackets_recursiveTransform(func(S s) -> S { getOrKeep(map, s) }, c); interpretations.add(c); } ret interpretations; } S formatForUser(S s) { ret tok_dropCurlyBrackets(s); }*/ LPair<LS, S> rulesAsPairs() { ret map(rules, func(Rule r) -> Pair<LS, S> { pair(r.in, r.out) }); } L<SimplifyWithRule> splitterRules() { ret (L) [Rule r : rules | r instanceof SimplifyWithRule && ((SimplifyWithRule) r).isSplitRule]; } void dropRulesWhere(IF1<Rule, Bool> pred) { rules = antiFilter(pred, rules); } void deleteRule(S ruleID) { dropRulesWhere(r -> eq(r.globalID, ruleID)); } Rule getRule(S ruleID) { ret firstWhere(rules, globalID := ruleID); } }
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: | 627 / 1259 |
Version history: | 122 change(s) |
Referenced in: | #1034167 - Standard Classes + Interfaces (LIVE, continuation of #1003674) |