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 { public static void main(String[] args) throws Exception { Prolog p = new Prolog(); int n = p.mark(); p.addStatement(" \"Piko\" schreibt man \"Pico\" "); p.addStatement(" \"Pico\" schreibt man \"Pico\" "); String x = p.solveAsText(print(" \"Piko\" schreibt man $x "), "x"); assertEqualsVerbose(quote("Pico"), x); print("ok"); } 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 void print() { print(""); } // slightly overblown signature to return original object... static A print(A o) { String s = String.valueOf(o) + "\n"; 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_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 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 void assertEqualsVerbose(Object x, Object y) { assertEqualsVerbose(null, x, y); } static void assertEqualsVerbose(String msg, Object x, Object y) { if (!(x == null ? y == null : x.equals(y))) fail((msg != null ? msg + ": " : "") + structure(x) + " != " + structure(y)); else print("assertEqualsVerbose OK: " + structure(x)); } 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)); } static RuntimeException fail(String msg, Object... args) { throw new RuntimeException(format(msg, args)); } // "seen" is now default static String structure(Object o) { return structure(o, 0, new IdentityHashMap()); } static String structure_seen(Object o) { return structure(o, 0, new IdentityHashMap()); } // leave to false, unless unstructure() breaks static boolean structure_allowShortening = false; static String structure(Object o, int stringSizeLimit, IdentityHashMap seen) { if (o == null) return "null"; // these are never back-referenced (for readability) if (o instanceof String) return quote(stringSizeLimit != 0 ? shorten((String) o, stringSizeLimit) : (String) o); if (o instanceof BigInteger) return "bigint(" + o + ")"; if (o instanceof Double) return "d(" + quote(str(o)) + ")"; if (o instanceof Long) return o + "L"; if (seen != null) { Integer ref = seen.get(o); if (ref != null) return "r" + ref; seen.put(o, seen.size()+1); } String name = o.getClass().getName(); StringBuilder buf = new StringBuilder(); if (o instanceof HashSet) return "hashset" + structure(new ArrayList((Set) o), stringSizeLimit, seen); if (o instanceof TreeSet) return "treeset" + structure(new ArrayList((Set) o), stringSizeLimit, seen); if (o instanceof Collection) { for (Object x : (Collection) o) { if (buf.length() != 0) buf.append(", "); buf.append(structure(x, stringSizeLimit, seen)); } return "[" + buf + "]"; } if (o instanceof Map) { for (Object e : ((Map) o).entrySet()) { if (buf.length() != 0) buf.append(", "); buf.append(structure(((Map.Entry) e).getKey(), stringSizeLimit, seen)); buf.append("="); buf.append(structure(((Map.Entry) e).getValue(), stringSizeLimit, seen)); } return (o instanceof HashMap ? "hashmap" : "") + "{" + buf + "}"; } if (o.getClass().isArray()) { int n = Array.getLength(o); for (int i = 0; i < n; i++) { if (buf.length() != 0) buf.append(", "); buf.append(structure(Array.get(o, i), stringSizeLimit, seen)); } return "array{" + buf + "}"; } if (o instanceof Class) return "class(" + quote(((Class) o).getName()) + ")"; if (o instanceof Throwable) return "exception(" + quote(((Throwable) o).getMessage()) + ")"; // Need more cases? This should cover all library classes... if (name.startsWith("java.") || name.startsWith("javax.")) return String.valueOf(o); String shortName = o.getClass().getName().replaceAll("^main\\$", ""); if (shortName.equals("Lisp")) { buf.append("l(" + structure(getOpt(o, "head"), stringSizeLimit, seen)); 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(arg, stringSizeLimit, seen)); } buf.append(")"); return str(buf); } int numFields = 0; String fieldName = ""; if (shortName.equals("DynamicObject")) { shortName = (String) get(o, "className"); Map fieldValues = (Map) get(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(value, stringSizeLimit, seen)); } ++numFields; } } else { // regular class // TODO: go to superclasses too Field[] fields = o.getClass().getDeclaredFields(); for (Field field : fields) { if ((field.getModifiers() & Modifier.STATIC) != 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(value, stringSizeLimit, seen)); } ++numFields; } } 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 s; } static String unnull(String s) { return s == null ? "" : s; } static List unnull(List l) { return l == null ? emptyList() : l; } static String format(String pat, Object... args) { return format3(pat, args); } static boolean nempty(Collection c) { return !isEmpty(c); } static boolean nempty(String s) { return !isEmpty(s); } static int l(Object[] array) { return array == null ? 0 : array.length; } static int l(byte[] array) { return array == null ? 0 : array.length; } static int l(int[] array) { return array == null ? 0 : array.length; } static int l(char[] array) { return array == null ? 0 : array.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(String s) { return s == null ? 0 : s.length(); } 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 String str(Object o) { return String.valueOf(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 boolean isTrue(Object o) { return booleanValue(o); } 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 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 call(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 { 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; } // 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; } // get purpose 2: access a field by reflection or a map static Object get(Object o, String field) { if (o instanceof Class) return get((Class) o, field); if (o instanceof Map) return ((Map) o).get(field); if (o.getClass().getName().equals("main$DynamicObject")) return call(get_raw(o, "fieldValues"), "get", field); return get_raw(o, field); } 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()); } // 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 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)); } static boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } static boolean isEmpty(String s) { return s == null || s.length() == 0; } static List emptyList() { return Collections.emptyList(); } static Object getBot(String botID) { return call(getMainBot(), "getBot", botID); } static boolean booleanValue(Object o) { return eq(true, o); } static boolean isIdentifier(String s) { return isJavaIdentifier(s); } static Object mainBot; static Object getMainBot() { return mainBot; } 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 boolean isNonNegativeInteger(String s) { return s != null && Pattern.matches("\\d+", s); } // 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 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 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) fail("thiz=null"); if (t == null) 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()) fail("done!"); // safety if (now() >= startTime+timeLimit*1000) 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) 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) 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 // 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) fail("variable * not set", var); if (!val.isEmpty()) fail("variable * not a string: *", var, val); return unquote(val.raw()); } String strOuter(String var) { Lisp val = getOuter(var); if (val == null) fail("variable * not set", var); if (!val.isEmpty()) fail("variable * not a string: *", var, 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)) fail(); } else if (!eq(lisp, outer)) { //print("neq " + lisp + " != " + outer); 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) 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 ()) 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 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(); } } public static String unquote(String s) { 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 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 { return getClass(realm).getClassLoader().loadClass(name); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} 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)) fail("multi-matching not implemented"); matches.put(key, val); return true; } public static String joinLines(List lines) { return fromLines(lines); } 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(Set s) { return s == null ? new ArrayList() : new ArrayList(s); } 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 String getStackTrace(Throwable throwable) { StringWriter writer = new StringWriter(); throwable.printStackTrace(new PrintWriter(writer)); return writer.toString(); } // 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 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, warnings1); Lisp parse2 = nlParse(subList(tok, 1, l(tok)-1), true, 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 void printStackTrace(Throwable e) { // we go to system.out now - system.err is nonsense print(getStackTrace(e)); } static void printStackTrace() { printStackTrace(new Throwable()); } // 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 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 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; } 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 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 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, 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; } // 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 void removeLast(List l) { if (!l.isEmpty()) l.remove(l(l)-1); } 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 long now_virtualTime; static long now() { return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis(); } static Object quickImport(Object o) { return quickExport(o, mc()); } 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, null); } static Lisp nlParse(List tok) { return nlParse(tok, true, null); } static Lisp nlParse(List tok, boolean unbracket, 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 = 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 String addPrefix(String prefix, String s) { return s.startsWith(prefix) ? s : prefix + s; } 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 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_findField(Class c, String field) { for (Field f : c.getDeclaredFields()) if (f.getName().equals(field)) return f; throw new RuntimeException("Field '" + field + "' not found in " + c.getName()); } static Field set_findStaticField(Class c, String field) { for (Field f : c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; throw new RuntimeException("Static field '" + field + "' not found in " + c.getName()); } static A or(A a, A b) { return a != null ? a : b; } // 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 match(String pat, String s) { return match3(pat, s); } static boolean match(String pat, String s, Matches matches) { return match3(pat, s, matches); } 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 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) get(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 String n(long l, String name) { return l + " " + (l == 1 ? name : getPlural(name)); } static boolean startsWithUpperCase(String s) { return nempty(s) && Character.isUpperCase(s.charAt(0)); } static int parseInt(String s) { return Integer.parseInt(s); } static Object quickExport(Object o, Object dest) { try { if (o == null || o instanceof String || o instanceof Number) return o; if (o instanceof List) { List l = (List) ( o); List destO = new ArrayList(l.size()); for (int i = 0; i < l.size(); i++) destO.add(quickExport(l.get(i), dest)); return destO; } if (o instanceof Map) { Map m = (Map) ( o); Map destO = new HashMap(); for (Object e : ((Map) o).entrySet()) destO.put( quickExport(((Map.Entry) e).getKey(), dest), quickExport(((Map.Entry) e).getValue(), dest)); return destO; } String className = o.getClass().getName(); if (className.startsWith("main$")) { Class destClass = getClass(dest, className); //print(o.getClass() + " => " + destClass); if (o.getClass() == destClass) return o; // no export necessary // actually make a new object(), copy fields Object destO = nuObject(destClass); // 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(value, dest)); } return destO; } // assume it's a shared library object return o; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static A popLast(List l) { return liftLast(l); } static String lstr(Map map) { return str(l(map)); } static String lstr(List l) { return str(l(l)); } static String lstr(String s) { return str(l(s)); } static boolean equals(Object a, Object b) { return a == null ? b == null : a.equals(b); } 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 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 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 String getPlural(String s) { if (s.endsWith("y")) return dropSuffix("y", s) + "ies"; 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; } /* Binding levels (well...): 0 = lowest 1 = arrows (> lowest) 2 = arrowsr (> arrows) 3 = word (> arrows) 4 = juxta, idword, subword (> word) */ static String snlParse_rules = "\n // highest (strongest binding) level \n = idword\n = subword\n = subword\n // = subword\n \n // random symbols we'd want to use\n // (they don't parse if not listed here)\n ? = symbol\n : = symbol\n * = symbol\n \"=\" = symbol\n + = symbol\n , = symbol\n . = symbol\n = idword\n \n = word\n = word\n \n // juxtaposition on highest level\n = juxta\n = word\n \n // bracketing takes us to highest level\n [ ] = square\n ( ) = round\n \n = subword\n = subword\n \n // < is intermediate\n = arrowsr\n > = realarrowr\n = arrowsr\n \n < = realarrow\n = arrows\n = arrows\n \n = lowest // allow empty here\n = lowest\n"; static Explain snlParse(String input) { return explainFull(snlTok(input), snlParse_rules, "lowest"); } 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 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; } // replacement for class JavaTok // maybe incomplete, might want to add floating point numbers // todo also: extended multi-line strings static List javaTok(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 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; tok.add(s.substring(i, j)); i = j; } if ((tok.size() % 2) == 0) tok.add(""); return tok; } static List javaTok(List tok) { return javaTok(join(tok)); } static int min(int a, int 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 ArrayList litlist(A... a) { return new ArrayList(Arrays.asList(a)); } // 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 codeTokens(List tok) { return codeTokensOnly(tok); } 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 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; } // 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 Class mc() { return getMainClass(); } static boolean neq(Object a, Object b) { return !eq(a, b); } 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 List concatLists(List... lists) { List l = new ArrayList(); for (List list : lists) if (list != null) l.addAll(list); return l; } static List concatLists(List> lists) { List l = new ArrayList(); for (List list : lists) if (list != null) l.addAll(list); return l; } 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); }} static Object nuObject(Class c, Object... args) { try { Constructor m = nuObject_findConstructor(c, args); m.setAccessible(true); return 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; } static String getClassName(Object o) { return o == null ? "null" : o.getClass().getName(); } public 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 substring(String s, int x) { return safeSubstring(s, x); } static String substring(String s, int x, int y) { return safeSubstring(s, x, y); } 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 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 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_findField(Class c, String field) { for (Field f : c.getDeclaredFields()) if (f.getName().equals(field)) return f; return null; } static Field setOpt_findStaticField(Class c, String field) { for (Field f : c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; return null; } // 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 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); } static List parse3(String s) { return dropPunctuation(javaTokPlusPeriod(s)); } 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); }} // 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()]); } static String dropSuffix(String suffix, String s) { return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s; } 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 List nlTok(String s) { return javaTokPlusPeriod(s); } 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); } synchronized(StringBuffer.class) { Object print_log = get(getMainClass(), "print_log"); assertTrue(print_log != null); setOpt(theClass, "print_log", print_log); } return theClass; } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static void callMain(Object c, String... args) { callOpt(c, "main", new Object[] {args}); } static void assertTrue(Object o) { assertEquals(true, o); } static boolean assertTrue(String msg, boolean b) { if (!b) fail(msg); return b; } static boolean assertTrue(boolean b) { if (!b) fail("oops"); return b; } static Class __javax; static Class getJavaX() { return __javax; } 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) fail("Bot name not found in source of " + botID); startBot(botName, botIDOrName); } } static File loadLibrary(String snippetID) { return loadBinarySnippet(snippetID); } static String loadTextFileFromZip(File inZip, String fileName) { return loadTextFileFromZipFile(inZip, fileName); } static String sendToLocalBot_cached(String botName, String s, Object... args) { DialogIO io = newFindBot(botName); if (io == null) fail("Bot not found: " + botName); try { return io.ask(s, args); } finally { io.close(); } } 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 void javaxBot(String botID) { startBotVM(); if (injectTo("Bot VM", botID) == null) 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); }} static void assertEquals(Object x, Object y) { assertEquals(null, x, y); } static void assertEquals(String msg, Object x, Object y) { if (!(x == null ? y == null : x.equals(y))) fail((msg != null ? msg + ": " : "") + structure(x) + " != " + structure(y)); } // 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 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 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 DialogIO talkTo(int port) { return talkTo("localhost", port); } static DialogIO talkTo(String ip, int port) { try { final Socket s = new Socket(ip, port); //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 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 (IOException 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(sendToLocalBot(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 void sleepSeconds(long s) { if (s > 0) sleep(s*1000); } 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; } public static long parseSnippetID(String snippetID) { long id = Long.parseLong(shortenSnippetID(snippetID)); if (id == 0) fail("0 is not a snippet ID"); return id; } static boolean preferCached = 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 = getSnippetFromBossBot(snippetID); if (text != null) return text; initSnippetCache(); text = DiskSnippetCache_get(snippetID); if (preferCached && text != null) return text; try { if (text != null) System.err.println("md5: " + md5(text)); URL url = new URL("http://tinybrain.de:8080/getraw.php?id=" + snippetID + "&utf8=1"); 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); } public static void copyStream(InputStream in, OutputStream out) throws IOException { byte[] buf = new byte[65536]; while (true) { int n = in.read(buf); if (n <= 0) return; out.write(buf, 0, n); } } 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 class DynamicObject { String className; Map fieldValues = new TreeMap(); } static Object unstructure(String text) { return unstructure(text, false); } // actually it's now almost the same as jsonDecode :) static Object unstructure(String text, final boolean allDynamic) { final List tok = javaTok(text); class X { int i = 1; Object parse() { String t = tok.get(i); if (t.startsWith("\"")) { String s = unquote(tok.get(i)); i += 2; return s; } if (t.equals("hashset")) return parseHashSet(); if (t.equals("treeset")) return parseTreeSet(); if (t.equals("hashmap")) return parseHashMap(); if (t.equals("{")) return parseMap(); if (t.equals("[")) return parseList(); if (t.equals("array")) return parseArray(); if (t.equals("class")) return parseClass(); if (t.equals("bigint")) return parseBigInt(); if (t.equals("d")) return parseDouble(); if (t.equals("l")) return parseLisp(); if (t.equals("null")) { i += 2; return null; } if (t.equals("false")) { i += 2; return false; } if (t.equals("true")) { i += 2; return true; } if (t.equals("-")) { t = tok.get(i+2); i += 4; t = dropSuffix("L", t); long l = -Long.parseLong(t); return l == (int) l ? (int) l : l; } if (isInteger(t) || isLongConstant(t)) { i += 2; t = dropSuffix("L", t); long l = Long.parseLong(t); return l == (int) l ? (int) l : l; } if (isJavaIdentifier(t)) { Class c = allDynamic ? null : findClass(t); DynamicObject dO = null; Object o = null; if (c != null) o = nuObject(c); else { dO = new DynamicObject(); dO.className = t; } i += 2; if (i < tok.size() && tok.get(i).equals("(")) { consume("("); while (!tok.get(i).equals(")")) { // It's like parsing a map. //Object key = parse(); //if (tok.get(i).equals(")")) // key = onlyField(); String key = unquote(tok.get(i)); i += 2; consume("="); Object value = parse(); if (o != null) setOpt(o, key, value); else dO.fieldValues.put(key, value); if (tok.get(i).equals(",")) i += 2; } consume(")"); } 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 parseList() { consume("["); List list = new ArrayList(); while (!tok.get(i).equals("]")) { list.add(parse()); if (tok.get(i).equals(",")) i += 2; } consume("]"); return list; } Object parseArray() { consume("array"); consume("{"); List list = new ArrayList(); while (!tok.get(i).equals("}")) { list.add(parse()); if (tok.get(i).equals(",")) i += 2; } consume("}"); 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 parseHashMap() { consume("hashmap"); 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 = unstructure(tok.get(i)); i += 2; 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()))); fail(quote(s) + " expected: " + prevToken + " " + nextTokens + " (" + i + "/" + tok.size() + ")"); } i += 2; } } return new X().parse(); } 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 ThreadLocal loadPage_charset = new ThreadLocal(); 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 < 60; tries++) try { URLConnection con = url.openConnection(); return loadPage(con, url); } catch (IOException _e) { e = _e; print("Retrying because of: " + e); 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 { return loadPage(new URL(loadPage_preprocess(url))); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadPage(URL url) { print("Loading: " + url.toExternalForm()); return loadPageSilently(url); } public static String loadPage(URLConnection con, URL url) throws IOException { String contentType = con.getContentType(); if (contentType == null) throw new IOException("Page could not be read: " + url); //print("Content-Type: " + contentType); String charset = loadPage_charset.get(); if (charset == null) charset = loadPage_guessCharset(contentType); Reader r = new InputStreamReader(con.getInputStream(), charset); StringBuilder buf = new StringBuilder(); while (true) { int ch = r.read(); if (ch < 0) break; //Log.info("Chars read: " + buf.length()); buf.append((char) ch); } 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[] 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 sendToLocalBot(String bot, String text, Object... args) { text = format3(text, args); DialogIO channel = findBot(bot); if (channel == null) 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(); } } // 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 boolean startsWithIgnoreCase(String a, String b) { return a != null && a.regionMatches(true, 0, b, 0, b.length()); } 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) { 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 String firstPartOfHelloString(String s) { int i = s.lastIndexOf('/'); return i < 0 ? s : rtrim(s.substring(0, i)); } static boolean isInteger(String s) { return s != null && Pattern.matches("\\-?\\d+", s); } static String getSnippetFromBossBot(long snippetID) { return boss(format3("get text for *", snippetID)); } /** 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"; if (contents != null) { FileOutputStream fileOutputStream = new FileOutputStream(tempFileName); 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 (!new File(tempFileName).renameTo(file)) throw new IOException("Can't rename " + tempFileName + " to " + fileName); } public static void saveTextFile(File fileName, String contents) { try { saveTextFile(fileName.getPath(), contents); } catch (IOException e) { throw new RuntimeException(e); } } /** 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); } } 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 byte[] loadBinaryPage(String url) throws IOException { return loadBinaryPage(new URL(url).openConnection()); } public static byte[] loadBinaryPage(URLConnection con) throws IOException { //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(); } 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); } } // 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) { try { return Class.forName("main$" + name); } catch (ClassNotFoundException e) { return null; } } static String boss(String line) { try { //S s = sendToLocalBotOpt("Boss Bot", line); DialogIO io = talkTo(4990); // Boss Bot port io.readLine(); io.sendLine(line); String s = io.readLine(); Matches m = new Matches(); if (match3("text: *", s, m)) return unquote(m.m[0]); return null; } catch (Exception e) { //e.printStackTrace(); return null; } } 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 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 long parseLong(String s) { return Long.parseLong(s); } 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 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; } public static byte[] loadBinaryFile(String fileName) throws IOException { 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(); } public static byte[] loadBinaryFile(File file) throws IOException { return loadBinaryFile(file.getPath()); } 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) 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 String sendToLocalBotOpt(String bot, String text) { if (bot == null) return null; 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(); } } static boolean isAndroid() { return System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0; } public static boolean isWindows() { return System.getProperty("os.name").contains("Windows"); } /** possibly improvable */ public static String bashQuote(String text) { if (text == null) return null; return "\"" + text .replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") + "\""; } /** 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) throws IOException { File outFile = File.createTempFile("nohup_" + nohup_sanitize(cmd), ".out"); nohup(cmd, outFile, false); return outFile; } 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) throws IOException { 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(); } } 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; } public static void mkdirsForFile(File file) { File dir = file.getParentFile(); if (dir != null) // is null if file is in current dir dir.mkdirs(); } 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; } } } static class ProgramScan { static int threads = isWindows() ? 500 : 10; static int timeout = 5000; // hmm... static String ip = "127.0.0.1"; static int quickScanFrom = 10000, quickScanTo = 10999; static int maxNumberOfBotPorts = 100; 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)); String name = toPort < 10000 ? "bot" : "program"; 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>(); for (int port : preferredPorts) futures.add(checkPort(es, ip, port, timeout)); for (int port = fromPort; port <= toPort; port++) if (!preferredPortsSet.contains(port)) futures.add(checkPort(es, ip, port, timeout)); es.shutdown(); List programs = new ArrayList(); for (final Future f : futures) { Program p = f.get(); if (p != null) programs.add(p); } stopTiming(); 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) { return scan(4990, 5000+maxNumberOfBotPorts-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 abstract class DialogIO { String line; boolean eos; 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 (!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) fail(); line = l; helloRead = false; } } static abstract class DialogHandler { abstract void run(DialogIO io); } static class Matches { String[] m; String get(int i) { return m[i]; } String unq(int i) { return unquote(m[i]); } String fsi(int i) { return formatSnippetID(unq(i)); } 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 String formatSnippetID(String id) { return "#" + parseSnippetID(id); } static String formatSnippetID(long id) { return "#" + id; } 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 startTiming_startTime; static void startTiming() { startTiming_startTime = now(); } static void stopTiming() { long end = now(); print("Time: " + (end-startTiming_startTime) + " ms"); } static boolean containsToken(List tok, String token) { return tok.contains(token); } static void printF(String s, Object... args) { printFormat(s, args); } static String string(Object o) { return String.valueOf(o); } static List nlUnroll(Lisp tree) { if (tree.is("[]")) return tree.args; else return litlist(tree); } static BufferedReader readLine_reader; static String readLine() { return (String) call(getJavaX(), "readLine"); } 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 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 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); } static Set keys(Map map) { return map.keySet(); } static Set keys(Object map) { return keys((Map) map); } }