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 PersistentMap theories; static Map parsedTheories = new TreeMap(); static Index index = new Index(); static class Index { List> byArity = new ArrayList>(); void addTheory(Lisp tree) { for (Lisp statement : tree) { int arity = statement.size(); while (arity >= l(byArity)) byArity.add(new ArrayList()); byArity.get(arity).add(statement); } } Lisp searchOne(String query) { Lisp tree = nlParse(query); int arity = tree.size(); List entries = get(byArity, arity); if (entries != null) for (Lisp statement : entries) if (match(tree, statement)) return statement; return null; } static boolean match(Lisp query, Lisp statement) { HashMap matches = new HashMap(); //print("Checking: " + nlUnparse(statement)); return nlMatch(query, statement, matches); } } public static void main(String[] args) throws Exception { File theoriesFile = getProgramFile("theories-downloaded"); int n = (int) theoriesFile.length(); String url = "http://ai1.lol/1002762/raw/theoriesInc" + hquery("n", n); String page = loadPage(url); String id = firstLine(page); page = dropFirstLine(page); int m = parseInt(firstLine(page).trim()); page = dropFirstLine(page); page = htmldecode(page); //(n + l(toUtf8(page))) appendToTextFile(theoriesFile, page); int n2 = (int) theoriesFile.length(); if (m == n2) print(n == n2 ? "Nothing to download (n=" + n + ")" : "Sizes ok - " + n + " -> " + n2); else print("Size mismatch? " + m + "/" + n2); theories = new PersistentMap(theoriesFile); print(theories.size() + " theories. Parsing..."); { long __time_startTime = now(); try { for (String theoryName : theories.keySet()) { String text = theories.get(theoryName); Lisp tree = nlParseExpand(text); parsedTheories.put(theoryName, tree); } } finally { __time_startTime = now()-__time_startTime; saveTiming(__time_startTime); } } print("Parsed all. Making index."); { long __time_startTime = now(); try { for (String theoryName : parsedTheories.keySet()) { Lisp tree = parsedTheories.get(theoryName); index.addTheory(tree); } } finally { __time_startTime = now()-__time_startTime; saveTiming(__time_startTime); } } print("Made index. Trying a search"); String query = "mrshutco lives in $x"; print(query); print(" " + nlUnparse(index.searchOne(query))); } static File getProgramFile(String progID, String fileName) { return new File(getProgramDir(progID), fileName); } static File getProgramFile(String fileName) { return getProgramFile(getProgramID(), fileName); } static Lisp nlParseExpand(String text) { Lisp tree = nlParse(text); if (!nlIsMultipleStatements(text)) return lisp("[]", tree); else return tree; } static String htmldecode(final String input) { if (input == null) return null; final int MIN_ESCAPE = 2; final int MAX_ESCAPE = 6; StringWriter writer = null; int len = input.length(); int i = 1; int st = 0; while (true) { // look for '&' while (i < len && input.charAt(i-1) != '&') i++; if (i >= len) break; // found '&', look for ';' int j = i; while (j < len && j < i + MAX_ESCAPE + 1 && input.charAt(j) != ';') j++; if (j == len || j < i + MIN_ESCAPE || j == i + MAX_ESCAPE + 1) { i++; continue; } // found escape if (input.charAt(i) == '#') { // numeric escape int k = i + 1; int radix = 10; final char firstChar = input.charAt(k); if (firstChar == 'x' || firstChar == 'X') { k++; radix = 16; } try { int entityValue = Integer.parseInt(input.substring(k, j), radix); if (writer == null) writer = new StringWriter(input.length()); writer.append(input.substring(st, i - 1)); if (entityValue > 0xFFFF) { final char[] chrs = Character.toChars(entityValue); writer.write(chrs[0]); writer.write(chrs[1]); } else { writer.write(entityValue); } } catch (NumberFormatException ex) { i++; continue; } } else { // named escape CharSequence value = htmldecode_lookupMap.get(input.substring(i, j)); if (value == null) { i++; continue; } if (writer == null) writer = new StringWriter(input.length()); writer.append(input.substring(st, i - 1)); writer.append(value); } // skip escape st = j + 1; i = st; } if (writer != null) { writer.append(input.substring(st, len)); return writer.toString(); } return input; } private static final String[][] htmldecode_ESCAPES = { {"\"", "quot"}, // " - double-quote {"&", "amp"}, // & - ampersand {"<", "lt"}, // < - less-than {">", "gt"}, // > - greater-than // Mapping to escape ISO-8859-1 characters to their named HTML 3.x equivalents. {"\u00A0", "nbsp"}, // non-breaking space {"\u00A1", "iexcl"}, // inverted exclamation mark {"\u00A2", "cent"}, // cent sign {"\u00A3", "pound"}, // pound sign {"\u00A4", "curren"}, // currency sign {"\u00A5", "yen"}, // yen sign = yuan sign {"\u00A6", "brvbar"}, // broken bar = broken vertical bar {"\u00A7", "sect"}, // section sign {"\u00A8", "uml"}, // diaeresis = spacing diaeresis {"\u00A9", "copy"}, // copyright sign {"\u00AA", "ordf"}, // feminine ordinal indicator {"\u00AB", "laquo"}, // left-pointing double angle quotation mark = left pointing guillemet {"\u00AC", "not"}, // not sign {"\u00AD", "shy"}, // soft hyphen = discretionary hyphen {"\u00AE", "reg"}, // registered trademark sign {"\u00AF", "macr"}, // macron = spacing macron = overline = APL overbar {"\u00B0", "deg"}, // degree sign {"\u00B1", "plusmn"}, // plus-minus sign = plus-or-minus sign {"\u00B2", "sup2"}, // superscript two = superscript digit two = squared {"\u00B3", "sup3"}, // superscript three = superscript digit three = cubed {"\u00B4", "acute"}, // acute accent = spacing acute {"\u00B5", "micro"}, // micro sign {"\u00B6", "para"}, // pilcrow sign = paragraph sign {"\u00B7", "middot"}, // middle dot = Georgian comma = Greek middle dot {"\u00B8", "cedil"}, // cedilla = spacing cedilla {"\u00B9", "sup1"}, // superscript one = superscript digit one {"\u00BA", "ordm"}, // masculine ordinal indicator {"\u00BB", "raquo"}, // right-pointing double angle quotation mark = right pointing guillemet {"\u00BC", "frac14"}, // vulgar fraction one quarter = fraction one quarter {"\u00BD", "frac12"}, // vulgar fraction one half = fraction one half {"\u00BE", "frac34"}, // vulgar fraction three quarters = fraction three quarters {"\u00BF", "iquest"}, // inverted question mark = turned question mark {"\u00C0", "Agrave"}, // ? - uppercase A, grave accent {"\u00C1", "Aacute"}, // ? - uppercase A, acute accent {"\u00C2", "Acirc"}, // ? - uppercase A, circumflex accent {"\u00C3", "Atilde"}, // ? - uppercase A, tilde {"\u00C4", "Auml"}, // ? - uppercase A, umlaut {"\u00C5", "Aring"}, // ? - uppercase A, ring {"\u00C6", "AElig"}, // ? - uppercase AE {"\u00C7", "Ccedil"}, // ? - uppercase C, cedilla {"\u00C8", "Egrave"}, // ? - uppercase E, grave accent {"\u00C9", "Eacute"}, // ? - uppercase E, acute accent {"\u00CA", "Ecirc"}, // ? - uppercase E, circumflex accent {"\u00CB", "Euml"}, // ? - uppercase E, umlaut {"\u00CC", "Igrave"}, // ? - uppercase I, grave accent {"\u00CD", "Iacute"}, // ? - uppercase I, acute accent {"\u00CE", "Icirc"}, // ? - uppercase I, circumflex accent {"\u00CF", "Iuml"}, // ? - uppercase I, umlaut {"\u00D0", "ETH"}, // ? - uppercase Eth, Icelandic {"\u00D1", "Ntilde"}, // ? - uppercase N, tilde {"\u00D2", "Ograve"}, // ? - uppercase O, grave accent {"\u00D3", "Oacute"}, // ? - uppercase O, acute accent {"\u00D4", "Ocirc"}, // ? - uppercase O, circumflex accent {"\u00D5", "Otilde"}, // ? - uppercase O, tilde {"\u00D6", "Ouml"}, // ? - uppercase O, umlaut {"\u00D7", "times"}, // multiplication sign {"\u00D8", "Oslash"}, // ? - uppercase O, slash {"\u00D9", "Ugrave"}, // ? - uppercase U, grave accent {"\u00DA", "Uacute"}, // ? - uppercase U, acute accent {"\u00DB", "Ucirc"}, // ? - uppercase U, circumflex accent {"\u00DC", "Uuml"}, // ? - uppercase U, umlaut {"\u00DD", "Yacute"}, // ? - uppercase Y, acute accent {"\u00DE", "THORN"}, // ? - uppercase THORN, Icelandic {"\u00DF", "szlig"}, // ? - lowercase sharps, German {"\u00E0", "agrave"}, // ? - lowercase a, grave accent {"\u00E1", "aacute"}, // ? - lowercase a, acute accent {"\u00E2", "acirc"}, // ? - lowercase a, circumflex accent {"\u00E3", "atilde"}, // ? - lowercase a, tilde {"\u00E4", "auml"}, // ? - lowercase a, umlaut {"\u00E5", "aring"}, // ? - lowercase a, ring {"\u00E6", "aelig"}, // ? - lowercase ae {"\u00E7", "ccedil"}, // ? - lowercase c, cedilla {"\u00E8", "egrave"}, // ? - lowercase e, grave accent {"\u00E9", "eacute"}, // ? - lowercase e, acute accent {"\u00EA", "ecirc"}, // ? - lowercase e, circumflex accent {"\u00EB", "euml"}, // ? - lowercase e, umlaut {"\u00EC", "igrave"}, // ? - lowercase i, grave accent {"\u00ED", "iacute"}, // ? - lowercase i, acute accent {"\u00EE", "icirc"}, // ? - lowercase i, circumflex accent {"\u00EF", "iuml"}, // ? - lowercase i, umlaut {"\u00F0", "eth"}, // ? - lowercase eth, Icelandic {"\u00F1", "ntilde"}, // ? - lowercase n, tilde {"\u00F2", "ograve"}, // ? - lowercase o, grave accent {"\u00F3", "oacute"}, // ? - lowercase o, acute accent {"\u00F4", "ocirc"}, // ? - lowercase o, circumflex accent {"\u00F5", "otilde"}, // ? - lowercase o, tilde {"\u00F6", "ouml"}, // ? - lowercase o, umlaut {"\u00F7", "divide"}, // division sign {"\u00F8", "oslash"}, // ? - lowercase o, slash {"\u00F9", "ugrave"}, // ? - lowercase u, grave accent {"\u00FA", "uacute"}, // ? - lowercase u, acute accent {"\u00FB", "ucirc"}, // ? - lowercase u, circumflex accent {"\u00FC", "uuml"}, // ? - lowercase u, umlaut {"\u00FD", "yacute"}, // ? - lowercase y, acute accent {"\u00FE", "thorn"}, // ? - lowercase thorn, Icelandic {"\u00FF", "yuml"}, // ? - lowercase y, umlaut {"'", "apos"}, // the controversial (but who cares!) ' // stackoverflow.com/questions/2083754/why-shouldnt-apos-be-used-to-escape-single-quotes }; private static final HashMap htmldecode_lookupMap; static { htmldecode_lookupMap = new HashMap(); for (final CharSequence[] seq : htmldecode_ESCAPES) htmldecode_lookupMap.put(seq[1].toString(), seq[0]); } 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; } static String dropFirstLine(String text) { int i = text.indexOf('\n'); return i >= 0 ? text.substring(i+1) : ""; } static String hquery(Map params) { return htmlQuery(params); } static String hquery(Object... data) { return htmlQuery(data); } 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); //Log.info("Content-Type: " + contentType); String 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/html;\\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 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 String firstLine(String text) { int i = text.indexOf('\n'); return i >= 0 ? text.substring(0, i) : text; } static int parseInt(String s) { return Integer.parseInt(s); } /** writes safely (to temp file, then rename) */ public static void appendToTextFile(File file, String contents) { try { File parentFile = file.getParentFile(); if (parentFile != null) parentFile.mkdirs(); if (contents != null) { FileOutputStream fileOutputStream = new FileOutputStream(file, true); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8"); PrintWriter printWriter = new PrintWriter(outputStreamWriter); printWriter.print(contents); printWriter.close(); } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} 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(""); } static void print(Object 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); } 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); } } } // 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 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 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 void sleepSeconds(long s) { if (s > 0) sleep(s*1000); } static String programID; static String getProgramID() { return programID; } // TODO: ask JavaX instead static String getProgramID(Class c) { return or((String) getOpt(c, "programID"), "?"); } static String getProgramID(Object o) { return getProgramID(getMainClass(o)); } static String htmlQuery(Map params) { return params.isEmpty() ? "" : "?" + makePostData(params); } static String htmlQuery(Object... data) { return htmlQuery(litmap(data)); } static boolean nlIsVar(Lisp l) { if (l == null || !l.isLeaf()) return false; String h = l.head; return l(h) > 1 && h.startsWith("$"); } static boolean neq(Object a, Object b) { return !eq(a, b); } static String dropPrefix(String prefix, String s) { return s.startsWith(prefix) ? s.substring(l(prefix)) : s; } 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; } // 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 A last(List l) { return l.isEmpty() ? null : l.get(l.size()-1); } // make a lisp form static Lisp lisp(String head, Object... args) { Lisp l = new Lisp(head); for (Object o : args) l.add(o); return l; } static Lisp lisp(String head, List args) { return new Lisp(head, args); } static 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 File getProgramDir() { return programDir(); } static File getProgramDir(String snippetID) { return programDir(snippetID); } // 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 String str(Object o) { return String.valueOf(o); } static RuntimeException fail() { throw new RuntimeException("fail"); } static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); } static RuntimeException fail(String msg) { throw new RuntimeException(unnull(msg)); } static RuntimeException fail(String msg, Object... args) { throw new RuntimeException(format(msg, args)); } 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 A popLast(List l) { return liftLast(l); } static Lisp nlUnbracket(Lisp tree) { while (tree.is("[]", 1)) tree = tree.get(0); return tree; } static String makePostData(Map map) { List l = new ArrayList(); for (Map.Entry e : map.entrySet()) { String key = (String) ( e.getKey()); String value = structureOrText(e.getValue()); l.add(urlencode(key) + "=" + urlencode(value)); } return join("&", l); } static File programDir() { return programDir(getProgramID()); } static File programDir(String snippetID) { return new File(javaxDataDir(), formatSnippetID(snippetID)); } // 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 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 Map litmap(Object... x) { TreeMap map = new TreeMap(); for (int i = 0; i < x.length-1; i += 2) if (x[i+1] != null) map.put(x[i], x[i+1]); return map; } static A liftLast(List l) { if (l.isEmpty()) return null; int i = l(l)-1; A a = l.get(i); l.remove(i); return a; } static String unnull(String s) { return s == null ? "" : s; } static List unnull(List l) { return l == null ? emptyList() : l; } static 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 Class getMainClass() { try { return Class.forName("main"); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Class getMainClass(Object o) { try { return (o instanceof Class ? (Class) o : o.getClass()).getClassLoader().loadClass("main"); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static 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 or(A a, A b) { return a != null ? a : b; } 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 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 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 String urlencode(String x) { try { return URLEncoder.encode(unnull(x), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } static Object getBot(String botID) { return call(getMainBot(), "getBot", botID); } public static String join(String glue, Iterable strings) { StringBuilder buf = new StringBuilder(); Iterator i = strings.iterator(); if (i.hasNext()) { buf.append(i.next()); while (i.hasNext()) buf.append(glue).append(i.next()); } return buf.toString(); } public static String join(String glue, String[] strings) { return join(glue, Arrays.asList(strings)); } public static String join(Iterable strings) { return join("", strings); } public static String join(String[] strings) { return join("", strings); } static String formatSnippetID(String id) { return "#" + parseSnippetID(id); } static String formatSnippetID(long id) { return "#" + id; } static File javaxDataDir_dir; // can be set to work on different base dir static File javaxDataDir() { return javaxDataDir_dir != null ? javaxDataDir_dir : new File(userHome(), "JavaX-Data"); } static List emptyList() { return Collections.emptyList(); } 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 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 String structureOrText(Object o) { return o instanceof String ? (String) o : structure(o); } static boolean isIdentifier(String s) { return isJavaIdentifier(s); } static String quote(String s) { if (s == null) return "null"; return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\r", "\\r").replace("\n", "\\n") + "\""; } static String quote(long l) { return quote("" + l); } static 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; } 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 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; } // "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 shorten(String s, int max) { if (s == null) return ""; return s.length() <= max ? s : s.substring(0, Math.min(s.length(), max)) + "..."; } 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 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 boolean nempty(Collection c) { return !isEmpty(c); } static boolean nempty(String s) { return !isEmpty(s); } static boolean isTrue(Object o) { return booleanValue(o); } static boolean isAndroid() { return System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0; } static boolean booleanValue(Object o) { return eq(true, o); } static boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } static boolean isEmpty(String s) { return s == null || s.length() == 0; } static long parseLong(String s) { return Long.parseLong(s); } // 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(); } } // a persistent map using a clever combination of persisting and logging // (well, only logging as of now) // Note: don't put in static initializer (programID not set yet) static class PersistentMap extends AbstractMap { Map m = new TreeMap(); File file; String id; // unique ID of this map PersistentMap(String fileName) { this(getProgramFile(fileName)); } PersistentMap(String progID, String fileName) { this(getProgramFile(progID, fileName)); } PersistentMap(File file) { this.file = file; for (String s : scanLog(file)) try { List l = (List) ( unstructure(s)); Object cmd = l.get(0); if (eq(cmd, "add")) m.put((A) l.get(1), (B) l.get(2)); else if (eq(cmd, "remove")) m.remove((A) l.get(1)); else if (eq(cmd, "id")) id = (String) l.get(1); else print("Unknown command in log: " + l.get(0)); } catch (Throwable __e) { printStackTrace(__e); } if (id == null) { id = makeRandomID(12); logQuoted(file, structure(litlist("id", id))); } } // just delegates public int size() { return m.size(); } public B get(Object a) { return m.get(a); } // TODO: calling remove() in the iterator will have unpersisted // effects. public Set> entrySet() { return m.entrySet(); } // delegates with logging public B put(A a, B b) { B c = m.get(a); if (neq(b, c)) { m.put(a, b); logQuoted(file, structure(litlist("add", a, b))); } return c; } public B remove(Object a) { B result = m.remove(a); logQuoted(file, structure(litlist("remove", a))); return result; } // unique id of this map public String id() { return id; } } 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 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 void printStackTrace(Throwable e) { // we go to system.out now - system.err is nonsense print(getStackTrace(e)); } static void printStackTrace() { printStackTrace(new Throwable()); } 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 logQuoted(String logFile, String line) { logQuoted(getProgramFile(logFile), line); } static void logQuoted(File logFile, String line) { appendToFile(logFile, quote(line) + "\n"); } static String quoteIfNotIdentifier(String s) { if (s == null) return null; return isJavaIdentifier(s) ? s : quote(s); } static long now_virtualTime; static long now() { return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis(); } static List scanLog(String progID, String fileName) { return scanLog(getProgramFile(progID, fileName)); } static List scanLog(String fileName) { return scanLog(getProgramFile(fileName)); } static List scanLog(File file) { List l = new ArrayList(); for (String s : toLines(file)) if (isQuoted(s)) l.add(unquote(s)); return l; } static String makeRandomID(int length) { Random random = new Random(); char[] id = new char[length]; for (int i = 0; i < id.length; i++) id[i] = (char) ((int) 'a' + random.nextInt(26)); return new String(id); } 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 boolean equals(Object a, Object b) { return a == null ? b == null : a.equals(b); } static ThreadLocal saveTiming_last = new ThreadLocal(); static void saveTiming(long ms) { print(ms + " ms"); saveTiming_last.set(ms); } // 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; } } // 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 Object newObject(Class c, Object... args) { return nuObject(c, args); } static Object newObject(String className, Object... args) { return nuObject(className, args); } static boolean isLongConstant(String s) { if (!s.endsWith("L")) return false; s = s.substring(0, l(s)-1); return isInteger(s); } static List toLines(File f) { return toLines(loadTextFile(f)); } public static List toLines(String s) { List lines = new ArrayList(); if (s == null) return lines; int start = 0; while (true) { int i = toLines_nextLineBreak(s, start); if (i < 0) { if (s.length() > start) lines.add(s.substring(start)); break; } lines.add(s.substring(start, i)); if (s.charAt(i) == '\r' && i+1 < s.length() && s.charAt(i+1) == '\n') i += 2; else ++i; start = i; } return lines; } private static int toLines_nextLineBreak(String s, int start) { for (int i = start; i < s.length(); i++) { char c = s.charAt(i); if (c == '\r' || c == '\n') return i; } return -1; } static boolean isInteger(String s) { return s != null && Pattern.matches("\\-?\\d+", s); } static String getStackTrace(Throwable throwable) { StringWriter writer = new StringWriter(); throwable.printStackTrace(new PrintWriter(writer)); return writer.toString(); } // Let's just generally synchronize this to be safe. static synchronized void appendToFile(String path, String s) { try { new File(path).getParentFile().mkdirs(); //print("[Logging to " + path + "]"); Writer writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(path, true), "UTF-8")); writer.write(s); writer.close(); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static void appendToFile(File path, String s) { appendToFile(path.getPath(), 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(); } 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(); } static double parseDouble(String s) { return Double.parseDouble(s); } static String dropSuffix(String suffix, String s) { return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s; } static boolean eqic(String a, String b) { if ((a == null) != (b == null)) return false; if (a == null) return true; return a.equalsIgnoreCase(b); } 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; } 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 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); } }