import java.util.*; import java.util.zip.*; import java.util.List; import java.util.regex.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.table.*; import java.io.*; import java.net.*; import java.lang.reflect.*; import java.lang.ref.*; import java.lang.management.*; import java.security.*; import java.security.spec.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import javax.imageio.*; import java.math.*; public class main { static Prolog p = new Prolog(); static void test(Lisp l) { print(l + " => " + p.canSolve(l)); } public static void main(String[] args) throws Exception { p.addClause(lisp("yes")); test(lisp("yes")); test(lisp("no")); // Test native test(lisp("nativeTest")); test(lisp("isQuoted", "x")); test(lisp("isQuoted", "A")); test(lisp("isQuoted", "\"hello\"")); test(lisp("isQuoted", "\"hello\"", "\"hello\"")); // will fail (2 args instead of 1) } static StringBuffer local_log = new StringBuffer(); // not redirected static volatile StringBuffer print_log = local_log; // might be redirected, e.g. to main bot static void print() { print(""); } static void print(Object o) { String s = String.valueOf(o) + "\n"; local_log.append(s); StringBuffer buf = print_log; if (buf != local_log && buf != null) buf.append(s); System.out.print(s); } static void print(long l) { print(String.valueOf(l)); } // 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 class Prolog { long varCount; boolean showStuff; List stack = new ArrayList(); Trail sofar = null; List program = new ArrayList(); long steps; 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 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; } } class Clause { Lisp head; Goal body; Clause(Lisp head, Goal body) { this.body = body; this.head = head;} Clause(Lisp head) { this.head = head;} Clause copy() { return new Clause(copy2(head), body == null ? null : body.copy()); } public String toString() { //ret head + " :- " + (body == null ? "true" : body); return 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_Push(Var x) { sofar = new Trail(x, sofar); } void Trail_Undo(Trail whereto) { for (; sofar != whereto; sofar = sofar.tcdr) 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.getName() + " = " + v); } } 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() { return new Goal(copy2(car), cdr == null ? null : cdr.copy()); } Goal append(Goal l) { return new Goal(car, cdr == null ? null : cdr.append(l)); } } // class Goal 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_Push(v); v.instance = 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 (neq(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_Push(v); v.instance = newVar(); } return v.instance; } return copy2(thiz); } Lisp newTermCons(Lisp t) { Lisp l = new Lisp(t.head); for (Lisp arg : t) l.add(copy(arg)); return l; } Lisp copy2(Lisp thiz) { return newTermCons(thiz); } 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)); } Clause clause(Lisp rule) { List 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)); if (showStuff) print("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)); } Lisp prologify(Lisp term, Map vars) { if (term == null) return null; if (snlIsVar(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; } // Executor stack entry static class Entry { Goal goal; int programIdx = -1; // -1 is natives Trail trail; boolean trailSet; Entry(Goal goal) { this.goal = goal;} } void start(Lisp goal) { start(new Goal(prologify(goal))); } // warning: doesn't prologify the goal void start(Goal goal) { if (showStuff) print("start: " + structure_seen(goal)); steps = 0; stack = new ArrayList(); Trail_Undo(null); stack.add(new Entry(goal)); } int level() { return l(stack)-1; } boolean done() { return empty(stack); } boolean step() { if (done()) fail("done!"); // safety ++steps; Entry e = last(stack); if (e.trailSet) { Trail_Undo(e.trail); e.trailSet = false; } if (e.programIdx >= l(program)) { // program loop ends removeLast(stack); return false; } e.trail = Trail_Note(); e.trailSet = true; if (e.programIdx == -1) { if (showStuff) print(indent(level()) + "solve@" + level() + ": " + e.goal); ++e.programIdx; // try native functions if (goNative(e.goal.car)) { if (showStuff) print(indent(level()) + "native!"); Goal gdash = e.goal.cdr; if (gdash == null) return true; else { stack.add(new Entry(gdash)); return false; } } } // now in program loop Clause c = program.get(e.programIdx).copy(); ++e.programIdx; Trail_Undo(e.trail); if (showStuff) print(indent(level()) + " try:" + c); if (unify(e.goal.car, c.head)) { Goal gdash = c.body == null ? e.goal.cdr : c.body.append(e.goal.cdr); if (gdash == null) return true; else { stack.add(new Entry(gdash)); return false; } } else if (showStuff) print(indent(level()) + " nomatch."); return false; } void addClause(Lisp c) { program.add(clause(c)); } void addClause(Clause c) { program.add(c); } boolean canSolve(Lisp goal) { return canSolve(new Goal(prologify(goal))); } boolean canSolve(Goal goal) { start(goal); while (!done()) if (step()) return true; return false; } // return variable map or null if unsolved Map solve(Lisp goal) { Goal g = new Goal(prologify(goal)); if (!canSolve(g)) return null; HashMap map = new HashMap(); for (Var v : findVars(g)) if (v.isUserVar()) map.put(v.getName(), v.getValue()); return map; } void addClauses(Lisp tree) { if (isJuxta(tree)) for (Lisp part : tree) addClauses(part); else addClause(tree); } boolean goNative(Lisp term) { term = resolve(term); if (term.is("nativeTest")) return true; if (term.is("isQuoted", 1)) { Lisp x = term.get(0); return !(x instanceof Var) && x.isLeaf() && isQuoted(x.head); } return false; } // resolve all variables Lisp resolve(Lisp term) { if (term instanceof Var) return ((Var) term).getValue(); // 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; } } // class Prolog // 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... 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 instanceof Lisp)) return false; Lisp l = (Lisp) ( o); return eq (head, l.head) && eq(args, l.args); } } static String structure(Object o) { return structure(o, 0, null /*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; Object value; try { field.setAccessible(true); value = field.get(o); } catch (Exception e) { value = "?"; } fieldName = field.getName(); // 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 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; } // is it "* *" etc.? static boolean isJuxta(Lisp l) { if (l == null) return false; if (l(l.head) == 0) return false; for (int i = 0; i < l(l.head); i++) if (l.head.charAt(i) != ((i & 1) == 0 ? '*' : ' ')) return false; return true; } static void removeLast(List l) { if (!l.isEmpty()) l.remove(l(l)-1); } 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 quoteIfNotIdentifier(String s) { if (s == null) return null; return isJavaIdentifier(s) ? s : quote(s); } // 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(); } // 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()); } static boolean neq(Object a, Object b) { return !eq(a, b); } public static String join(String glue, Iterable strings) { StringBuilder buf = new StringBuilder(); Iterator i = strings.iterator(); if (i.hasNext()) { buf.append(i.next()); while (i.hasNext()) buf.append(glue).append(i.next()); } return buf.toString(); } public static String join(String glue, String[] strings) { return join(glue, Arrays.asList(strings)); } public static String join(Iterable strings) { return join("", strings); } public static String join(String[] strings) { return join("", strings); } static 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 A last(List l) { return l.isEmpty() ? null : l.get(l.size()-1); } // split smth like "if * then *" into "if *" and "then *" // returns null if no split static List snlSplitOps(Lisp code) { String ops = code.head; List tok = codeTokensOnly(javaTok(ops)); if ((l(tok) & 1) != 0) return null; List out = new ArrayList(); for (int i = 0; i < l(tok); i += 2) { String op = tok.get(i); if (!isIdentifier(op) || neq("*", tok.get(i+1))) return null; out.add(lisp(op + " *", code.get(i/2))); } //print("splitOps => " + structure(out)); return out; } static boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } static boolean isEmpty(String s) { return s == null || s.length() == 0; } 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 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 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)); } static boolean snlIsVar(Lisp l) { return l != null && l.isEmpty() && startsWithUpperCase(l.raw()); } static boolean equals(Object a, Object b) { return a == null ? b == null : a.equals(b); } static boolean isIdentifier(String s) { return isJavaIdentifier(s); } // 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 String unnull(String s) { return s == null ? "" : s; } static List unnull(List l) { return l == null ? emptyList() : l; } static String quote(String s) { if (s == null) return "null"; return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\r", "\\r").replace("\n", "\\n") + "\""; } static String quote(long l) { return quote("" + l); } static String shorten(String s, int max) { if (s == null) return ""; return s.length() <= max ? s : s.substring(0, Math.min(s.length(), max)) + "..."; } 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)); } static String repeat(char c, int n) { 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 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; } static boolean nempty(Collection c) { return !isEmpty(c); } static boolean nempty(String s) { return !isEmpty(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; } static String format(String pat, Object... args) { return format3(pat, args); } static boolean eqic(String a, String b) { if ((a == null) != (b == null)) return false; if (a == null) return true; return a.equalsIgnoreCase(b); } static boolean startsWithUpperCase(String s) { return nempty(s) && Character.isUpperCase(s.charAt(0)); } 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 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; } static String getClassName(Object o) { return o == null ? "null" : o.getClass().getName(); } static boolean isJavaIdentifier(String s) { if (s.length() == 0 || !Character.isJavaIdentifierStart(s.charAt(0))) return false; for (int i = 1; i < s.length(); i++) if (!Character.isJavaIdentifierPart(s.charAt(i))) return false; return true; } static String 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 Object getBot(String botID) { return call(getMainBot(), "getBot", botID); } // 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 boolean booleanValue(Object o) { return eq(true, o); } static List emptyList() { return Collections.emptyList(); } 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 Object mainBot; static Object getMainBot() { return mainBot; } }