import java.util.*; import java.util.zip.*; import java.util.List; import java.util.regex.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.table.*; import java.io.*; import java.net.*; import java.lang.reflect.*; import java.lang.ref.*; import java.lang.management.*; import java.security.*; import java.security.spec.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import javax.imageio.*; import java.math.*; public class main { static final class Prolog { boolean upperCaseVariables = false; // true for SNL, false for NL long varCount; boolean showStuff; List stack = new ArrayList(); Trail sofar = null; List program = new ArrayList(); List> programByArity = new ArrayList>(); long steps; List log; int maxLogSize = 1000; // lines int maxLevel = 10000; // max stack size int timeLimit = 20; // 20 seconds long startTime; boolean allowEval = true; // name of theory to memorize stuff to String outTheory; // stats int maxLevelSeen; // maximum stack level reached during computation long topUnifications, exceptions; static interface Native { public boolean yo(Prolog p, Lisp term); } boolean headeq(String a, String b) { return isQuoted (a) ? eq(a, b) : eqic(a, b); } static class Var extends Lisp { long id; Lisp instance; Var(String name) { super(name); instance = this; } Var(long id) { super("___"); this.id = id; instance = this; } void reset() { instance = this; } public String toString() { if (instance != this) return instance.toString(); return prettyName(); } String prettyName() { return isUserVar() ? getName() : "_" + id; } boolean isUserVar() { return id == 0; } String getName() { return head; } Lisp getValue() { Lisp l = instance; while (l instanceof Var) { Var v = (Var) ( l); if (v.instance == v) return v; l = v.instance; } return l; } } static class Collector extends Lisp { List solutions = new ArrayList(); Collector() { super("___"); } public String toString() { return ""; } } static class Clause { Lisp head; String loadedFrom; // theory name boolean used; // Either body or nat will be set (or none) Native nat; Goal body; Clause(Lisp head, Native nat) { this.nat = nat; this.head = head;} Clause(Lisp head, Goal body) { this.body = body; this.head = head;} Clause(Lisp head, Goal body, Native nat) { this.nat = nat; this.body = body; this.head = head;} Clause(Lisp head) { this.head = head;} Clause copy(Prolog p) { return new Clause(p.copy(head), body == null ? null : body.copy(p), nat); } public String toString() { //ret head + " :- " + (body == null ? "true" : body); return nat != null ? head + " :- native" : (body == null ? head.toString() : head + " :- " + body); } } class Trail { Var tcar; Trail tcdr; Trail(Var tcar, Trail tcdr) { this.tcdr = tcdr; this.tcar = tcar;} } Trail Trail_Note() { return sofar; } void Trail_Set(Var x, Lisp value) { sofar = new Trail(x, sofar); x.instance = value; /*if (showStuff) log(indent(level()) + " Push " + x.prettyName() + " (" + x + ")");*/ } void Trail_Undo(Trail whereto) { for (; sofar != whereto; sofar = sofar.tcdr) { /*if (showStuff) log(indent(level()) + " Resetting variable " + sofar.tcar.prettyName() + " (" + sofar.tcar + ")");*/ sofar.tcar.reset(); } } static class TermVarMapping { List vars = new ArrayList(); TermVarMapping(List vars) { this.vars = vars;} TermVarMapping(Var... vars) { this.vars.addAll(asList(vars)); } void showanswer() { print("TRUE."); for (Var v : vars) print(" " + v.prettyName() + " = " + v); } } static class Goal { Lisp car; Goal cdr; Goal(Lisp car, Goal cdr) { this.cdr = cdr; this.car = car;} Goal(Lisp car) { this.car = car;} public String toString() { return cdr == null ? car.toString() : car + "; " + cdr; } Goal copy(Prolog p) { return new Goal(p.copy(car), cdr == null ? null : cdr.copy(p)); } Goal append(Goal l) { return new Goal(car, cdr == null ? l : cdr.append(l)); } } // class Goal boolean unifyOrRollback(Lisp a, Lisp b) { Trail t = Trail_Note(); if (unify(a, b)) return true; Trail_Undo(t); return false; } boolean unify(Lisp thiz, Lisp t) { if (thiz == null) throw fail("thiz=null"); if (t == null) throw fail("t=null"); if (thiz instanceof Var) { // TermVar::unify Var v = (Var) ( thiz); if (v.instance != v) return unify(v.instance, t); Trail_Set(v, t); return true; } // TermCons::unify return unify2(t, thiz); } boolean unify2(Lisp thiz, Lisp t) { if (thiz instanceof Var) return unify(thiz, t); int arity = thiz.size(); if (!headeq(thiz.head, t.head) || arity != t.size()) return false; for (int i = 0; i < arity; i++) if (!unify(thiz.get(i), t.get(i))) return false; return true; } Lisp copy(Lisp thiz) { if (thiz instanceof Var) { Var v = (Var) ( thiz); if (v.instance == v) { Trail_Set(v, newVar()); } return v.instance; } // copy2 (copy non-var) Lisp l = new Lisp(thiz.head); for (Lisp arg : thiz) l.add(copy(arg)); return l; } Var newVar() { return new Var(++varCount); } Var newVar(String name) { return new Var(name); } Clause clause(Lisp head, Goal body) { return prologify(new Clause(head, body)); } // primary processor for freshly parsed rules Clause clause(Lisp rule) { List ops = snlSplitOps(rule); /*if (showStuff) log("clause(Lisp): " + rule + " => " + structure(ops)); */ // expand rule shortcuts (say/memorize/...) for (int i = 0; i < l(ops)-1; i++) if (ops.get(i).is("memorize *")) ops.set(i, lisp("and *", lisp("[]", "memorize", ops.get(i)))); if (!empty(ops) && last(ops).is("say *")) ops.set(l(ops)-1, lisp("then *", lisp("[]", "say", last(ops).get(0)))); rule = empty(ops) ? rule : snlJoinOps(ops); if (allowEval) rule = new EvalTransform().evalTransformRule(rule); ops = snlSplitOps(rule); if (!empty(ops) && last(ops).is("then *")) { Lisp head = last(ops).get(0); Goal goal = null; // TODO: check the actual words (if/and/...) for (int i = l(ops)-2; i >= 0; i--) goal = new Goal(ops.get(i).get(0), goal); return clause(head, goal); } else return clause(rule, (Lisp) null); } Clause clause(Lisp head, Lisp body) { return clause(head, body == null ? null : new Goal(body)); } Lisp prologify(Lisp term) { return prologify(term, new HashMap()); } Clause prologify(Clause c) { HashMap vars = new HashMap(); c = new Clause( prologify(c.head, vars), prologify(c.body, vars), c.nat); /*if (showStuff) log("Clause made: " + structure_seen(c));*/ return c; } Goal prologify(Goal goal, Map vars) { if (goal == null) return null; return new Goal( prologify(goal.car, vars), prologify(goal.cdr, vars)); } // Note: only for un-prologified boolean isVar(Lisp term) { return upperCaseVariables ? snlIsVar(term) : nlIsVar(term); } // for prologified (e.g. in native clauses) boolean isLiveVar(Lisp term) { return term instanceof Var; } Lisp prologify(Lisp term, Map vars) { if (term == null) return null; if (isVar(term)) { Var v = vars.get(term.head); if (v == null) vars.put(term.head, v = newVar(term.head)); return v; } else { Lisp l = new Lisp(term.head); for (Lisp arg : term) l.add(prologify(arg, vars)); return l; } } List findVars(Goal g) { IdentityHashMap map = new IdentityHashMap(); while (g != null) { findVars(g.car, map); g = g.cdr; } return asList(map.keySet()); } List findVars(Lisp term) { IdentityHashMap map = new IdentityHashMap(); findVars(term, map); return asList(map.keySet()); } void findVars(Lisp term, IdentityHashMap map) { if (term instanceof Var) map.put((Var) term, Boolean.TRUE); else for (Lisp arg : term) findVars(arg, map); } static Map makeVarMap(List vars) { HashMap map = new HashMap(); for (Var v : vars) map.put(v.getName(), v); return map; } List emptyProgram = new ArrayList(); // Executor stack entry class Entry { Goal goal; List program; // full program or filtered by arity int programIdx = 0; Trail trail; boolean trailSet; Entry(Goal goal) { this.goal = goal; Lisp car = resolve1(goal.car); if (car instanceof Var) { if (showStuff) log("Weird: Goal is variable: " + car); program = Prolog.this.program; } else { int arity = car.size(); if (showStuff) log(indent(level()) + "Goal arity " + arity + ": " + render(car)); program = arity >= l(programByArity) ? emptyProgram : programByArity.get(arity); } } } void start(String goal) { start(nlParse(goal)); } void start(Lisp goal) { start(new Goal(prologify(goal))); } String render(Lisp term) { return nlUnparse(resolve(term)); } String render(Goal goal) { return goal == null ? "-" : render(goal.car); } String render(Clause c) { return c == null ? "-" : render(c.head); } // warning: doesn't prologify the goal void start(Goal goal) { if (showStuff) log("Starting on goal: " + render(goal)); steps = 0; stack = new ArrayList(); Trail_Undo(null); stackAdd(new Entry(goal)); startTime = now(); } void log(String s) { if (log != null) { if (l(log) == maxLogSize) { log.add("[Log overflow]"); showStuff = false; } else if (l(log) < maxLogSize) log.add(s); } else if (showStuff) print("prolog: " + s); } int level() { return l(stack)-1; } boolean done() { boolean result = empty(stack); /*if (showStuff && result) log("Done with goal!");*/ return result; } boolean gnext(Goal g) { Goal gdash = g.cdr; if (gdash == null) { if (showStuff) log("gnext true"); return true; } else { stackAdd(new Entry(gdash)); return false; } } void stackPop() { Entry e = popLast(stack); if (e.trailSet) Trail_Undo(e.trail); } void backToCutPoint(int n) { if (showStuff) log("back to cut point " + n); while (l(stack) >= n) { if (showStuff) log("cut: dropping " + structure(last(stack).goal)); stackPop(); } } boolean step() { if (done()) throw fail("done!"); // safety if (now() >= startTime+timeLimit*1000) throw fail("time limit reached: " + timeLimit + " s"); ++steps; Entry e = last(stack); if (e.trailSet) { Trail_Undo(e.trail); e.trailSet = false; } e.trail = Trail_Note(); e.trailSet = true; // cut operator - suceeds first time if (e.goal.car.is("!", 1)) { if (showStuff) log("cut " + e.programIdx + ". " + structure(e.goal)); if (e.programIdx == -1) { ++e.programIdx; return gnext(e.goal); } else if (e.programIdx == 0) { ++e.programIdx; // fails 2nd time and cuts //e.goal.car.head = "false"; // super-hack :D backToCutPoint(parseInt(e.goal.car.get(0).raw())); return false; } else { stackPop(); return false; } } if (e.programIdx >= l(e.program)) { // program loop ends removeLast(stack); return false; } // now in program loop - get next clause to try Clause cc = e.program.get(e.programIdx); ++e.programIdx; // copy the clause; synchronize on it because it may come // from a shared Prolog interpreter Clause c; synchronized(cc) { c = cc.copy(this); Trail_Undo(e.trail); } String text = null; if (showStuff) //text = " Goal: " + e.goal + ". Got clause: " + c; text = "Got clause: " + render(c); ++topUnifications; if (unify(e.goal.car, c.head)) { cc.used = true; // mark clause used if (showStuff) { log(indent(level()) + text); log(indent(level()) + " Clause unifies to: " + render(c)); } Goal gdash; if (c.nat != null) { if (showStuff) log(indent(level()) + " Clause is native."); if (!c.nat.yo(this, c.head)) { if (showStuff) log(indent(level()) + "Native clause fails"); return false; } gdash = e.goal.cdr; } else gdash = c.body == null ? e.goal.cdr : resolveCut(c.body).append(e.goal.cdr); if (showStuff) log(indent(level()) + " gdash: " + render(gdash)); if (gdash == null) { if (showStuff) log("SUCCESS!"); return true; } else { Entry e2 = new Entry(gdash); /*if (showStuff) log(indent(level()) + "New goal: " + render(gdash));*/ stackAdd(e2); return false; } } /*else if (showStuff) log(indent(level()) + "No match for clause.");*/ return false; } // resolve cut in clause body Goal resolveCut(Goal g) { if (g.car.is("!", 0)) return fixCut(g); if (g.cdr == null) return g; return new Goal(g.car, resolveCut(g.cdr)); } // note stack length in cut Goal fixCut(Goal g) { return new Goal(lisp("!", lstr(stack)), g.cdr); // max. one cut per clause } void stackAdd(Entry e) { stack.add(e); int n = l(stack); if (n > maxLevel) throw fail("Maximum stack level reached: " + n); if (n > maxLevelSeen) maxLevelSeen = n; } void addClause(String text) { addClause(nlParse(text)); } // synonym of addClause void addStatement(String text) { addClause(text); } void addClause(Lisp c) { addClause(clause(c)); } void addClause(Lisp c, String name) { addClause(clause(c), name); } void addClause(Clause c) { addClause(c, null); } void addClause(Clause c, String name) { c.loadedFrom = name; program.add(c); if (c.head instanceof Var) { if (showStuff) log("WARNING: Clause is variable, will not be executed right now: " + c); return; } int arity = c.head.size(); growArityList(arity); programByArity.get(arity).add(c); } void growArityList(int arity) { while (arity >= l(programByArity)) programByArity.add(new ArrayList()); } boolean canSolve(Lisp goal) { return canSolve(new Goal(prologify(goal))); } boolean canSolve(String goal) { return canSolve(nlParse(goal)); } boolean canSolve(Goal goal) { start(goal); while (!done()) if (step()) return true; return false; } String solveAsText(String goal, String var) { Map solution = solve(goal); return solution == null ? null : nlUnparse(solution.get(addPrefix("$", var))); } // return variable map or null if unsolved Map solve(Lisp goal) { start(goal); return nextSolution(); } Map solve(String text) { return solve(nlParse(text)); } Map getUserVarMap() { Goal g = stack.get(0).goal; if (showStuff) log("UserVarMap goal: " + g); HashMap map = new HashMap(); for (Var v : findVars(g)) if (v.isUserVar()) { Lisp term = resolve(v); boolean ok = !(term instanceof Var) || (term != v && ((Var) term).isUserVar()); if (showStuff) log("UserVarMap var " + v + " ok: " + ok); if (ok) map.put(v.getName(), term); } return map; } Map nextSolution() { if (showStuff) log("nextSolution"); int n = 0; while (!done()) { ++n; if (step()) { if (showStuff) log(" solution found in step " + n); return getUserVarMap(); } } if (showStuff) log("\nNo solution"); return null; } void addTheory(String text, String name) { for (Clause c : parseProgram(text)) addClause(c, name); } void addTheory(String text, Lisp tree) { addTheory(text, tree, null); } void addTheory(String text, Lisp tree, String name) { for (Clause c : parseProgram(text, tree)) addClause(c, name); } List parseProgram(String text) { return parseProgram(text, nlParse(text)); } List parseProgram(String text, Lisp tree) { List l = new ArrayList(); if (nlIsMultipleStatements(text)) for (Lisp part : tree) l.add(clause(part)); else l.add(clause(tree)); return l; } // Resolve top-level only Lisp resolve1(Lisp term) { if (term instanceof Var) return ((Var) term).getValue(); return term; } // resolve all variables Lisp resolve(Lisp term) { term = resolve1(term); // smart recurse for (int i = 0; i < term.size(); i++) { Lisp l = term.get(i); Lisp r = resolve(l); if (l != r) { Lisp x = new Lisp(term.head); for (int j = 0; j < i; j++) x.add(term.get(j)); x.add(r); for (i++; i < term.size(); i++) x.add(resolve(term.get(i))); return x; } } return term; } // looks for a bodyless rule in the program that matches the term // todo: eqic boolean containsStatement(Lisp term) { for (Clause c : program) if (c.body == null && c.nat == null && eq(c.head, term)) return true; return false; } // closed == contains no variables boolean isClosedTerm(Lisp term) { if (term instanceof Var) return false; else for (Lisp arg : term) if (!isClosedTerm(arg)) return false; return true; } void addNative(BaseNative n) { addClause(prologify(new Clause(nlParse(n.pat), n)), "nat"); } void addNatives(BaseNative... l) { for (BaseNative n : l) addNative(n); } void addNatives(List l) { for (BaseNative n : l) addNative(n); } List getStackTerms() { List l = new ArrayList(); for (Entry e : stack) l.add(e.goal.car); return l; } void logOn() { log = new ArrayList(); showStuff = true; } static abstract class BaseNative implements Native { String pat; Prolog p; Map m; BaseNative(String pat) { this.pat = pat;} abstract boolean yo(); public boolean yo(Prolog p, Lisp term) { m = new HashMap(); this.p = p; try { // Terms are always resolved for native code! return nlMatch(pat, p.resolve(term), m) && yo(); } catch (Exception e) { ++p.exceptions; if (p.showStuff) p.log("Exception in native class " + getClass().getName() + ": " + getStackTrace(e)); return false; } finally { this.p = null; } } boolean unify(String var, Lisp term) { return set(var, term); } boolean set(String var, Lisp term) { Lisp v = m.get(var); if (v == null) throw fail("Variable " + var + " not found"); return p.unify(v, term); } boolean unifyForeign(IdentityHashMap varMap, Map solution) { if (p.showStuff) p.log("unifyForeign with: " + structure(solution)); for (Lisp v : varMap.keySet()) { String name = varMap.get(v); Lisp value = solution.get(name); if (value == null) continue; if (!p.unify(v, escapeVariables(value))) { return false; // rollback? } } return true; } // fills varMap Lisp exportVars(Lisp tree, IdentityHashMap varMap) { if (tree instanceof Var) { String name = varMap.get(tree); if (name == null) { name = "$_" + (varMap.size()+1); varMap.put(tree, name); } return lisp(name); } Lisp lisp = new Lisp(tree.head); for (Lisp sub : tree) lisp.add(exportVars(sub, varMap)); return lisp; } // var = without $ Lisp get(String var) { return m.get(var); } String unq(String var) { return m.get(var).unq(); } } // BaseNative // Prolog constructor Prolog() { addClause(lisp("true")); addBaseNatives(); } static class State { Trail trail; List stack; } State saveState() { State s = new State(); s.trail = sofar; s.stack = stack; sofar = null; return s; } void restoreState(State s) { sofar = s.trail; stack = s.stack; } // auto-rewrite with all clauses in DB List rewrite() { return rewriteWith(program); } List rewriteWith(List rewriteTheory) { List result = new ArrayList(); for (Prolog.Clause clause : rewriteTheory) { if (clause.body == null) continue; // need conditions to rewrite anything // usual safe clause copying Trail t = Trail_Note(); Clause c; synchronized(clause) { c = clause.copy(this); Trail_Undo(t); } State state = saveState(); try { start(c.body); while (!done()) { if (step()) { Lisp term = resolve(c.head); if (!isClosedTerm(term)) { if (showStuff) log("Not a closed term, skipping: " + term); continue; } if (containsStatement(term)) { if (showStuff) log("Statement exists, skipping: " + term); continue; } if (result.contains(term)) continue; if (showStuff) log("Found new statement(): " + term); result.add(term); } } } finally { restoreState(state); } } return result; } static class NewCollector extends BaseNative { NewCollector() { super("$x = new collector"); } boolean yo() { return set("x", new Collector()); } } class Save extends BaseNative { Save() { super("saveTo($x, $collector)"); } boolean yo() { Collector collector = (Collector) get("collector"); Lisp x = get("x"); collector.solutions.add(resolve(x)); return true; } } static class Retrieve extends BaseNative { Retrieve() { super("$x = retrieve($collector)"); } boolean yo() { print("Retrieve: collector = " + get("collector")); Collector collector = (Collector) get("collector"); return set("x", nlList(collector.solutions)); } } void addBaseNatives() { addNatives(new NewCollector(), new Save(), new Retrieve()); } static Lisp escapeVariables(Lisp tree) { if (tree instanceof Var) { String name = ((Var) tree).prettyName(); return lisp("[]", "var", name); } Lisp lisp = new Lisp(tree.head); for (Lisp sub : tree) lisp.add(escapeVariables(sub)); return lisp; } boolean addClauseIfNew(Lisp clause, String name) { if (containsStatement(clause)) return false; addClause(clause, name); return true; } List getUsedClauses() { List l = new ArrayList(); for (Clause c : program) if (c.used) l.add(c); return l; } List getUsedTheories() { TreeSet theories = new TreeSet(); for (Clause c : getUsedClauses()) theories.add(or(c.loadedFrom, "?")); return asList(theories); } void think(Lisp x) { addClauseIfNew(x, "thought"); // TODO: vars and stuff? } List getMemorizedStatements() { List l = new ArrayList(); for (Clause c : program) if (c.body == null && c.nat == null && c.head.is("[]", 2) && c.head.get(0).is("memorized")) l.add(c.head); return l; } String showClause(Clause c) { return nlUnparse(c.head) + (c.body == null ? "" : " :- ..."); } String showProgram() { List l = new ArrayList(); for (Clause c : program) l.add(showClause(c)); return n(l(program), "statement") + " " + slackSnippet(joinLines(l )); } void copyProgramFrom(Prolog p) { program.addAll(p.program); growArityList(l(p.programByArity)); for (int i = 0; i < l(p.programByArity); i++) programByArity.get(i).addAll(p.programByArity.get(i)); } void addProgram(List clauses, String loadedFrom) { for (Clause c : clauses) addClause(c, loadedFrom); } void addClauses(String text) { addTheory(text, "ad hoc"); } List statementsAsText() { return statementsAsText(0); } List statementsAsText(int startingFromIndex) { List l = new ArrayList(); for (int i = startingFromIndex; i < l(program); i++) { Clause c = program.get(i); if (c.nat == null) { if (c.body != null) l.add("[if ? then " + nlUnparse(c.head, false) + "]"); // TODO else l.add(nlUnparse(c.head, false)); } } return l; } int mark() { return l(program); } } // class Prolog // Prolog with shared clauses // TODO: use BaseNative /*static class MyNative implements Prolog.Native { public boolean yo(Prolog p, Lisp term) { Map m = new TreeMap(); if (nlMatch("$n = a random number between $a and $b inc.", term, m)) { int a = parseInt(m.get("a").raw()); int b = parseInt(m.get("b").raw()); int n = a+random(b-a+1); ret p.unify(m.get("n"), lisp(str(n))); } ret false; } }*/ static class IsQuoted extends Prolog.BaseNative { IsQuoted() { super("isQuoted($x)"); } boolean yo() { Lisp x = get("x"); return !(x instanceof Prolog.Var) && x.isLeaf() && isQuoted(x.head); } } static class IsWord extends Prolog.BaseNative { IsWord() { super("isWord($x)"); } boolean yo() { Lisp x = get("x"); return !(x instanceof Prolog.Var) && x.isLeaf() && isExtendedIdentifier(x.head); } } static class IntMul extends Prolog.BaseNative { IntMul() { super("$a = intmul($x, $y)"); } boolean yo() { BigInteger x = bigint(m.get("x").raw()); BigInteger y = bigint(m.get("y").raw()); return p.unify(m.get("a"), lisp(str(x.multiply(y)))); } } static class IntDiv extends Prolog.BaseNative { IntDiv() { super("$a = intdiv($x, $y)"); } boolean yo() { BigInteger x = bigint(m.get("x").raw()); BigInteger y = bigint(m.get("y").raw()); return p.unify(m.get("a"), lisp(str(x.divide(y)))); } } static class IntAdd extends Prolog.BaseNative { IntAdd() { super("$a = intadd($x, $y)"); } boolean yo() { BigInteger x = bigint(m.get("x").raw()); BigInteger y = bigint(m.get("y").raw()); return p.unify(m.get("a"), lisp(str(x.add(y)))); } } static class IntMinus extends Prolog.BaseNative { IntMinus() { super("$a = intminus($x, $y)"); } boolean yo() { BigInteger x = bigint(m.get("x").raw()); BigInteger y = bigint(m.get("y").raw()); return p.unify(m.get("a"), lisp(str(x.subtract(y)))); } } static class LessThan extends Prolog.BaseNative { LessThan() { super("$x is less than $y"); } boolean yo() { BigInteger x = bigint(m.get("x").raw()); BigInteger y = bigint(m.get("y").raw()); return x.compareTo(y) < 0; } } static class GreaterThan extends Prolog.BaseNative { GreaterThan() { super("$x is greater than $y"); } boolean yo() { BigInteger x = bigint(m.get("x").raw()); BigInteger y = bigint(m.get("y").raw()); return x.compareTo(y) > 0; } } // TODO: fix static class HeadExists extends Prolog.BaseNative { HeadExists() { super("head $h exists in theory $t"); } boolean yo() { Lisp head = get("h"); String theoryName = unq("t"); Lisp theory = getParsedTheory(theoryName); // TODO: this is probably inefficient for (Lisp rule : theory) { Prolog.Clause clause = p.clause(rule); if (p.unifyOrRollback(head, clause.head)) return true; } return false; } } static class TheoriesList extends Prolog.BaseNative { TheoriesList() { super("$x = all theory names"); } boolean yo() { return unify("x", nlMinimalList(getTheoryNames())); } } static class tocons extends Prolog.BaseNative { tocons() { super("$x = tocons($y)"); } boolean yo() { Lisp y = m.get("y"); return unify("x", nlToCons(y)); } } static class fromcons extends Prolog.BaseNative { fromcons() { super("$x = fromcons($y)"); } boolean yo() { Lisp y = m.get("y"); return unify("x", nlFromCons(y)); } } static class operator extends Prolog.BaseNative { operator() { super("$x = operator($y)"); } boolean yo() { Lisp y = get("y"); return unify("x", lisp(quote(y.head))); } } static class Unquote extends Prolog.BaseNative { Unquote() { super("$x = unquote($y)"); } boolean yo() { Lisp y = get("y"); return unify("x", lisp(unquote(y.head))); } } static class arg extends Prolog.BaseNative { arg() { super("$x = arg $i in $y"); } boolean yo() { Lisp y = m.get("y"); int i = parseInt(m.get("i").raw()); if (i-1 >= 0 && i-1 < y.size()) return unify("x", y.get(i-1)); else return false; } } static class Arity extends Prolog.BaseNative { Arity() { super("$x = arity of $y"); } boolean yo() { Lisp y = m.get("y"); return unify("x", lisp(str(y.size()))); } } static class RemoveWord extends Prolog.BaseNative { RemoveWord() { super("$x = removeWord($word, $y)"); } boolean yo() { Lisp y = get("y"); String word = unq("word"); return unify("x", nlRemoveWord(word, y)); } } static class GetTheory extends Prolog.BaseNative { GetTheory() { super("$x = theory $name"); } boolean yo() { String name = unq("name"); Lisp parsed = getParsedTheory(name); return parsed != null && unify("x", parsed); } } static class TextOfTheory extends Prolog.BaseNative { TextOfTheory() { super("$x = text of theory $name"); } boolean yo() { String name = unq("name"); String text = getTheoryFromBot(name); return text != null && unify("x", lisp(quote(text))); } } static class Solve1 extends Prolog.BaseNative { Solve1() { super("solve1 $text in $theories"); } boolean yo() { Lisp question = get("text"); Lisp t = m.get("theories"); List theoryNames = nlParseStringList(t); Prolog sub = newSubProlog(theoryNames, false); IdentityHashMap varMap = new IdentityHashMap(); Lisp exported = exportVars(question, varMap); if (showLog) { p.log("Exported: " + exported); p.log("Var map: " + structure(varMap)); } sub.start(exported); sub.startTime = p.startTime; Map solution = sub.nextSolution(); if (solution == null) return false; return unifyForeign(varMap, solution); } } static class Think extends Prolog.BaseNative { Think() { super("think $x"); } boolean yo() { p.think(m.get("x")); return true; } } static class MemorizeImpl extends Prolog.BaseNative { MemorizeImpl() { super("memorize_impl $x"); } boolean yo() { Lisp x = m.get("x"); if (!p.isClosedTerm(x)) { if (p.showStuff) p.log("Not a closed term, can't memorize: " + nlUnparse(x)); return false; } if (p.outTheory != null) incrementTheory(p.outTheory, x); p.think(lisp("[]", "memorized", x)); return true; } } static class RewriteWithTheory extends Prolog.BaseNative { RewriteWithTheory() { super("rewrite with theory $x"); } boolean yo() { String theoryName = unq("x"); String text = findTheory(theoryName); if (text == null) { // clause succeeds even if theory not found if (p.showStuff) p.log(format("Warning: rewrite theory * not found", theoryName)); } else { List rewriteTheory = getProgram(text); if (p.showStuff) for (Prolog.Clause c : rewriteTheory) p.log(" REW " + p.showClause(c)); List rewritten = p.rewriteWith(rewriteTheory); if (p.showStuff) p.log("Got " + l(rewritten) + " rewritten terms."); for (Lisp term : rewritten) { if (p.showStuff) p.log("Adding rewritten term: " + nlUnparse(term)); p.addClause(term, "rewritten"); } } return true; } } static class NLSafeParse extends Prolog.BaseNative { NLSafeParse() { super("$x = nlSafeParse($y)"); } boolean yo() { String input = unq("y"); return unify("x", nlSafeParse(input)); } } // ignores case static class StartsWith extends Prolog.BaseNative { StartsWith() { super("$x starts with $y"); } boolean yo() { String x = unq("x"), y = unq("y"); return swic(x, y); } } // Natives static boolean showLog, printTimings = true; static Prolog p; static Prolog parsingInterpreter; //static O theoryBot; static Method getTheoryMethod; public static void main(String[] args) throws Exception { Prolog p = new Prolog(); parsingInterpreter = p; p.addNatives(new IntMul(), new IntDiv(), new IntAdd(), new IntMinus()); p.addNatives(new GreaterThan(), new HeadExists(), new TheoriesList()); p.addNatives(new tocons(), new fromcons(), new LessThan(), new arg(), new Arity()); p.addNatives(new operator(), new Unquote(), new IsQuoted(), new IsWord(), new RemoveWord(), new GetTheory(), new TextOfTheory()); p.addNatives(new Solve1(), new Think(), new MemorizeImpl(), new RewriteWithTheory(), new NLSafeParse()); p.addNatives(new StartsWith()); } static class CacheEntry { String text; Lisp parsed; List program; CacheEntry(String text) { this.text = text; } Lisp getParsed() { if (parsed == null) parsed = nlParse(text); return parsed; } List getProgram() { if (program == null) program = parsingInterpreter.parseProgram(text); return program; } } static Map parseCache = new HashMap(); static String getTheoryFromBot(String name) { try { if (getTheoryMethod != null) return (String) getTheoryMethod.invoke(null, name); return (String) callOpt(theoryBot(), "getTheoryOpt", name); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static void cacheGetTheoryMethod() { getTheoryMethod = findMethod(theoryBot(), "getTheoryOpt", ""); } static String findTheory(String name) { return getTheoryFromBot(name); } static String saveTheory(String name, String text) { return (String) callOpt(theoryBot(), "saveTheory", name, text); } static Lisp getParsedTheory(String name) { CacheEntry entry = getCacheEntry(name); return entry == null ? null : entry.getParsed(); } static List getProgram(String name) { CacheEntry entry = getCacheEntry(name); return entry == null ? null : entry.getProgram(); } static CacheEntry getCacheEntry(String name) { String text = findTheory(name); if (text == null) return null; CacheEntry entry = parseCache.get(new CIString(name)); if (entry == null || neq(entry.text, text)) { entry = new CacheEntry(text); parseCache.put(new CIString(name), entry); } return entry; } static String prettyPrintTerms(List l, boolean outerBrackets) { List out = new ArrayList(); for (Lisp term : l) { String s = nlUnparse(term, !outerBrackets); //out.add(outerBrackets ? "[" + s + "]" : s); out.add(s); } return joinLines(out); } static void loadTheories(Prolog p, List theoryNames) { if (theoryNames.contains("signed")) { theoryNames.remove("signed"); theoryNames.addAll(getLiveTheories()); } if (theoryNames.contains("signedstefan")) { theoryNames.remove("signedstefan"); theoryNames.addAll(getLiveTheories ("stefanreich")); } cacheGetTheoryMethod(); try { for (String t : asSet(theoryNames)) { // todo: ignore case loadTheoryImpl(p, t); long n = getTheoryCounter(t); for (long i = 1; i <= n; i++) loadTheoryImpl(p, t + "." + i); } } finally { getTheoryMethod = null; } } static long getTheoryCounter(String name) { //ret (long) call(theoryBot(), "getTheoryCounter", name); String text = findTheory(name + ".count"); return isEmpty(text) ? 0 : parseLong(text); } static void loadTheoryImpl(Prolog p, String name) { List program = getProgram(name); if (program != null) p.addProgram(program, name); } static String solutionPrettyPrint(Map solution) { String s = "FALSE"; if (solution != null) { List l = new ArrayList(); solution = new TreeMap(solution); // sort for (String var : solution.keySet()) l.add(var + " = " + nlUnparse(solution.get(var), false)); s = "TRUE.\n" + slackSnippetOpt(joinLines(l)); } /*if (showStack) s += "\nStack:" + slackSnippetOpt(prettyPrintTerms(p.getStackTerms(), false));*/ if (showLog) { List usedTheories = p.getUsedTheories(); List lines = litlist("Used theories: " + join(", ", usedTheories)); List mem = p.getMemorizedStatements(); if (nempty(mem)) { lines.add("Thoughts:"); for (Lisp x : mem) lines.add(" " + nlUnparse(x)); } lines.addAll(p.log); s += "\n" + slackSnippet(lines); } return s; } static Prolog newProlog(List theoryNames) { return p = newSubProlog(theoryNames, showLog); } static Prolog newProlog(List theoryNames, boolean showLog) { return p = newSubProlog(theoryNames, showLog); } static Prolog newSubProlog(List theoryNames, boolean showLog) { long startTime = now(); int parses = nlParse_count.get(); Prolog p = new Prolog(); if (showLog) p.logOn(); p.copyProgramFrom(parsingInterpreter); // natives! long t2 = now(); loadTheories(p, theoryNames); if (printTimings) { parses = nlParse_count.get()-parses; long end = now(); long time = end-startTime; t2 = end-t2; print("Making prolog interpreter with " + l(theoryNames) + " theories: " + time + " ms (" + parses + " parses, " + t2 + " ms in loadTheories)"); } return p; } // n = how many solutions to print static String solve(Lisp question, List theoryNames, int n) { StringBuilder buf = new StringBuilder(); question = new EvalTransform().evalTransformQuestion(question); newProlog(theoryNames); { long _startTime_0 = now(); try { try { Map solution = p.solve(question); if (solution == null) buf.append("FALSE in " + structure(theoryNames) + (p.log != null ? "\n" + slackSnippet(p.log) : "")); else { buf.append(solutionPrettyPrint(solution) + "\n"); while (--n > 0) { solution = p.nextSolution(); if (solution == null) break; buf.append(solutionPrettyPrint(solution) + "\n"); } if (p.nextSolution() != null) buf.append("[more]"); else buf.append("[done]"); } } catch (Exception e) { return e + "\n" + slackSnippet(p.log); } } finally { _startTime_0 = now()-_startTime_0; saveTiming(_startTime_0); } } buf.append(" " + lastTiming() + " ms"); return str(buf); } static List nlParseStringList(Lisp tree) { if (tree.isLeaf()) return litlist(unquote(tree.raw())); List l = new ArrayList(); for (Lisp t : tree) { if (!t.isLeaf()) continue; // huh? String s = t.head; if (eq(s, ",") /*|| eq(s, "and")*/) continue; l.add(unquote(s)); } return l; } static List getTheoryNames() { return (List) callOpt(theoryBot(), "getTheoryNames"); } static List getLiveTheories() { return concatLists ( getLiveTheories (getUserName ()), getLiveTheories ("stefanreich")); } static List getLiveTheories(String user) { return empty(user) ? litlist() : (List) callOpt(theoryBot(), "getTheoriesSignedByAs", user, "live"); } static String solve(String s, boolean showLog) { main.showLog = showLog; s = s.trim(); int n = 1; int i = s.indexOf(']'); if (i >= 2) { String s2 = dropPrefix("[", substring(s, 0, i)); if (isInteger(s2)) { n = parseInt(s2); s = s.substring(i+1).trim(); } } Lisp tree = nlParse(s); //ret structure(tree); Map m = new TreeMap(); if (nlMatch("$text in $theory", tree, m)) { print("text in theory"); Lisp question = m.get("text"); Lisp t = m.get("theory"); List theoryNames = nlParseStringList(t); return solve(question, theoryNames, n); } if (nlMatch("theory $t", tree, m)) { print("theory"); String theoryName = unquote(m.get("t").raw()); return solve(nlParse(findTheory(theoryName)), litstringlist("signed"), n); } if (nlMatch("$text", tree, m)) { print("text"); Lisp question = m.get("text"); return solve(question, litstringlist("signed"), n); } return "hm?"; } synchronized static String answer(String s) { final Matches m = new Matches(); if (match("top unifications", s, m)) return p == null ? "-" : str(p.topUnifications); if (!attn()) return null; if (swic(s, "solve ")) try { s = dropPrefixIgnoreCase("solve ", s); return solve(s, false); } catch (Throwable __e) { return exceptionToUser(__e); } if (swic(s, "eval ")) try { s = trim(dropPrefixIgnoreCase("eval ", s)); String var = nlMakeUnusedVar(s); s = var + " = " + s; return solve(s, false); } catch (Throwable __e) { return exceptionToUser(__e); } if (swic(s, "log ")) try { s = dropPrefixIgnoreCase("log ", s); return solve(s, true); } catch (Throwable __e) { return exceptionToUser(__e); } if (swic(s, "say ")) try { s = dropPrefixIgnoreCase("say ", s); Map mm = new TreeMap(); if (nlMatch("$text in $theory", s, mm)) { Lisp question = mm.get("text"); Lisp t = mm.get("theory"); s = "[[think [user says " + nlUnparse(question, false) + "]] and [say $x]] in " + nlUnparse(t, false); print("s = " + s); return solve(s, true); } return "woot syntax"; } catch (Throwable __e) { return exceptionToUser(__e); } if (match("produce *", s, m)) try { return produce(m.unq(0)); } catch (Throwable __e) { return exceptionToUser(__e); } if (match("my program", s, m)) try { String user = getUserName(); if (empty(user)) return "go to slack please"; p = new Prolog(); loadTheories(p, getLiveTheories()); return p.showProgram(); } catch (Throwable __e) { return exceptionToUser(__e); } if (match("log", s, m)) try { return slackSnippet(p.log); } catch (Throwable __e) { return exceptionToUser(__e); } if (match("rewrite * with *", s, m)) try { return rewrite(m.unq(0), m.unq(1), null); } catch (Throwable __e) { return exceptionToUser(__e); } if (match("rewrite * with * as *", s, m)) try { return rewrite(m.unq(0), m.unq(1), m.unq(2)); } catch (Throwable __e) { return exceptionToUser(__e); } if (match("time findTheory", s, m)) try { { long _startTime_1 = now(); try { //theoryBot = getBot("#1002762"); cacheGetTheoryMethod(); try { for (int i = 0; i < 1000; i++) findTheory("and"); } finally { //theoryBot = null; getTheoryMethod = null; } } finally { _startTime_1 = now()-_startTime_1; saveTiming(_startTime_1); } } return (lastTiming()/1000.0) + " ms"; } catch (Throwable __e) { return exceptionToUser(__e); } return null; } static String rewrite(String tname1, String tname2, String tname3) { newProlog(litlist(tname1, "signed"), true); List rewriteTheory = p.parseProgram(findTheory(tname2)); List rewritten = p.rewriteWith(rewriteTheory); String text = prettyPrintTerms(rewritten, true); if (tname3 == null) return slackSnippet(text) + "\n" + slackSnippet(p.log); return saveTheory(tname3, text); } static String produce(String theoryName) { String outName = theoryName + ".out"; showLog = false; newProlog(litlist(theoryName)); String question = "say $x"; Lisp out = lisp("[]"); p.start(question); int max = 100; while (out.size() < max) { Map solution = p.nextSolution(); if (solution == null) break; out.add(solution.get("$x")); } return (out.size() >= max ? "WARNING MAXED OUT\n" : "") + saveTheory(outName, nlUnparse(out)); } static void incrementTheory(String name, Lisp value) { call(theoryBot(), "incrementTheory", name, nlUnparse(value)); } static Object theoryBot() { return /*or(theoryBot,*/ getBot("#1002762")/*)*/; } static Class getClass(String name) { try { return Class.forName(name); } catch (ClassNotFoundException e) { return null; } } static Class getClass(Object o) { return o instanceof Class ? (Class) o : o.getClass(); } static Class getClass(Object realm, String name) { try { try { return getClass(realm).getClassLoader().loadClass(classNameToVM(name)); } catch (ClassNotFoundException e) { return null; } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static String getName() { return getProgramName(); } static boolean nlMatch(String pat, String nl, Map matches) { matches.clear(); return nlMatch_sub(nlParse/*_cached*/(pat), nlParse(nl), matches); } static boolean nlMatch(Lisp pat, String nl, Map matches) { matches.clear(); return nlMatch_sub(pat, nlParse(nl), matches); } static boolean nlMatch(String pat, Lisp nl, Map matches) { matches.clear(); return nlMatch_sub(nlParse/*_cached*/(pat), nl, matches); } static boolean nlMatch(Lisp pat, Lisp nl, Map matches) { matches.clear(); return nlMatch_sub(pat, nl, matches); } static boolean nlMatch_sub(Lisp pat, Lisp nl, Map m) { if (pat == null || nl == null) return false; if (pat.isA("*")) return true; if (nlIsVar(pat)) return nlMatch_putMatch(m, pat.head, nl); if (neq(pat.head, nl.head)) return false; // heads identical, proceed to children int n = pat.size(); if (n != nl.size()) return false; for (int i = 0; i < n; i++) if (!nlMatch_sub(pat.get(i), nl.get(i), m)) return false; return true; } static boolean nlMatch_putMatch(Map matches, String key, Lisp val) { key = dropPrefix("$", key); if (matches.containsKey(key) && neq(matches.get(key), val)) throw fail("multi-matching not implemented"); matches.put(key, val); return true; } static String joinLines(List lines) { return fromLines(lines); } static String joinLines(String glue, String text) { return join(glue, toLines(text)); } static List litstringlist(String... data) { return litlist(data); } static boolean nlIsVar(Lisp l) { if (l == null || !l.isLeaf()) return false; String h = l.head; return l(h) > 1 && h.startsWith("$"); } static ArrayList asList(A[] a) { return new ArrayList(Arrays.asList(a)); } static ArrayList asList(int[] a) { ArrayList l = new ArrayList(); for (int i : a) l.add(i); return l; } static ArrayList asList(Iterable s) { if (s instanceof ArrayList) return (ArrayList) s; ArrayList l = new ArrayList(); if (s != null) for (A a : s) l.add(a); return l; } static boolean empty(Collection c) { return isEmpty(c); } static boolean empty(String s) { return isEmpty(s); } static boolean empty(Map map) { return map == null || map.isEmpty(); } static boolean empty(Object[] o) { return o == null || o.length == 0; } static boolean empty(Object o) { if (o instanceof Collection) return empty((Collection) o); if (o instanceof String) return empty((String) o); if (o instanceof Map) return empty((Map) o); if (o instanceof Object[]) return empty((Object[]) o); throw fail("unknown type for 'empty': " + getType(o)); } static Lisp nlRemoveWord(String word, Lisp in) { if (in.isLeaf() && eqic(in.head, word)) return lisp("[]"); Lisp lisp = new Lisp(in.head); for (int i = 0; i < in.size(); i++) { Lisp translated = nlRemoveWord(word, in.get(i)); if (!translated.is("[]", 0)) lisp.add(translated); } return lisp; } static boolean isInteger(String s) { return s != null && Pattern.matches("\\-?\\d+", s); } static String quote(String s) { if (s == null) return "null"; return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\r", "\\r").replace("\n", "\\n") + "\""; } static String quote(long l) { return quote("" + l); } static String quote(char c) { return quote("" + c); } static String getStackTrace(Throwable throwable) { StringWriter writer = new StringWriter(); throwable.printStackTrace(new PrintWriter(writer)); return writer.toString(); } static String trim(String s) { return s == null ? null : s.trim(); } static String trim(StringBuilder buf) { return buf.toString().trim(); } static String trim(StringBuffer buf) { return buf.toString().trim(); } // supports the usual quotings (', ", variable length double brackets) static boolean isQuoted(String s) { if (s.startsWith("'") || s.startsWith("\"")) return true; if (!s.startsWith("[")) return false; int i = 1; while (i < s.length() && s.charAt(i) == '=') ++i; return i < s.length() && s.charAt(i) == '['; //return Pattern.compile("^\\[=*\\[").matcher(s).find(); } static String getUserName() { return (String) callOpt(getMainBot(), "getUserName"); } static Object getBot(String botID) { return callOpt(getMainBot(), "getBot", botID); } static boolean neq(Object a, Object b) { return !eq(a, b); } public static String join(String glue, Iterable strings) { StringBuilder buf = new StringBuilder(); Iterator i = strings.iterator(); if (i.hasNext()) { buf.append(i.next()); while (i.hasNext()) buf.append(glue).append(i.next()); } return buf.toString(); } public static String join(String glue, String[] strings) { return join(glue, Arrays.asList(strings)); } public static String join(Iterable strings) { return join("", strings); } public static String join(String[] strings) { return join("", strings); } static String dropPrefix(String prefix, String s) { return s.startsWith(prefix) ? s.substring(l(prefix)) : s; } // doesn't perform any sanity checks... // and this is NL-level, not SNL. static Lisp snlJoinOps(List ops) { Lisp t = lisp("[]"); for (Lisp op : ops) { int i = op.head.indexOf(' '); t.add(lisp(substring(op.head, 0, i))); t.add(op.get(0)); } return t; } static boolean nlIsMultipleStatements(String text) { List tok = codeTokens(snlTok(text)); //ret eq(first(tok), "[") && eq(last(tok), "]"); if (!(eq(first(tok), "[") && eq(last(tok), "]"))) return false; List warnings1 = new ArrayList(); List warnings2 = new ArrayList(); Lisp parse1 = nlParse(tok, true, false, warnings1); Lisp parse2 = nlParse(subList(tok, 1, l(tok)-1), true, false, warnings2); //ret !eq(parse1, parse2); return l(warnings2) > l(warnings1); //ret isJuxta(tree) && snlSplitOps(tree) == null; } static A last(List l) { return l.isEmpty() ? null : l.get(l.size()-1); } static char last(String s) { return empty(s) ? '#' : s.charAt(l(s)-1); } // make a lisp form static Lisp lisp(String head, Object... args) { Lisp l = new Lisp(head); for (Object o : args) l.add(o); return l; } static Lisp lisp(String head, List args) { return new Lisp(head, args); } static String format(String pat, Object... args) { return format3(pat, args); } static boolean eq(Object a, Object b) { if (a == null) return b == null; if (a.equals(b)) return true; if (a instanceof BigInteger) { if (b instanceof Integer) return a.equals(BigInteger.valueOf((Integer) b)); if (b instanceof Long) return a.equals(BigInteger.valueOf((Long) b)); } return false; } static int done_minPrint = 10; static long done(long startTime, String desc) { long time = now()-startTime; if (time >= done_minPrint) print(desc + " [" + time + " ms]"); return time; } static long done(String desc, long startTime) { return done(startTime, desc); } static long done(long startTime) { return done(startTime, ""); } static String str(Object o) { return String.valueOf(o); } static RuntimeException fail() { throw new RuntimeException("fail"); } static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); } static RuntimeException fail(String msg) { throw new RuntimeException(unnull(msg)); } // disabled for now to shorten some programs /*static RuntimeException fail(S msg, O... args) { throw new RuntimeException(format(msg, args)); }*/ static long lastTiming() { return getLastTiming(); } static List concatLists(List... lists) { List l = new ArrayList(); for (List list : lists) if (list != null) l.addAll(list); return l; } static List concatLists(Collection> lists) { List l = new ArrayList(); for (List list : lists) if (list != null) l.addAll(list); return l; } static boolean snlIsVar(Lisp l) { if (l == null || !l.isLeaf()) return false; String h = l.head; return startsWithUpperCase(h) || (l(h) > 1 && h.startsWith("$")); } static Lisp nlList(List list) { Lisp tree = lisp("[]"); for (Object s : list) { if (!tree.isEmpty()) tree.add(","); if (s instanceof Lisp) tree.add((Lisp) s); else if (s instanceof String) tree.add(quote((String) s)); else tree.add("?"); } return tree; } static boolean attn() { return amAddressed(); } static boolean isExtendedIdentifier(String s) { if (empty(s)) return false; for (int i = 0; i < l(s); i++) { char c = s.charAt(i); if (c != '\'' && !(i == 0 ? Character.isLetter(c) : Character.isLetterOrDigit(c))) return false; } return true; } static int indent_default = 2; static String indent(int indent) { return repeat(' ', indent); } static String indent(int indent, String s) { return indent(repeat(' ', indent), s); } static String indent(String indent, String s) { return indent + s.replace("\n", "\n" + indent); } static String indent(String s) { return indent(indent_default, s); } static List indent(String indent, List lines) { List l = new ArrayList(); for (String s : lines) l.add(indent + s); return l; } static Object callOpt(Object o) { if (o == null) return null; return callF(o); } static Object callOpt(Object o, String method, Object... args) { try { if (o == null) return null; if (o instanceof Class) { Method m = callOpt_findStaticMethod((Class) o, method, args, false); if (m == null) return null; m.setAccessible(true); return m.invoke(null, args); } else { Method m = callOpt_findMethod(o, method, args, false); if (m == null) return null; m.setAccessible(true); return m.invoke(o, args); } } catch (Exception e) { throw new RuntimeException(e); } } static Method callOpt_findStaticMethod(Class c, String method, Object[] args, boolean debug) { Class _c = c; while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (!m.getName().equals(method)) { if (debug) System.out.println("Method name mismatch: " + method); continue; } if ((m.getModifiers() & Modifier.STATIC) == 0 || !callOpt_checkArgs(m, args, debug)) continue; return m; } c = c.getSuperclass(); } return null; } static Method callOpt_findMethod(Object o, String method, Object[] args, boolean debug) { Class c = o.getClass(); while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (m.getName().equals(method) && callOpt_checkArgs(m, args, debug)) return m; } c = c.getSuperclass(); } return null; } private static boolean callOpt_checkArgs(Method m, Object[] args, boolean debug) { Class[] types = m.getParameterTypes(); if (types.length != args.length) { if (debug) System.out.println("Bad parameter length: " + args.length + " vs " + types.length); return false; } for (int i = 0; i < types.length; i++) if (!(args[i] == null || isInstanceX(types[i], args[i]))) { if (debug) System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]); return false; } return true; } static String slackSnippetOpt(Object contents) { if (contents instanceof List) contents = join("\n", (List) contents); String s = str(contents); if (empty(s)) return ""; return slackSnippet(s); } static boolean swic(String a, String b) { return startsWithIgnoreCase(a, b); } static Lisp nlFromCons(Lisp term) { Lisp l = lisp("[]"); while (term.is("[]", 3) && term.get(0).is("cons", 0)) { l.add(term.get(1)); term = term.get(2); } if (!term.is("end")) l.add(term); return l; } static void removeLast(List l) { if (!l.isEmpty()) l.remove(l(l)-1); } static String substring(String s, int x) { return safeSubstring(s, x); } static String substring(String s, int x, int y) { return safeSubstring(s, x, y); } static Lisp nlMinimalList(List list) { Lisp tree = lisp("[]"); for (Object s : list) { if (s instanceof Lisp) tree.add((Lisp) s); else if (s instanceof String) tree.add(quote((String) s)); else tree.add("?"); } return tree; } static Method findMethod(Object o, String method, Object... args) { try { if (o == null) return null; if (o instanceof Class) { Method m = findMethod_static((Class) o, method, args, false); if (m == null) return null; m.setAccessible(true); return m; } else { Method m = findMethod_instance(o, method, args, false); if (m == null) return null; m.setAccessible(true); return m; } } catch (Exception e) { throw new RuntimeException(e); } } static Method findMethod_static(Class c, String method, Object[] args, boolean debug) { Class _c = c; while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (!m.getName().equals(method)) { if (debug) System.out.println("Method name mismatch: " + method); continue; } if ((m.getModifiers() & Modifier.STATIC) == 0 || !findMethod_checkArgs(m, args, debug)) continue; return m; } c = c.getSuperclass(); } return null; } static Method findMethod_instance(Object o, String method, Object[] args, boolean debug) { Class c = o.getClass(); while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (m.getName().equals(method) && findMethod_checkArgs(m, args, debug)) return m; } c = c.getSuperclass(); } return null; } static boolean findMethod_checkArgs(Method m, Object[] args, boolean debug) { Class[] types = m.getParameterTypes(); if (types.length != args.length) { if (debug) System.out.println("Bad parameter length: " + args.length + " vs " + types.length); return false; } for (int i = 0; i < types.length; i++) if (!(args[i] == null || isInstanceX(types[i], args[i]))) { if (debug) System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]); return false; } return true; } static String nlMakeUnusedVar(String text) { List tok = nlTok(text); TreeSet usedVars = new TreeSet(); for (int i = 1; i < l(tok); i += 2) { String t = tok.get(i); if (t.startsWith("$") && l(t) > 1) usedVars.add(t); } for (int i = 0; i < 26; i++) { String var = "$" + (char) (((int) 'a') + i); if (!usedVars.contains(var)) return var; } throw fail("Help help help too many variables!"); } static ArrayList litlist(A... a) { return new ArrayList(Arrays.asList(a)); } static long now_virtualTime; static long now() { return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis(); } // get purpose 1: access a list/array (safer version of x.get(y)) static A get(List l, int idx) { return idx >= 0 && idx < l(l) ? l.get(idx) : null; } static A get(A[] l, int idx) { return idx >= 0 && idx < l(l) ? l[idx] : null; } // default to false static boolean get(boolean[] l, int idx) { return idx >= 0 && idx < l(l) ? l[idx] : false; } static Class get_dynamicObject = findClass("DynamicObject"); // get purpose 2: access a field by reflection or a map static Object get(Object o, String field) { try { if (o instanceof Class) return get((Class) o, field); if (o instanceof Map) return ((Map) o).get(field); Field f = getOpt_findField(o.getClass(), field); if (f != null) { f.setAccessible(true); return f.get(o); } if (get_dynamicObject != null && get_dynamicObject.isInstance(o)) return call(get_raw(o, "fieldValues"), "get", field); } catch (Exception e) { throw asRuntimeException(e); } throw new RuntimeException("Field '" + field + "' not found in " + o.getClass().getName()); } static Object get_raw(Object o, String field) { try { Field f = get_findField(o.getClass(), field); f.setAccessible(true); return f.get(o); } catch (Exception e) { throw new RuntimeException(e); } } static Object get(Class c, String field) { try { Field f = get_findStaticField(c, field); f.setAccessible(true); return f.get(null); } catch (Exception e) { throw new RuntimeException(e); } } static Field get_findStaticField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; _c = _c.getSuperclass(); } while (_c != null); throw new RuntimeException("Static field '" + field + "' not found in " + c.getName()); } static Field get_findField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field)) return f; _c = _c.getSuperclass(); } while (_c != null); throw new RuntimeException("Field '" + field + "' not found in " + c.getName()); } static AtomicInteger nlParse_count = new AtomicInteger(); // how often did we parse something? static Lisp nlParse(String s) { return nlParse(s, true); } static Lisp nlParse(String s, boolean unbracket) { return nlParse(codeTokens(snlTok(s)), unbracket, false, null); } static Lisp nlParse(String s, boolean unbracket, boolean explicitBrackets) { return nlParse(codeTokens(snlTok(s)), unbracket, explicitBrackets, null); } static Lisp nlParse(List tok) { return nlParse(tok, true, false, null); } // unbracket refers to outermost brackets // explicitBrackets refers to inner brackets static Lisp nlParse(List tok, boolean unbracket, final boolean explicitBrackets, List warnings) { nlParse_count.incrementAndGet(); class Entry { int i; Lisp tree; String wrapper; Entry(int i) { this.i = i; tree = lisp("[]"); } Entry(int i, String bracket) { this.i = i; tree = lisp("[]"); wrapper = bracket; } Lisp wrapped() { Lisp t = explicitBrackets ? tree : nlUnbracket(tree); return wrapper == null ? t : lisp(wrapper, t); } } List stack = new ArrayList(); stack.add(new Entry(0)); for (int i = 0; i < l(tok); i++) { String t = tok.get(i); if (eq(t, "[") || eq(t, "(")) { stack.add(new Entry(i, eq(t, "(") ? "()" : null)); } else if (eq(t, "]") || eq(t, ")")) { if (l(stack) == 1) warn("too many closing brackets", warnings); else { Entry e = popLast(stack); /*if (!bracketsMatch(tok.get(e.i), t)) warn("non-matching brackets");*/ last(stack).tree.add(e.wrapped()); } } else last(stack).tree.add(t); } while (l(stack) > 1) { warn("too many opening brackets", warnings); Entry e = popLast(stack); last(stack).tree.add(e.wrapped()); } Lisp result = last(stack).wrapped(); return unbracket ? nlUnbracket(result) : result; } static Set asSet(Object[] array) { HashSet set = new HashSet(); for (Object o : array) if (o != null) set.add(o); return set; } static Set asSet(String[] array) { TreeSet set = new TreeSet(); for (String o : array) if (o != null) set.add(o); return set; } static Set asSet(Collection l) { TreeSet set = new TreeSet(); for (String o : l) if (o != null) set.add(o); return set; } static BigInteger bigint(String s) { return new BigInteger(s); } static BigInteger bigint(long l) { return BigInteger.valueOf(l); } public static String unquote(String s) { if (s == null) return null; if (s.startsWith("[")) { int i = 1; while (i < s.length() && s.charAt(i) == '=') ++i; if (i < s.length() && s.charAt(i) == '[') { String m = s.substring(1, i); if (s.endsWith("]" + m + "]")) return s.substring(i+1, s.length()-i-1); } } if (s.startsWith("\"") /*&& s.endsWith("\"")*/ && s.length() > 1) { String st = s.substring(1, s.endsWith("\"") ? s.length()-1 : s.length()); StringBuilder sb = new StringBuilder(st.length()); for (int i = 0; i < st.length(); i++) { char ch = st.charAt(i); if (ch == '\\') { char nextChar = (i == st.length() - 1) ? '\\' : st .charAt(i + 1); // Octal escape? if (nextChar >= '0' && nextChar <= '7') { String code = "" + nextChar; i++; if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { code += st.charAt(i + 1); i++; if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { code += st.charAt(i + 1); i++; } } sb.append((char) Integer.parseInt(code, 8)); continue; } switch (nextChar) { case '\\': ch = '\\'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case '\"': ch = '\"'; break; case '\'': ch = '\''; break; // Hex Unicode: u???? case 'u': if (i >= st.length() - 5) { ch = 'u'; break; } int code = Integer.parseInt( "" + st.charAt(i + 2) + st.charAt(i + 3) + st.charAt(i + 4) + st.charAt(i + 5), 16); sb.append(Character.toChars(code)); i += 5; continue; default: ch = nextChar; // added by Stefan } i++; } sb.append(ch); } return sb.toString(); } else return s; // return original } static String addPrefix(String prefix, String s) { return s.startsWith(prefix) ? s : prefix + s; } static Lisp nlToCons(Lisp term) { if (term.isLeaf() /*|| !term.is("[]")*/) return lisp("[]", "cons", term, "end"); int n = term.size(); Lisp x = lisp("end"); for (int i = n-1; i >= 0; i--) x = lisp("[]", "cons", term.get(i), x); return x; } static void set(Object o, String field, Object value) { if (o instanceof Class) set((Class) o, field, value); else try { Field f = set_findField(o.getClass(), field); smartSet(f, o, value); } catch (Exception e) { throw new RuntimeException(e); } } static void set(Class c, String field, Object value) { try { Field f = set_findStaticField(c, field); smartSet(f, null, value); } catch (Exception e) { throw new RuntimeException(e); } } static Field set_findStaticField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; _c = _c.getSuperclass(); } while (_c != null); throw new RuntimeException("Static field '" + field + "' not found in " + c.getName()); } static Field set_findField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field)) return f; _c = _c.getSuperclass(); } while (_c != null); throw new RuntimeException("Field '" + field + "' not found in " + c.getName()); } static A or(A a, A b) { return a != null ? a : b; } static String dropPrefixIgnoreCase(String prefix, String s) { return startsWithIgnoreCase(s, prefix) ? s.substring(l(prefix)) : s; } static Lisp nlSafeParse(String s) { return nlEscapeVariables2(nlParse(s)); } // split smth like "if * then *" into "if *" and "then *" // returns null if no split // now also for NL trees static List snlSplitOps(Lisp code) { List out = new ArrayList(); if (code.is("[]")) { // NL if ((code.size() & 1) != 0) return null; for (int i = 0; i < code.size(); i += 2) { Lisp op = code.get(i), arg = code.get(i+1); if (!op.isLeaf() || !isExtendedIdentifier(op.head)) return null; out.add(lisp(op + " *", nlUnbracket(arg))); } return out; } String ops = code.head; List tok = codeTokensOnly(javaTok(ops)); if ((l(tok) & 1) != 0) return null; for (int i = 0; i < l(tok); i += 2) { String op = tok.get(i); if (!isIdentifier(op) || neq("*", tok.get(i+1))) return null; out.add(lisp(op + " *", code.get(i/2))); } //print("splitOps => " + structure(out)); return out; } static boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } static boolean isEmpty(CharSequence s) { return s == null || s.length() == 0; } static boolean isEmpty(Object[] a) { return a == null || a.length == 0; } static boolean isEmpty(Map map) { return map == null || map.isEmpty(); } static boolean nempty(Collection c) { return !isEmpty(c); } static boolean nempty(CharSequence s) { return !isEmpty(s); } static boolean nempty(Object[] o) { return !isEmpty(o); } static boolean nempty(Map m) { return !isEmpty(m); } static boolean nempty(Iterator i) { return i != null && i.hasNext(); } static boolean eqic(String a, String b) { if ((a == null) != (b == null)) return false; if (a == null) return true; return a.equalsIgnoreCase(b); } static String slackSnippet(Object contents) { if (contents instanceof List) contents = join("\n", (List) contents); String s = str(contents); //ret "```" + (empty(s) ? "\n" : s) + "```"; return "```\n" + s + "```"; } static int l(Object[] a) { return a == null ? 0 : a.length; } static int l(boolean[] a) { return a == null ? 0 : a.length; } static int l(byte[] a) { return a == null ? 0 : a.length; } static int l(int[] a) { return a == null ? 0 : a.length; } static int l(float[] a) { return a == null ? 0 : a.length; } static int l(char[] a) { return a == null ? 0 : a.length; } static int l(Collection c) { return c == null ? 0 : c.size(); } static int l(Map m) { return m == null ? 0 : m.size(); } static int l(CharSequence s) { return s == null ? 0 : s.length(); } static int l(Object o) { return l((List) o); // incomplete } static String structure(Object o) { HashSet refd = new HashSet(); return structure_2(structure_1(o, new structure_Data(refd)), refd); } // leave to false, unless unstructure() breaks static boolean structure_allowShortening = false; static int structure_shareStringsLongerThan = 20; static class structure_Data { int stringSizeLimit; IdentityHashMap seen = new IdentityHashMap(); HashSet refd; HashMap strings = new HashMap(); HashSet concepts = new HashSet(); Class conceptClass = findClass("Concept"); structure_Data(HashSet refd) { this.refd = refd;} } static String structure_1(Object o, structure_Data d) { if (o == null) return "null"; // these are never back-referenced (for readability) if (o instanceof BigInteger) return "bigint(" + o + ")"; if (o instanceof Double) return "d(" + quote(str(o)) + ")"; if (o instanceof Float) return "fl " + quote(str(o)); if (o instanceof Long) return o + "L"; if (o instanceof Integer) return str(o); if (o instanceof Boolean) return ((Boolean) o).booleanValue() ? "t" : "f"; if (o instanceof Character) return quoteCharacter((Character) o); if (o instanceof File) return "File " + quote(((File) o).getPath()); // referencable objects follow Integer ref = d.seen.get(o); if (ref != null) { d.refd.add(ref); return "r" + ref; } if (o instanceof String && (ref = d.strings.get((String) o)) != null) { d.refd.add(ref); return "r" + ref; } ref = d.seen.size()+1; d.seen.put(o, ref); String r = "m" + ref + " "; // marker if (o instanceof String) { String s = d.stringSizeLimit != 0 ? shorten((String) o, d.stringSizeLimit) : (String) o; if (l(s) >= structure_shareStringsLongerThan) d.strings.put(s, ref); return r + quote(s); } String name = o.getClass().getName(); StringBuilder buf = new StringBuilder(); if (o instanceof HashSet) return r + "hashset " + structure_1(new ArrayList((Set) o), d); if (o instanceof TreeSet) return r + "treeset " + structure_1(new ArrayList((Set) o), d); if (o instanceof Collection) { for (Object x : (Collection) o) { if (buf.length() != 0) buf.append(", "); buf.append(structure_1(x, d)); } return r + "[" + buf + "]"; } if (o instanceof Map) { for (Object e : ((Map) o).entrySet()) { if (buf.length() != 0) buf.append(", "); buf.append(structure_1(((Map.Entry) e).getKey(), d)); buf.append("="); buf.append(structure_1(((Map.Entry) e).getValue(), d)); } return r + (o instanceof HashMap ? "hm" : "") + "{" + buf + "}"; } if (o.getClass().isArray()) { if (o instanceof byte[]) return "ba " + quote(bytesToHex((byte[]) o)); int n = Array.getLength(o); if (o instanceof boolean[]) { String hex = boolArrayToHex((boolean[]) o); int i = l(hex); while (i > 0 && hex.charAt(i-1) == '0' && hex.charAt(i-2) == '0') i -= 2; return "boolarray " + n + " " + quote(substring(hex, 0, i)); } String atype = "array", sep = ", "; if (o instanceof int[]) { //ret "intarray " + quote(intArrayToHex((int[]) o)); atype = "intarray"; sep = " "; } for (int i = 0; i < n; i++) { if (buf.length() != 0) buf.append(sep); buf.append(structure_1(Array.get(o, i), d)); } return r + atype + "{" + buf + "}"; } if (o instanceof Class) return r + "class(" + quote(((Class) o).getName()) + ")"; if (o instanceof Throwable) return r + "exception(" + quote(((Throwable) o).getMessage()) + ")"; if (o instanceof BitSet) { BitSet bs = (BitSet) o; for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) { if (buf.length() != 0) buf.append(", "); buf.append(i); } return "bitset{" + buf + "}"; } // Need more cases? This should cover all library classes... if (name.startsWith("java.") || name.startsWith("javax.")) return r + String.valueOf(o); String shortName = o.getClass().getName().replaceAll("^main\\$", ""); if (shortName.equals("Lisp")) { buf.append("l(" + structure_1(getOpt(o, "head"), d)); List args = (List) ( getOpt(o, "args")); if (nempty(args)) for (int i = 0; i < l(args); i++) { buf.append(", "); Object arg = args.get(i); // sweet shortening if (arg != null && eq(arg.getClass().getName(), "main$Lisp") && isTrue(call(arg, "isEmpty"))) arg = get(arg, "head"); buf.append(structure_1(arg, d)); } buf.append(")"); return r + str(buf); } int numFields = 0; String fieldName = ""; if (shortName.equals("DynamicObject")) { shortName = (String) get_raw(o, "className"); Map fieldValues = (Map) get_raw(o, "fieldValues"); for (String _fieldName : fieldValues.keySet()) { fieldName = _fieldName; Object value = fieldValues.get(fieldName); if (value != null) { if (buf.length() != 0) buf.append(", "); buf.append(fieldName + "=" + structure_1(value, d)); } ++numFields; } } else { // regular class Class c = o.getClass(); if (d.conceptClass != null && d.conceptClass.isInstance(o) && !d.concepts.contains(c.getName())) { d.concepts.add(c.getName()); r += "c "; } while (c != Object.class) { List fields = asList(c.getDeclaredFields()); for (int i = 1; i < l(fields); i++) if (eq(fields.get(i).getName(), "this$1")) { swapElements(fields, 0, i); break; } for (Field field : fields) { if ((field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue; fieldName = field.getName(); // skip outer object reference //if (fieldName.indexOf("$") >= 0) continue; Object value; try { field.setAccessible(true); value = field.get(o); } catch (Exception e) { value = "?"; } // put special cases here... if (value != null) { if (buf.length() != 0) buf.append(", "); buf.append(fieldName + "=" + structure_1(value, d)); } ++numFields; } c = c.getSuperclass(); } } String b = buf.toString(); if (numFields == 1 && structure_allowShortening) b = b.replaceAll("^" + fieldName + "=", ""); // drop field name if only one String s = shortName; if (buf.length() != 0) s += "(" + b + ")"; return r + s; } // drop unused markers static String structure_2(String s, HashSet refd) { List tok = javaTok(s); StringBuilder out = new StringBuilder(); for (int i = 1; i < l(tok); i += 2) { String t = tok.get(i); if (t.startsWith("m") && isInteger(t.substring(1)) && !refd.contains(parseInt(t.substring(1)))) continue; out.append(t).append(tok.get(i+1)); } return str(out); } static String n(long l, String name) { return l + " " + (l == 1 ? name : getPlural(name)); } static int parseInt(String s) { return empty(s) ? 0 : Integer.parseInt(s); } static Object call(Object o) { return callFunction(o); } // varargs assignment fixer for a single string array argument static Object call(Object o, String method, String[] arg) { return call(o, method, new Object[] {arg}); } static Object call(Object o, String method, Object... args) { try { if (o instanceof Class) { Method m = call_findStaticMethod((Class) o, method, args, false); m.setAccessible(true); return m.invoke(null, args); } else { Method m = call_findMethod(o, method, args, false); m.setAccessible(true); return m.invoke(o, args); } } catch (Exception e) { throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e); } } static Method call_findStaticMethod(Class c, String method, Object[] args, boolean debug) { Class _c = c; while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (!m.getName().equals(method)) { if (debug) System.out.println("Method name mismatch: " + method); continue; } if ((m.getModifiers() & Modifier.STATIC) == 0 || !call_checkArgs(m, args, debug)) continue; return m; } c = c.getSuperclass(); } throw new RuntimeException("Method '" + method + "' (static) with " + args.length + " parameter(s) not found in " + _c.getName()); } static Method call_findMethod(Object o, String method, Object[] args, boolean debug) { Class c = o.getClass(); while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (m.getName().equals(method) && call_checkArgs(m, args, debug)) return m; } c = c.getSuperclass(); } throw new RuntimeException("Method '" + method + "' (non-static) with " + args.length + " parameter(s) not found in " + o.getClass().getName()); } private static boolean call_checkArgs(Method m, Object[] args, boolean debug) { Class[] types = m.getParameterTypes(); if (types.length != args.length) { if (debug) System.out.println("Bad parameter length: " + args.length + " vs " + types.length); return false; } for (int i = 0; i < types.length; i++) if (!(args[i] == null || isInstanceX(types[i], args[i]))) { if (debug) System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]); return false; } return true; } static volatile StringBuffer local_log = new StringBuffer(); // not redirected static volatile StringBuffer print_log = local_log; // might be redirected, e.g. to main bot // in bytes - will cut to half that static volatile int print_log_max = 1024*1024; static volatile int local_log_max = 100*1024; //static int print_maxLineLength = 0; // 0 = unset static boolean print_silent; // total mute if set static void print() { print(""); } // slightly overblown signature to return original object... static A print(A o) { if (print_silent) return o; String s = String.valueOf(o) + "\n"; // TODO if (print_maxLineLength != 0) StringBuffer loc = local_log; StringBuffer buf = print_log; int loc_max = print_log_max; if (buf != loc && buf != null) { print_append(buf, s, print_log_max); loc_max = local_log_max; } if (loc != null) print_append(loc, s, loc_max); System.out.print(s); return o; } static void print(long l) { print(String.valueOf(l)); } static void print(char c) { print(String.valueOf(c)); } static void print_append(StringBuffer buf, String s, int max) { synchronized(buf) { buf.append(s); max /= 2; if (buf.length() > max) try { int newLength = max/2; int ofs = buf.length()-newLength; String newString = buf.substring(ofs); buf.setLength(0); buf.append("[...] ").append(newString); } catch (Exception e) { buf.setLength(0); } } } static A popLast(List l) { return liftLast(l); } static long parseLong(String s) { if (s == null) return 0; return Long.parseLong(dropSuffix("L", s)); } static long parseLong(Object s) { return Long.parseLong((String) s); } static String lstr(Map map) { return str(l(map)); } static String lstr(Collection c) { return str(l(c)); } static String lstr(String s) { return str(l(s)); } static String nlUnparse(Lisp tree) { return nlUnparse(tree, true); } static String nlUnparse(Lisp tree, boolean dropOuterBrackets) { StringBuilder buf = new StringBuilder(); nlUnparse(tree, buf, dropOuterBrackets); return str(buf); } static void nlUnparse(Lisp tree, StringBuilder buf, boolean dropOuterBrackets) { if (tree == null) return; // special classes - Var etc. if (tree.getClass() != Lisp.class) { buf.append(tree); return; } if (tree.isA("[]")) { if (!dropOuterBrackets) buf.append("["); } else if (tree.isA("()")) buf.append("("); else { if (tree.isLeaf()) buf.append(tree.head); else buf.append("?? " + tree); return; } for (int i = 0; i < tree.size(); i++) { if (i != 0) buf.append(" "); nlUnparse(tree.get(i), buf, false); } if(tree.isA("[]")) { if (!dropOuterBrackets) buf.append("]"); } else buf.append(")"); } static Object callF(Object f, Object... args) { return callFunction(f, args); } static Object callFunction(Object f, Object... args) { if (f == null) return null; if (f instanceof Runnable) { ((Runnable) f).run(); return null; } else if (f instanceof String) return call(mc(), (String) f, args); else return call(f, "get", args); //else throw fail("Can't call a " + getClassName(f)); } // hopefully covers all cases :) static String safeSubstring(String s, int x, int y) { if (s == null) return null; if (x < 0) x = 0; if (x > s.length()) return ""; if (y < x) y = x; if (y > s.length()) y = s.length(); return s.substring(x, y); } static String safeSubstring(String s, int x) { return safeSubstring(s, x, l(s)); } static String shorten(String s, int max) { if (s == null) return ""; return s.length() <= max ? s : s.substring(0, Math.min(s.length(), max)) + "..."; } static String quoteCharacter(char c) { if (c == '\'') return "'\\''"; if (c == '\\') return "'\\\\'"; return "'" + c + "'"; } static String getPlural(String s) { if (s.endsWith("y")) return dropSuffix("y", s) + "ies"; if (s.endsWith("ss")) return s + "es"; return s + "s"; } static List codeTokensOnly(List tok) { List l = new ArrayList(); for (int i = 1; i < tok.size(); i += 2) l.add(tok.get(i)); return l; } // currently finds only inner classes of class "main" // returns null on not found // this is the simple version that is not case-tolerant static Class findClass(String name) { if (!isJavaIdentifier(name)) return null; try { return Class.forName("main$" + name); } catch (ClassNotFoundException e) { return null; } } static String getType(Object o) { return getClassName(o); } static Lisp nlUnbracket(Lisp tree) { while (tree.is("[]", 1)) tree = tree.get(0); return tree; } static A liftLast(List l) { if (l.isEmpty()) return null; int i = l(l)-1; A a = l.get(i); l.remove(i); return a; } static String unnull(String s) { return s == null ? "" : s; } static List unnull(List l) { return l == null ? emptyList() : l; } static Iterable unnull(Iterable i) { return i == null ? emptyList() : i; } static Object[] unnull(Object[] a) { return a == null ? new Object[0] : a; } static BitSet unnull(BitSet b) { return b == null ? new BitSet() : b; } // replacement for class JavaTok // maybe incomplete, might want to add floating point numbers // todo also: extended multi-line strings static int javaTok_n, javaTok_elements; static boolean javaTok_opt; static List javaTok(String s) { return javaTok(s, null); } static List javaTok(String s, List existing) { ++javaTok_n; int nExisting = javaTok_opt && existing != null ? existing.size() : 0; List tok = existing != null ? new ArrayList(nExisting) : new ArrayList(); int l = s.length(); int i = 0, n = 0; while (i < l) { int j = i; char c; String cc; // scan for whitespace while (j < l) { c = s.charAt(j); cc = s.substring(j, Math.min(j+2, l)); if (c == ' ' || c == '\t' || c == '\r' || c == '\n') ++j; else if (cc.equals("/*")) { do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/")); j = Math.min(j+2, l); } else if (cc.equals("//")) { do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0); } else break; } if (n < nExisting && javaTok_isCopyable(existing.get(n), s, i, j)) tok.add(existing.get(n)); else tok.add(quickSubstring(s, i, j)); ++n; i = j; if (i >= l) break; c = s.charAt(i); // cc is not needed in rest of loop body cc = s.substring(i, Math.min(i+2, l)); // scan for non-whitespace if (c == '\'' || c == '"') { char opener = c; ++j; while (j < l) { if (s.charAt(j) == opener || s.charAt(j) == '\n') { // end at \n to not propagate unclosed string literal errors ++j; break; } else if (s.charAt(j) == '\\' && j+1 < l) j += 2; else ++j; } } else if (Character.isJavaIdentifierStart(c)) do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || "'".indexOf(s.charAt(j)) >= 0)); // for stuff like "don't" else if (Character.isDigit(c)) { do ++j; while (j < l && Character.isDigit(s.charAt(j))); if (j < l && s.charAt(j) == 'L') ++j; // Long constants like 1L } else if (cc.equals("[[")) { do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]")); j = Math.min(j+2, l); } else if (cc.equals("[=") && i+2 < l && s.charAt(i+2) == '[') { do ++j; while (j+2 < l && !s.substring(j, j+3).equals("]=]")); j = Math.min(j+3, l); } else ++j; if (n < nExisting && javaTok_isCopyable(existing.get(n), s, i, j)) tok.add(existing.get(n)); else tok.add(quickSubstring(s, i, j)); ++n; i = j; } if ((tok.size() % 2) == 0) tok.add(""); javaTok_elements += tok.size(); return tok; } static List javaTok(List tok) { return javaTok(join(tok), tok); } static boolean javaTok_isCopyable(String t, String s, int i, int j) { return t.length() == j-i && s.regionMatches(i, t, 0, j-i); // << could be left out, but that's brave } static Object mainBot; static Object getMainBot() { return mainBot; } static List toLines(File f) { return toLines(loadTextFile(f)); } public static List toLines(String s) { List lines = new ArrayList(); if (s == null) return lines; int start = 0; while (true) { int i = toLines_nextLineBreak(s, start); if (i < 0) { if (s.length() > start) lines.add(s.substring(start)); break; } lines.add(s.substring(start, i)); if (s.charAt(i) == '\r' && i+1 < s.length() && s.charAt(i+1) == '\n') i += 2; else ++i; start = i; } return lines; } private static int toLines_nextLineBreak(String s, int start) { for (int i = start; i < s.length(); i++) { char c = s.charAt(i); if (c == '\r' || c == '\n') return i; } return -1; } // This is made for SNL parsing. // It does NOT recognize multiline strings as these conflict // with syntax like [[a] [b]]. static List snlTok(String s) { List tok = new ArrayList(); int l = s.length(); int i = 0; while (i < l) { int j = i; char c; String cc; // scan for whitespace while (j < l) { c = s.charAt(j); cc = s.substring(j, Math.min(j+2, l)); if (c == ' ' || c == '\t' || c == '\r' || c == '\n') ++j; else if (cc.equals("/*")) { do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/")); j = Math.min(j+2, l); } else if (cc.equals("//")) { do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0); } else break; } tok.add(s.substring(i, j)); i = j; if (i >= l) break; c = s.charAt(i); cc = s.substring(i, Math.min(i+2, l)); // scan for non-whitespace if (c == '\u201C' || c == '\u201D') c = '"'; // normalize quotes if (c == '\'' || c == '"') { char opener = c; ++j; while (j < l) { char _c = s.charAt(j); if (_c == '\u201C' || _c == '\u201D') _c = '"'; // normalize quotes if (_c == opener) { ++j; break; } else if (s.charAt(j) == '\\' && j+1 < l) j += 2; else ++j; } if (j-1 >= i+1) { tok.add(opener + s.substring(i+1, j-1) + opener); i = j; continue; } } else if (Character.isJavaIdentifierStart(c)) do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || s.charAt(j) == '\'')); // for things like "this one's" else if (Character.isDigit(c)) do ++j; while (j < l && Character.isDigit(s.charAt(j))); /*else if (cc.equals("[[")) { do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]")); j = Math.min(j+2, l); }*/ else if (s.substring(j, Math.min(j+3, l)).equals("...")) j += 3; else if (c == '$' || c == '#') do ++j; while (j < l && Character.isLetterOrDigit(s.charAt(j))); else ++j; tok.add(s.substring(i, j)); i = j; } if ((tok.size() % 2) == 0) tok.add(""); return tok; } static List subList(List l, int startIndex) { return subList(l, startIndex, l(l)); } static List subList(List l, int startIndex, int endIndex) { startIndex = max(0, min(l(l), startIndex)); endIndex = max(0, min(l(l), endIndex)); if (startIndex > endIndex) return litlist(); return l.subList(startIndex, endIndex); } static List codeTokens(List tok) { return codeTokensOnly(tok); } static boolean startsWithUpperCase(String s) { return nempty(s) && Character.isUpperCase(s.charAt(0)); } // hmm, this shouldn't call functions really. That was just // for coroutines. static boolean isTrue(Object o) { if (o instanceof Boolean) return ((Boolean) o).booleanValue(); if (o == null) return false; return ((Boolean) callF(o)).booleanValue(); } static boolean isTrue(Object pred, Object arg) { return booleanValue(callF(pred, arg)); } static Object getOpt(Object o, String field) { if (o instanceof String) o = getBot ((String) o); if (o == null) return null; if (o instanceof Class) return getOpt((Class) o, field); if (o.getClass().getName().equals("main$DynamicObject")) return ((Map) getOpt_raw(o, "fieldValues")).get(field); if (o instanceof Map) return ((Map) o).get(field); return getOpt_raw(o, field); } static Object getOpt_raw(Object o, String field) { try { Field f = getOpt_findField(o.getClass(), field); if (f == null) return null; f.setAccessible(true); return f.get(o); } catch (Exception e) { throw new RuntimeException(e); } } static Object getOpt(Class c, String field) { try { if (c == null) return null; Field f = getOpt_findStaticField(c, field); if (f == null) return null; f.setAccessible(true); return f.get(null); } catch (Exception e) { throw new RuntimeException(e); } } static Field getOpt_findStaticField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; _c = _c.getSuperclass(); } while (_c != null); return null; } static Field getOpt_findField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field)) return f; _c = _c.getSuperclass(); } while (_c != null); return null; } static String boolArrayToHex(boolean[] a) { return bytesToHex(boolArrayToBytes(a)); } static Object first(Object list) { return ((List) list).isEmpty() ? null : ((List) list).get(0); } static A first(List list) { return list.isEmpty() ? null : list.get(0); } static A first(A[] bla) { return bla == null || bla.length == 0 ? null : bla[0]; } static A first(Iterable i) { if (i == null) return null; Iterator it = i.iterator(); return it.hasNext() ? it.next() : null; } static boolean startsWithIgnoreCase(String a, String b) { return a != null && a.regionMatches(true, 0, b, 0, b.length()); } static long getLastTiming() { ThreadLocal tl = (ThreadLocal) ( getOpt(getMainClass(), "saveTiming_last")); if (tl == null) return -1; Long l = tl.get(); return l == null ? -1 : l; } static boolean isIdentifier(String s) { return isJavaIdentifier(s); } static String format3(String pat, Object... args) { if (args.length == 0) return pat; List tok = javaTokPlusPeriod(pat); int argidx = 0; for (int i = 1; i < tok.size(); i += 2) if (tok.get(i).equals("*")) tok.set(i, format3_formatArg(argidx < args.length ? args[argidx++] : "null")); return join(tok); } static String format3_formatArg(Object arg) { if (arg == null) return "null"; if (arg instanceof String) { String s = (String) arg; return isIdentifier(s) || isNonNegativeInteger(s) ? s : quote(s); } if (arg instanceof Integer || arg instanceof Long) return String.valueOf(arg); return quote(structure(arg)); } public static String bytesToHex(byte[] bytes) { return bytesToHex(bytes, 0, bytes.length); } public static String bytesToHex(byte[] bytes, int ofs, int len) { StringBuilder stringBuilder = new StringBuilder(len*2); for (int i = 0; i < len; i++) { String s = "0" + Integer.toHexString(bytes[ofs+i]); stringBuilder.append(s.substring(s.length()-2, s.length())); } return stringBuilder.toString(); } static void swapElements(List l, int i, int j) { if (i == j) return; Object o = l.get(i); l.set(i, l.get(j)); l.set(j, o); } static RuntimeException asRuntimeException(Throwable t) { return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t); } // extended over Class.isInstance() to handle primitive types static boolean isInstanceX(Class type, Object arg) { if (type == boolean.class) return arg instanceof Boolean; if (type == int.class) return arg instanceof Integer; if (type == long.class) return arg instanceof Long; if (type == float.class) return arg instanceof Float; if (type == short.class) return arg instanceof Short; if (type == char.class) return arg instanceof Character; if (type == byte.class) return arg instanceof Byte; if (type == double.class) return arg instanceof Double; return type.isInstance(arg); } static String classNameToVM(String name) { return name.replace(".", "$"); } static boolean warn_on = false; static void warn(String s) { if (warn_on) print("Warn: " + s); } static void warn(String s, List warnings) { warn(s); if (warnings != null) warnings.add(s); } static boolean amAddressed() { return (boolean) callOpt(getMainBot(), "isAddressed"); } static String fromLines(List lines) { StringBuilder buf = new StringBuilder(); if (lines != null) for (String line : lines) buf.append(line).append('\n'); return buf.toString(); } static String fromLines(String... lines) { return fromLines(asList(lines)); } static String repeat(char c, int n) { n = max(n, 0); char[] chars = new char[n]; for (int i = 0; i < n; i++) chars[i] = c; return new String(chars); } static List repeat(A a, int n) { List l = new ArrayList(); for (int i = 0; i < n; i++) l.add(a); return l; } static Lisp nlEscapeVariables2(Lisp tree) { if (nlIsVar(tree)) return lisp("[]", "var", quote(tree.head)); Lisp lisp = new Lisp(tree.head); for (Lisp sub : tree) lisp.add(nlEscapeVariables2(sub)); return lisp; } static String dropSuffix(String suffix, String s) { return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s; } static void smartSet(Field f, Object o, Object value) throws Exception { f.setAccessible(true); // take care of common case (long to int) if (f.getType() == int.class && value instanceof Long) value = ((Long) value).intValue(); f.set(o, value); } static List nlTok(String s) { return javaTokPlusPeriod(s); } static String getProgramName_cache; static synchronized String getProgramName() { if (getProgramName_cache == null) getProgramName_cache = getSnippetTitle(getProgramID()); return getProgramName_cache; } static byte[] boolArrayToBytes(boolean[] a) { byte[] b = new byte[(l(a)+7)/8]; for (int i = 0; i < l(a); i++) if (a[i]) b[i/8] |= 1 << (i & 7); return b; } static String quickSubstring(String s, int i, int j) { if (i == j) return ""; return s.substring(i, j); } static boolean isJavaIdentifier(String s) { if (s.length() == 0 || !Character.isJavaIdentifierStart(s.charAt(0))) return false; for (int i = 1; i < s.length(); i++) if (!Character.isJavaIdentifierPart(s.charAt(i))) return false; return true; } static String programID; static String getProgramID() { return nempty(programID) ? formatSnippetID(programID) : "?"; } // TODO: ask JavaX instead static String getProgramID(Class c) { String id = (String) getOpt(c, "programID"); if (nempty(id)) return formatSnippetID(id); return "?"; } static String getProgramID(Object o) { return getProgramID(getMainClass(o)); } static int min(int a, int b) { return Math.min(a, b); } static double min(double a, double b) { return Math.min(a, b); } static double min(double[] c) { double x = Double.MAX_VALUE; for (double d : c) x = Math.min(x, d); return x; } static byte min(byte[] c) { byte x = 127; for (byte d : c) if (d < x) x = d; return x; } static Class getMainClass() { try { return Class.forName("main"); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Class getMainClass(Object o) { try { return (o instanceof Class ? (Class) o : o.getClass()).getClassLoader().loadClass("main"); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static int max(int a, int b) { return Math.max(a, b); } static long max(int a, long b) { return Math.max((long) a, b); } static long max(long a, long b) { return Math.max(a, b); } static double max(int a, double b) { return Math.max((double) a, b); } static float max(float a, float b) { return Math.max(a, b); } static int max(Collection c) { int x = Integer.MIN_VALUE; for (int i : c) x = max(x, i); return x; } static double max(double[] c) { if (c.length == 0) return Double.MIN_VALUE; double x = c[0]; for (int i = 1; i < c.length; i++) x = Math.max(x, c[i]); return x; } static byte max(byte[] c) { byte x = -128; for (byte d : c) if (d > x) x = d; return x; } static String getSnippetTitle(String id) { try { if (!isSnippetID(id)) return "?"; return trim(loadPageSilently(new URL("http://tinybrain.de:8080/tb-int/getfield.php?id=" + parseSnippetID(id) + "&field=title" + standardCredentials()))); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Object mc() { return getMainClass(); } static boolean isNonNegativeInteger(String s) { return s != null && Pattern.matches("\\d+", s); } static boolean booleanValue(Object o) { return eq(true, o); } // This is made for NL parsing. // It's javaTok extended with "..." token, "$n" and "#n" and // special quotes (which are converted to normal ones). static List javaTokPlusPeriod(String s) { List tok = new ArrayList(); int l = s.length(); int i = 0; while (i < l) { int j = i; char c; String cc; // scan for whitespace while (j < l) { c = s.charAt(j); cc = s.substring(j, Math.min(j+2, l)); if (c == ' ' || c == '\t' || c == '\r' || c == '\n') ++j; else if (cc.equals("/*")) { do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/")); j = Math.min(j+2, l); } else if (cc.equals("//")) { do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0); } else break; } tok.add(s.substring(i, j)); i = j; if (i >= l) break; c = s.charAt(i); cc = s.substring(i, Math.min(i+2, l)); // scan for non-whitespace if (c == '\u201C' || c == '\u201D') c = '"'; // normalize quotes if (c == '\'' || c == '"') { char opener = c; ++j; while (j < l) { char _c = s.charAt(j); if (_c == '\u201C' || _c == '\u201D') _c = '"'; // normalize quotes if (_c == opener) { ++j; break; } else if (s.charAt(j) == '\\' && j+1 < l) j += 2; else ++j; } if (j-1 >= i+1) { tok.add(opener + s.substring(i+1, j-1) + opener); i = j; continue; } } else if (Character.isJavaIdentifierStart(c)) do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || s.charAt(j) == '\'')); // for things like "this one's" else if (Character.isDigit(c)) do ++j; while (j < l && Character.isDigit(s.charAt(j))); else if (cc.equals("[[")) { do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]")); j = Math.min(j+2, l); } else if (cc.equals("[=") && i+2 < l && s.charAt(i+2) == '[') { do ++j; while (j+2 < l && !s.substring(j, j+3).equals("]=]")); j = Math.min(j+3, l); } else if (s.substring(j, Math.min(j+3, l)).equals("...")) j += 3; else if (c == '$' || c == '#') do ++j; while (j < l && Character.isDigit(s.charAt(j))); else ++j; tok.add(s.substring(i, j)); i = j; } if ((tok.size() % 2) == 0) tok.add(""); return tok; } static String getClassName(Object o) { return o == null ? "null" : o.getClass().getName(); } static List emptyList() { return new ArrayList(); //ret Collections.emptyList(); } public static String loadTextFile(String fileName) { try { return loadTextFile(fileName, null); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadTextFile(String fileName, String defaultContents) throws IOException { if (!new File(fileName).exists()) return defaultContents; FileInputStream fileInputStream = new FileInputStream(fileName); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8"); return loadTextFile(inputStreamReader); } public static String loadTextFile(File fileName) { try { return loadTextFile(fileName, null); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadTextFile(File fileName, String defaultContents) throws IOException { try { return loadTextFile(fileName.getPath(), defaultContents); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadTextFile(Reader reader) throws IOException { StringBuilder builder = new StringBuilder(); try { char[] buffer = new char[1024]; int n; while (-1 != (n = reader.read(buffer))) builder.append(buffer, 0, n); } finally { reader.close(); } return builder.toString(); } static String standardCredentials() { String user = trim(loadTextFile(new File(userHome(), ".tinybrain/username"))); String pass = trim(loadTextFile(new File(userHome(), ".tinybrain/userpass"))); if (nempty(user) && nempty(pass)) return "&_user=" + urlencode(user) + "&_pass=" + urlencode(pass); return ""; } public static boolean isSnippetID(String s) { try { parseSnippetID(s); return true; } catch (RuntimeException e) { return false; } } static ThreadLocal loadPage_charset = new ThreadLocal(); static boolean loadPage_allowGzip = true, loadPage_debug; static boolean loadPage_anonymous; // don't send computer ID static int loadPage_verboseness = 100000; static int loadPage_retries = 60; // seconds public static String loadPageSilently(String url) { try { return loadPageSilently(new URL(loadPage_preprocess(url))); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadPageSilently(URL url) { try { IOException e = null; for (int tries = 0; tries < loadPage_retries; tries++) try { URLConnection con = openConnection(url); return loadPage(con, url); } catch (IOException _e) { e = _e; print("Trying proxy because of: " + e); try { return loadPageThroughProxy(str(url)); } catch (Throwable e2) { print(" " + exceptionToStringShort(e2)); } sleepSeconds(1); } throw e; } catch (IOException e) { throw new RuntimeException(e); } } static String loadPage_preprocess(String url) { if (url.startsWith("tb/")) url = "tinybrain.de:8080/" + url; if (url.indexOf("://") < 0) url = "http://" + url; return url; } public static String loadPage(String url) { try { url = loadPage_preprocess(url); print("Loading: " + hideCredentials(url)); return loadPageSilently(new URL(url)); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadPage(URL url) { print("Loading: " + hideCredentials(url.toExternalForm())); return loadPageSilently(url); } public static String loadPage(URLConnection con, URL url) throws IOException { try { if (!loadPage_anonymous) { String computerID = getComputerID(); if (computerID != null) con.setRequestProperty("X-ComputerID", computerID); } if (loadPage_allowGzip) con.setRequestProperty("Accept-Encoding", "gzip"); } catch (Throwable e) {} // fails if within doPost String contentType = con.getContentType(); if (contentType == null) throw new IOException("Page could not be read: " + url); //print("Content-Type: " + contentType); String charset = loadPage_charset == null ? null : loadPage_charset.get(); if (charset == null) charset = loadPage_guessCharset(contentType); InputStream in = con.getInputStream(); if ("gzip".equals(con.getContentEncoding())) { if (loadPage_debug) print("loadPage: Using gzip."); in = new GZIPInputStream(in); } Reader r = new InputStreamReader(in, charset); StringBuilder buf = new StringBuilder(); int n = 0; while (true) { int ch = r.read(); if (ch < 0) break; buf.append((char) ch); ++n; if ((n % loadPage_verboseness) == 0) print(" " + n + " chars read"); } return buf.toString(); } static String loadPage_guessCharset(String contentType) { Pattern p = Pattern.compile("text/[a-z]+;\\s+charset=([^\\s]+)\\s*"); Matcher m = p.matcher(contentType); /* If Content-Type doesn't match this pre-conception, choose default and hope for the best. */ return m.matches() ? m.group(1) : "ISO-8859-1"; } static String formatSnippetID(String id) { return "#" + parseSnippetID(id); } static String formatSnippetID(long id) { return "#" + id; } public static long parseSnippetID(String snippetID) { long id = Long.parseLong(shortenSnippetID(snippetID)); if (id == 0) throw fail("0 is not a snippet ID"); return id; } static String urlencode(String x) { try { return URLEncoder.encode(unnull(x), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } static String shortenSnippetID(String snippetID) { if (snippetID.startsWith("#")) snippetID = snippetID.substring(1); String httpBlaBla = "http://tinybrain.de/"; if (snippetID.startsWith(httpBlaBla)) snippetID = snippetID.substring(httpBlaBla.length()); return "" + parseLong(snippetID); } static String getComputerID() { try { return computerID(); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static String _userHome; static String userHome() { if (_userHome == null) { if (isAndroid()) _userHome = "/storage/sdcard0/"; else _userHome = System.getProperty("user.home"); //System.out.println("userHome: " + _userHome); } return _userHome; } static File userHome(String path) { return new File(userDir(), path); } static String hideCredentials(String url) { return url.replaceAll("&_pass=[^&]*", "&_pass="); } static String exceptionToStringShort(Throwable e) { e = getInnerException(e); String msg = unnull(e.getMessage()); if (msg.indexOf("Error") < 0 && msg.indexOf("Exception") < 0) return baseClassName(e) + ": " + msg; else return msg; } static String loadPageThroughProxy(String url) { adbForward(); DialogIO io = talkToOpt("localhost", 4995); // Test ADB Bridge if (io == null) io = talkToOpt(gateway(), 4999); // Phone, Public Comm Bot if (io == null) return null; String answer = unquote(io.ask(forward("Awareness", format("loadPage *", url)))); io.close(); if (swic(answer, "ok ")) return answer.substring(3); throw fail(answer); } static URLConnection openConnection(URL url) { try { ping(); return url.openConnection(); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static void sleepSeconds(double s) { if (s > 0) sleep(round(s*1000)); } static DialogIO talkToOpt(String host, int port) { try { return talkTo(host, port); } catch (Throwable e) { return null; } } static volatile boolean ping_pauseAll; static int ping_sleep = 100; // poll pauseAll flag every 100 static volatile boolean ping_anyActions; static Map ping_actions = synchroMap(new WeakHashMap()); // returns true if it did anything static boolean ping() { if (ping_pauseAll) { do sleep(ping_sleep); while (ping_pauseAll); return true; } if (ping_anyActions) { Object action; synchronized(mc()) { action = ping_actions.get(currentThread()); if (action instanceof Runnable) ping_actions.remove(currentThread()); if (ping_actions.isEmpty()) ping_anyActions = false; } if (action instanceof Runnable) ((Runnable) action).run(); else if (eq(action, "cancelled")) throw fail("Thread cancelled."); } return false; } static void sleep(long ms) { try { Thread.sleep(ms); } catch (Exception e) { throw new RuntimeException(e); } } static void sleep() { try { print("Sleeping."); synchronized(main.class) { main.class.wait(); } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static String baseClassName(String className) { return substring(className, className.lastIndexOf('.')); } static String baseClassName(Object o) { return baseClassName(getClassName(o)); } static String forward(String bot, String msg, Object... args) { return format("please forward to bot *: *", bot, format(msg, args)); } static Throwable getInnerException(Throwable e) { while (e.getCause() != null) e = e.getCause(); return e; } static String _computerID; public static String computerID() { try { if (_computerID == null) { File file = new File(userHome(), ".tinybrain/computer-id"); _computerID = loadTextFile(file.getPath(), null); if (_computerID == null) { _computerID = makeRandomID(12); saveTextFile(file.getPath(), _computerID); } } return _computerID; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static TimedCache adbForward_list = new TimedCache(10); static void adbForward() { if (!isOnPATH("adb")) print("adb not on path"); else { backtick_verbose = true; String list = adbForward_list.get(new Object() { Object get() { return backtick("adb forward --list"); }}); if (!contains(list, "4995")) backtick("adb forward tcp:4995 tcp:4999"); } } static long round(double d) { return Math.round(d); } static File userDir() { return new File(userHome()); } static File userDir(String path) { return new File(userHome(), path); } static boolean isAndroid() { return System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0; } static String gateway() { return first(detectGateways()); } static Thread currentThread() { return Thread.currentThread(); } static boolean contains(Collection c, Object o) { return c != null && c.contains(o); } static boolean contains(Object[] x, Object o) { if (x != null) for (Object a : x) if (eq(a, o)) return true; return false; } static boolean contains(String s, char c) { return s != null && s.indexOf(c) >= 0; } static boolean contains(String s, String b) { return s != null && s.indexOf(b) >= 0; } static DialogIO talkTo(int port) { return talkTo("localhost", port); } static int talkTo_defaultTimeout = 10000; static DialogIO talkTo(String ip, int port) { try { final Socket s = new Socket(); s.connect(new InetSocketAddress(ip, port), talkTo_defaultTimeout); //print("Talking to " + ip + ":" + port); final Writer w = new OutputStreamWriter(s.getOutputStream(), "UTF-8"); final BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8")); return new DialogIO() { boolean isLocalConnection() { return s.getInetAddress().isLoopbackAddress(); } boolean isStillConnected() { return !(eos || s.isClosed()); } void sendLine(String line) { try { w.write(line + "\n"); w.flush(); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} String readLineImpl() { try { return in.readLine(); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} void close() { try { s.close(); } catch (IOException e) { // whatever } } Socket getSocket() { return s; } }; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static int backtick_exitValue; static boolean backtick_verbose; public static String backtick(String cmd) { try { File outFile = File.createTempFile("_backtick", ""); File scriptFile = File.createTempFile("_backtick", isWindows() ? ".bat" : ""); String command = cmd + " >" + bashQuote(outFile.getPath()) + " 2>&1"; //Log.info("[Backtick] " + command); try { if (backtick_verbose) print("backtick: command " + command); saveTextFile(scriptFile.getPath(), command); String[] command2; if (isWindows()) command2 = new String[] { scriptFile.getPath() }; else command2 = new String[] { "/bin/bash", scriptFile.getPath() }; if (backtick_verbose) print("backtick: command2 " + structure(command2)); Process process = Runtime.getRuntime().exec(command2); try { process.waitFor(); } catch (InterruptedException e) { throw new RuntimeException(e); } backtick_exitValue = process.exitValue(); if (backtick_verbose) System.out.println("Process return code: " + backtick_exitValue); String result = loadTextFile(outFile.getPath(), ""); if (backtick_verbose) print("[[\n" + result + "]]"); return result; } finally { scriptFile.delete(); } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static boolean isOnPATH(String cmd) { String path = System.getenv("PATH"); List dirs = splitAt(path, File.pathSeparator); String c = isWindows() ? cmd + ".exe" : cmd; for (String dir : dirs) if (new File(dir, c).isFile()) return true; return false; } static TimedCache> detectGateways_cache = new TimedCache(5); static synchronized List detectGateways() { try { if (detectGateways_cache.has()) return detectGateways_cache.getNoClean(); boolean win = isWindows(); String s = backtick(win ? "ipconfig" : "ip route show"); TreeSet ips = new TreeSet(); for (String line : toLines(s)) if (indexOfIgnoreCase(line, win ? "Gateway" : "via") >= 0) ips.addAll(regexpAll("\\d+\\.\\d+\\.\\d+\\.\\d+", line)); return detectGateways_cache.set(new ArrayList(ips)); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} /** writes safely (to temp file, then rename) */ public static void saveTextFile(String fileName, String contents) throws IOException { File file = new File(fileName); File parentFile = file.getParentFile(); if (parentFile != null) parentFile.mkdirs(); String tempFileName = fileName + "_temp"; File tempFile = new File(tempFileName); if (contents != null) { if (tempFile.exists()) try { String saveName = tempFileName + ".saved." + now(); copyFile(tempFile, new File(saveName)); } catch (Throwable e) { printStackTrace(e); } FileOutputStream fileOutputStream = new FileOutputStream(tempFile.getPath()); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8"); PrintWriter printWriter = new PrintWriter(outputStreamWriter); printWriter.print(contents); printWriter.close(); } if (file.exists() && !file.delete()) throw new IOException("Can't delete " + fileName); if (contents != null) if (!tempFile.renameTo(file)) throw new IOException("Can't rename " + tempFile + " to " + file); } public static void saveTextFile(File fileName, String contents) { try { saveTextFile(fileName.getPath(), contents); } catch (IOException e) { throw new RuntimeException(e); } } static Map synchroMap() { return synchroHashMap(); } static Map synchroMap(Map map) { return Collections.synchronizedMap(map); } static String makeRandomID(int length) { Random random = new Random(); char[] id = new char[length]; for (int i = 0; i < id.length; i++) id[i] = (char) ((int) 'a' + random.nextInt(26)); return new String(id); } public static boolean isWindows() { return System.getProperty("os.name").contains("Windows"); } /** possibly improvable */ static String bashQuote(String text) { if (text == null) return null; return "\"" + text .replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") + "\""; } static String bashQuote(File f) { return bashQuote(f.getAbsolutePath()); } static void printStackTrace(Throwable e) { // we go to system.out now - system.err is nonsense print(getStackTrace(e)); } static void printStackTrace() { printStackTrace(new Throwable()); } static void printStackTrace(String indent, Throwable e) { if (endsWithLetter(indent)) indent += " "; printIndent(indent, getStackTrace(e)); } public static void copyFile(File src, File dest) { try { mkdirsForFile(dest); FileInputStream inputStream = new FileInputStream(src.getPath()); FileOutputStream outputStream = new FileOutputStream(dest.getPath()); try { copyStream(inputStream, outputStream); inputStream.close(); } finally { outputStream.close(); } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static List splitAt(String s, String splitter) { List parts = new ArrayList(); int i = 0; if (s != null) while (i < l(s)) { int j = indexOf(s, splitter, i); if (j < 0) j = l(s); parts.add(substring(s, i, j)); i = j+l(splitter); } return parts; } // works on lists and strings and null static int indexOfIgnoreCase(Object a, Object b) { if (a == null) return -1; if (a instanceof String) { Matcher m = Pattern.compile((String) b, Pattern.CASE_INSENSITIVE + Pattern.LITERAL).matcher((String) a); if (m.find()) return m.start(); else return -1; } if (a instanceof List) { for (int i = 0; i < ((List) a).size(); i++) { Object o = ((List) a).get(i); if (o != null && ((String) o).equalsIgnoreCase((String) b)) return i; } return -1; } throw fail("Unknown type: " + a); } static List regexpAll(String pattern, String text) { List matches = new ArrayList(); Matcher matcher = Pattern.compile(pattern).matcher(text); while (matcher.find()) matches.add(matcher.group()); return matches; } static Map synchroHashMap() { return Collections.synchronizedMap(new HashMap()); } static void printIndent(Object o) { print(indentx(str(o))); } static void printIndent(String indent, Object o) { print(indentx(indent, str(o))); } static boolean endsWithLetter(String s) { return nempty(s) && isLetter(last(s)); } static int indexOf(List l, A a, int startIndex) { if (l == null) return -1; for (int i = startIndex; i < l(l); i++) if (eq(l.get(i), a)) return i; return -1; } static int indexOf(List l, A a) { if (l == null) return -1; return l.indexOf(a); } static int indexOf(String a, String b) { return a == null || b == null ? -1 : a.indexOf(b); } static int indexOf(String a, String b, int i) { return a == null || b == null ? -1 : a.indexOf(b, i); } static int indexOf(String a, char b, int i) { return a == null ? -1 : a.indexOf(b, i); } static int indexOf(String a, int i, String b) { return a == null || b == null ? -1 : a.indexOf(b, i); } static int indexOf(A[] x, A a) { if (x == null) return -1; for (int i = 0; i < l(x); i++) if (eq(x[i], a)) return i; return -1; } public static File mkdirsForFile(File file) { File dir = file.getParentFile(); if (dir != null) // is null if file is in current dir dir.mkdirs(); return file; } static void copyStream(InputStream in, OutputStream out) { try { byte[] buf = new byte[65536]; while (true) { int n = in.read(buf); if (n <= 0) return; out.write(buf, 0, n); } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static String indentx(String s) { return dropSuffix(repeat(' ', indent_default), indent(indent_default, s)); } static String indentx(String indent, String s) { return dropSuffix(indent, indent(indent, s)); } static boolean isLetter(char c) { return Character.isLetter(c); } static class TimedCache { long timeout; A value; long set; // stats int stores, fails, hits; // 0 = no timeout TimedCache(double timeoutSeconds) { timeout = toMS(timeoutSeconds); } synchronized A set(A a) { ++stores; value = a; set = now(); return a; } synchronized boolean has() { clean(); if (set != 0) { ++hits; return true; } ++fails; return false; } synchronized A get() { clean(); if (set != 0) ++hits; else ++fails; return value; } synchronized A get(Object makerFunction) { if (has()) return getNoClean(); A a = (A) ( callF(makerFunction)); return set(a); } synchronized A getNoClean() { return value; } // clear if timed out synchronized void clean() { if (timeout > 0 && now() > set+timeout) clear(); } // clear now synchronized void clear() { set = 0; value = null; } synchronized String stats() { return "Stores: " + stores + ", hits: " + hits + ", fails: " + fails; } } static class Var { A v; Var() {} Var(A v) { this.v = v;} synchronized void set(A a) { v = a; } synchronized A get() { return v; } } static class CIString implements Comparable { final String s; CIString(String s) { this.s = s; assertNotNull(s); } // note that eq(s, cistring) is still false public boolean equals(Object o) { if (o instanceof CIString) return s.equalsIgnoreCase(((CIString) o).s); if (o instanceof String) // One-way interoperability! return s.equalsIgnoreCase((String) o); return false; } public int compareTo(CIString cis) { return s.compareToIgnoreCase(cis.s); } public String toString() { return s; } public String str() { return s; } } static class EvalTransform { int varCount; Exception exception; boolean recurse = true; Lisp newVar() { return lisp("$e" + (++varCount)); } Lisp transformEval(Lisp tree, List out) { if (tree.is("[]", 2) && tree.get(0).is("eval", 0) && tree.get(1).is("()", 1)) { Lisp exp = tree.get(1).get(0); if (recurse) exp = transformEval(exp, out); Lisp var = newVar(); out.add(lisp("[]", var, "=").addAll(nlUnroll(exp))); return var; } // recurse Lisp n = new Lisp(tree.head); for (Lisp t : tree) n.add(transformEval(t, out)); return n; } Lisp evalTransformRule(Lisp rule) { try { List ops = snlSplitOps(rule); // expand unconditional rule if (ops == null || ops.isEmpty() || !ops.get(0).is("if *", 1)) ops = litlist(lisp("then *", rule)); for (int i = 0; i < l(ops); i++) { Lisp op = ops.get(i).get(0); List out = new ArrayList(); Lisp op2 = transformEval(op, out); if (out.isEmpty()) continue; for (Lisp l : out) { ops.add(i, lisp(i == 0 ? "if *" : "and *", l)); ++i; } ops.set(i, lisp(i == l(ops)-1 ? "then *" : "and *", op2)); } return snlJoinOps(ops); } catch (Exception e) { exception = e; return rule; } } Lisp evalTransformQuestion(Lisp question) { try { List out = new ArrayList(); Lisp tree = transformEval(question, out); for (int i = l(out)-1; i >= 0; i--) tree = lisp("[]", out.get(i), "and", tree); return tree; } catch (Exception e) { exception = e; return question; } } } // class for linking Java methods to NL static class Native { Lisp snl; SNLMatches m; SNLMatches trans; boolean printExceptions; int exceptions; List ins, strings; Native() {} // good to have Native(Lisp snl) { this.snl = snl;} // assume it's a Lisp structure from another bot - // restructure to import it /* *(O o) { snl = (Lisp) restructure(o); }*/ Native(String s) { snl = snlToTree(s); } boolean match(String pat) { m = new SNLMatches(); trans = new SNLMatches(); Lisp tree = snlToTree_cached(pat); ins = new ArrayList(); strings = new ArrayList(); tree = findInVariables(tree, ins, strings); //print("tree now: " + tree); if (!snlMatch2(tree, snl, trans)) return false; for (String in : ins) if (get(in) == null) return false; for (String string : strings) if (get(string) == null || !get(string).isEmpty()) return false; return true; } Lisp findInVariables(Lisp tree, List ins, List strings) { if (tree == null) return tree; if (tree.isEmpty()) { if (tree.head.startsWith("in S ")) { String var = dropPrefix("in S ", tree.head); if (startsWithUpperCase(var)) { if (!strings.contains(var)) strings.add(var); return lisp(var); } } if (tree.head.startsWith("in ")) { String var = dropPrefix("in ", tree.head); if (startsWithUpperCase(var)) { if (!ins.contains(var)) ins.add(var); return lisp(var); } } return tree; } else { // recurse Lisp lisp = new Lisp(tree.head); for (Lisp child : tree) lisp.add(findInVariables(child, ins, strings)); return lisp; } } Lisp get(String var) { Lisp val = trans.get(var); if (!isVar(val)) return val; return getOuter(val.raw()); } // if only one in var Lisp get() { return get(anyInVar()); } // ditto String str() { return str(anyInVar()); } String anyInVar() { if (!empty(ins)) return first(ins); return first(strings); } boolean isVar(Lisp l) { return l != null && l.isEmpty() && startsWithUpperCase(l.raw()); } // called from outside native Lisp getOuter(String var) { return m.get(var); } // called from inside native String str(String var) { Lisp val = get(var); if (val == null) throw fail("variable " + quote(var) + " not set"); if (!val.isEmpty()) throw fail("variable " + quote(var) + " not a string: " + struct(val)); return unquote(val.raw()); } String strOuter(String var) { Lisp val = getOuter(var); if (val == null) throw fail("variable " + quote(var) + " not set"); if (!val.isEmpty()) throw fail("variable " + quote(var) + " not a string: " + struct(val)); return unquote(val.raw()); } // may throw an exception if variable problem (already set to different value) void set(String var, Object val) { if (val instanceof List) val = structure(val); // TODO Lisp lisp = val instanceof Lisp ? (Lisp) val : lisp(quote(main.str(val))); Lisp outer = trans.get(var); if (isVar(outer)) { if (!m.put(trans.raw(var), lisp)) throw fail(); } else if (!eq(lisp, outer)) { //print("neq " + lisp + " != " + outer); throw fail(); } } boolean callExternal(Object bot) { try { Object copy = newObject(main.getClass(bot, "main$Native"), quickExport(snl, bot)); //O copy = quickExport(this, bot); if (!isTrue(callOpt(bot, "yo", copy))) return false; m = (SNLMatches) quickImport(main.get(copy, "m")); return true; } catch (Exception e) { handle(e); return false; } } boolean callExternal(Method yo) { try { Class c = yo.getDeclaringClass(); //print("Declaring class: " + c + " (" + identityHashCode(c) + ")"); Object copy = newObject(main.getClass(c, "main$Native"), quickExport(snl, c)); if (!isTrue(yo.invoke(null, copy))) return false; m = (SNLMatches) quickImport(main.get(copy, "m")); return true; } catch (Exception e) { handle(e); return false; } } void handle(Throwable t) { if (printExceptions) printStackTrace(t); ++exceptions; } } // a map of assignments that can be rolled back to some state static class SNLMatches { HashMap map = new HashMap(); List log = new ArrayList(); boolean put(String key, Lisp val) { if (map.containsKey(key)) // existing return eq(map.get(key), val); // new map.put(key, val); log.add(key); return true; } Lisp get(String key) { return map.get(key); } String raw(String key) { Lisp x = get(key); if (x == null) throw fail("var not set: " + key); return x.raw(); } boolean containsKey(String key) { return map.containsKey(key); } int save() { return l(log); } void restore(int i) { while (l(log) > i) { String key = log.get(l(log)-1); map.remove(key); log.remove(l(log)-1); } } boolean addAll(SNLMatches m) { for (String var : m.keySet()) if (!put(var, m.get(var))) return false; return true; } Set keySet() { return map.keySet(); } } // a Lisp-like form static class Lisp implements Iterable { String head; List args = new ArrayList(); Object more; // additional info, user-defined Lisp() {} Lisp(String head) { this.head = head;} Lisp(String head, Lisp... args) { this.head = head; this.args.addAll(asList(args)); } Lisp(String head, List args) { this.head = head; for (Object arg : args) add(arg); } // INEFFICIENT public String toString() { if (args.isEmpty()) return quoteIfNotIdentifier(head); List bla = new ArrayList(); for (Lisp a : args) bla.add(a.toString()); String inner = join(", ", bla); if (head.equals("")) return "{" + inner + "}"; // list else return quoteIfNotIdentifier(head) + "(" + inner + ")"; } String raw() { if (!isEmpty ()) throw fail("not raw: " + this); return head; } Lisp add(Lisp l) { args.add(l); return this; } Lisp add(String s) { args.add(new Lisp(s)); return this; } Lisp add(Object o) { if (o instanceof Lisp) add((Lisp) o); else if (o instanceof String) add((String) o); else throw fail("Bad argument type: " + structure(o)); return this; } int size() { return args.size(); } boolean isEmpty() { return args.isEmpty(); } boolean isLeaf() { return args.isEmpty(); } Lisp get(int i) { return args.get(i); } String getString(int i) { return get(i).head; } String s(int i) { return getString(i); } boolean isA(String head) { return eq(head, this.head); } boolean is(String head, int size) { return isA(head) && size() == size; } boolean is(String head) { return isA(head); } boolean is(String... heads) { return asList(heads).contains(head); } // check head for one of these (ignore case) boolean isic(String... heads) { return containsIgnoreCase(heads, head); } public Iterator iterator() { return args.iterator(); } Lisp subList(int fromIndex, int toIndex) { Lisp l = new Lisp(head); l.args.addAll(args.subList(fromIndex, toIndex)); // better to copy here I guess - safe return l; } public boolean equals(Object o) { if (o == null || o.getClass() != Lisp.class) return false; Lisp l = (Lisp) ( o); return eq (head, l.head) && eq(args, l.args); } Lisp addAll(List args) { for (Object arg : args) add(arg); return this; } String unquoted() { return unquote(raw()); } String unq() { return unquoted(); } } static abstract class DialogIO { String line; boolean eos, loud; abstract String readLineImpl(); abstract boolean isStillConnected(); abstract void sendLine(String line); abstract boolean isLocalConnection(); abstract Socket getSocket(); abstract void close(); int getPort() { return getSocket().getPort(); } boolean helloRead; String readLineNoBlock() { String l = line; line = null; return l; } boolean waitForLine() { try { if (line != null) return true; //print("Readline"); line = readLineImpl(); //print("Readline done: " + line); if (line == null) eos = true; return line != null; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} String readLine() { waitForLine(); helloRead = true; return readLineNoBlock(); } String ask(String s, Object... args) { if (loud) return askLoudly(s, args); if (!helloRead) readLine(); if (args.length != 0) s = format3(s, args); sendLine(s); return readLine(); } String askLoudly(String s, Object... args) { if (!helloRead) readLine(); if (args.length != 0) s = format3(s, args); print("> " + s); sendLine(s); String answer = readLine(); print("< " + answer); return answer; } void pushback(String l) { if (line != null) throw fail(); line = l; helloRead = false; } } static abstract class DialogHandler { abstract void run(DialogIO io); } static class Matches { String[] m; String get(int i) { return i < m.length ? m[i] : null; } String unq(int i) { return unquote(get(i)); } String fsi(int i) { return formatSnippetID(unq(i)); } String fsi() { return fsi(0); } String tlc(int i) { return unq(i).toLowerCase(); } boolean bool(int i) { return "true".equals(unq(i)); } String rest() { return m[m.length-1]; } // for matchStart int psi(int i) { return Integer.parseInt(unq(i)); } } static A assertNotNull(A a) { assertTrue(a != null); return a; } static A assertNotNull(String msg, A a) { assertTrue(msg, a != null); return a; } // snlMatch2 keeps existing matches and restores matches on false static boolean snlMatch2(String pat, String snl, SNLMatches matches) { return snlMatch2(snlToTree_cached(pat), snlToTree(snl), matches); } static boolean snlMatch2(Lisp pat, String snl, SNLMatches matches) { return snlMatch2(pat, snlToTree(snl), matches); } static boolean snlMatch2(String pat, Lisp snl, SNLMatches matches) { return snlMatch2(snlToTree_cached(pat), snl, matches); } static boolean snlMatch2(Lisp pat, Lisp snl, SNLMatches matches) { int level = matches.save(); if (snlMatch2_sub(pat, snl, matches)) return true; matches.restore(level); return false; } static boolean snlMatch2_sub(Lisp pat, Lisp snl, SNLMatches m) { if (pat == null || snl == null) return false; if (pat.isA("*")) return true; if (startsWithUpperCase(pat.head)) return snlMatch2_putMatch(m, pat.head, snl); if (neq(pat.head, snl.head)) return false; // heads identical, proceed to children int n = pat.size(); if (n != snl.size()) return false; for (int i = 0; i < n; i++) if (!snlMatch2_sub(pat.get(i), snl.get(i), m)) return false; return true; } static boolean snlMatch2_putMatch(SNLMatches matches, String key, Lisp val) { return matches.put(key, val); } static String quoteIfNotIdentifier(String s) { if (s == null) return null; return isJavaIdentifier(s) ? s : quote(s); } static Object newObject(Class c, Object... args) { return nuObject(c, args); } static Object newObject(String className, Object... args) { return nuObject(className, args); } static Object quickImport(Object o) { return quickExport(o, mc()); } static void addAll(Collection c, Collection b) { c.addAll(b); } static void addAll(Collection c, Object[] b) { c.addAll(Arrays.asList(b)); } static HashMapsnlToTree_cached_cache = new HashMap(); static Lisp snlToTree_cached(String s) { Lisp l = snlToTree_cached_cache.get(s); if (l == null) snlToTree_cached_cache.put(s, l = snlToTree (s)); return l; } static List nlUnroll(Lisp tree) { if (tree.is("[]")) return tree.args; else return litlist(tree); } static String getString(Map map, Object key) { return map == null ? null : (String) map.get(key); } static String getString(List l, int idx) { return (String) get(l, idx); } static String getString(Object o, Object key) { if (o instanceof Map) return getString((Map) o, key); if (key instanceof String) return (String) getOpt(o, (String) key); throw fail("Not a string key: " + getClassName(key)); } static boolean containsIgnoreCase(List l, String s) { for (String x : l) if (eqic(x, s)) return true; return false; } static boolean containsIgnoreCase(String[] l, String s) { for (String x : l) if (eqic(x, s)) return true; return false; } static boolean containsIgnoreCase(String s, char c) { return indexOfIgnoreCase(s, String.valueOf(c)) >= 0; } static boolean containsIgnoreCase(String a, String b) { return indexOfIgnoreCase(a, b) >= 0; } static BufferedReader readLine_reader; static String readLine() { return (String) call(getJavaX(), "readLine"); } static ThreadLocal saveTiming_last = new ThreadLocal(); static void saveTiming(long ms) { print(ms + " ms"); saveTiming_last.set(ms); } static String struct(Object o) { return structure(o); } static String fsi(String id) { return formatSnippetID(id); } static long toMS(double seconds) { return (long) (seconds*1000); } static Object quickExport(Object o, Object dest) { try { return quickExport_impl(o, dest, new IdentityHashMap()); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Object quickExport_impl(Object o, Object dest, IdentityHashMap seen) { try { if (o == null || o instanceof String || o instanceof Number) return o; Object oo = seen.get(o); if (oo != null) return oo; if (o instanceof List) { List l = (List) ( o); List destO = new ArrayList(l.size()); seen.put(o, destO); for (int i = 0; i < l.size(); i++) destO.add(quickExport_impl(l.get(i), dest, seen)); return destO; } if (o instanceof Map) { Map m = (Map) ( o); Map destO = new HashMap(); seen.put(o, destO); for (Object e : ((Map) o).entrySet()) destO.put( quickExport_impl(((Map.Entry) e).getKey(), dest, seen), quickExport_impl(((Map.Entry) e).getValue(), dest, seen)); return destO; } String className = o.getClass().getName(); if (className.startsWith("main$") && !isAnonymousClassName(className)) { Class destClass = getClass(dest, className); if (destClass == null) return o; // Class not found in target realm, keep object as is //print(o.getClass() + " => " + destClass); if (o.getClass() == destClass) return o; // no export necessary // actually make a new object(), copy fields Object destO = nuObject(destClass); seen.put(o, destO); // TODO: superclasses Field[] fields = o.getClass().getDeclaredFields(); for (Field field : fields) { if ((field.getModifiers() & Modifier.STATIC) != 0) continue; field.setAccessible(true); Object value = field.get(o); setOpt(destO, field.getName(), quickExport_impl(value, dest, seen)); } return destO; } // assume it's a shared library object return o; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static boolean equals(Object a, Object b) { return a == null ? b == null : a.equals(b); } static boolean snlToTree_debug = false; static Lisp snlToTree(String snl) { return snlToTree(snlParse(snl)); } static Lisp snlToTree(Explain e) { class T { List scanJuxta(Explain e) { boolean isJuxta = isA(e, "juxta"); if (snlToTree_debug) print("scanJuxta " + e.string() + " " + isJuxta); if (isJuxta) { Explain juxta = descend(e, "juxta"); Explain a = juxta.sub(0), b = juxta.sub(1); return concatLists(scanJuxta(a), scanJuxta(b)); } else return litlist(e); } boolean isSame(Explain a, Explain b) { return a.fromToken() == b.fromToken() && a.toToken() == b.toToken(); } List getClasses(Explain e) { List l = new ArrayList(); while (e != null) { l.add(e.className()); if (l(e.subs) == 1 && e.sub(0) != null && isSame(e, e.sub(0))) e = e.sub(0); else break; } return l; } Explain castTo(Explain e, String className) { return descend(e, className); } Explain descend(Explain e, String className) { List l = new ArrayList(); while (e != null) { if (eq(e.className(), className)) return e; if (l(e.subs) == 1) e = e.sub(0); else break; } return null; } boolean isA(Explain e, String className) { return getClasses(e).contains(className); } boolean allAre(List l, String className) { for (Explain e: l) if (!isA(e, className)) return false; return true; } boolean anyAre(List l, String className) { for (Explain e : l) if (isA(e, className)) return true; return false; } List getOperators(List parts) { List l = new ArrayList(); for (Explain e : parts) l.add(makeOperator(e)); return l; } // true = it's a sub part boolean[] opMap(List parts) { boolean[] map = new boolean[l(parts)]; if (anyAre(parts, "symbol")) { for (int i = 0; i < l(parts); i++) if (!isA(parts.get(i), "symbol")) map[i] = true; } else for (int i = 0; i < l(parts); i++) //if (!isA(parts.get(i), "extidentifier")) if (!startsWithLetter(parts.get(i).string())) map[i] = true; return map; } String makeOperatorFromJuxta(List parts) { List l = new ArrayList(); boolean[] map = opMap(parts); for (int i = 0; i < l(parts); i++) { //print("Operator part " + (++i) + ": " + e.string() + " / " + structure(getClasses(e))); if (map[i]) l.add("*"); else l.add(parts.get(i).string()); } String op = join(" ", l); if (snlToTree_debug) print("makeOperatorFromJuxta " + structure(parts) + " => " + op); return op; } List getJuxtaArgs(List parts) { List l = new ArrayList(); boolean[] map = opMap(parts); for (int i = 0; i < l(parts); i++) if (map[i]) l.add(parts.get(i)); return l; } String makeOperator(Explain e) { if (isA(e, "realarrow")) return descend(e, "realarrow").sub(0).string() + " *"; else if (isA(e, "juxta")) { List j = scanJuxta(e); //print("scanJuxta => " + structure(j)); return makeOperatorFromJuxta(j); } else return e.string(); } Lisp getTree(Explain e) { //print("getTree " + structure(getClasses(e))); if (isA(e, "realarrow")) { //print("realarrow"); Explain a = castTo(e, "realarrow"); return snlToTree_lisp(e, "<", getTree(a.sub(0)), getTree(a.sub(1))); } if (isA(e, "realarrowr")) { //print("realarrowr"); Explain a = castTo(e, "realarrowr"); return snlToTree_lisp(e, ">", getTree(a.sub(0)), getTree(a.sub(1))); } if (isA(e, "square")) { //print("square"); Explain a = castTo(e, "square"); return snlToTree_lisp(e, "[]", getTree(a.sub(0))); } if (isA(e, "round")) { Explain a = castTo(e, "round"); return snlToTree_lisp(e, "()", getTree(a.sub(0))); } if (isA(e, "juxta")) { Explain juxta = descend(e, "juxta"); List parts = scanJuxta(juxta); if (snlToTree_debug) { print("juxta " + e.string()); for (int i = 0; i < l(parts); i++) print(" part " + parts.get(i).string() + " " + structure(getClasses(parts.get(i)))); } if (l(parts) == 2 && eq(parts.get(0).string(), "=")) return snlToTree_lisp(e, "= *", removeBrackets(getTree(parts.get(1)))); else /*if (anyAre(parts, "subword") || anyAre(parts, "ucid")) */ { if (snlToTree_debug) print("subwords!"); List args = getJuxtaArgs(parts); Lisp l = snlToTree_lisp(e, makeOperatorFromJuxta(parts)); for (Explain arg : args) l.add(removeBrackets(getTree(arg))); return l; // snlSimplifyJuxta(l); } // fall back to simple string } return snlToTree_lisp(e, e.string()); } Lisp removeBrackets(Lisp l) { while (l != null && eq(l.head, "[]") && l.size() == 1) l = l.get(0); return l; } } if (e == null) return null; return simplifySNLTree(new T().getTree(e)); } static Lisp snlToTree_lisp(Explain e, String head, Lisp... args) { Lisp l = lisp(head, args); // this is problematic with serialization... //SNLInfo info = new SNLInfo(); //info.originalExplain = e; //l.more = info; return l; } // also logs full exception to console static String exceptionToUser(Throwable e) { return throwableToUser(e); } static boolean match(String pat, String s) { return match3(pat, s); } static boolean match(String pat, String s, Matches matches) { return match3(pat, s, matches); } /* Binding levels (well...): 0 = lowest 1 = arrows (> lowest) 2 = arrowsr (> arrows) 3 = word (> arrows) 4 = juxta, idword, subword (> word) */ static String snlParse_rules = "\r\n // highest (strongest binding) level \r\n = idword\r\n = subword\r\n = subword\r\n // = subword\r\n \r\n // random symbols we'd want to use\r\n // (they don't parse if not listed here)\r\n ? = symbol\r\n ! = symbol\r\n : = symbol\r\n * = symbol\r\n \"=\" = symbol\r\n + = symbol\r\n , = symbol\r\n . = symbol\r\n = idword\r\n \r\n = word\r\n = word\r\n \r\n // juxtaposition on highest level\r\n = juxta\r\n = word\r\n \r\n // bracketing takes us to highest level\r\n [ ] = square\r\n ( ) = round\r\n \r\n = subword\r\n = subword\r\n \r\n // < is intermediate\r\n = arrowsr\r\n > = realarrowr\r\n = arrowsr\r\n \r\n < = realarrow\r\n = arrows\r\n = arrows\r\n \r\n = lowest // allow empty here\r\n = lowest\r\n"; static Explain snlParse(String input) { return explainFull(snlTok(input), snlParse_rules, "lowest"); } static Class __javax; static Class getJavaX() { return __javax; } static boolean isAnonymousClassName(String s) { for (int i = 0; i < l(s); i++) if (s.charAt(i) == '$' && Character.isDigit(s.charAt(i+1))) return true; return false; } // class Matches is added by #752 static boolean match3(String pat, String s) { return match3(pat, s, null); } static boolean match3(String pat, String s, Matches matches) { if (s == null) return false; return match3(pat, parse3(s), matches); } static boolean match3(String pat, List toks, Matches matches) { List tokpat = parse3(pat); return match3(tokpat,toks,matches); } static boolean match3(List tokpat, List toks, Matches matches) { String[] m = match2(tokpat, toks); //print(structure(tokpat) + " on " + structure(toks) + " => " + structure(m)); if (m == null) return false; else { if (matches != null) matches.m = m; return true; } } static void setOpt(Object o, String field, Object value) { if (o instanceof Class) setOpt((Class) o, field, value); else try { Field f = setOpt_findField(o.getClass(), field); if (f != null) smartSet(f, o, value); } catch (Exception e) { throw new RuntimeException(e); } } static void setOpt(Class c, String field, Object value) { try { Field f = setOpt_findStaticField(c, field); if (f != null) smartSet(f, null, value); } catch (Exception e) { throw new RuntimeException(e); } } static Field setOpt_findStaticField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; _c = _c.getSuperclass(); } while (_c != null); return null; } static Field setOpt_findField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field)) return f; _c = _c.getSuperclass(); } while (_c != null); return null; } static Lisp simplifySNLTree(Lisp tree) { while (tree != null && tree.isA("[]") && tree.size() == 1) tree = tree.get(0); if (tree == null) return null; Lisp lisp = new Lisp(tree.head); for (Lisp child : tree) lisp.add(simplifySNLTree(child)); return lisp; } static boolean startsWithLetter(String s) { return nempty(s) && Character.isLetter(s.charAt(0)); } static void assertTrue(Object o) { assertEquals(true, o); } static boolean assertTrue(String msg, boolean b) { if (!b) throw fail(msg); return b; } static boolean assertTrue(boolean b) { if (!b) throw fail("oops"); return b; } static Object nuObject(String className, Object... args) { try { return nuObject(Class.forName(className), args); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} // too ambiguous - maybe need to fix some callers /*static O nuObject(O realm, S className, O... args) { ret nuObject(_getClass(realm, className), args); }*/ static A nuObject(Class c, Object... args) { try { Constructor m = nuObject_findConstructor(c, args); m.setAccessible(true); return (A) m.newInstance(args); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Constructor nuObject_findConstructor(Class c, Object... args) { for (Constructor m : c.getDeclaredConstructors()) { if (!nuObject_checkArgs(m.getParameterTypes(), args, false)) continue; return m; } throw new RuntimeException("Constructor with " + args.length + " matching parameter(s) not found in " + c.getName()); } static boolean nuObject_checkArgs(Class[] types, Object[] args, boolean debug) { if (types.length != args.length) { if (debug) System.out.println("Bad parameter length: " + args.length + " vs " + types.length); return false; } for (int i = 0; i < types.length; i++) if (!(args[i] == null || isInstanceX(types[i], args[i]))) { if (debug) System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]); return false; } return true; } // also logs full exception to console static String throwableToUser(Throwable e) { return formatExceptionForUser(e); } static Explain explainFull(String input, String rules, String className) { return explainFull(javaTok(input), rules, className); } static Explain explainFull(List tok, String rules, String className) { Object parseResult = parse1(tok, rules); List e = (List) ( call(parseResult, "explainFull", className)); if (e == null) return null; return new Explain(parseResult, e); } // match2 matches multiple "*" (matches a single token) wildcards and zero or one "..." wildcards (matches multiple tokens) static String[] match2(List pat, List tok) { // standard case (no ...) int i = pat.indexOf("..."); if (i < 0) return match2_match(pat, tok); pat = new ArrayList(pat); // We're modifying it, so copy first pat.set(i, "*"); while (pat.size() < tok.size()) { pat.add(i, "*"); pat.add(i+1, ""); // doesn't matter } return match2_match(pat, tok); } static String[] match2_match(List pat, List tok) { List result = new ArrayList(); if (pat.size() != tok.size()) { /*if (debug) print("Size mismatch: " + structure(pat) + " vs " + structure(tok));*/ return null; } for (int i = 1; i < pat.size(); i += 2) { String p = pat.get(i), t = tok.get(i); /*if (debug) print("Checking " + p + " against " + t);*/ if (eq(p, "*")) result.add(t); else if (!equalsIgnoreCase(unquote(p), unquote(t))) // bold change - match quoted and unquoted now return null; } return result.toArray(new String[result.size()]); } // also logs full exception to console static String formatExceptionForUser(Throwable e) { printStackTrace(e); String msg = unnull(e.getMessage()); if (msg.indexOf("Error") < 0 && msg.indexOf("Exception") < 0) return dropPrefix("java.lang.", str(e)); else return msg; } static List parse3(String s) { return dropPunctuation(javaTokPlusPeriod(s)); } static A assertEquals(Object x, A y) { return assertEquals(null, x, y); } static A assertEquals(String msg, Object x, A y) { if (!(x == null ? y == null : x.equals(y))) throw fail((msg != null ? msg + ": " : "") + structure(x) + " != " + structure(y)); return y; } static boolean equalsIgnoreCase(String a, String b) { return a == null ? b == null : a.equalsIgnoreCase(b); } static Object parse1_parser; static Object parse1(String text, String rules) { return parse1(javaTok(text), rules); } // only parse, no explain static synchronized Object parse1(List tok, String rules) { if (parse1_parser == null) parse1_parser = run_overBot(/*"#1002472"*/ "#1002719"); //setOpt(parse1_parser, "debug", true); return call(parse1_parser, "parse", tok, rules); } static List dropPunctuation_keep = litlist("*", "<", ">"); static List dropPunctuation(List tok) { tok = new ArrayList(tok); for (int i = 1; i < tok.size(); i += 2) { String t = tok.get(i); if (t.length() == 1 && !Character.isLetter(t.charAt(0)) && !Character.isDigit(t.charAt(0)) && !dropPunctuation_keep.contains(t)) { tok.set(i-1, tok.get(i-1) + tok.get(i+1)); tok.remove(i); tok.remove(i); i -= 2; } } return tok; } static String dropPunctuation(String s) { return join(dropPunctuation(nlTok(s))); } static Class run_overBot(String progID) { Class main = hotwire_overBot(progID); callMain(main); return main; } static void callMain(Object c, String... args) { callOpt(c, "main", new Object[] {args}); } static Class hotwire_overBot(String progID) { try { String compilerBot = "Compiler Bot with caching!"; startBot(compilerBot, "#1002203"); String ss = "please compile this javax snippet: *"; print("Sending to compiler bot: " + ss); String s = sendToLocalBot_cached(compilerBot, ss, progID); Matches m = new Matches(); print(s); assertTrue("Compiler Bot response: " + s, match("ok, *", s, m)); String jarPath = m.unq(0); File jar = new File(jarPath); assertTrue(jar.getAbsolutePath(), jar.isFile()); // collect urls (program + libraries) List urls = litlist(jar.toURI().toURL()); String dehlibs = unnull(loadTextFileFromZip(jar, "libraries")); Matcher matcher = Pattern.compile("\\d+").matcher(dehlibs); while (matcher.find()) { String libID = matcher.group(); urls.add(loadLibrary(libID).toURI().toURL()); } // make class loader URLClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[l(urls)])); // load & return main class Class theClass = classLoader.loadClass("main"); Class j = getJavaX(); String src = loadTextFileFromZip(jar, "main.java"); call(j, "registerSourceCode", theClass, src); synchronized(j) { // hopefully this goes well... call(j, "setVars", theClass, progID); callOpt(j, "addInstance", progID, theClass); } hotwire_copyOver(theClass); return theClass; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Map startBot_assignments = new TreeMap(); static void startBot(String botName, String botID) { DialogIO bot = newFindBot(botName); if (bot != null) bot.close(); else { print("Bot " + quote(botName) + " not found. Starting " + botID); javaxBot(botID); waitForBotStartUp(botName); } } static void startBot(String botIDOrName) { String botID = startBot_assignments.get(botIDOrName); if (botID != null) startBot(botIDOrName, botID); else { String botName = getBotNameFromSnippet(botIDOrName); if (botName == null) throw fail("Bot name not found in source of " + botID); startBot(botName, botIDOrName); } } static String loadTextFileFromZip(File inZip, String fileName) { return loadTextFileFromZipFile(inZip, fileName); } static void hotwire_copyOver(Class c) { synchronized(StringBuffer.class) { for (String field : litlist("print_log", "print_silent", "androidContext")) { Object o = getOpt(mc(), field); if (o != null) setOpt(c, field, o); } Object mainBot = getMainBot(); if (mainBot != null) setOpt(c, "mainBot", mainBot); setOpt(c, "creator_class", new WeakReference(mc())); } } static File loadLibrary(String snippetID) { return loadBinarySnippet(snippetID); } static String sendToLocalBot_cached(String botName, String s, Object... args) { DialogIO io = newFindBot(botName); if (io == null) throw fail("Bot not found: " + botName); try { return io.ask(s, args); } finally { io.close(); } } static void javaxBot(String botID) { startBotVM(); if (injectTo("Bot VM", botID) == null) throw fail("Couldn't inject to Bot VM"); //nohupJavax(botID); } static long waitForBotStartUp_timeoutSeconds = 60; // returns address or fails static String waitForBotStartUp(String botName) { for (int i = 0; i < waitForBotStartUp_timeoutSeconds; i++) { sleepSeconds(i == 0 ? 0 : 1); String addr = getBotAddress(botName); if (addr != null) return addr; } throw fail("Bot not found: " + quote(botName)); } static File loadBinarySnippet(String snippetID) { try { long id = parseSnippetID(snippetID); File f = DiskSnippetCache_getLibrary(id); if (f == null) { byte[] data = loadDataSnippetImpl(snippetID); DiskSnippetCache_putLibrary(id, data); f = DiskSnippetCache_getLibrary(id); } return f; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} // covers the common cases static String getBotNameFromSnippet(String programID) { try { String src = loadSnippet(programID); List tok = javaTok(src); String[] m; for (String pat : litlist( "new Android3(*", "makeAndroid(*", "makeAndroid3(*", "makeSilentAndroid(*", "addToMultiPort(*")) { m = find2(javaTok(pat), tok); if (m != null && isQuoted(m[0])) return unquote(m[0]); } return null; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static int newFindBot_botVM; static synchronized DialogIO newFindBot(String name) { if (newFindBot_botVM == 0) { print("Looking for Bot VM."); List botVMs = quickBotScan("This is a JavaX VM. Bot VM."); if (!botVMs.isEmpty()) { newFindBot_botVM = botVMs.get(0).port; print("Bot VM is at port " + newFindBot_botVM + "."); } } if (newFindBot_botVM != 0) { DialogIO io = talkTo(newFindBot_botVM); String q = format("has bot *", name); String s = io.ask(q); //print(format("Answer of Bot VM to *: *", q, s)); if (match("yes", s)) { io = talkToSubBot(name, io); call(io, "pushback", "?"); // put some hello string in (yes, this should be improved.) return io; } } return findBot(name); } static String loadTextFileFromZipFile(File inZip, String fileName) { try { ZipFile zip = new ZipFile(inZip); try { ZipEntry entry = zip.getEntry(fileName); if (entry == null) //fail("Entry " + fileName + " not found in zip file: " + inZip.getAbsolutePath()); return null; InputStream fin = zip.getInputStream(entry); ByteArrayOutputStream baos = new ByteArrayOutputStream(); copyStream(fin, baos); fin.close(); return fromUTF8(baos.toByteArray()); } finally { zip.close(); } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static DialogIO talkToSubBot(final long vport, final DialogIO io) { return talkToSubBot(String.valueOf(vport), io); } static DialogIO talkToSubBot(final String subBot, final DialogIO io) { if (subBot == null) return io; return new DialogIO() { // delegate all but sendLine boolean isStillConnected() { return io.isStillConnected(); } String readLineImpl() { return io.readLineImpl(); } boolean isLocalConnection() { return io.isLocalConnection(); } Socket getSocket() { return io.getSocket(); } void close() { io.close(); } void sendLine(String line) { io.sendLine(format3("please forward to bot *: *", subBot, line)); } }; } // Data files are immutable, use centralized cache public static File DiskSnippetCache_getLibrary(long snippetID) throws IOException { File file = new File(getGlobalCache(), "data_" + snippetID + ".jar"); return file.exists() ? file : null; } public static void DiskSnippetCache_putLibrary(long snippetID, byte[] data) throws IOException { saveBinaryFile(new File(getGlobalCache(), "data_" + snippetID).getPath() + ".jar", data); } static byte[] loadDataSnippetImpl(String snippetID) throws IOException { byte[] data; try { URL url = new URL("http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_" + parseSnippetID(snippetID) + "&contentType=application/binary"); System.err.println("Loading library: " + url); try { data = loadBinaryPage(url.openConnection()); } catch (RuntimeException e) { data = null; } if (data == null || data.length == 0) { url = new URL("http://data.tinybrain.de/blobs/" + parseSnippetID(snippetID)); System.err.println("Loading library: " + url); data = loadBinaryPage(url.openConnection()); } System.err.println("Bytes loaded: " + data.length); } catch (FileNotFoundException e) { throw new IOException("Binary snippet #" + snippetID + " not found or not public"); } return data; } // We dropped the "***" support here (use match3 for that) static String[] find2(List pat, List tok) { for (int idx = 0; idx < tok.size(); idx += 2) { String[] result = find2(pat, tok, idx); if (result != null) return result; } return null; } static String[] find2(List pat, List tok, int idx) { if (idx+pat.size() > tok.size()) return null; List result = new ArrayList(); for (int i = 1; i < pat.size(); i += 2) { String p = pat.get(i), t = tok.get(idx+i); if (eq(p, "*")) result.add(t); else if (!p.equalsIgnoreCase(t)) return null; } return toStringArray(result); } static Map findBot_cache = new TreeMap(); static int findBot_timeout = 5000; static DialogIO findBot(String searchPattern) { // first split off sub-bot suffix String subBot = null; int i = searchPattern.indexOf('/'); if (i >= 0 && (isJavaIdentifier(searchPattern.substring(0, i)) || isInteger(searchPattern.substring(0, i)))) { subBot = searchPattern.substring(i+1); searchPattern = searchPattern.substring(0, i); if (!isInteger(searchPattern)) searchPattern = "Multi-Port at " + searchPattern + "."; } // assume it's a port if it's an integer if (isInteger(searchPattern)) return talkToSubBot(subBot, talkTo(parseInt(searchPattern))); if (eq(searchPattern, "remote")) return talkToSubBot(subBot, talkTo("second.tinybrain.de", 4999)); Integer port = findBot_cache.get(searchPattern); if (port != null) try { DialogIO io = talkTo("localhost", port); io.waitForLine(/*findBot_timeout*/); // TODO: implement String line = io.readLineNoBlock(); if (indexOfIgnoreCase(line, searchPattern) == 0) { call(io, "pushback", line); // put hello string back in return talkToSubBot(subBot, io); } } catch (Exception e) { e.printStackTrace(); } List bots = quickBotScan(); // find top-level bots for (ProgramScan.Program p : bots) { if (indexOfIgnoreCase(p.helloString, searchPattern) == 0) { // strict matching - start of hello string only, but case-insensitive findBot_cache.put(searchPattern, p.port); return talkToSubBot(subBot, talkTo("localhost", p.port)); } } // find sub-bots for (ProgramScan.Program p : bots) { String botName = firstPartOfHelloString(p.helloString); boolean isVM = startsWithIgnoreCase(p.helloString, "This is a JavaX VM."); boolean shouldRecurse = startsWithIgnoreCase(botName, "Multi-Port") || isVM; if (shouldRecurse) try { Map subBots = (Map) unstructure(sendToLocalBotQuietly(p.port, "list bots")); for (Number vport : subBots.keySet()) { String name = subBots.get(vport); if (startsWithIgnoreCase(name, searchPattern)) return talkToSubBot(vport.longValue(), talkTo("localhost", p.port)); } } catch (Exception e) { e.printStackTrace(); } } return null; } static int getBotAddress_timeout = 5000; static String getBotAddress(String searchPattern) { String subBot = ""; int i = searchPattern.indexOf('/'); if (i >= 0 && (isJavaIdentifier(searchPattern.substring(0, i)) || isInteger(searchPattern.substring(0, i)))) { subBot = searchPattern.substring(i); searchPattern = searchPattern.substring(0, i); if (!isInteger(searchPattern)) searchPattern = "Multi-Port at " + searchPattern + "."; } // assume it's a port if it's an integer if (isInteger(searchPattern)) return searchPattern + subBot; Integer port = findBot_cache.get(searchPattern); if (port != null) try { DialogIO io = talkTo("localhost", port); try { io.waitForLine(/*getBotAddress_timeout*/); // TODO: implement String line = io.readLineNoBlock(); if (startsWithIgnoreCase(line, searchPattern)) return port + subBot; } finally { io.close(); } } catch (Exception e) { e.printStackTrace(); } List bots = quickBotScan(); // find top-level bots for (ProgramScan.Program p : bots) { if (indexOfIgnoreCase(p.helloString, searchPattern) == 0) { // strict matching - start of hello string only, but case-insensitive return p.port + subBot; } } // find sub-bots for (ProgramScan.Program p : bots) { String botName = firstPartOfHelloString(p.helloString); boolean isVM = startsWithIgnoreCase(p.helloString, "This is a JavaX VM."); boolean shouldRecurse = startsWithIgnoreCase(botName, "Multi-Port") || isVM; if (shouldRecurse) try { Map subBots = (Map) unstructure(sendToLocalBot(p.port, "list bots")); for (Number vport : subBots.keySet()) { String name = subBots.get(vport); if (startsWithIgnoreCase(name, searchPattern)) return p.port + "/" + vport; } } catch (Exception e) { e.printStackTrace(); } } return null; } static List quickBotScan() { return ProgramScan.quickBotScan(); } static List quickBotScan(int[] preferredPorts) { return ProgramScan.quickBotScan(preferredPorts); } static List quickBotScan(String searchPattern) { List l = new ArrayList(); for (ProgramScan.Program p : ProgramScan.quickBotScan()) if (indexOfIgnoreCase(p.helloString, searchPattern) == 0) l.add(p); return l; } static class Injection { int vmPort; String injectionID; } static Injection injectTo(int vmPort, String progID) { return injectTo(String.valueOf(vmPort), progID); } static Injection injectTo(String vmName, String progID) { String line = format3("Please inject program *.", progID); DialogIO injectionPoint = findBot(isInteger(vmName) ? vmName : "This is a JavaX VM. " + vmName); if (injectionPoint == null) return null; try { injectionPoint.readLine(); String answer = injectionPoint.askLoudly(line); Matches m = new Matches(); if (match3("OK. Injection ID: *", answer, m)) { Injection i = new Injection(); i.vmPort = injectionPoint.getSocket().getPort(); i.injectionID = m.unq(0); return i; } throw fail(answer); } finally { injectionPoint.close(); } } static boolean preferCached = false; static boolean loadSnippet_debug = false; public static String loadSnippet(String snippetID) { try { return loadSnippet(parseSnippetID(snippetID), preferCached); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadSnippet(String snippetID, boolean preferCached) throws IOException { return loadSnippet(parseSnippetID(snippetID), preferCached); } public static String loadSnippet(long snippetID, boolean preferCached) throws IOException { String text; // boss bot disabled for now for shorter transpilations /*text = getSnippetFromBossBot(snippetID); if (text != null) return text;*/ initSnippetCache(); text = DiskSnippetCache_get(snippetID); if (preferCached && text != null) return text; try { if (loadSnippet_debug && text != null) System.err.println("md5: " + md5(text)); URL url = new URL("http://tinybrain.de:8080/getraw.php?id=" + snippetID + "&utf8=1" + standardCredentials()); text = loadPage(url); } catch (RuntimeException e) { e.printStackTrace(); throw new IOException("Snippet #" + snippetID + " not found or not public"); } try { initSnippetCache(); DiskSnippetCache_put(snippetID, text); } catch (IOException e) { System.err.println("Minor warning: Couldn't save snippet to cache (" + DiskSnippetCache_getDir() + ")"); } return text; } static File DiskSnippetCache_dir; public static void initDiskSnippetCache(File dir) { DiskSnippetCache_dir = dir; dir.mkdirs(); } public static synchronized String DiskSnippetCache_get(long snippetID) throws IOException { return loadTextFile(DiskSnippetCache_getFile(snippetID).getPath(), null); } private static File DiskSnippetCache_getFile(long snippetID) { return new File(DiskSnippetCache_dir, "" + snippetID); } public static synchronized void DiskSnippetCache_put(long snippetID, String snippet) throws IOException { saveTextFile(DiskSnippetCache_getFile(snippetID).getPath(), snippet); } public static File DiskSnippetCache_getDir() { return DiskSnippetCache_dir; } public static void initSnippetCache() { if (DiskSnippetCache_dir == null) initDiskSnippetCache(new File(System.getProperty("user.home"), ".tinybrain/snippet-cache")); } static void startBotVM() { startBotSeparateVM("This is a JavaX VM. Bot VM.", "#1001710"); } static String fromUTF8(byte[] bytes) { return fromUtf8(bytes); } static Object unstructure(String text) { return unstructure(text, false); } static Object unstructure(String text, final boolean allDynamic) { return unstructure(text, allDynamic, null); } static int structure_internStringsLongerThan = 50; // TODO: backrefs for hashmap{} etc // classFinder: name -> class static Object unstructure(String text, final boolean allDynamic, final Object classFinder) { if (text == null) return null; final List tok = javaTok(text); final boolean debug = unstructure_debug; class X { int i = 1; HashMap refs = new HashMap(); HashSet concepts = new HashSet(); Object parse() { String t = tok.get(i); int refID = 0; if (t.startsWith("m") && isInteger(t.substring(1))) { refID = parseInt(t.substring(1)); i += 2; t = tok.get(i); } if (debug) print("parse: " + quote(t)); if (t.startsWith("'")) { char c = unquoteCharacter(tok.get(i)); i += 2; return c; } if (t.equals("bigint")) return parseBigInt(); if (t.equals("d")) return parseDouble(); if (t.equals("fl")) return parseFloat(); if (t.equals("false") || t.equals("f")) { i += 2; return false; } if (t.equals("true") || t.equals("t")) { i += 2; return true; } if (t.equals("-")) { t = tok.get(i+2); i += 4; return isLongConstant(t) ? (Object) (-parseLong(t)) : (Object) (-parseInt(t)); } if (isInteger(t) || isLongConstant(t)) { i += 2; if (debug) print("isLongConstant " + quote(t) + " => " + isLongConstant(t)); if (isLongConstant(t)) return parseLong(t); long l = parseLong(t); boolean isInt = l == (int) l; if (debug) print("l=" + l + ", isInt: " + isInt); return isInt ? (Object) new Integer((int) l) : (Object) new Long(l); } if (t.equals("File")) { File f = new File(unquote(tok.get(i+2))); i += 4; return f; } if (t.startsWith("r") && isInteger(t.substring(1))) { i += 2; int ref = Integer.parseInt(t.substring(1)); Object o = refs.get(ref); if (o == null) print("Warning: unsatisfied back reference " + ref); return o; } Object o = parse_inner(refID); if (refID != 0) refs.put(refID, o); return o; } // everything that can be backreferenced Object parse_inner(int refID) { String t = tok.get(i); if (debug) print("parse_inner: " + quote(t)); if (t.startsWith("\"")) { String s = internIfLongerThan(unquote(tok.get(i)), structure_internStringsLongerThan); i += 2; return s; } if (t.equals("hashset")) return parseHashSet(); if (t.equals("treeset")) return parseTreeSet(); if (eqOneOf(t, "hashmap", "hm")) return parseHashMap(); if (t.equals("{")) return parseMap(); if (t.equals("[")) return parseList(); if (t.equals("bitset")) return parseBitSet(); if (t.equals("array") || t.equals("intarray")) return parseArray(); if (t.equals("ba")) { String hex = unquote(tok.get(i+2)); i += 4; return hexToBytes(hex); } if (t.equals("boolarray")) { int n = parseInt(tok.get(i+2)); String hex = unquote(tok.get(i+4)); i += 6; return boolArrayFromBytes(hexToBytes(hex), n); } if (t.equals("class")) return parseClass(); if (t.equals("l")) return parseLisp(); if (t.equals("null")) { i += 2; return null; } /* in dev. if (!allDynamic && t.equals("run")) { S snippetID = unquote(t.get(i+2)); i += 4; run( } */ boolean concept = eq(t, "c"); if (concept) { consume("c"); t = tok.get(i); assertTrue(isJavaIdentifier(t)); concepts.add(t); } // any other class name if (isJavaIdentifier(t)) { // First, find class Class c; if (allDynamic) c = null; else if (classFinder != null) c = (Class) callF(classFinder, t); else c = findClass(t); // Second, check if it has an outer reference i += 2; boolean hasOuter = eq(get(tok, i), "(") && eq(get(tok, i+2), "this$1"); DynamicObject dO = null; Object o = null; if (c != null) o = hasOuter ? nuStubInnerObject(c) : nuObject(c); else { if (concepts.contains(t) && (c = findClass("Concept")) != null) dO = (DynamicObject) nuObject(c); else dO = new DynamicObject(); dO.className = t; } // Now, save in references list. if (refID != 0) refs.put(refID, o != null ? o : dO); // NOW parse the fields! Map fields = new TreeMap(); if (i < tok.size() && tok.get(i).equals("(")) { consume("("); while (!tok.get(i).equals(")")) { String key = unquote(tok.get(i)); i += 2; consume("="); Object value = parse(); fields.put(key, value); if (tok.get(i).equals(",")) i += 2; } consume(")"); } if (o != null) setOptAll(o, fields); else dO.fieldValues.putAll(fields); if (o != null) pcallOpt(o, "_doneLoading"); return o != null ? o : dO; } throw new RuntimeException("Unknown token " + (i+1) + ": " + t); } Object parseSet(Set set) { set.addAll((List) parseList()); return set; } Object parseLisp() { consume("l"); consume("("); List list = new ArrayList(); while (!tok.get(i).equals(")")) { list.add(parse()); if (tok.get(i).equals(",")) i += 2; } consume(")"); return newObject("main$Lisp", (String) list.get(0), subList(list, 1)); } Object parseBitSet() { consume("bitset"); consume("{"); BitSet bs = new BitSet(); while (!tok.get(i).equals("}")) { bs.set((Integer) parse()); if (tok.get(i).equals(",")) i += 2; } consume("}"); return bs; } Object parseList() { consume("["); List list = new ArrayList(); while (!tok.get(i).equals("]")) { Object o = parse(); if (debug) print("List element type: " + getClassName(o)); list.add(o); if (tok.get(i).equals(",")) i += 2; } consume("]"); return list; } Object parseArray() { String type = tok.get(i); i += 2; consume("{"); List list = new ArrayList(); while (!tok.get(i).equals("}")) { list.add(parse()); if (tok.get(i).equals(",")) i += 2; } consume("}"); if (type.equals("intarray")) return toIntArray(list); return list.toArray(); } Object parseClass() { consume("class"); consume("("); String name = tok.get(i); i += 2; consume(")"); Class c = allDynamic ? null : findClass(name); if (c != null) return c; DynamicObject dO = new DynamicObject(); dO.className = "java.lang.Class"; dO.fieldValues.put("name", name); return dO; } Object parseBigInt() { consume("bigint"); consume("("); String val = tok.get(i); i += 2; if (eq(val, "-")) { val = "-" + tok.get(i); i += 2; } consume(")"); return new BigInteger(val); } Object parseDouble() { consume("d"); consume("("); String val = unquote(tok.get(i)); i += 2; consume(")"); return Double.parseDouble(val); } Object parseFloat() { consume("fl"); String val; if (eq(tok.get(i), "(")) { consume("("); val = unquote(tok.get(i)); i += 2; consume(")"); } else { val = unquote(tok.get(i)); i += 2; } return Float.parseFloat(val); } Object parseHashMap() { i += 2; return parseMap(new HashMap()); } Object parseHashSet() { consume("hashset"); return parseSet(new HashSet()); } Object parseTreeSet() { consume("treeset"); return parseSet(new TreeSet()); } Object parseMap() { return parseMap(new TreeMap()); } Object parseMap(Map map) { consume("{"); while (!tok.get(i).equals("}")) { Object key = parse(); consume("="); Object value = parse(); map.put(key, value); if (tok.get(i).equals(",")) i += 2; } consume("}"); return map; } void consume(String s) { if (!tok.get(i).equals(s)) { String prevToken = i-2 >= 0 ? tok.get(i-2) : ""; String nextTokens = join(tok.subList(i, Math.min(i+4, tok.size()))); throw fail(quote(s) + " expected: " + prevToken + " " + nextTokens + " (" + i + "/" + tok.size() + ")"); } i += 2; } } return new X().parse(); } static boolean unstructure_debug; static String[] toStringArray(List list) { return list.toArray(new String[list.size()]); } static String[] toStringArray(Object o) { if (o instanceof String[]) return (String[]) o; else if (o instanceof List) return toStringArray((List) o); else throw fail("Not a list or array: " + structure(o)); } static File getGlobalCache() { File file = new File(userHome(), ".tinybrain/snippet-cache"); file.mkdirs(); return file; } static String firstPartOfHelloString(String s) { int i = s.lastIndexOf('/'); return i < 0 ? s : rtrim(s.substring(0, i)); } static String sendToLocalBotQuietly(String bot, String text, Object... args) { text = format3(text, args); DialogIO channel = findBot(bot); if (channel == null) throw fail(quote(bot) + " not found"); try { channel.readLine(); channel.sendLine(text); String s = channel.readLine(); return s; } catch (Throwable e) { e.printStackTrace(); return null; } finally { channel.close(); } } static String sendToLocalBotQuietly(int port, String text, Object... args) { text = format3(text, args); DialogIO channel = talkTo(port); try { channel.readLine(); channel.sendLine(text); String s = channel.readLine(); return s; } catch (Throwable e) { e.printStackTrace(); return null; } finally { if (channel != null) channel.close(); } } /** writes safely (to temp file, then rename) */ public static void saveBinaryFile(String fileName, byte[] contents) throws IOException { File file = new File(fileName); File parentFile = file.getParentFile(); if (parentFile != null) parentFile.mkdirs(); String tempFileName = fileName + "_temp"; FileOutputStream fileOutputStream = new FileOutputStream(tempFileName); fileOutputStream.write(contents); fileOutputStream.close(); if (file.exists() && !file.delete()) throw new IOException("Can't delete " + fileName); if (!new File(tempFileName).renameTo(file)) throw new IOException("Can't rename " + tempFileName + " to " + fileName); } static void saveBinaryFile(File fileName, byte[] contents) { try { saveBinaryFile(fileName.getPath(), contents); } catch (IOException e) { throw new RuntimeException(e); } } static String sendToLocalBot(String bot, String text, Object... args) { text = format3(text, args); DialogIO channel = findBot(bot); if (channel == null) throw fail(quote(bot) + " not found"); try { channel.readLine(); print(bot + "> " + shorten(text, 80)); channel.sendLine(text); String s = channel.readLine(); print(bot + "< " + shorten(s, 80)); return s; } catch (Throwable e) { e.printStackTrace(); return null; } finally { channel.close(); } } static String sendToLocalBot(int port, String text, Object... args) { text = format3(text, args); DialogIO channel = talkTo(port); try { channel.readLine(); print(port + "> " + shorten(text, 80)); channel.sendLine(text); String s = channel.readLine(); print(port + "< " + shorten(s, 80)); return s; } catch (Throwable e) { e.printStackTrace(); return null; } finally { if (channel != null) channel.close(); } } static String md5(String text) { try { if (text == null) return "-"; return bytesToHex(md5_impl(text.getBytes("UTF-8"))); // maybe different than the way PHP does it... } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static String md5(byte[] data) { if (data == null) return "-"; return bytesToHex(md5_impl(data)); } static byte[] md5_impl(byte[] data) { try { return MessageDigest.getInstance("MD5").digest(data); } catch (Exception e) { throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e); } } static String md5(File file) { try { return md5(loadBinaryFile(file)); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static byte[] loadBinaryPage(String url) { try { return loadBinaryPage(new URL(url).openConnection()); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} public static byte[] loadBinaryPage(URLConnection con) { try { //setHeaders(con); ByteArrayOutputStream buf = new ByteArrayOutputStream(); InputStream inputStream = con.getInputStream(); int n = 0; while (true) { int ch = inputStream.read(); if (ch < 0) break; buf.write(ch); if (++n % 100000 == 0) System.err.println(" " + n + " bytes loaded."); } inputStream.close(); return buf.toByteArray(); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static String fromUtf8(byte[] bytes) { try { return new String(bytes, "UTF-8"); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static void startBotSeparateVM(String botName, String scriptID) { DialogIO bot = findBot(botName); if (bot != null) bot.close(); else { print("Bot " + quote(botName) + " not found. Starting separately: " + scriptID); nohupJavax(scriptID); waitForBotStartUp(botName); } } static byte[] hexToBytes(String s) { int n = l(s) / 2; byte[] bytes = new byte[n]; for (int i = 0; i < n; i++) bytes[i] = (byte) parseHexByte(substring(s, i*2, i*2+2)); return bytes; } static void pcallOpt(Object o, String method, Object... args) { try { /* pcall 1*/ callOpt(o, method, args); /* pcall 2 */ } catch (Throwable __e) { printStackTrace(__e); } } static void setOptAll(Object o, Map fields) { if (fields == null) return; for (String field : keys(fields)) setOpt(o, field, fields.get(field)); } static char unquoteCharacter(String s) { assertTrue(s.startsWith("'") && s.length() > 1); return unquote("\"" + s.substring(1, s.endsWith("'") ? s.length()-1 : s.length()) + "\"").charAt(0); } public static byte[] loadBinaryFile(String fileName) { try { if (!new File(fileName).exists()) return null; FileInputStream in = new FileInputStream(fileName); byte buf[] = new byte[1024]; ByteArrayOutputStream out = new ByteArrayOutputStream(); int l; while (true) { l = in.read(buf); if (l <= 0) break; out.write(buf, 0, l); } in.close(); return out.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); } } public static byte[] loadBinaryFile(File file) { return loadBinaryFile(file.getPath()); } static A nuStubInnerObject(Class c) { try { Class outerType = getOuterClass(c); Constructor m = c.getDeclaredConstructor(outerType); m.setAccessible(true); return (A) m.newInstance(new Object[] {null}); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static boolean[] boolArrayFromBytes(byte[] a, int n) { boolean[] b = new boolean[n]; int m = min(n, l(a)*8); for (int i = 0; i < m; i++) b[i] = (a[i/8] & 1 << (i & 7)) != 0; return b; } static boolean isLongConstant(String s) { if (!s.endsWith("L")) return false; s = s.substring(0, l(s)-1); return isInteger(s); } static void nohupJavax(String javaxargs) { nohupJavax(javaxargs, ""); } // vm args are ignored if pre-spun VM found... static void nohupJavax(String javaxargs, String vmArgs) { javaxargs = javaxargs.trim(); if (javaxargs.startsWith("#")) javaxargs = javaxargs.substring(1); String snippetID = javaTok(javaxargs).get(1); int idx = javaxargs.indexOf(' '); String args = idx < 0 ? "" : javaxargs.substring(idx+1).trim(); String line; if (args.length() != 0) line = format3("please start program * with arguments *", snippetID, args); else line = format3("please start program *", snippetID); String answer = sendToLocalBotOpt("A pre-spun VM.", line); if (match3("ok", answer)) { print("OK, used pre-spun VM."); } else { if (answer != null) print("> " + answer); print("Using standard nohup."); classicNohupJavax(javaxargs, vmArgs); } } static String internIfLongerThan(String s, int l) { return s == null ? null : l(s) >= l ? s.intern() : s; } static int[] toIntArray(List l) { int[] a = new int[l(l)]; for (int i = 0; i < a.length; i++) a[i] = l.get(i); return a; } static boolean eqOneOf(Object o, Object... l) { for (Object x : l) if (eq(o, x)) return true; return false; } static double parseDouble(String s) { return Double.parseDouble(s); } public static String rtrim(String s) { int i = s.length(); while (i > 0 && " \t\r\n".indexOf(s.charAt(i-1)) >= 0) --i; return i < s.length() ? s.substring(0, i) : s; } static void classicNohupJavax(String javaxargs) { classicNohupJavax(javaxargs, ""); } static void classicNohupJavax(String javaxargs, String vmArgs) { try { int x = latestInstalledJavaX(); File xfile = new File(userHome(), ".javax/x" + Math.max(x, 30) + ".jar"); if (!xfile.isFile()) { String url = "http://tinybrain.de/x30.jar"; byte[] data = loadBinaryPage(url); if (data.length < 1000000) throw fail("Could not load " + url); saveBinaryFile(xfile.getPath(), data); } String jarPath = xfile.getPath(); if (javaxargs.startsWith("#")) javaxargs = javaxargs.substring(1); nohup("java " + vmArgs + " -jar " + (isWindows() ? winQuote(jarPath) : bashQuote(jarPath)) + " " + javaxargs); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Class getOuterClass(Class c) { try { String s = c.getName(); int i = s.lastIndexOf('$'); return Class.forName(substring(s, 0, i)); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Set keys(Map map) { return map.keySet(); } static Set keys(Object map) { return keys((Map) map); } static int parseHexByte(String s) { return Integer.parseInt(s, 16); } static String sendToLocalBotOpt(String bot, String text, Object... args) { if (bot == null) return null; text = format(text, args); DialogIO channel = findBot(bot); if (channel == null) { print(quote(bot) + " not found, skipping send: " + quote(text)); return null; } try { channel.readLine(); print(bot + "> " + text); channel.sendLine(text); String s = channel.readLine(); print(bot + "< " + s); return s; } catch (Throwable e) { e.printStackTrace(); return null; } finally { channel.close(); } } /** possibly improvable */ public static String winQuote(String text) { if (text == null) return null; return "\"" + text .replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") + "\""; } static int latestInstalledJavaX() { File[] files = new File(userHome(), ".javax").listFiles(); int v = 0; if (files != null) for (File f : files) { Matcher m = Pattern.compile("x(\\d\\d\\d?)\\.jar").matcher(f.getName()); if (m.matches()) v = Math.max(v, Integer.parseInt(m.group(1))); } return v; } public static File nohup(String cmd) { try { File outFile = File.createTempFile("nohup_" + nohup_sanitize(cmd), ".out"); nohup(cmd, outFile, false); return outFile; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static String nohup_sanitize(String s) { return s.replaceAll("[^a-zA-Z0-9\\-_]", ""); } /** outFile takes stdout and stderr. */ public static void nohup(String cmd, File outFile, boolean append) { try { String command = nohup_makeNohupCommand(cmd, outFile, append); File scriptFile = File.createTempFile("_realnohup", isWindows() ? ".bat" : ""); System.out.println("[Nohup] " + command); try { //System.out.println("[RealNohup] Script file: " + scriptFile.getPath()); saveTextFile(scriptFile.getPath(), command); String[] command2; if (isWindows()) command2 = new String[] {"cmd", "/c", "start", "/b", scriptFile.getPath() }; else command2 = new String[] {"/bin/bash", scriptFile.getPath() }; Process process = Runtime.getRuntime().exec(command2); try { process.waitFor(); } catch (InterruptedException e) { throw new RuntimeException(e); } int value = process.exitValue(); //System.out.println("exit value: " + value); } finally { if (!isWindows()) scriptFile.delete(); } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} public static String nohup_makeNohupCommand(String cmd, File outFile, boolean append) { mkdirsForFile(outFile); String command; if (isWindows()) command = cmd + (append ? " >>" : " >") + winQuote(outFile.getPath()) + " 2>&1"; else command = "nohup " + cmd + (append ? " >>" : " >") + bashQuote(outFile.getPath()) + " 2>&1 &"; return command; } static boolean DynamicObject_loading; static class DynamicObject { String className; // just the name, without the "main$" Map fieldValues = new TreeMap(); DynamicObject() {} // className = just the name, without the "main$" DynamicObject(String className) { this.className = className;} } static class ProgramScan { static int threads = isWindows() ? 500 : 10; static int timeout = 5000; // hmm... static String ip = "127.0.0.1"; // This range is not used anymore anyway static int quickScanFrom = 10000, quickScanTo = 10999; static int maxNumberOfVMs_android = 4; // Android will always only have one if we don't screw up static int maxNumberOfVMs_nonAndroid = 50; // 100; static int maxNumberOfVMs; static boolean verbose; static class Program { int port; String helloString; Program(int port, String helloString) { this.helloString = helloString; this.port = port;} } static List scan() { try { return scan(1, 65535); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static List scan(int fromPort, int toPort) { return scan(fromPort, toPort, new int[0]); } static List scan(int fromPort, int toPort, int[] preferredPorts) { try { Set preferredPortsSet = new HashSet(asList(preferredPorts)); int scanSize = toPort-fromPort+1; String name = toPort < 10000 ? "bot" : "program"; int threads = isWindows() ? min(500, scanSize) : min(scanSize, 10); final ExecutorService es = Executors.newFixedThreadPool(threads); if (verbose) print(firstToUpper(name) + "-scanning " + ip + " with timeout " + timeout + " ms in " + threads + " threads."); startTiming(); List> futures = new ArrayList>(); List ports = new ArrayList(); for (int port : preferredPorts) { futures.add(checkPort(es, ip, port, timeout)); ports.add(port); } for (int port = fromPort; port <= toPort; port++) if (!preferredPortsSet.contains(port) && !forbiddenPort(port)) { futures.add(checkPort(es, ip, port, timeout)); ports.add(port); } es.shutdown(); List programs = new ArrayList(); long time = now(); int i = 0; for (final Future f : futures) { if (verbose) print("Waiting for port " + get(ports, i++) + " at time " + (now()-time)); Program p = f.get(); if (p != null) programs.add(p); } stopTiming("Port Scan " + scanSize + ": ", 250); if (verbose) print("Found " + programs.size() + " " + name + "(s) on " + ip); return programs; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Future checkPort(final ExecutorService es, final String ip, final int port, final int timeout) { return es.submit(new Callable() { @Override public Program call() { try { Socket socket = new Socket(); socket.setSoTimeout(timeout); socket.connect(new InetSocketAddress(ip, port), timeout); //if (verbose) print("Connected to " + ip + ":" + port); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8")); String hello = or(in.readLine(), "?"); socket.close(); return new Program(port, hello); } catch (Exception ex) { return null; } } }); } static List quickScan() { return scan(quickScanFrom, quickScanTo); } static List quickBotScan() { return quickBotScan(new int[0]); } static List quickBotScan(int[] preferredPorts) { if (maxNumberOfVMs == 0) maxNumberOfVMs = isAndroid() ? maxNumberOfVMs_android : maxNumberOfVMs_nonAndroid; return scan(4999, 5000+maxNumberOfVMs-1, preferredPorts); } } // parser explainer static class Explain { static List primitiveClasses = litlist("quoted", "identifier", "any", "int"); Object parseResult; List tok; List e; List subs = new ArrayList(); static boolean debug; Explain(Object parseResult, List e) { this.e = e; this.parseResult = parseResult; tok = (List) get(parseResult, "tok"); makeSubExplains(); } void makeSubExplains() { for (int i = 4; i < l(e); i++) { List sub = (List) ( get(e, i)); int t1 = (int) get(sub, 0); int t2 = (int) get(sub, 1); String className = getString(sub, 2); List subE = sub; if (!primitiveClasses.contains(className)) subE = (List) call(parseResult, "explainFull", t1, t2, className); if (debug) printF("Explaining * * * => *", t1, t2, className, subE); if (subE == null) subs.add(null); else subs.add(new Explain(parseResult, subE)); } } String className() { return getString(e, 2); } int fromToken() { return (int) get(e, 0); } int toToken() { return (int) get(e, 1); } // return tokens, padded with empty non-code tokens first and last // to make actual CNC List tok() { return concatLists( litlist(""), subList(tok, fromToken(), toToken()-1), litlist("")); } String string() { return join(subList(tok, fromToken(), toToken()-1)); } boolean containsToken(String t) { return main.containsToken(tok(), t); } void findAll(String className, List out) { if (eq(className, className())) out.add(string()); else // << this is new - don't recurse for (Explain e : subs) if (e != null) e.findAll(className, out); } List findAll(String className) { List l = new ArrayList(); findAll(className, l); return l; } // short for findFirst Explain find(String className) { return findFirst(className); } Explain findFirst(String className) { if (eq(className, className())) return this; return findFirstSub(className); } // find class, but exclude myself Explain findFirstSub(String className) { for (Explain e : subs) if (e != null) { Explain result = e.findFirst(className); if (result != null) return result; } return null; } boolean has(String className) { return findFirst(className) != null; } boolean hasSub(String className) { return findFirstSub(className) != null; } void findAll2(String className, List out) { if (eq(className, className())) out.add(this); for (Explain e : subs) if (e != null) e.findAll2(className, out); } List findAll2(String className) { List l = new ArrayList(); findAll2(className, l); return l; } // short for pruneSubs Explain prune(String className) { return pruneSubs(className); } // returns self after pruning Explain pruneSubs(String className) { for (int i = 0; i < l(subs); ) { Explain e = sub(i); if (e == null) ++i; else if (eq(e.className(), className)) subs.remove(i); else { e.pruneSubs(className); ++i; } } return this; } Explain sub(int i) { return get(subs, i); } } // returns null if className is not a parsed main class static Explain explainParse(Object parseResult, String className) { List exp = (List) ( call(parseResult, "explainFull", className)); if (exp == null) return null; return new Explain(parseResult, exp); } static String findBestMatch(String query, List cmds) { query = query.toLowerCase(); Map map = new TreeMap(); for (String cmd : cmds) map.put(cmd, l(commonPrefix(cmd.toLowerCase(), query))); return highest(map); } static List findAll(JFrame f, Class theClass) { List l = new ArrayList(); scanForComponents(f, theClass, l); return l; } static A highest(Map map) { return keyWithBiggestValue(map); } static long stopTiming_defaultMin = 10; static long startTiming_startTime; static void startTiming() { startTiming_startTime = now(); } static void stopTiming() { stopTiming(null); } static void stopTiming(String text) { stopTiming(text, stopTiming_defaultMin); } static void stopTiming(String text, long minToPrint) { long time = now()-startTiming_startTime; if (time >= minToPrint) { text = or2(text, "Time: "); print(text + time + " ms"); } } static boolean containsToken(List tok, String token) { return tok.contains(token); } static boolean forbiddenPort(int port) { return port == 5037; // adb } static void printF(String s, Object... args) { printFormat(s, args); } static String string(Object o) { return String.valueOf(o); } static String className(Object o) { return getClassName(o); } public static String commonPrefix(String a, String b) { int i = 0; while (i < a.length() && i < b.length() && a.charAt(i) == b.charAt(i)) ++i; return a.substring(0, i); } static String find(String pattern, String text) { Matcher matcher = Pattern.compile(pattern).matcher(text); if (matcher.find()) return matcher.group(1); return null; } static A find(Collection c, Object... data) { for (A x : c) if (checkFields(x, data)) return x; return null; } static String firstToUpper(String s) { if (s.length() == 0) return s; return Character.toUpperCase(s.charAt(0)) + s.substring(1); } static A keyWithBiggestValue(Map map) { A best = null; Number bestScore = null; for (A key : keys(map)) { Number score = map.get(key); if (best == null || cmp(score, bestScore) > 0) { best = key; bestScore = score; } } return best; } static void printFormat(String s, Object... args) { print(format(s, args)); } static String or2(String a, String b) { return nempty(a) ? a : b; } static boolean checkFields(Object x, Object... data) { for (int i = 0; i < l(data); i += 2) if (neq(getOpt(x, (String) data[i]), data[i+1])) return false; return true; } static void scanForComponents(Component c, Class theClass, List l) { if (theClass.isInstance(c)) l.add((A) c); if (c instanceof Container) for (Component comp : ((Container) c).getComponents()) scanForComponents(comp, theClass, l); } static int cmp(Number a, Number b) { return a == null ? b == null ? 0 : -1 : cmp(a.doubleValue(), b.doubleValue()); } static int cmp(double a, double b) { return a < b ? -1 : a == b ? 0 : 1; } static int cmp(String a, String b) { return a == null ? b == null ? 0 : -1 : a.compareTo(b); } static int cmp(Object a, Object b) { if (a == null) return b == null ? 0 : -1; return ((Comparable) a).compareTo(b); } }