sclass NLLogicChecker_v2 { S input; new L facts; new L rules; StringMatcher matcher = ai_standardMatcher(); new L posted; new ThreadLocal checkingRule; VF2 onChecking; // first arg: IfThen or Exp VF3 onChecked; // first arg: IfThen or Exp, last arg: Bool or Throwable static bool staticVerbose; List recentHistory; L entities; F2 evaluator = func(Exp e, Matching m) { nlLogic_evalExp(e, m) }; sclass Matching { SS matches = new LinkedHashMap; // var -> match new L output; //L ruleFails; bool verbose = staticVerbose; toString { ret sfu(matches) + "/" + sfu(output); } } bool checkRule(final IfThen rule, final Matching m) { if (rule == null) false; ret logCheck(rule, m, func -> Bool { checkRule_impl(rule, m) }); } bool checkRule_impl(IfThen rule, Matching m) { temp tempSetThreadLocal(checkingRule, rule); if (!checkExpression(rule.in, m)) false; m.output.add(apply(rule.out, m)); true; } // returns (success, remaining conditions) Pair checkFirstCondition(IfThen rule, Matching m) { temp tempSetThreadLocal(checkingRule, rule); ret checkFirstCondition(rule.in, m); } Pair checkFirstCondition(Exp exp, Matching m) { Pair p = nlLogic_extractFirstCondition(exp); if (p == null) ret pair(true, null); if (!checkExpression(p.a, m)) ret pair(false, null); ret pair(true, p.b); } Exp apply(Exp e, Matching m) { ret apply(e, m.matches); } Exp apply(Exp e, SS m) { if (e == null) null; if (e cast And) ret And(apply(e.a, m), apply(e.b, m)); if (e cast ExpNot) ret ExpNot(apply(e.a, m)); if (e cast Func) ret Func(e.name, apply(e.arg, m)); if (e cast Sentence) ret Sentence(javaTok(matcher.apply(e.text(), m))); if (e cast Sentence2) ret Sentence2(matcher.apply(e.text(), m)); ret e; } A logCheck(O o, Matching m, F0 f) { callF(onChecking, o, m); try { A result = callF(f); callF(onChecked, o, m, result); ret result; } catch e { callF(onChecked, o, m, e); throw rethrow(e); } } final bool checkExpression(final Exp e, final Matching m) { ret logCheck(e, m, func -> Bool { checkExpression_impl(e, m) }); } bool checkExpression_impl_1(Exp e, Matching m) { if (e cast And) ret checkExpression(e.a, m) && checkExpression(e.b, m); ret checkExpression_single(e, m); } bool checkExpression_single(Exp e, Matching m) { if (e cast ExpNot) { temp tempBackupMatches(m); ret !checkExpression(e.a, m); } if (e cast Func) { if (eq(e.name, "input")) { S pat = nlLogic_text(e.arg); ret nlLogic_matchVBarPattern(this, pat, input, m); } else if (eq(e.name, "fact")) { S pat = nlLogic_text(e.arg); if (contains(e.options, "random")) ret nlLogic_matchRandomFact(this, pat, m); else ret nlLogic_matchAnyFact(this, pat, m); } else if (eq(e.name, "allFacts")) { S pat = nlLogic_text(e.arg); L> l = nlLogic_matchAllFacts(this, pat, m); for i over l: { SS vars = reverseGet(l, i); // facts are normally reversed, so re-reverse to get them in order int n = i+1; for (S var, value : vars) { if (!strictPutIC(m.matches, var + n, value)) false; } } true; } } false; } AutoCloseable tempBackupMatches(final Matching m) { final LinkedHashMap oldMatches = (LinkedHashMap) cloneMap(m.matches); ret autocloseable { m.matches = oldMatches; }; } O evalExp(Exp e, Matching m) { ret callF(evaluator, e, m); } bool checkExpression_impl(Exp e, Matching m) { new Matches mm; if (e cast Func) { // FUNCTION CONDITIONS if (eqOneOf(e.name, "verbPhraseFromThirdPerson", "verbFromThirdPerson")) ret nlLogic_stringFunction(f ai_verbPhraseFromThirdPerson, e, m.matches); else if (eq(e.name, "singular")) ret nlLogic_stringFunction(f singular, e, m.matches); else if (eq(e.name, 'eval)) ret eq("true", str(evalExp(e.arg, m))); else if (eq(e.name, 'entity)) { if (entities == null) { long time = sysNow(); fS switched = switcheroo(input); print("Switched >> " + switched); entities = evalWithTimeoutOrNull(5000, func -> LS { mapMethod('text, ai_extractEntities_v1(switched)) }); print("Entities (" + elapsedMS(time) + " ms): " + joinWithComma(entities)); if (entities == null) entities = new L; } for (S entity : entities) if (matcher.match(nlLogic_text(e.arg), entity, m.matches)) true; } else if (startsWith(e.name, "line", mm) && isInteger(mm.rest()) && checkingRule! != null) { int n = parseInt(mm.rest())-nlLogic_numberOfLinesReferenced(checkingRule->in); S line = n == 0 ? input : getString(get(recentHistory, l(recentHistory)+n), 'text); //print("Recent: " + recentHistory); S pat = nlLogic_text(e.arg); //print("n=" + n + ", Matching " + e + " with " + line); ret matcher.match(pat, line, m.matches); } else if (eq(e.name, 'unknownIf)) { S statement = nlLogic_text(apply(e.arg, m)); print("Checking statement: " + statement); ret !cic(facts, statement) && !cic(facts, "Untrue: " + statement); } else if (eq(e.name, 'inputContainsTokens)) ret jcontains(input, nlLogic_text(e.arg)); else if (eq(e.name, 'inputStartsWith)) ret startsWith(input, nlLogic_text(e.arg)); else if (eq(e.name, 'anyInput)) ret nempty(input); else if (eq(e.name, 'preciseInput)) ret eq(nlLogic_text(apply(e.arg, m)), input); // Incomplete var support else if (eq(e.name, 'followingUpOn)) { S text = nlLogic_text(e.arg); Map prevPrev = nextToLast(recentHistory); S msgID = getString(prevPrev, 'globalID); S input = getString(prevPrev, 'text); S ruleID = null, dollarInput = null; // e.g. "lhnshnhcklhabvmu with input=$input" if (match("* with input=*", text, mm) && !isDollarVar(mm.get(1))) { ruleID = mm.unq(0); dollarInput = mm.get(1); } else ruleID = text; //print("followingUpOn: msgID=" + msgID + ", input=" + input); //printStruct(+prevPrev); S pat = format("Rule * fired on message * ", ruleID, msgID); //print("pat=" + pat); for (S ruleFired : mL("Telegram Rule Fires")) if (swic(ruleFired, pat)) { if (dollarInput != null && !strictPutIC(m.matches, dollarInput, input)) false; L tok = javaTokWithBrackets2(ruleFired); if (find3("with vars *", tok, mm)) { SS vars = safeUnstructMap(mm.get(0)); if (!addMapToMapWithoutOverwritingIC(m.matches, vars)) false; } true; } false; } } if (e cast Eq) { // EQUATION CONDITIONS Exp r = e.right; S var = nlLogic_text(e.left); if (endsWith(var, "!")) { // $x != bla temp tempBackupMatches(m); ret !matcher.match(dropSuffixTrim("!", var), r.text(), m.matches); } if (r cast Func) pcall { if (eq(r.name, 'eval)) { O result = evalExp(r.arg, m); if (result != null) { // Not allowing null results anymore LS vars = tok_splitAtComma(var); if (l(vars) == 2 && shortClassNameIs(result, 'Pair)) ret ai_matchMulti(vars, ll(strGet(result, 'a), strGet(result, 'b)), m.matches); if (l(vars) == 3 && shortClassNameIs(result, 'T3)) ret ai_matchMulti(vars, ll(strGet(result, 'a), strGet(result, 'b), strGet(result, 'c)), m.matches); if (result cast Collection) if (l(result) == l(vars)) ret ai_matchMulti(vars, allToString(result), m.matches); S sResult = str(result); StringMatcher _matcher = matcher; if (isDollarVar(var)) _matcher = new NLStringMatcher_singleDollarVar; else if (contains(var, ',')) { // TODO: use Pair instead - there is only like one rule using this if (allDollarVars(vars) && ai_matchMulti(vars, tok_splitAtComma(sResult), m.matches)) true; } ret _matcher.match(var, sResult, m.matches); } } } } ret checkExpression_impl_1(e, m); } void iterate(final Exp e, final Matching m, final Runnable onMatch) { if (e cast And) { iterate(e.a, m, r { iterate(e.b, m, onMatch) }); } if (e cast Func) { if (eq(e.name, "fact")) { S pat = nlLogic_text(e.arg); for (S fact : facts) { temp tempBackupMatches(m); if (m.verbose) print("iterate matching: " + pat + " with " + fact); if (matcher.match(pat, fact, m.matches)) { if (m.verbose) print("iterate match: " + pat + " / " + fact + " - " + m); callF(onMatch); } } } } } bool checkHelper(Exp e, Matching m) { if (e cast Func) { S arg = e.arg.text(); if (eq(e.name, 'newID) && isDollarVar(arg)) ret matcher.match(arg, aGlobalID(), m.matches); } false; } }