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; bool useIterate, allowUnsafeEvals = true; new HashMap funcCheckers; new HashMap funcIterators; S followingUpOn; List recentHistory; L entities; F2 evaluator = func(Exp e, Matching m) { S code = e.text(); if (!allowUnsafeEvals && !isSafeCodeFragment(code)) null; ret nlLogic_evalExp(NLLogicChecker_v2.this, code, m); }; abstract sclass FuncChecker { abstract bool check(Func e, Matching m); } abstract sclass FuncIterator { abstract void iterate(Func e, Matching m, Runnable onMatch); } sclass Matching { new VarMatches matches; // var -> string value 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); } IfThen apply(IfThen r, Matching m) { ret r == null ? null : IfThen(apply(r.in, m), apply(r.out, m)); } 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) { if (contains(e.options, 'javaTokNoQuotes) && e.arg instanceof Sentence2) ret Func(e.name, Sentence2(join(replaceVars_withDollarQ(javaTokNoQuotes(e.arg.text()), m)))); else 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) && !cic(e.options, 'flexMatch)) { S pat = nlLogic_text(e.arg); if (contains(e.options, 're)) // regular expression ret regexpFindIC(pat, input); 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 VarMatches oldMatches = new (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 FuncChecker checker = funcCheckers.get(e.name); if (checker != null && checker.check(e, m)) true; if (eq(e.name, "singular")) ret nlLogic_stringFunction(f singular, e, m.matches); 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 = e.arg.text(); //print("n=" + n + ", Matching " + e + " with " + line); ret matcher.match(pat, line, m.matches); } else if (eq(e.name, 'iSaid)) { S line = getString(last(recentHistory), 'text); if (line == null) false; ret matcher.match(e.arg.text(), 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); if (eq(text, followingUpOn)) true; 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; } else if (eq(e.name, 'debug)) ret true with print("[debug] " + apply(e.arg, m)); } 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); Bool x = ai_matchObjectWithMultipleVars(vars, result, m.matches); if (x != null) ret x; 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); } } } } if (e cast Sentence2) { // SENTENCE CONDITIONS S text = e.text(); if (eq(text, 'true)) true; } 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) }); ret; } if (e cast ExpNot) { if (m.verbose) print("iterate not: " + e); final new Flag flag; withCancelPoint(voidfunc(final CancelPoint cp) { iterate(e.a, m, r { if (m.verbose) print("iterate not: raising flag"); flag.raise(); cancelTo(cp); }); }); if (!flag.isUp()) callF(onMatch); ret; } iterate_single(e, m, onMatch); } void iterate_single(final Exp e, final Matching m, final Runnable onMatch) { if (e cast Func) { //print("Checking func iterators: " + e.name); FuncIterator it = funcIterators.get(e.name); if (it != null) it.iterate(e, m, onMatch); if (eq(e.name, 'fact)) { S pat = nlLogic_text(e.arg); VarMatches oldMatches = new(m.matches); for (S fact : facts) { if (m.verbose) print("iterate matching: " + pat + " with " + fact + " - " + sfu(m.matches)); for (S pat2 : tok_splitAtVerticalBar(pat)) if (matcher.match(pat2, fact, m.matches)) { if (m.verbose) print("iterate match: " + pat + " / " + fact + " - " + m); try { callF(onMatch); } finally { m.matches = new VarMatches(oldMatches); } } } ret; } else if (eq(e.name, "input") && cic(e.options, 'flexMatch)) { S pat = nlLogic_text(e.arg); if (m.verbose) print("input flexMatch " + pat); VarMatches oldMatches = new(m.matches); nlLogic_flexMatch_iterate_vbar(pat, input, m, onMatch, oldMatches); ret; } } if (e instanceof Sentence2 && contains(e.text(), " in ")) { L tok = javaTokPlusAllThreeBrackets(e.text()); S var, var2; new Matches mm; if (m.verbose) print("iterate in eval tok: " + sfu(tok)); if (jmatch("* in eval *", tok, mm) && isDollarVar(var = mm.get(0)) && isRoundBracketed(mm.get(1))) { if (m.verbose) print("iterate in eval: " + mm.get(1)); Iterable l = cast evalExp(Sentence2(deRoundBracket(mm.get(1))), m); VarMatches oldMatches = new(m.matches); for (O element : unnull(l)) { continue unless element != null; S s = str(element); if (m.verbose) print("iterate in eval got element: " + s); if (strictPutIC(m.matches, var, s)) try { callF(onMatch); } finally { m.matches = new VarMatches(oldMatches); } } } if (jmatch("*, * in eval *", tok, mm) && isDollarVar(var = mm.get(0)) && isDollarVar(var2 = mm.get(1)) && isRoundBracketed(mm.get(2))) { LS vars = ll(var, var2); Iterable l = cast evalExp(Sentence2(deRoundBracket(mm.get(2))), m); VarMatches oldMatches = new(m.matches); for (O element : unnull(l)) { continue unless element != null; if (m.verbose) print("iterate in eval got element: " + element); if (isTrue(ai_matchObjectWithMultipleVars(vars, element, m.matches))) try { callF(onMatch); } finally { m.matches = new VarMatches(oldMatches); } } } } temp tempBackupMatches(m); if (m.verbose) print("iterate fallback: " + sfu(e)); if (checkExpression(e, m)) { if (m.verbose) print("iterate fallback match: " + e); callF(onMatch); } } bool checkHelper(Exp e, Matching m) { ret nlLogic_checkHelper(e, m.matches); } *() { initCheckers(); initIterators(); } void initCheckers { mapPutMultipleKeys(funcCheckers, new FuncChecker { bool check(Func e, Matching m) { ret nlLogic_stringFunction(f ai_verbPhraseFromThirdPerson, e, m.matches); } }, 'verbPhraseFromThirdPerson, 'verbFromThirdPerson); funcCheckers.put('eval, new FuncChecker { bool check(Func e, Matching m) { ret eq("true", str(evalExp(e.arg, m))); } }); } void initIterators { funcIterators.put('phrase, new FuncIterator { void iterate(Func e, Matching m, Runnable onMatch) { //if (!contains(e.options, 'words)) ret; S pat = e.argText(); LS patTok = standardTok(pat); LS inputTok = standardTok(input); // optimizable for (LS inputPart : tok_marchPatternThroughInput(patTok, inputTok)) if (matcher.match(pat, join(inputPart), m.matches)) callF(onMatch); } }); } // TODO: cache LS standardTok(S s) { ret javaTokWithAllBrackets(s); } }