import java.util.*; import java.io.*; import java.util.regex.*; import java.net.*; import java.math.*; public class main { static boolean debug = false; static List standardFunctions; public static void main(String[] args) throws IOException { long startTime = now(); standardFunctions = new ArrayList(); standardFunctions.addAll((List) loadVariableDefinition(loadSnippet("#761"), "standardFunctions")); standardFunctions.addAll((List) loadVariableDefinition(loadSnippet("#1006654"), "standardFunctions")); String s = loadMainJava(); Map sf = new HashMap(); for (String x : standardFunctions) { String[] f = x.split("/"); sf.put(f[1], f[0]); } for (int i = 0; ; i++) { Set defd = new HashSet(findFunctions(s)); List tok = javaTok(s); // changes tok Set invocations = findInvocations(tok, sf); s = join(tok); List needed = diff(invocations, defd); print("Functions needed: " + needed); if (needed.isEmpty()) break; for (String x : needed) { if (defd.contains(x)) continue; String id = sf.get(x); System.out.println("Adding function: " + x + " (" + id + ")"); s = addFunction(s, id); defd = new HashSet(findFunctions(s)); } for (String x : needed) if (!defd.contains(x)) fail("Function not defined properly: " + x); System.out.println("Iteration " + (i+2)); if (i >= 1000) fail("Too many iterations"); } saveMainJava(s); print("629: " + (now()-startTime) + " ms"); } static Set findInvocations(List tok, Map sf) { int i; Set l = new HashSet(); while ((i = jfind(tok, "please include function *.")) >= 0) { String fname = tok.get(i+6); l.add(fname); clearAllTokens(tok.subList(i, i+10)); } boolean result = false; for (i = 1; i+2 < tok.size(); i += 2) { String f = tok.get(i); if (!isIdentifier(f)) continue; if ((i == 1 || !tok.get(i-2).equals(".")) && tok.get(i+2).equals("(")) { boolean inSF = sf.containsKey(f); if (debug) print("Possible invocation: " + f + ", inSF: " + inSF); if (inSF) l.add(f); } } return l; } static boolean substringIs(String s, int i, String pat) { return i >= 0 && i+pat.length() < s.length() && s.substring(i, i+pat.length()).equals(pat); } // OK, this should be fast enough. static List findFunctions(String src) { int idx = src.indexOf("main {"); // yes it's a hack... if (idx >= 0) src = src.substring(idx); Pattern pattern = Pattern.compile("static[^={]*\\s+(\\w+)\\("); //System.out.println("Scanning for functions"); List functions = new ArrayList(); for (String line : toLines(src)) { Matcher matcher = pattern.matcher(line); if (matcher.find()) { String f = matcher.group(1); functions.add(f); //System.out.println("Function found: " + f); } } return functions; } public static String addFunction(String s, String fID) throws IOException { int i = s.lastIndexOf('}'); String function = loadSnippet(fID, false); return s.substring(0, i) + "\n" + function +"\n" + s.substring(i); } public static List toLines(String s) { List lines = new ArrayList(); 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 String mainJava; static String loadMainJava() throws IOException { if (mainJava != null) return mainJava; return loadTextFile("input/main.java", ""); } static void saveMainJava(String s) throws IOException { if (mainJava != null) mainJava = s; else saveTextFile("output/main.java", s); } static void saveMainJava(List tok) throws IOException { saveMainJava(join(tok)); } static String charsetForTextFiles = "UTF-8"; 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, charsetForTextFiles); return loadTextFile(inputStreamReader); } public static String loadTextFile(Reader reader) throws IOException { StringBuilder builder = new StringBuilder(); try { BufferedReader bufferedReader = new BufferedReader(reader); String line; while ((line = bufferedReader.readLine()) != null) builder.append(line).append('\n'); } finally { reader.close(); } return builder.length() == 0 ? "" : builder.substring(0, builder.length()-1); } /** writes safely (to temp file, then rename) */ public static void saveTextFile(String fileName, String contents) throws IOException { File file = new File(fileName); File parentFile = file.getParentFile(); if (parentFile != null) parentFile.mkdirs(); String tempFileName = fileName + "_temp"; FileOutputStream fileOutputStream = new FileOutputStream(tempFileName); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, charsetForTextFiles); PrintWriter printWriter = new PrintWriter(outputStreamWriter); printWriter.print(contents); printWriter.close(); if (file.exists() && !file.delete()) throw new IOException("Can't delete " + fileName); if (!new File(tempFileName).renameTo(file)) throw new IOException("Can't rename " + tempFileName + " to " + fileName); } public static String loadSnippet(String snippetID) { try { return loadSnippet(snippetID, preferCached); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadSnippet(String snippetID, boolean preferCached) throws IOException { return loadSnippet(parseSnippetID(snippetID), preferCached); } public static long parseSnippetID(String snippetID) { return Long.parseLong(shortenSnippetID(snippetID)); } private 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 snippetID; } public static boolean isSnippetID(String snippetID) { snippetID = shortenSnippetID(snippetID); return isInteger(snippetID) && Long.parseLong(snippetID) != 0; } public static boolean isInteger(String s) { return Pattern.matches("\\-?\\d+", s); } static boolean preferCached = false; public static String loadSnippet(long snippetID) throws IOException { return loadSnippet(snippetID, preferCached); } public static String loadSnippet(long snippetID, boolean preferCached) throws IOException { if (preferCached) { initSnippetCache(); String text = DiskSnippetCache_get(snippetID); if (text != null) return text; } String text; try { URL url = new URL("http://tinybrain.de:8080/getraw.php?id=" + snippetID); text = loadPage(url); } catch (FileNotFoundException e) { throw new IOException("Snippet #" + snippetID + " not found or not public"); } try { initSnippetCache(); DiskSnippetCache_put(snippetID, text); } catch (IOException e) { System.err.println("Minor warning: Couldn't save snippet to cache (" + DiskSnippetCache_getDir() + ")"); } return text; } private static String loadPage(URL url) throws IOException { System.out.println("Loading: " + url.toExternalForm()); URLConnection con = url.openConnection(); return loadPage(con, 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 = 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(); } public static String 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 File DiskSnippetCache_dir; public static void initDiskSnippetCache(File dir) { DiskSnippetCache_dir = dir; dir.mkdirs(); } public static synchronized String DiskSnippetCache_get(long snippetID) throws IOException { return loadTextFile(DiskSnippetCache_getFile(snippetID).getPath(), null); } private static File DiskSnippetCache_getFile(long snippetID) { return new File(DiskSnippetCache_dir, "" + snippetID); } public static synchronized void DiskSnippetCache_put(long snippetID, String snippet) throws IOException { saveTextFile(DiskSnippetCache_getFile(snippetID).getPath(), snippet); } public static File DiskSnippetCache_getDir() { return DiskSnippetCache_dir; } public static void initSnippetCache() { if (DiskSnippetCache_dir == null) initDiskSnippetCache(new File(System.getProperty("user.home"), ".tinybrain/snippet-cache")); } static RuntimeException fail() { throw new RuntimeException("fail"); } static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); } // 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) { ++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))); 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 ++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)); } 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); } // leaves tok properly tokenized // returns true iff anything was replaced static boolean jreplace(List tok, String in, String out) { return jreplace(tok, in, out, false, true); } static boolean jreplace(List tok, String in, String out, boolean ignoreCase, boolean reTok) { List tokin = javaTok(in); replaceSublist(tokin, litlist("<", "", "quoted", "", ">"), litlist("")); replaceSublist(tokin, litlist("<", "", "id", "", ">"), litlist("")); boolean anyChange = false; for (int n = 0; n < 10000; n++) { int i = findCodeTokens(tok, ignoreCase, toStringArray(codeTokensOnly(tokin))); if (i < 0) { if (anyChange && reTok) replaceCollection(tok, javaTok(tok)); return anyChange; } List subList = tok.subList(i-1, i+l(tokin)-1); // N to N String expansion = jreplace_expandRefs(out, subList); clearAllTokens(tok.subList(i, i+l(tokin)-2)); // code to code tok.set(i, expansion); anyChange = true; } throw fail("woot? 10000!"); } // "$1" is first code token, "$2" second code token etc. static String jreplace_expandRefs(String s, List tokref) { List tok = javaTok(s); for (int i = 1; i < l(tok)-2; i += 2) { if (tok.get(i).startsWith("$") && isInteger(tok.get(i).substring(1))) { String x = tokref.get(-1+parseInt(tok.get(i).substring(1))*2); tok.set(i, x); } } return join(tok); } static void replaceToken(List tok, String in, String out) { renameToken(tok, in, out); } static void replaceCollection(Collection dest, Collection src) { dest.clear(); dest.addAll(src); } static ArrayList litlist(A... a) { return new ArrayList(Arrays.asList(a)); } 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 void replaceSublist(List l, List x, List y) { int i = 0; while (true) { i = indexOfSubList(l, x, i); if (i < 0) return; // It's inefficient :D for (int j = 0; j < l(x); j++) l.remove(i); l.addAll(i, y); i += l(y); } } static int l(Object[] 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 int parseInt(String s) { return Integer.parseInt(s); } static void renameToken(List tok, String in, String out) { int renames = 0; for (int i = 1; i < tok.size(); i += 2) { if (tok.get(i).equals(in)) { tok.set(i, out); ++renames; } } } static String[] toStringArray(List list) { return list.toArray(new String[list.size()]); } static String[] toStringArray(Object o) { if (o instanceof String[]) return (String[]) o; else if (o instanceof List) return toStringArray((List) o); else throw fail("Not a list or array: " + o); } static int findCodeTokens(List tok, String... tokens) { return findCodeTokens(tok, 1, false, tokens); } static int findCodeTokens(List tok, boolean ignoreCase, String... tokens) { return findCodeTokens(tok, 1, ignoreCase, tokens); } static int findCodeTokens(List tok, int startIdx, boolean ignoreCase, String... tokens) { outer: for (int i = startIdx | 1; i+tokens.length*2-2 < tok.size(); i += 2) { for (int j = 0; j < tokens.length; j++) { String p = tokens[j], t = tok.get(i+j*2); boolean match; if (eq(p, "*")) match = true; else if (eq(p, "")) match = isQuoted(t); else if (eq(p, "")) match = isIdentifier(t); else match = ignoreCase ? eqic(p, t) : eq(p, t); if (!match) continue outer; } return i; } return -1; } static void clearAllTokens(List tok) { for (int i = 0; i < tok.size(); i++) tok.set(i, ""); } static void clearAllTokens(List tok, int i, int j) { for (; i < j; i++) tok.set(i, ""); } static boolean isIdentifier(String s) { return isJavaIdentifier(s); } static boolean eqic(String a, String b) { if ((a == null) != (b == null)) return false; if (a == null) return true; return a.equalsIgnoreCase(b); } // 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 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 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 int indexOfSubList(List x, List y, int i) { outer: for (; i+l(y) <= l(x); i++) { for (int j = 0; j < l(y); j++) if (neq(x.get(i+j), y.get(j))) continue outer; return i; } return -1; } static boolean neq(Object a, Object b) { return !eq(a, b); } static long now() { return System.currentTimeMillis(); } static void print(String s) { System.out.println(s); } static List diff(Collection a, Set b) { List l = new ArrayList(); for (String s : a) if (!b.contains(s)) l.add(s); return l; } static int jfind(List tok, String in) { List tokin = javaTok(in); replaceSublist(tokin, litlist("<", "", "quoted", "", ">"), litlist("")); replaceSublist(tokin, litlist("<", "", "id", "", ">"), litlist("")); return findCodeTokens(tok, false, toStringArray(codeTokensOnly(tokin))); } // currently only works with string lists ("= litlist(...)") // and strings. static Object loadVariableDefinition(String progIDOrSrc, String varName) { if (isSnippetID(progIDOrSrc)) progIDOrSrc = loadSnippet(progIDOrSrc); List tok = javaTok(progIDOrSrc); int i = findCodeTokens(tok, varName, "="); if (i < 0) return null; i += 4; if (isQuoted(tok.get(i))) return unquote(tok.get(i)); if (eq(get(tok, i), "litlist") && eq(get(tok, i+2), "(")) { int opening = i+2; int closing = findEndOfBracketPart(tok, opening)-1; List l = new ArrayList(); for (i = opening+2; i < closing; i += 4) l.add(unquote(tok.get(i))); return l; } throw fail("Unknown variable type or no definition in source: " + shorten(progIDOrSrc, 100) + "/" + varName); } static A get(List l, int idx) { return idx >= 0 && idx < l(l) ? l.get(idx) : null; } public static String unquote(String s) { if (s == null) return null; if (s.startsWith("[")) { int i = 1; while (i < s.length() && s.charAt(i) == '=') ++i; if (i < s.length() && s.charAt(i) == '[') { String m = s.substring(1, i); if (s.endsWith("]" + m + "]")) return s.substring(i+1, s.length()-i-1); } } if (s.startsWith("\"") /*&& s.endsWith("\"")*/ && s.length() > 1) { String st = s.substring(1, s.endsWith("\"") ? s.length()-1 : s.length()); StringBuilder sb = new StringBuilder(st.length()); for (int i = 0; i < st.length(); i++) { char ch = st.charAt(i); if (ch == '\\') { char nextChar = (i == st.length() - 1) ? '\\' : st .charAt(i + 1); // Octal escape? if (nextChar >= '0' && nextChar <= '7') { String code = "" + nextChar; i++; if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { code += st.charAt(i + 1); i++; if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { code += st.charAt(i + 1); i++; } } sb.append((char) Integer.parseInt(code, 8)); continue; } switch (nextChar) { case '\\': ch = '\\'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case '\"': ch = '\"'; break; case '\'': ch = '\''; break; // Hex Unicode: u???? case 'u': if (i >= st.length() - 5) { ch = 'u'; break; } int code = Integer.parseInt( "" + st.charAt(i + 2) + st.charAt(i + 3) + st.charAt(i + 4) + st.charAt(i + 5), 16); sb.append(Character.toChars(code)); i += 5; continue; default: ch = nextChar; // added by Stefan } i++; } sb.append(ch); } return sb.toString(); } else return s; // return original } static String shorten(String s, int max) { if (s == null) return ""; return s.length() <= max ? s : s.substring(0, Math.min(s.length(), max)) + "..."; } // i must point at the opening bracket (any of the 2 types, not type parameters) // index returned is index of closing bracket + 1 static int findEndOfBracketPart(List cnc, int i) { int j = i+2, level = 1; while (j < cnc.size()) { if (litlist("{", "(").contains(cnc.get(j))) ++level; else if (litlist("}", ")").contains(cnc.get(j))) --level; if (level == 0) return j+1; ++j; } return cnc.size(); } }