Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

311
LINES

< > BotCompany Repo | #1021389 // RuleEngine2 - with multiple conditions per rule & facts

JavaX fragment (include)

// 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);
  }
}

Author comment

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)