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 java.util.concurrent.locks.*;
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.*;

//!7

import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.stmt.LocalClassDeclarationStmt;
import com.github.javaparser.printer.*;
import com.github.javaparser.*;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.visitor.*;
import com.github.javaparser.Problem;
import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.text.NumberFormat;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import java.nio.file.Files;
import java.nio.file.Path;

class main {


static int findCodeTokens(List<String> tok, String... tokens) {
  return findCodeTokens(tok, 1, false, tokens);
}

static int findCodeTokens(List<String> tok, boolean ignoreCase, String... tokens) {
  return findCodeTokens(tok, 1, ignoreCase, tokens);
}

static int findCodeTokens(List<String> tok, int startIdx, boolean ignoreCase, String... tokens) {
  return findCodeTokens(tok, startIdx, ignoreCase, tokens, null);
}

static HashSet<String> findCodeTokens_specials = lithashset("*", "<quoted>", "<id>", "<int>", "\\*");
static int findCodeTokens_bails, findCodeTokens_nonbails;

static interface findCodeTokens_Matcher {
  boolean get(String token);
}

static int findCodeTokens(List<String> tok, int startIdx, boolean ignoreCase, String[] tokens, Object condition) {
  int end = tok.size()-tokens.length*2+2, nTokens = tokens.length;
  int i = startIdx | 1;

  findCodeTokens_Matcher[] matchers = new findCodeTokens_Matcher[nTokens];
  IContentsIndexedList2 indexedList = tok instanceof IContentsIndexedList2 ? (IContentsIndexedList2) tok : null;
  
  
  TreeSet<HasIndex> indices = null;
  int indicesOfs = 0;

  for (int j = 0; j < nTokens; j++) {
    String p = tokens[j];
    findCodeTokens_Matcher matcher;
    if (p.equals("*"))
      matcher = t -> true;
    else if (p.equals("<quoted>"))
      matcher = t -> isQuoted(t);
    else if (p.equals("<id>"))
      matcher = t -> isIdentifier(t);
    else if (p.equals("<int>"))
      matcher = t -> isInteger(t);
    else if (p.equals("\\*"))
      matcher = t -> t.equals("*");
    else if (ignoreCase)
      matcher = t -> eqic(p, t);
    else {
      matcher = t -> t.equals(p);
      if (indexedList != null) {
        TreeSet<HasIndex> indices2 = indexedList.indicesOf_treeSetOfHasIndex(p);
        
        if (indices2 == null) return -1;
        if (indices == null || indices2.size() < indices.size()) {
          // found shorter list
          indices = indices2;
          indicesOfs = j;
        }
      }
    }
    matchers[j] = matcher;
  }
  
  // go over shortest index
  if (indices != null) {
    int min = i+indicesOfs*2;
    SortedSet<HasIndex> tailSet = min == 1 ? indices : indices.tailSet(new HasIndex(min));
    
    outer: for (HasIndex h : tailSet) {
      int idx = h.idx-indicesOfs*2;
      if (idx >= end) break;
      
      for (int j = 0; j < nTokens; j++) try {
        if (!matchers[j].get(tok.get(idx+j*2)))
          continue outer;
      } catch (IndexOutOfBoundsException e) {
        print("fct indicesOfs=" + indicesOfs + ", h=" + h + ", idx=" + idx);
        throw e;
      }

      if (condition == null || checkTokCondition(condition, tok, idx-1))
        return idx;
    }
    return -1;
  }
 
  outer: for (; i < end; i += 2) {
    for (int j = 0; j < nTokens; j++)
      if (!matchers[j].get(tok.get(i+j*2)))
        continue outer;

    if (condition == null || checkTokCondition(condition, tok, i-1)) // pass N index
      return i;
  }
  return -1;
}
static boolean autoQuine = true;
static int maxQuineLength = 80;
static boolean assumeTriple = true;
static boolean quickInstanceOfEnabled = false; // interferes with things

// _registerThread usually costs nothing because we need
// the registerWeakHashMap mechanism anyway for ping().
// Anyway - no forced functions for now :)
static List<String> functionsToAlwaysInclude = ll(
  //"_registerThread",
  //"asRuntimeException"
);

// classes with two type parameters that can written with just one
// e.g. Pair<S> => Pair<S, S>
static Set<String> pairClasses = lithashset("Pair", "Either", "Map", "AbstractMap", "HashMap", "TreeMap", "LinkedHashMap", "MultiMap", "CompactHashMap", "WrappedMap", "F1", "IF1", "AllOnAll", "AllOnAllWithUpdates");

static String transpilingSnippetID;
//static new AtomicInteger varCount;
static ThreadLocal<AtomicInteger> varCountByThread = new ThreadLocal();
static Map<String,String> snippetCache = new HashMap();
static boolean useIndexedList2 = false, useTokenIndexedList = true;
static boolean opt_javaTok = true;
static boolean cacheStdFunctions = true, cacheStdClasses = true;
static HashMap<Long,CachedInclude> cachedIncludes = new HashMap();
static ExecutorService executor;
static List lclasses;
static long startTime, lastPrint;

// These variables have to be cleared manually for each transpilation

static HashSet<Long> included = new HashSet();
static Set<String> definitions = ciSet();
static HashMap<String, String> rewrites = new HashMap();
static HashSet<String> shouldNotIncludeFunction = new HashSet();
static HashSet<String> shouldNotIncludeClass = new HashSet();
static HashSet<String> doNotIncludeFunction = new HashSet();
static HashSet<String> addedFunctions = new HashSet();
static HashSet<String> addedClasses = new HashSet();
static HashSet<String> hardFunctionReferences = new HashSet();
static HashSet<String> mapLikeFunctions = new HashSet();
static HashSet<String> mapMethodLikeFunctions = new HashSet();
static HashSet<String> nuLikeFunctions = new HashSet();
static Map<String, String> extraStandardFunctions;
static boolean quickmainDone1, quickmainDone2;
static TreeSet<String> libs = new TreeSet();
static String mainBaseClass, mainPackage, mainClassName;
static boolean localStuffOnly = false; // for transpiling a fragment
static boolean asInclude = false; // for transpiling an include (auto-close scopes)
static boolean allowMetaCode = false; // run any embedded meta code
static List<String> metaPostBlocks, metaTransformers;
static boolean dontPrintSource = false;
static boolean dontLoadCachedIncludesFromVM = false; // for benchmarking

static class CachedInclude {
  String javax;
  Future<String> java;
  String realJava;
  
  String java() {
    return realJava != null ? realJava : getFuture(java);
  }
  
  Future<String> javaFuture() {
    return realJava != null ? nowFuture(realJava) : java;
  }
  
  void clean() {
    if (java != null) {
      realJava = getFuture(java);
      java = null;
    }
  }
}

public static void main(final String[] args) throws Exception {
  startTime = lastPrint = sysNow();
  try {
    if (!dontLoadCachedIncludesFromVM)
      vmKeepWithProgramMD5_get("cachedIncludes");
  } catch (Throwable __e) { _handleException(__e); }
  executor = Executors.newFixedThreadPool(numberOfCores());
  transpilingSnippetID = or(getThreadLocal((ThreadLocal<String>) getOpt(javax(), "transpilingSnippetID")), transpilingSnippetID);
  print("transpilingSnippetID", transpilingSnippetID);
  transpileRaw_dontCopyFromCreator = true;
  
  final Object oldPrint = or(print_byThread().get(), "print_raw");
   AutoCloseable __2 = tempInterceptPrint(new F1<String, Boolean>() {
    Boolean get(String s) {
      long now = sysNow();
      long time = now-lastPrint; // -startTime;
      lastPrint = now;
      callF(oldPrint, "[" + formatInt(time/1000, 2) + ":" + formatInt(time % 1000, 3) + "] " + s);
      return false;
    }
  }); try {
  
  try {
    _main();
  } finally {
    interceptPrintInThisThread(oldPrint);
    if (executor != null) executor.shutdown();
    executor = null;
    localStuffOnly = false;
    asInclude = false;
  }
} finally { _close(__2); }}

static void _main() { try {
  if (sameSnippetID(programID(), defaultJavaXTranslatorID())) setDefaultJavaXTranslatorID("#7");
  
  //reTok_modify_check = true;
  //if (useIndexedList) findCodeTokens_debug = true;
  javaTok_opt = opt_javaTok;
  //findCodeTokens_indexed = findCodeTokens_unindexed = 0;
  findCodeTokens_bails = findCodeTokens_nonbails = 0;
  javaTok_n = javaTok_elements = 0;
  String in = loadMainJava();
  
  print("759 STARTING " + identityHashCode(main.class));
  included.clear();
  definitions.clear();
  rewrites.clear();
  definitions.add("SymbolAsString");
  shouldNotIncludeFunction.clear();
  shouldNotIncludeClass.clear();
  doNotIncludeFunction.clear();
  addedFunctions.clear();
  addedClasses.clear();
  hardFunctionReferences.clear();
  mapLikeFunctions = cloneHashSet(tok_mapLikeFunctions());
  mapMethodLikeFunctions = cloneHashSet(tok_mapMethodLikeFunctions());
  nuLikeFunctions.clear();
  extraStandardFunctions = new HashMap();
  libs.clear();
  mainBaseClass = mainPackage = mainClassName = null;
  varCountByThread.set(null);
  quickmainDone1 = quickmainDone2 = false;
  metaPostBlocks = new ArrayList();
  metaTransformers = new ArrayList();
  dontPrintSource = false;
  
  //L ts = findTranslators(toLines(join(tok)));
  //print("Translators in source at start: " + structure(ts));
  
  List<String> tok = jtok(in);
  
  try {
    tok_definitions(tok);
    
    // add m { }
    
    if (!localStuffOnly && !hasCodeTokens(tok, "m", "{") && !hasCodeTokens(tok, "main", "{") && !hasCodeTokens(tok, "class", "main")) {
      //tok = jtok(moveImportsUp("m {\n" + in + "\n}"));
      if (l(tok) == 1) tok = singlePlusList(first(tok), dropFirst(javaTok("m {}")));
      else {
        replaceTokens_reTok(tok, 1, 2, "m {\n\n" + unnull(get(tok, 1)));
        replaceTokens_reTok(tok, l(tok)-2, l(tok)-1, unnull(get(tok, l(tok)-2)) + "}");
      }
      tok_moveImportsUp(tok);
    }
    
    // standard translate
    
    //ts = findTranslators(toLines(join(tok)));
    //print("Translators in source: " + structure(ts));
    
    if (tok_hasTranslators(tok))
      tok = jtok(defaultTranslate(join(tok)));
    
    //print("end of default translate");
    //print(join(tok));
  
    //tok_autoCloseBrackets(tok);    
    
    tok_metaTransformNow(tok);
    
    tok_processEarlyIncludes(tok);
      
    tok_earlyGeneralStuff(tok);
    
    tok = tok_processIncludes(tok); // before standard functions
    if (processConceptsDot(tok))
      tok = tok_processIncludes(tok);
    tok = localStuff1(tok);
    
   if (!localStuffOnly) {
    int safety = 0;
    boolean same = false;
    do { // BIG LOOP
      String before = join(tok);
      
      // do the non-local stuff (flags and rewrites, things that correlate across includes like tok_selfType)
      tok_selfType(tok);
      tok_mainClassNameAndPackage(tok);
      tok_definitions(tok);
      tok_ifndef(tok);
      tok_ifdef(tok);
      defineMapLikes(tok);
      if (tok_applyMapLikeFunctions(tok, mapLikeFunctions))
        functionReferences(tok);
      defineMapMethodLikes(tok);
      tok_applyMapMethodLikeFunctions(tok, mapMethodLikeFunctions);
      defineNuLikes(tok);
      tok_applyNuLikeFunctions(tok, nuLikeFunctions);
      tok_dropExtraCommas(tok); // from e.g. tok_applyMapMethodLikeFunctions
      tok_delegateTo(tok);
      tok_replaceWith(tok);
      tok_findRewrites(tok);
      tok_processRewrites(tok);
      
      // main bla(...) => mainClassName.bla(...)
      jreplace(tok, "main <id>(", or(mainClassName, "main") + ".$2(");
      
      try {
        if (safety == 0) tok = quickmain(tok);
      } catch (Throwable e) {
        printSources(tok);
        rethrow(e);
      }
      tok_collectMetaPostBlocks(tok, metaPostBlocks);
      tok_collectTransformers(tok, metaTransformers);
      tok_metaTransformNow(tok);
      
      // Hack to allow DynModule to reimplement _registerThread
      /*if (tok.contains("DynModule") && !addedClasses.contains("DynModule"))
        addStandardClasses_v2(tok);*/
      
      defineExtraSF(tok);
      tok = standardFunctions(tok);
      tok = stdstuff(tok); // all the keywords, standard
      String diff;
      long startTime = now();
      //diff = unidiff(before, join(tok));
      //print("unidiff: " + (now()-startTime) + " ms");
      //same = eq(diff, "");
      same = tok_sameTest(tok, before);
      if (!same) {
        print("Not same " + safety + ".");
        //print(indent(2, diff));
      }
      if (safety++ >= 10) {
        //print(unidiff(before, join(tok)));
        printSources(tok);
        throw fail("safety 10 error!");
      }
    } while (!same); // END OF BIG LOOP
    
    print("Post.");
    
    if (mainBaseClass != null) {
      jreplace1(tok, "class main", "class main extends " + mainBaseClass);
      mainBaseClass = null;
    }
    
    print("moveImportsUp"); tok_moveImportsUp(tok);
    
    print("Indexing"); tok = indexTokenList(tok);
    
    // POST-PROCESSING after stdstuff loop
    
    if (transpilingSnippetID != null)
      jreplace_dyn(tok, "class whatever", 
        new F2<List<String>, Integer, String>() { String get(List<String> tok, Integer cIndex) { try { 
          try { return "class " + stringToLegalIdentifier(getSnippetTitle(transpilingSnippetID)); } catch (Throwable __e) { _handleException(__e); }
          return "class Whatever";
         } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "pcall { ret \"class \" + stringToLegalIdentifier(getSnippetTitle(transpilingSni..."; }});
    
    //print("Type<A, A>"); // Type<A> to Type<A, A>
    
    print("extendClasses"); tok = extendClasses(tok);
    print("libs"); libs(tok);
    print("sourceCodeLine"); sourceCodeLine(tok);
    
    tok_overridableFunctionDefs(tok, null);
    
    // escaping for lambdas
    //jreplace(tok, "-=>", "->");
  
    // Stuff that depends on the list of inner classes (haveClasses)
    HashSet<String> haveClasses = haveClasses_actual(tok);
    print("innerClassesVar"); innerClassesVar(tok, haveClasses);
    fillVar_transpilationDate(tok);
    haveClasses_addImported(tok, haveClasses);
    print("ifclass"); tok_ifclass(tok, haveClasses);
    print("slashCasts"); slashCasts(tok, haveClasses);
    print("newWithoutNew"); newWithoutNew(tok, haveClasses);
    
    if (!assumeTriple) {
      print("Triple");
      if (tok.contains("Triple") && !haveClasses.contains("Triple")) {
        jreplace(tok, "Triple", "T3");
        haveClasses.remove("Triple");
        haveClasses.add("T3");
        slashCasts(tok, lithashset("T3"));
        tok_quickInstanceOf(tok, lithashset("T3"));
      }
      expandTriple(tok);
    }
    
    if (hasDef("SymbolAsString"))
      jreplace(tok, "Symbol", "String");
  
    print("classReferences"); expandClassReferences_lazy(tok, haveClasses);
    
    if (metaCodeAllowed()) runMetaPostBlocks(tok);

    // Error-checking
    print("Error-checking"); 
    Set<String> functions = new HashSet(findFunctions(tok));
    for (String f : hardFunctionReferences)
      if (!functions.contains(f))
        throw fail("Function " + f + " requested, but not supplied");
  
    print("autoImports"); tok = autoImports(tok); // faster to do it at the end
    
    print("definitions=" + sfu(definitions));
    if (containsOneOfIC(definitions, "allpublic", "reparse", "PublicExceptTopClass")) {
      // Fire up the Java parser & pretty printer!
      print(containsIC(definitions, "allpublic")? "Making all public." : "Reparsing.");
      //try {
        String src = join(tok);
        try {
          if (containsIC(definitions, "PublicExceptTopClass"))
            src = javaParser_makeAllPublic(src, "notTopLevelClassDecl" , true);
          else if (containsIC(definitions, "keepComments"))
            src = javaParser_makeAllPublic_keepComments(src);
          else if (containsIC(definitions, "allpublic"))
            src = javaParser_makeAllPublic(src);
          else
            src = javaParser_reparse_keepComments(src);
        } catch (Throwable e) {
          extractAndPrintJavaParseError(src, e);
          File f = javaxCachesDir("error-source.java");
          saveTextFileVerbose(f, src);
          dontPrintSource = true;
          throw e;
        }
        tok = jtok(src);
      /*} catch e {
        S src = join(tok);
        if (!dontPrintSource)
          print(src);
        print(f2s(saveProgramTextFile("error.java", src)));
        throw rethrow(e);
      }*/
    }
    
    // Do this after JavaParser (because it doesn't like package after class)
    
    if (mainPackage != null) {
      print("mainPackage");
      tokPrepend(tok, 1, "package " + mainPackage + ";\n");
      reTok(tok, 1, 2);
    }
    
    if (mainClassName != null) {
      print("mainClassName");
      jreplace(tok, "class main", "class " + mainClassName);
      jreplace(tok, "main.class", mainClassName + ".class");
      //tokPrepend(tok, 1, "class main {}\n"); // so main.class is generated and compiler sanity checks succeed. we can later skip it in the JavaXClassLoader
    }
    
    if (nempty(libs)) {
      print("Adding libs: " + libs);
      tok.add(concatMap_strings(new F1<String, String>() { String get(String s) { try {  return "\n!" + s;  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "\"\\n!\" + s"; }}, libs));
    }
   } // if (!localStuffOnly)
  } catch (Throwable e) {
    String src = join(tok);
    if (!dontPrintSource)
      print(src);
    print(f2s(saveProgramTextFile("error.java", src)));
    throw rethrow(e);
  }
  
  /*if (useIndexedList)
    print("Indexed/unindexed lookups: " + findCodeTokens_indexed + "/" + findCodeTokens_unindexed + ", lists made: " + IndexedList2.instances);
  print("findCodeToken bails: " + findCodeTokens_bails + "/" + findCodeTokens_nonbails);
  print("javaToks: " + javaTok_n + "/" + javaTok_elements);*/
  
  print("Saving.");
  
  // for dexcompile.php
  if (mainClassName != null)
    tokPrepend(tok, 0, "//FILENAME: " 
      + (mainPackage != null ? mainPackage.replace(".", "/") + "/" : "") + mainClassName + ".java\n");
    
  if (mainJava != null)
    mainJava = join(tok);
  else if (tok.contains("package"))
    splitJavaFiles(tok);
  else
    saveMainJava(tok);
} catch (Exception __e) { throw rethrow(__e); } }

static List<String> localStuff1(List<String> tok) {
  int safety = 0, i;
  boolean same = false;
  tok = indexTokenList(tok);
  
  tok_scopes(tok, "autoCloseScopes" , true);
    
  do {
    String before = join(tok);
    
    //print("localStuff loop " + safety);
    
    earlyStuff(tok);
    
    // EARLY local stuff goes here
    
    tok_earlyGeneralStuff(tok);
    
    conceptDeclarations(tok);
    tok_recordDecls(tok);
  
    tok = multilineStrings(tok);
    tok_singleQuoteIdentifiersToStringConstants(tok);
    inStringEvals(tok);
    tok_listComprehensions(tok);
    
    tok_for_single(tok);
    
    tok_for_unpair(tok); // Do this...
    tok_doubleFor_v2(tok); // ...before this
    
    tok_forUnnull(tok);
    tok_ifCast(tok);
    forPing(tok);
    tok_directSnippetRefs(tok);
    quicknu(tok);
    //tok_juxtaposeCalls(tok);
    
    jreplace(tok, "LLS", "L<LS>");
    jreplace(tok, "LS", "L<S>");
    jreplace(tok, "ES", "Ext<S>");
    jreplace(tok, "ExtS", "Ext<S>");
    
    jreplace(tok, "dispose <id>;", "{ cleanUp($2); $2 = null; }");
  
    jreplace(tok, "do ping {", "do { ping();");
    
    replaceKeywordBlock(tok,
      "swing",
      "{ swing(r {",
      "}); }");
      
    replaceKeywordBlock(tok,
      "androidUI",
      "{ androidUI(r {",
      "}); }");
      
    replaceKeywordBlock(tok,
      "withDBLock",
      "{ withDBLock(r {",
      "}); }");
      
    replaceKeywordBlock(tok, "afterwards", "temp tempAfterwards(r {", "});");
      
    for (String keyword : ll("tokcondition", "tokCondition"))
      replaceKeywordBlock(tok,
        keyword,
        "new TokCondition { bool get(final L<S> tok, final int i) {",
        "}}");
      
    jreplace(tok, "synced <id>", "synchronized $2");
    jreplace(tok, "sync <id>", "synchronized $2");
    
    replaceKeywordBlock(tok, "answer",
      "static S answer(S s) {\nfinal new Matches m;\n",
      "\nret null;\n}");
      
    replaceKeywordBlock(tok, "static-pcall",
      "static { pcall {",
      "}}");
      
    replaceKeywordBlock(tok, "loading",
      "{ temp tempShowLoadingAnimation(); ",
      "}");
  
    replaceKeywordPlusQuotedBlock(tok, "loading",
      new Object() { String[] get(List<String> tok, int i) {
        String text = tok.get(i+2);
        return new String[] {
          "{ temp tempShowLoadingAnimation(" + text + "); ",
          "}" };
      }});
      
    while ((i = jfind(tok, "visualize as")) >= 0) {
      int j = tok_findEndOfStatement(tok, i); // expression, rather
      tok.set(i+2, "{ ret");
      tok.set(j-1, "; }");
      reTok(tok, i, j);
    }
      
    jreplace(tok, "visualize {", "public JComponent visualize() {", tokCondition_beginningOfMethodDeclaration());
  
    jreplace(tok, "visualize2 {", "JComponent visualize2() {", tokCondition_beginningOfMethodDeclaration());
    
    replaceKeywordBlock(tok, "start-thread-printDone", "start-thread {", "printDone(); }");

    replaceKeywordBlock(tok, "start-thread", "start { thread \"Start\" { temp enter(); pcall {", "}}}");
    
    jreplace(tok, "start {", "void start() { super.start();", tokCondition_beginningOfMethodDeclaration());
    
    // run { ... } => public void run() { ... }
    jreplace(tok, "run {", "public void run() {", new TokCondition() { boolean get(final List<String> tok, final int i) {
      return neqGet(tok, i-1, "void");
    }});

    replaceKeywordBlock(tok, "html",
      "static O html(S uri, fMap<S, S> params) ctex " + "{\n", "}");
    
    replaceKeywordBlock(tok, "afterVisualize",
      "visualize { JComponent _c = super.visualize();",
      "ret _c; }");
    
    replaceKeywordBlock(tok, "enhanceFrame",
      "void enhanceFrame(Container f) { super.enhanceFrame(f);",
      "}");
    
    if (assumeTriple) {
      jreplace(tok, "Triple", "T3");
      expandTriple(tok);
    }
    
    tok_shortFinals(tok);
    
    tok_moduleClassDecls(tok);

    jreplace(tok, "static sync", "static synchronized");
    jreplace(tok, "sclass", "static class");
    jreplace(tok, "srecord", "static record");
    jreplace(tok, "record noeq", "noeq record");
    jreplace(tok, "asclass", "abstract static class");
    jreplace(tok, "sinterface", "static interface");
    jreplace(tok, "ssynchronized", "static synchronized");
  
    jreplace(tok, "ssvoid", "static synchronized void");
    jreplace(tok, "sbool", "static bool");
    jreplace(tok, "fbool", "final bool");
    jreplace(tok, "sint", "static int");
    jreplace(tok, "snew", "static new");
    jreplace(tok, "sv <id>", "static void $2");
    jreplace(tok, "pvoid", "public void");
  
    // "sS" => static S
    jreplace(tok, "sS", "static S");
  
    // "sO" => static O
    jreplace(tok, "sO", "static O");
  
    // "sL" => static L
    jreplace(tok, "sL", "static L");
  
    // "toString {" => "public S toString() {"
    jreplace(tok, "toString {", "public S toString() {");
    
    jreplace(tok, "Int", "Integer");
    jreplace(tok, "Bool", "Boolean");
    jreplace(tok, "BigInt", "BigInteger");
    jreplace(tok, "Char", "Character");
    
    jreplace(tok, "Sym", "Symbol");
    jreplace(tok, "SymSym", "SymbolSymbol");
    
    jreplace(tok, "SS", "Map<S>");
    jreplace(tok, "SymbolSymbol", "Map<Symbol>");
    
    jreplace(tok, "PairS", "Pair<S>");
    jreplace(tok, "LPairS", "L<Pair<S>>");
    
    jreplace(tok, "T3S", "T3<S>");
    jreplace(tok, "F1S", "F1<S>");
    
    jreplace(tok, "ItIt", "IterableIterator");
    jreplace(tok, "CloseableItIt", "CloseableIterableIterator");

    jreplace(tok, "class <id> > <id> {", "class $2 extends $4 {");
    jreplace(tok, "class <id> > <id><<id>> {", "class $2 extends $4  $5 $6 $7 {");
    
    jreplace(tok, "ISegmenter", "IF1<BufferedImage, L<Rect>>");
    
    // IPred<A> => IF1<A, Bool>
    jreplace(tok, "IPred<<id>>", "IF1<$3, Bool>");
    
    // "on fail {" => "catch (Throwable _e) { ... rethrow(_e); }"
    replaceKeywordBlock(tok, "on fail",
      "catch (Throwable _e) {",
      "\nthrow rethrow(_e); }");
  
    // "catch {" => "catch (Throwable _e) {"
    jreplace(tok, "catch {", "catch (Throwable _e) {");
  
    // "catch print e {" => "catch e { _handleException(e); "
    jreplace(tok, "catch print <id> {", "catch $3 { _handleException($3);");
  
    // "catch print short e {" => "catch e { printExceptionShort(e); "
    jreplace(tok, "catch print short <id> {", "catch $4 { printExceptionShort($4);");
    
    // "catch X e {" => "catch (X e) {"
    jreplace(tok, "catch <id> <id> {", "catch ($2 $3) {");
  
    // "catch e {" => "catch (Throwable e) {" (if e is lowercase)
    jreplace(tok, "catch <id> {", "catch (Throwable $2) {", new TokCondition() { boolean get(final List<String> tok, final int i) {
      String word = tok.get(i+3);
      return startsWithLowerCaseOrUnderscore(word);
    }});
    
    jreplace(tok, "+ +", "+", new TokCondition() { boolean get(final List<String> tok, final int i) {
      //printStructure("++: ", subList(tok, i-1, i+6));
      if (empty(_get(tok, i+2))) return false; // no space between the pluses
      if (empty(_get(tok, i)) && eq("+", _get(tok, i-1))) return false;  // an actual "++" at the left
      if (empty(_get(tok, i+4)) && eq("+", _get(tok, i+5))) return false;  // an actual "++" at the right
      //print("doing it");
      return true;
    }});
  
    // some crazy fancy syntax
    jreplace(tok, "set <id>;", "$2 = true;");
    
    // single underscore (not allowed in Java anymore) to double underscore
    jreplace(tok, "_", "__");
    
    // [stdEq] -> implementation of equals() and hashCode()
    jreplace(tok, "[stdEq]",
      "public bool equals(O o) { ret stdEq2(this, o); }\n" +
      "public int hashCode() { ret stdHash2(this); }");
    
    // [concepts] "concept.field!" for dereferencing references
    
    jreplace(tok, "*!", "$1.get()", new TokCondition() { boolean get(final List<String> tok, final int i) {
      String l = tok.get(i+1);
      if (!(isIdentifier(l) || eq(l, ")"))) return false;
      if (tok.get(i+2).contains("\n")) return false; // no line break between <id> and !
      if (nempty(tok.get(i+4))) return true; // space after = ok
      String t = _get(tok, i+5);
      if (t == null) return false;
      if (isIdentifier(t) || eqOneOf(t, "=", "(")) return false;
      return true;
    }});
    
    jreplace(tok, "for (<id> <id>)", "for ($3 $4 : list($3))");
    jreplace(tok, "for (final <id> <id>)", "for (final $4 $5 : list($4))");

    // "continue unless", "break unless"
    
    for (String phrase : ll("continue unless", "break unless"))
      while ((i = jfind(tok, phrase)) >= 0) {
        String keyword = tok.get(i);
        int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
        replaceTokens(tok, i, i+4, "{ if (!(");
        tok.set(j, ")) " + keyword + "; }");
        reTok(tok, i, j+1);
      }
    
    // "continue if", "break if"
    
    for (String phrase : ll("continue if", "break if"))
      while ((i = jfind(tok, phrase)) >= 0) {
        String keyword = tok.get(i);
        int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
        replaceTokens(tok, i, i+4, "{ if (");
        tok.set(j, ") " + keyword + "; }");
        reTok(tok, i, j+1);
      }
    
    // "return if"
    
    while ((i = jfind(tok, "return if")) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      replaceTokens(tok, i, i+4, "{ if (");
      tok.set(j, ") return; }");
      reTok(tok, i, j+1);
    }
    
    // "return unless"
    
    while ((i = jfind(tok, "return unless")) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      replaceTokens(tok, i, i+4, "{ if (!(");
      tok.set(j, ")) return; }");
      reTok(tok, i, j+1);
    }
    
    // "return <id> if"
    
    while ((i = jfind(tok, "return <id> if")) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      tok.set(j, ") return " + tok.get(i+2) + "; }");
      replaceTokens(tok, i, i+6, "{ if (");
      reTok(tok, i, j+1);
    }
    
    // "return with <statement>" / "continue with <statement>" / "break with <statement>"
    
    while ((i = jfind(tok, "<id> with", new TokCondition() { boolean get(final List<String> tok, final int i) { return eqOneOf(tok.get(i+1), "return", "continue", "break"); }})) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      tok.set(j, "; " + tok.get(i) + "; }");
      replaceTokens(tok, i, i+3, "{");
      reTok(tok, i, j+1);
    }
    
    // return "bla" with <statement>
    
    while ((i = jfindOneOf(tok,
      "return <quoted> with", "return <id> with")) >= 0) {
      String result = tok.get(i+2);
      int j = scanOverExpression(tok, getBracketMap(tok), i+6, ";");
      replaceTokens(tok, i, i+5, "{");
      tok.set(j, "; return " + result + "; }");
      reTok(tok, i, j+1);
    }
    
    tok_debugStatements(tok);

    // while not null (...) / if not null (...)
    
    while ((i = jfind_check("not",tok, "<id> not null (", new TokCondition() { boolean get(final List<String> tok, final int i) {
      return eqOneOf(_get(tok, i+1), "if", "while");
    }})) >= 0) {
      int closingBracket = findEndOfBracketPart(tok, i+6)-1;
      replaceTokens(tok, i+2, i+6, "(");
      tok.set(closingBracket, ") != null)");
      reTok(tok, i, closingBracket+1);
    }
    
    // while null (...) / if null (...)
    
    while ((i = jfind_check("null",tok, "<id> null (", new TokCondition() { boolean get(final List<String> tok, final int i) {
      return eqOneOf(_get(tok, i+1), "if", "while");
    }})) >= 0) {
      int closingBracket = findEndOfBracketPart(tok, i+4)-1;
      replaceTokens(tok, i+2, i+4, "(");
      tok.set(closingBracket, ") == null)");
      reTok(tok, i, closingBracket+1);
    }
    
    // Replace $1 with m.unq(0) etc. - caveat: this blocks identifiers $1, $2, ...
    for (i = 1; i < l(tok); i += 2) {
      String s = tok.get(i);
      if (s.startsWith("$")) {
        s = substring(s, 1);
        if (isInteger(s)) {
          tok.set(i, "m.unq(" + (parseInt(s)-1) + ")");
          reTok(tok, i);
        }
      }
    }
  
    // instanceof trickery
    
    jreplace(tok, "is a <id>", "instanceof $3");
    jreplace(tok, "!<id> instanceof <id>.<id>", "!($2 instanceof $4.$6)");
    jreplace(tok, "!<id> instanceof <id>", "!($2 instanceof $4)");
    jreplace(tok, "<id> !instanceof <id>", "!($1 instanceof $4)");
    
    // map func1 func2 func3(...) => mapFGH(f func1, f func2, f func3, ...)
    jreplace(tok, "map <id> <id> <id>(", "mapFGH(f $2, f $3, f $4,");

    // map func1 func2(...) => mapFG(f func1, f func2, ...)
    jreplace(tok, "map <id> <id>(", "mapFG(f $2, f $3,");

    // "ref->bla" for dereferencing Concept.Ref or ThreadLocal or other
    // For lambdas, use SPACES on the left or right of the arrow!
    //jreplace(tok, "<id> ->", "$1.get().");
    jreplace(tok, "->", ".get().", new TokCondition() { boolean get(final List<String> tok, final int i) {
      return empty(tok.get(i)) // no space on left of arrow
        && empty(tok.get(i+2)) // no space inside of arrow
        && empty(tok.get(i+4)) // no space on right of arrow
        && !eq(_get(tok, i-1), "-"); // i-->0;
    }});
    
    // shortened subconcept declaration (before star constructors!)
    shortenedSubconcepts(tok);
    
    // "case" as a variable name ( => _case)
    
    caseAsVariableName(tok);
    
    // "do" as a function name ( => dO)
    
    tok_doAsMethodName(tok);
    
    // "continue" as a function name ( => _continue)
    continueAsFunctionName(tok);
    
    tok_extend(tok);
    
    jreplace(tok, "pn {", "p-noconsole {");
      
    // Do these BEFORE awt replacement! ("p-awt" contains "awt" token)
    replaceKeywordBlock(tok, "r-awt", "r { awt {", "}}");
    if (hasCodeTokens(tok, "p", "-")) tok_p_old(tok);

    replaceKeywordBlock(tok, "awt-messagebox", "awt { pcall-messagebox {", "}}");
    replaceKeywordBlock(tok, "awt", "swingLater(r {", "});");
  
    jreplace(tok, "p-android {", "set flag Android. p {");
      
    unswing(tok);
    lockBlocks(tok);
    tempBlocks(tok);
    tok_switchTo(tok);
    
    // trim x;
    
    jreplace(tok, "trim <id>;", "$2 = trim($2);");
  
    // iterate with index
  
    jreplace (tok, "for <id> over <id>:", "for (int $2 = 0; $2 < l($4); $2++)");
    jreplace (tok, "for <id> backwards over <id>:", "for (int $2 = l($5)-1; $2 >= 0; $2--)");
    jreplace (tok, "for <id>, <id> <id> over <id>: {", "for (int $2 = 0; $2 < l($7); $2++) { $4 $5 = $7.get($2);");
    jreplace (tok, "for <id>, <id> <id> backwards over <id>: {", "for (int $2 = l($8)-1; $2 >= 0; $2--) { $4 $5 = $8.get($2);");
    jreplace (tok, "for <id> to <id>:", "for (int $2 = 0; $2 < $4; $2++)");
    jreplace (tok, "for <id> to <int>:", "for (int $2 = 0; $2 < $4; $2++)");
    
    tok = expandShortTypes(tok);
      
    tok_equalsCast(tok);
    tok_equalsOptCast(tok);

    replaceKeywordBlock(tok, "r-thread-messagebox", "r-thread { pcall-messagebox {", "}}");
    
    replaceKeywordBlock(tok, "thread-messagebox", "thread { pcall-messagebox {", "}}");
    
    jreplace(tok, "rThread {", "r-thread {");
    jreplace(tok, "rThreadEnter {", "r-thread { temp enter(); ");
  
    replaceKeywordBlock(tok, "r-thread", "runnableThread(r {", "})");
    rNamedThread(tok);
    
    // only works in the scope of a DynModule
    jreplace(tok, "rEnter {", "r { temp enter(); ");
  
    replaceKeywordBlock(tok, "r-messagebox", "r { pcall-messagebox {", "}}");
    
    jreplace(tok, "r <id> + r <id>", "r { $2(); $5(); }");
    
    // runnable and r - now also with automatic toString if enabled
    for (String keyword : ll("runnable", "r")) {
      while ((i = jfind(tok, keyword + " {")) >= 0) {
        int idx = findCodeTokens(tok, i, false, "{");
        int j = findEndOfBracketPart(tok, idx);
        List<String> contents = subList(tok, idx+1, j-1);
        replaceTokens(tok, i, j+1, "new Runnable {"
          + "  public void run() ctex { " + tok_addSemicolon(contents) + "\n}"
          + (autoQuine ? tok_autoQuineFunc(contents) : "")
          + "}");
        reTok(tok, i, j+1);
      }
      
      while ((i = jfind(tok, keyword + " <quoted> {")) >= 0) {
        int idx = findCodeTokens(tok, i, false, "{");
        int j = findEndOfBracketPart(tok, idx);
        List<String> contents = subList(tok, idx+1, j-1);
        replaceTokens(tok, i, j+1, "new Runnable {"
          + "  public void run() ctex { " + tok_addSemicolon(contents) + "\n}"
          + "  toString { ret " + tok.get(i+2) + "; }"
          + (autoQuine ? tok_autoQuineFunc(contents, "_shortenedSourceCode") : "")
          + "}");
        reTok(tok, i, j+1);
      }
    }
    
    replaceKeywordBlock(tok,
      "expectException",
      "{ bool __ok = false; try {",
      "} catch { __ok = true; } assertTrue(\"expected exception\", __ok); }");
      
    while ((i = tok.indexOf("tex")) >= 0) {
      tok.set(i, "throws Exception");
      tok = jtok(tok);
    }
    
    // shorter & smarter whiles
    
    jreplace(tok, "while true", "while (true)");
    jreplace(tok, "while licensed", "while (licensed())");
    jreplace(tok, "repeat {", "while (licensed()) {");
    tok_repeatWithSleep(tok);
    
    // null; => return null; etc.
  
    Object cond = new TokCondition() { boolean get(final List<String> tok, final int i) {
      return tok_tokenBeforeLonelyReturnValue(tok, i-1);
    }};
    jreplace(tok, "null;", "return null;", cond);
    jreplace(tok, "false;", "return false;", cond);
    jreplace(tok, "true;", "return true;", cond);
    jreplace(tok, "this;", "return this;", cond);
    
    // ok <cmd> => ret "OK" with <cmd>
    jreplace(tok, "ok <id>", "return \"OK\" with $2");
    replaceKeywordBlock(tok, "ok", "{", " return \"OK\"; }");
  
    // "myFunction;" instead of "myFunction();" - quite rough
    // (isolated identifier as function call)
    cond = new TokCondition() {
      boolean get(List<String> tok, int i) {
        String word = tok.get(i+3);
        //print("single word: " + word);
        return !eqOneOf(word, "break", "continue", "return", "else", "endifdef", "endif");
      }
    };
    for (String pre : litlist("}", ";"))
      jreplace(tok, pre + " <id>;", "$1 $2();", cond);
  
    // shorter match syntax for answer methods
    
    jreplace(tok, "if <quoted> || <quoted>",
      "if (matchOneOf(s, m, $2, $5))");
      
    // "bla * bla | blubb * blubb"
    jreplace_dyn(tok, "if <quoted>", new F2<List<String>, Integer, String>() { String get(List<String> tok, Integer cIdx) { try { 
      String s = unquote(tok.get(cIdx+2));
      //print("multimatch: " + quote(s));
      List<String> l = new ArrayList();
      for (String pat : splitAtJavaToken(s, "|")) {
        //print("multimatch part: " + quote(pat));
        if (pat.contains("..."))
          l.add("matchX(" + quote(trim(pat)) + ", s, m)");
        else if (javaTok(pat).contains("*"))
          l.add("match(" + quote(trim(pat)) + ", s, m)");
        else
          l.add("match(" + quote(trim(pat)) + ", s)");
      }
      return "if (" + join(" || ", l) + ")";
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S s = unquote(tok.get(cIdx+2));\r\n      //print(\"multimatch: \" + quote(s));\r\n ..."; }}, new TokCondition() { boolean get(final List<String> tok, final int i) {
      return javaTokC(unquote(tok.get(i+3))).contains("|");
    }});
    
    // "...bla..."
    jreplace(tok, "if <quoted>", "if (find3plusRestsX($2, s, m))",
      new TokCondition() { boolean get(final List<String> tok, final int i) {
        return startsAndEndsWith(unquote(tok.get(i+3)), "...");
      }});
      
    // "bla..."
    jreplace(tok, "if <quoted>", "if (matchStartX($2, s, m))",
      new TokCondition() { boolean get(final List<String> tok, final int i) {
        return unquote(tok.get(i+3)).endsWith("...");
      }});
      
    // "bla"
    jreplace(tok, "if <quoted>", "if (match($2, s))",
      new TokCondition() { boolean get(final List<String> tok, final int i) {
        return !javaTokC(unquote(tok.get(i+3))).contains("*");
      }});
    // "bla * bla"
    jreplace(tok, "if <quoted>", "if (match($2, s, m))");
    jreplace(tok, "if match <quoted>", "if (match($3, s, m))");
    
    jreplace(tok, "if <id> eq <quoted>", "if (eq($2, $4))");
  
    tok_dropExtraCommas(tok);
    
    // additional translations (if necessary)
    
    jreplace(tok, "pcall ping {", "pcall { ping();");
    
    replaceKeywordBlock(tok, ") pcall",
      ") { pcall {",
      "}}");
    
    replaceKeywordBlock(tok,
      "pcall",
      "try {",
      "} catch (Throwable __e) { _handleException(__e); }");
  
    replaceKeywordBlock(tok,
      "pcall-short",
      "try {",
      "} catch (Throwable __e) { print(exceptionToStringShort(__e)); }");
  
    replaceKeywordBlock(tok,
      "pcall-silent",
      "try {",
      "} catch (Throwable __e) { silentException(__e); }");
  
    replaceKeywordBlock(tok,
      "pcall-messagebox",
      "try {",
      "} catch __e { messageBox(__e); }");
  
    replaceKeywordBlock(tok,
      "pcall-infobox",
      "try {",
      "} catch __e { infoBox(__e); }");
  
    tok = dialogHandler(tok);
    
    replaceKeywordBlock(tok, "exceptionToUser",
      "try {",
      "} catch (Throwable __e) { ret exceptionToUser(__e); }"); 
  
    if (hasCodeTokens(tok, "twice", "{"))
      replaceKeywordBlock(tok, "twice",
        "for (int __twice = 0; __twice < 2; __twice++) {",
        "}"); 
  
    while ((i = findCodeTokens(tok, "bench", "*", "{")) >= 0) {
      int j = findEndOfBracketPart(tok, i+4)-1;
      String time = makeVar("time");
      String v = makeVar("bench");
      String n = tok.get(i+2);
      tok.set(i, "{ long " + time + " = sysNow(); for (int " + v + " = 0; " + v + " < " + n + "; " + v + "++)");
      tok.set(i+2, "");
      tok.set(j, "} printBenchResult(sysNow()-" + time + ", " + n + "); }");
      reTok(tok, i, j+1);
    }
  
    replaceKeywordBlockDyn(tok,
      "time",
      new Object() { String[] get() {
        String var = makeVar("startTime");
        return new String[] {
          "{ long " + var + " = sysNow(); try { ",
          "} finally { " + var + " = sysNow()-" + var + "; saveTiming(" + var + "); } }"};
      }});
    
    // version without { }
    replaceKeywordBlockDyn(tok,
      "time2",
      new Object() { String[] get() {
        String var = makeVar("startTime");
        return new String[] {
          "long " + var + " = sysNow(); ",
          " " + var + " = sysNow()-" + var + "; saveTiming(" + var + "); "};
      }});
    
    // time "bla" {
    // time msg {
    replaceKeywordPlusQuotedOrIDBlock(tok,
      "time",
      new Object() { String[] get(List<String> tok, int i) {
        String var = makeVar("startTime");
        return new String[] {
          "long " + var + " = sysNow(); ",
          " done2_always(" + tok.get(i+2) + ", " + var + "); "};
      }});
    
    if (hasCodeTokens(tok, "assertFail", "{")) {
      String var = makeVar("oops");
      
      replaceKeywordBlock(tok,
        "assertFail",
        "boolean " + var + " = false; try {",
        "\n" + var + " = true; } catch (Exception e) { /* ok */ } assertFalse(" + var + ");"); 
    }
    
    replaceKeywordBlock(tok,
      "yo",
      "try {",
      "} catch (Exception " + makeVar("e") + ") { ret false; }",
      new TokCondition() { boolean get(final List<String> tok, final int i) {
        return neqOneOf(_get(tok, i-1), "svoid", "void");
      }});
  
    replaceKeywordBlock(tok,
      "awtIfNecessary",
      "swingNowOrLater(r " + "{",
      "});");
      
    ctex(tok);
      
    replaceKeywordBlock(tok,
      "actionListener",
      "new java.awt.event.ActionListener() { " +
        "public void actionPerformed(java.awt.event.ActionEvent _evt) { pcall-messagebox {",
      "}}}");
      
    for (String keyword : ll("autocloseable", "autoCloseable"))
      /*replaceKeywordBlock(tok,
        keyword,
        "new AutoCloseable() { public void close() throws Exception {",
        "}}");*/
        replaceKeywordBlock_dyn2_legacy(tok, keyword, new Object() {
          String[] get(List<String> tok, int iOpening, int iClosing) {
            List<String> contents = subList(tok, iOpening+1, iClosing);
            return new String[] {
              "new AutoCloseable() { toString { ret " + quote(shorten(maxQuineLength, trimJoin(contents))) + "; } public void close() throws Exception {",
              "}}"
            };
          }
        });
      
    // try answer (string, test with nempty)
    while ((i = findCodeTokens(tok, "try", "answer")) >= 0) {
      int j = findEndOfStatement(tok, i);
      String v = makeVar();
      boolean needCurly = !eqGet(tok, i-2, "{");
      tok.set(i, (needCurly ? "{" : "") + " S " + v);
      tok.set(i+2, "=");
      tok.set(j-1, "; if (!empty(" + v + ")) ret " + v + "; " + (needCurly ? "}" : ""));
      reTok(tok, i, j);
    }
  
    // try bool[ean] (try answer with Bool type)
    while ((i = findCodeTokens(tok, "try", "boolean")) >= 0) {
      int j = findEndOfStatement(tok, i);
      String v = makeVar();
      tok.set(i, "{ Bool " + v);
      tok.set(i+2, "=");
      tok.set(j-1, "; if (" + v + " != null) ret " + v + "; }");
      reTok(tok, i, j);
    }
    
    // S s = bla(), return if null; => S s = bla(); if (s == null) return;
    while ((i = jfind(tok, ", return if null;")) >= 0) {
      int j = tok_findBeginningOfStatement(tok, i);
      print("Found statement " + j + "/" + i + " - " + joinSubList(tok, j-1, i+5*2-1));
      String var = getVarDeclarationName(subList(tok, j-1, i));
      replaceTokens_reTok(tok, i, i+5*2-1, "; if (" + var + " == null) ret;");
    }
    
    // <statement>, print "..."; => { <statement>; print("..."); }
    while ((i = jfind(tok, ", print <quoted>;")) >= 0) {
      int j = tok_findBeginningOfStatement(tok, i);
      replaceTokens_reTok(tok, i, i+4*2-1, "; print(" + tok.get(i+4) + "); }");
      tokPrepend_reTok(tok, j, "{ ");
    }
    
    // return if null <expression> => if (<expression> == null) return;
    while ((i = jfind(tok, "return if null")) >= 0) {
      int j = findEndOfStatement(tok, i);
      clearTokens(tok, i, i+2);
      tok.set(i+4, "((");
      tok.set(j-1, ") == null) ret;");
      reTok(tok, i, j);
    }
    
    // return optional (return if not null)
    while ((i = jfind_check("optional",tok, "return optional <id> =")) >= 0) {
      int j = findEndOfStatement(tok, i);
      String v = tok.get(i+4);
      clearTokens(tok, i+2, i+4);
      tok.set(i, "{");
      tok.set(j-1, "; if (" + v + " != null) ret " + v + "; }");
      reTok(tok, i, j);
    }
    
    // try object (return if not null)
    while ((i = jfind_check("object",tok, "try object")) >= 0) {
      int j = findEndOfStatement(tok, i);
      clearTokens(tok, i, i+3);
      boolean isDecl = isIdentifier(get(tok, i+4)) && isIdentifier(get(tok, i+6)) && eqGet(tok, i+8, "=");
      if (isDecl) {
        String v = get(tok, i+6);
        tok.set(i, "{");
        tok.set(j-1, "; if (" + v + " != null) ret " + v + "; }");
      } else {
        String v = makeVar();
        tok.set(i, "{ O " + v + "=");
        tok.set(j-1, "; if (" + v + " != null) ret " + v + "; }");
      }
      reTok(tok, i, j);
    }
    
    // try Int i = ...; (return if not null, shorter version of "try object")
    while ((i = jfind(tok, "try <id> <id> =")) >= 0) {
      int j = findEndOfStatement(tok, i);
      String type = tok.get(i+2), v = tok.get(i+4);
      tok.set(i, "{");
      tok.set(j-1, "; if (" + v + " != null) ret " + v + "; }");
      reTok(tok, i, j);
    }
    
    // debug print ...; => if (debug) print(...);
    while ((i = jfind(tok, "debug print")) >= 0) {
      int j = findEndOfStatement(tok, i);
      replaceTokens(tok, i, i+4, "if (debug) print(");
      tok.set(j-1, ");");
      reTok(tok, i, j);
    }
    
    functionReferences(tok);
    tok_expandLPair(tok);
    tok_expandPairL(tok);
    tok_expandLT3(tok);
    quicknew2(tok);
    
    // X x = nu(+...)  =>  X x = nu X(+...)
    jreplace(tok, "<id> <id> = nu(+", "$1 $2 = nu $1(+");
    // X x = nu(a := ...)  =>  X x = nu X(a := ...)
    jreplace(tok, "<id> <id> = nu(<id> :=", "$1 $2 = nu $1($6 :=");
    
    tok_expandVarCopies(tok); // AFTER the lines just above
    tok_replaceColonEqualsSyntax(tok); // ditto

    tok_unpair(tok);
    tok_cachedFunctions(tok);
    tok_simplyCachedFunctions(tok);
    
    tok_optPar(tok);
    
    throwFailEtc(tok);
    tok_typeAA(tok, pairClasses);

    // do this after expanding sclass etc.
    tok = tok_expandStarConstructors(tok);
    
    tok_kiloConstants(tok);
    //tok_colonMessages(tok);
    
    while ((i = jfind(tok, "shit:")) >= 0) {
      int j = tok_findEndOfStatement(tok, i);
      tok.set(i+2, "(");
      tok.set(j-1, ");");
      reTok(tok, i, j);
    }
      
    jreplace(tok, "shit(", "ret with print(");
    
    tok_virtualTypes(tok);
    tok_autoLongConstants(tok);
    
    // common misordering of type arguments
    jreplace(tok, "boolean <A>", "<A> boolean");
    
    tok_unimplementedMethods(tok);
    tok_switchableFields(tok);
    tok_autoDisposeFields(tok);
    tok_shortVisualize(tok);
    tok_whileGreaterThan(tok);
    tok_ifThenEnd(tok);
    
    tok_autoInitVars(tok);
    
    tok_fixBadTypeParameterOrder(tok);
    
    // shortened method declarations BEFORE standardFunctions
    jreplace(tok, "svoid", "static void");
    jreplace(tok, "void <id> {", "$1 $2() {");
    jreplace(tok, "void <id> thread {", "$1 $2() thread {");
    jreplace(tok, "String <id> {", "$1 $2() {");
    jreplace(tok, "Object <id> {", "$1 $2() {");
    jreplace(tok, "List <id> {", "$1 $2() {");

    namedThreads(tok);
    threads(tok);
    
    //tok_maxEquals(tok);
    
    tok_questionDot(tok);
    
    tok_embeddedFunctions(tok);
    
    jreplace(tok, "<id>[] <id> = new[", "$1[] $4 = new $1[");
    
    jreplace(tok, "<id> ifNull =", "if ($1 == null) $1 =");
    
    jreplace(tok, "class <id> extends <id><.<id>>", "class $2 extends $4<$2.$7>");
    
    tok_once(tok); // must come before next line so you can combine them
    tok_ifRecordMatch(tok);
    
    jreplace(tok, "<id> ||=", "$1 = $1 ||");
    
    // magicValue followed by a numerical constant
    jreplace(tok, "magicValue", "", (_tok, _i) -> {
      String t = _get(_tok, _i+3);
      return eqOneOf(t, ".", "-") || isInteger(t);
    });
    
    // end of local stuff
    
    tok_processMetaBlocks(tok, metaCodeAllowed());
    if (metaCodeAllowed()) runMetaTransformers(tok);

    same = tok_sameTest(tok, before);
    /*if (!same)
      print("local not same " + safety + " (" + l(tok) + " tokens)");*/
    if (safety++ >= 10) {
      printSources(tok);
      throw fail("safety 10 error!");
    }
  } while (!same);
  
  return tok;
}

static List<String> reTok_include(List<String> tok, int i, int j) {
  return reTok_modify(tok, i, j, "localStuff1");
}

static List<String> includeInMainLoaded_reTok(List<String> tok, int i, int j) {
  return reTok_include(tok, i, j);
}

static List<String> stdstuff(List<String> tok) {
  //if (++level >= 10) fail("woot? 10");
  
  print("stdstuff!");
  int i;
  
  List<String> ts = new ArrayList();
  tok_findTranslators(tok, ts);
  if (nempty(ts))
    print("DROPPING TRANSLATORS: " + structure(ts));

  print("quickmain"); tok = quickmain(tok);
  print("includes"); tok = tok_processIncludes(tok);
  print("conceptsDot"); if (processConceptsDot(tok))
    tok = tok_processIncludes(tok);
  
  //print('starConstructors); tok = tok_expandStarConstructors(tok);
    
  // drop Java 8 annotations since we're compiling for Java 7
  jreplace(tok, "@Nullable", "");

  // STANDARD CLASSES & INTERFACES
  
  print("standard classes");
  final Set<String> haveClasses = addStandardClasses_v2(tok);
  
  tok_quickInstanceOf(tok, haveClasses);

  // concept-related stuff
  
  // auto-import concepts
  boolean _a = tok_hasClassRef2(tok, /*"extends",*/ "Concept") || tok_hasClassRef2(tok, "Concepts"), _b = !haveClasses.contains("Concept");
  //print("auto-import: " + _a + ", " + _b);
  if (_a && _b) {
    print("Auto-including concepts.");
    if (shouldNotIncludeClass.contains("Concepts")) {
      print(join(tok));
      throw fail("Unwanted concepts import");
    }
    printStruct(haveClasses);
    tok = includeInMainLoaded(tok, "concepts.");
    reTok(tok, l(tok)-1, l(tok));
    //processConceptsDot(tok);
  }
  
  return tok;
} // end of stdStuff!

static List<String> multilineStrings(List<String> tok) {
  for (int i = 1; i < tok.size(); i += 2) {
    String t = tok.get(i);
    if (isQuoted(t))
      if (t.startsWith("[") || t.contains("\r") || t.contains("\n"))
        tok.set(i, quote(unquote(t)));
  }
  return tok;
}

static void inStringEvals(List<String> tok) {
  boolean change = false;
  for (int i = 1; i < tok.size(); i += 2) {
    String t = tok.get(i);
    if (!isQuoted(t)) continue;
    if (t.contains("\\*") && !t.contains("\\\\")) { // << rough
      tok.set(i, inStringEval(t));
      change = true;
    }
  }
  if (change) reTok(tok);
}

static String inStringEval(String t) {
  t = dropPrefix("\"", dropSuffix("\"", t));
  List<String> l = new ArrayList();
  int idx;
  while ((idx = t.indexOf("\\*")) >= 0) {
    int j = indexOf(t, idx, "*/");
    if (j < 0) break;
    if (idx > 0)
      l.add("\"" + substring(t, 0, idx) + "\"");
    l.add("(" + trim(substring(t, idx+2, j)) + ")");
    t = substring(t, j+2);
  }
  if (nempty(t))
    l.add("\"" + t + "\"");
  return "(" + join(" + ", l) + ")";
}

static List<String> quickmain(List<String> tok) {
  if (quickmainDone1 && quickmainDone2) return tok;

  int i = findCodeTokens(tok, "main", "{");
  if (i < 0) i = findCodeTokens(tok, "m", "{");
  if (i >= 0 && !(i-2 > 0 && tok.get(i-2).equals("class"))) {
    tokSet_reTok(tok, i, "class main");
    quickmainDone1 = true;
  }
    
  i = findCodeTokens(tok, "psvm", "{");
  if (i < 0) i = findCodeTokens(tok, "p", "{");
  if (i >= 0) {
    int idx = i+2;
    int j = findEndOfBracketPart(tok, idx)-1;
    List<String> contents = subList(tok, idx+1, j);
    //print("contents: " + sfu(contents));
    tok.set(i, "public static void main(final String[] args) throws Exception");
    replaceTokens(tok, idx+1, j, tok_addSemicolon(contents));
    reTok(tok, i, j);
    quickmainDone2 = true;
  }
    
  return tok;
}

static String makeVar(String name) {
  AtomicInteger counter = varCountByThread.get();
  if (counter == null)
    varCountByThread.set(counter = new AtomicInteger());
  return "_" + name + "_" + getAndInc(counter);
}

static String makeVar() { return makeVar(""); }

static List<String> rtq(List<String> tok, String id) {
  return runTranslatorQuick(tok, id);
}

static List<String> expandShortTypes(List<String> tok) {
  // replace <int> with <Integer>
  for (int i = 1; i+4 < tok.size(); i += 2)
    if (tok.get(i).equals("<")
      && litlist(">", ",").contains(tok.get(i+4))) {
      String type = tok.get(i+2);
      if (type.equals("int")) type = "Integer";
      else if (type.equals("long")) type = "Long";
      tok.set(i+2, type);
    }
    
  // O = Object, S = String, ret = return
  for (int i = 1; i < tok.size(); i += 2) {
    String t = tok.get(i);
    if (t.equals("O")) t = "Object";
    if (t.equals("S")) t = "String";
    else if (t.equals("L")) t = "List";
    else if (t.equals("Cl")) t = "Collection";
    //else if (t.equals("F")) t = "Function";
    else if (t.equals("ret") && neqOneOf(get(tok, i+2), "=", ")", ".")) t = "return";
    else if (t.equals("bool") && i+2 < tok.size() && neq(tok.get(i+2), "(")) t = "boolean"; // bool -> boolean if it's not a function name
    else if (t.equals("AtomicBool")) t = "AtomicBoolean";
    tok.set(i, t);
  }
  
  jreplace(tok, "LL< <id> >", "L<L<$3>>");
  jreplace(tok, "LL<?>", "L<L<?>>");
  jreplace(tok, "LL", "L<L>");
  
  jreplace(tok, "Clusters< <id> >", "Map<$3, Collection<$3>>");
  
  return tok;
}

static List<String> autoImports(List<String> tok) {
  HashSet<String> imports = new HashSet(tok_findImports(tok));
  StringBuilder buf = new StringBuilder();
  for (String c : standardImports)
    if (!(imports.contains(c)))
      buf.append("import " + c + ";\n");
  if (buf.length() == 0) return tok;
  tok.set(0, buf+tok.get(0));
  return reTok(tok, 0, 1);
}

static String[] standardImports = {
  "java.util.*",
  "java.util.zip.*",
  "java.util.List",
  "java.util.regex.*",
  "java.util.concurrent.*",
  "java.util.concurrent.atomic.*",
  "java.util.concurrent.locks.*",
  "javax.swing.*",
  "javax.swing.event.*",
  "javax.swing.text.*",
  "javax.swing.table.*",
  "java.io.*",
  "java.net.*",
  "java.lang.reflect.*",
  "java.lang.ref.*",
  "java.lang.management.*",
  "java.security.*",
  "java.security.spec.*",
  "java.awt.*",
  "java.awt.event.*",
  "java.awt.image.*",
  "javax.imageio.*",
  "java.math.*"
};

static List<String> tok_processIncludes(List<String> tok) {
  int safety = 0;
  while (hasCodeTokens(tok, "!", "include") && ++safety < 100)
    tok = tok_processIncludesSingle(tok);
  
  //tok_autoCloseBrackets(tok);
  return tok;
}

static void tok_processEarlyIncludes(List<String> tok) {
  int i;
  while ((i = jfind_check("include",tok, "!include early #<int>")) >= 0) {
    String id = tok.get(i+8);
    included.add(parseLong(id));
    replaceTokens_reTok(tok, i, i+10, "\n" + cacheGet(id) + "\n");
  }
}

static List<String> tok_processIncludesSingle(List<String> tok) {
  int i;
  while ((i = jfind_check("include",tok, "!include #<int>")) >= 0) {
    String id = tok.get(i+6);
    included.add(parseLong(id));
    replaceTokens(tok, i, i+8, "\n" + cacheGet(id) + "\n");
    reTok_include(tok, i, i+8);
  }
  while ((i = jfind_check("include",tok, "!include once #<int>")) >= 0) {
    String id = tok.get(i+8);
    boolean isNew = included.add(parseLong(id));
    replaceTokens(tok, i, i+10, 
      isNew ? "\n" + cacheGet(id) + "\n" : "");
    reTok_include(tok, i, i+10);
  }
  return tok;
}

static void ctex(List<String> tok) {
  replaceKeywordBlock(tok, "ctex",
    "{ try {",
    "} catch (Exception __e) { throw rethrow(__e); } }");
  for (String keyword : ll("null on exception", "null on error"))
    replaceKeywordBlock(tok, keyword,
      "{ try {",
      "} catch (Throwable __e) { return null; } }");
  replaceKeywordBlock(tok, "false on exception",
    "{ try {",
    "} catch (Throwable __e) { return false; } }");
}
  
static List<String> dialogHandler(List<String> tok) {
  return replaceKeywordBlock(tok,
    "dialogHandler",
    "new DialogHandler() {\n" +
      "public void run(final DialogIO io) {",
    "}}");
}

static void quicknew2(List<String> tok) {
  tok_quicknew(tok);

  /*jreplace(tok, "new <id> <id>;", "$2 $3 = new $2;");
  jreplace(tok, "new <id><<id>> <id>;", "$2<$4> $6 = new $2;");
  jreplace(tok, "new <id>.<id> <id>;", "$2.$4 $5 = new $2.$4();");
  jreplace(tok, "new <id><<id>> <id>, <id>;", "$2<$4> $6 = new $2, $8 = new $2;");
  jreplace(tok, "new <id><<id>,<id>> <id>;", "$2<$4,$6> $8 = new $2;");
  jreplace(tok, "new <id><<id><<id>>> <id>;", "$2<$4<$6>> $9 = new $2;");
  jreplace(tok, "new <id><<id>[]> <id>;", "$2 $3 $4 $5 $6 $7 $8 = new $2;");
  jreplace(tok, "new <id>< <id><<id>>, <id>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 = new $2;");
  jreplace(tok, "new <id>< <id><<id>,<id>> > <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 = new $2;");
  jreplace(tok, "new <id>< <id>, <id><<id>> > <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 = new $2;");
  jreplace(tok, "new <id>< <id><<id>,<id>,<id>> > <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 = new $2;");
  jreplace(tok, "new <id>< <id><<id>,<id>>, <id>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 = new $2;");
  jreplace(tok, "new <id>< <id>, <id><<id>,<id>>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 = new $2;");
  jreplace(tok, "new <id>< <id>< <id>, <id><<id>,<id>>>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 = new $2;");
  jreplace(tok, "new <id>< <id>< <id>.<id>, <id><<id>,<id>>>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 $17 $18 = new $2;");
  jreplace(tok, "new <id>< <id>< <id><<id>>, <id><<id>>>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 $17 = new $2;");*/
  
  // [abandoned, confusing, looks like a function definition] with arguments - new A a(...); => A a = new A(...);
  //jreplace(tok, "new <id> <id>(", "$2 $3 = new $2(");

  jreplace(tok, "for args " + "{", "for (int i = 0; i < args.length; i++) { final String arg = args[i];");
  
  // Constructor calls without parentheses
  // So you can say something like: predictors.add(new P1);
  
  jreplace1(tok, "new <id>", "new $2()", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i+5), "{", ",", ")", ";", ":");
  }});
  
  jreplace1(tok, "new <id> < <id> >", "new $2<$4>()", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i+11), "{", ",", ")", ";", ":");
  }});
  
  jreplace(tok, "new List(", "new ArrayList(");
  jreplace(tok, "new Map(", "new HashMap(");
  jreplace(tok, "new Set(", "new HashSet(");
  jreplace(tok, "new (Hash)Set", "new HashSet"); // rough
  jreplace(tok, "new (Tree)Set", "new TreeSet");
  jreplace(tok, "new (Hash)Map", "new HashMap");
  jreplace(tok, "new (Tree)Map", "new TreeMap");
  jreplace(tok, "\\*<id>[<id>] <id>;", "$2[] $6 = new $2[$4];");
  
  // X x = new(...)  =>  X x = new X(...)
  // X x = new {...} =>  X x = new X {...}
  // X x = new       =>  X x = new
  jreplace(tok, "<id> <id> = new", "$1 $2 = new $1", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i+9), "(", ";", ",", ")", "{");
  }});
  jreplace(tok, "<id> <id> = new \\*", "$1 $2 = new $1");
  jreplace(tok, "\\* <id> = new <id>", "$5 $2 = new $5");
  
  // TODO: use backward type scanning
  
  jreplace(tok, "<id><<id>> <id> = new", "$1 $2 $3 $4 $5 = new $1", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i+9+3*2), "(", ";", ",", ")");
  }});
  jreplace(tok, "<id><<id>,<id>> <id> = new", "$1 $2 $3 $4 $5 $6 $7 = new $1", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i+9+5*2), "(", ";", ",", ")");
  }});
  jreplace(tok, "<id><<id><<id>>> <id> = new", "$1 $2 $3 $4 $5 $6 $7 $8 = new $1", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i+9+6*2), "(", ";", ",", ")");
  }});
}

static List<String> extendClasses(List<String> tok) {
  int i;
  while ((i = jfind(tok, "extend <id> {")) >= 0) {
    String className = tok.get(i+2);
    int idx = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, idx+2);
    String content = join(subList(tok, idx+1, j-1));
    List<String> c = findInnerClassOfMain(tok, className);
    print("Extending class " + className);
    clearTokens(tok.subList(i, j+1));
    if (c == null) {
      print("Warning: Can't extend class " + className + ", not found");
      continue;
    }
    int startOfClass = indexOfSubList(tok, c); // magicIndexOfSubList is broken
    int endOfClass = startOfClass + l(c)-1;
    //print("Extending class " + className + " ==> " + join(subList(tok, startOfClass, endOfClass)));
    while (neq(tok.get(endOfClass), "}")) --endOfClass;
    //print("Extending class " + className + " ==> " + join(subList(tok, startOfClass, endOfClass)));
    tok.set(endOfClass, content + "\n" + tok.get(endOfClass));
    
    doubleReTok(tok, i, j+1, endOfClass, endOfClass+1);
  }
  return tok;
}

// for ping / while ping
static void forPing(List<String> tok) {
  int i;
  for (String keyword : ll("for", "fOr", "while"))
    while ((i = jfind(tok, keyword + " ping (")) >= 0) {
      int bracketEnd = findEndOfBracketPart(tok, i+4)-1;
      int iStatement = bracketEnd+2;
      int iEnd = findEndOfStatement(tok, iStatement);
      tok.set(i+2, "");
      
      // turn into block
      if (!eq(get(tok, iStatement), "{")) {
        tok.set(iStatement, "{ " + tok.get(iStatement));
        tok.set(iEnd-1, tok.get(iEnd-1) + " }");
      }
        
      // add ping
      tok.set(iStatement, "{ ping(); " + dropPrefixTrim("{", tok.get(iStatement)));
      reTok(tok, i+2, iEnd);
    }
}

// lib 123 => !123
static void libs(List<String> tok) {
  int i;
  while ((i = jfind(tok, "lib <int>")) >= 0) {
    String id = tok.get(i+2);
    print("lib " + id);
    if (!libs.contains(id)) {
      libs.add(id);
      clearAllTokens(tok, i, i+3);
      /*tok.set(i, "!");
      tok.set(i+1, "");*/
    } else {
      print("...ignoring (duplicate)");
      clearAllTokens(tok, i, i+3);
      reTok(tok, i, i+3);
    }
  }
  print("libs found: " + libs);
}

// sourceCodeLine() => 1234
static void sourceCodeLine(List<String> tok) {
  int i ;
  while ((i = jfind(tok, "sourceCodeLine()")) >= 0) {
    replaceTokens(tok, i, i+5, str(countChar(join(subList(tok, 0, i)), '\n')+1));
    reTok(tok, i, i+5);
  }
}

// done before any other processing
static void earlyStuff(List<String> tok) {
  int i;
  
  tok_scopes(tok, "autoCloseScopes" , asInclude);
  tok_autosemi(tok);
  tok_autoCloseBrackets(tok);
  jreplace(tok, "°", "()");
  
  // Note: this makes the word "quine" a special operator
  // (unusable as a function name)
  
  while ((i = jfind(tok, "quine(")) >= 0) {
    int idx = findCodeTokens(tok, i, false, "(");
    int j = findEndOfBracketPart(tok, idx+2);
    tok.set(i, "new Quine");
    tok.set(idx, "(" + quote(join(subList(tok, idx+1, j-1))) + ", ");
    reTok(tok, i, idx+1);
  }
  
  jreplace_check("after",tok, "void <id> after super {", "void $2 { super.$2();");
  
  // do this before func & voidfunc because otherwise they swallow it
  jreplace(tok, "enter {", "{ temp enter();");
  
  // func keyword for lambdas - now automatically quines toString() if enabled
  
  // do func & voidfunc early to preserve original code as toString
  
  while ((i = jfind(tok, "func(")) >= 0) {
    int argsFrom = i+4, argsTo = findCodeTokens(tok, i, false, ")");
    int idx = findCodeTokens(tok, argsTo, false, "{");
    int j = findEndOfBracketPart(tok, idx);
    List<String> contents = subList(tok, idx+1, j-1);
    
    String returnType = "O";
    if (eq(tok.get(argsTo+2), "-") && eq(tok.get(argsTo+4), ">"))
      returnType = tok_toNonPrimitiveTypes(join(subList(tok, argsTo+6, idx-1)));
      
    String toString = autoQuine ? "  public S toString() { ret " + quote(shorten(maxQuineLength, trimJoin(contents))) + "; }" : "";
    
    List<String> args = cloneSubList(tok, argsFrom-1, argsTo);
    tok_shortFinals(args);
    tok_toNonPrimitiveTypes(args);
    
    List<String> types = tok_typesOfParams(args);
    Object type = "O";
    if (l(types) <= 3)
      type = "F" + l(types) + "<" + joinWithComma(types) + ", " + returnType + ">";
    String body = tok_addReturn(contents);
    
    replaceTokens_reTok(tok, i, j,
      "new " + type + "() { "
        + returnType + " get(" + trimJoin(args) + ") ctex { "
          + body
        + " }\n" + toString + "}");
  }
  
  while ((i = jfind(tok, "voidfunc(")) >= 0) {
    int argsFrom = i+4, argsTo = findCodeTokens(tok, i, false, ")");
    int idx = findCodeTokens(tok, argsTo, false, "{");
    int j = findEndOfBracketPart(tok, idx);
    List<String> contents = subList(tok, idx+1, j-1);
    
    List<String> args = cloneSubList(tok, argsFrom-1, argsTo);
    tok_shortFinals(args);
    tok_toNonPrimitiveTypes(args);
    
    List<String> types = tok_typesOfParams(args);
    Object type = "O";
    if (l(types) <= 4)
      type = "VF" + l(types) + "<" + joinWithComma(types) + ">";

    replaceTokens(tok, i, j, "new " + type + "() { public void get(" + trimJoin(args) + ") ctex { " + tok_addSemicolon(contents) + " }\n" +
    (autoQuine ? "  public S toString() { ret " + quote(shorten(maxQuineLength, trim(join(contents)))) + "; }" : "") + "}");
    reTok(tok, i, j);
  }
  
  jreplace(tok, "func {", "func -> O {");
  jreplace(tok, "f {", "f -> O {");
  
  // swing -> S { ... } => swing(func -> S { ... })
  while ((i = jfind(tok, "swing ->")) >= 0) {
    int bracket = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, bracket);
    tok.set(i, "swing(func");
    tok.set(j-1, "})");
    reTok(tok, i, j);
  }
  
  tok_qFunctions(tok);
  
  for (String keyword : ll(/*"f",*/ "func")) {
    while ((i = jfind(tok, keyword + " ->", new TokCondition() { boolean get(final List<String> tok, final int i) {
      return isIdentifier(_get(tok, i+7)); // avoid lambda declaration like: f -> { ... }
    }})) >= 0) {
      // I think there is a bug here for something like func -> x { new x { } }
      int idx = findCodeTokens(tok, i, false, "{");
      int j = findEndOfBracketPart(tok, idx);
      String returnType = tok_toNonPrimitiveTypes(join(subList(tok, i+6, idx-1)));
      List<String> contents = subList(tok, idx+1, j-1);
      replaceTokens(tok, i, j, "new F0<" + returnType + ">() { " + returnType + " get() ctex { " + tok_addReturn(contents) + " }\n" +
      (autoQuine ? "  public S toString() { ret " + quote(shorten(maxQuineLength, trim(join(contents)))) + "; }" : "") + "}");
      reTok(tok, i, j);
    }
  }
    
  while ((i = jfind(tok, "time repeat * {")) >= 0) {
    int j = findEndOfBlock(tok, i+6)-1;
    tok.set(i, "time {");
    tok.set(j, "}}");
    reTok(tok, i, j+1);
  }

  // do this before "m {" stuff because "repeat m" [doesn't seem to work yet though)
  while ((i = findCodeTokens(tok, "repeat", "*", "{")) >= 0) {
    String v = makeVar("repeat");
    tok.set(i, "for (int " + v + " = 0; " + v + " < " + tok.get(i+2) + "; " + v + "++)");
    tok.set(i+2, "");
    reTok(tok, i, i+3);
  }
}

static void throwFailEtc(List<String> tok) {
  boolean anyChange = false;
  for (int i = 1; i+4 < l(tok); i += 2)
    if (eqOneOf(get(tok, i+2), "fail", "todo")
      && eq(get(tok, i+4), "(")
      && !eqOneOf(get(tok, i), "throw", "RuntimeException", "return", "f", "function")) {
      tok.set(i+2, "throw " + tok.get(i+2));
      anyChange = true;
    }
  if (anyChange)
    reTok(tok);
}

static void namedThreads(List<String> tok) {
  for (int i = 0; i < 100; i++) {
    int idx = findCodeTokens(tok, "thread", "<quoted>", "{");
    if (idx < 0) idx = findCodeTokens(tok, "thread", "<id>", "{");
    if (idx < 0)
      break;
    int j = findEndOfBracketPart(tok, idx+4);
    String tName = tok.get(idx+2);
    
    String pre = "startThread(" + tName + ", r { ";
    String post = "})";
    if (!tok_tokenLeftOfExpression(tok, idx-2)) post += ";";

    tok.set(idx, pre);
    tok.set(idx+2, "");
    tok.set(idx+4, "");
    tok.set(j-1, post);
    //print(join(subList(tok, idx, j)));
    reTok(tok, idx, j);
  }
}

static void rNamedThread(List<String> tok) {
  for (int i = 0; i < 100; i++) {
    int idx = findCodeTokens(tok, "r", "-", "thread", "<quoted>", "{");
    if (idx < 0) idx = findCodeTokens(tok, "r", "-", "thread", "<id>", "{");
    if (idx < 0)
      break;
    int j = findEndOfBracketPart(tok, idx+8);
    String tName = tok.get(idx+6);
    
    String pre = "r { thread " + tName + " {";
    String post = "}}";

    replaceTokens(tok, idx, idx+9, pre);
    tok.set(j-1, post);
    reTok(tok, idx, j);
  }
}

static void threads(List<String> tok) {
  replaceKeywordBlock(tok, "daemon", "startDaemonThread(r {", "});");
  //replaceKeywordBlock(tok, "thread", "startThread(r {", "});");
  
  // now enclose in { } to allow using like "void bla() thread { ... }" - if it's not used as an expression
  
  replaceKeywordBlock_dyn2_legacy(tok, "thread", new Object() { // don't use func here, it can't be transpiled
    String[] get (List<String> tok, int iOpening, int iClosing) {
      // is the thread clause used as an expression?
      boolean isExpression = tok_tokenLeftOfExpression(tok, iOpening-4);
      return new String[] {
        (isExpression ? "" : "{ ") + "startThread(r {",
        "})" +
        (isExpression ? "" : "; }")
      };
    }
  });
}

static Map<String, String> sf; // standard standard functions (haha)
static Boolean _761ok;

static List<String> standardFunctions(List<String> tok) {
  if (sf == null) {
    String _761 = cacheGet("#761");
    if (_761ok == null)
      _761ok = isBracketHygienic(_761);
    assertTrue("Whoa! #761 truncated?", _761ok);
    List<String> standardFunctions = concatLists(
      (List) loadVariableDefinition(_761, "standardFunctions"),
      (List) loadVariableDefinition(cacheGet("#1006654"), "standardFunctions"));

    sf = new HashMap();
    for (String x : standardFunctions) {
      String[] f = x.split("/");
      sf.put(f[1], f[0]);
    }
  }
  
  Map<String, String> sfMap = combinedMap(extraStandardFunctions, sf);
    
  for (int i = 0; ; i++) {
    Set<String> defd = new HashSet(findFunctions(tok));
    
    int j;
    while ((j = jfind(tok, "should not include function *.")) >= 0) {
      String fname = tok.get(j+8);
      shouldNotIncludeFunction.add(fname);
      clearAllTokens(tok.subList(j, j+12));
    }

    while ((j = jfind(tok, "do not include function *.")) >= 0) {
      String fname = tok.get(j+8);
      doNotIncludeFunction.add(fname);
      clearAllTokens(tok.subList(j, j+12));
    }
    
    doNotIncludeFunction.addAll(tok_importedStaticFunctionNames(tok));
    
    // changes tok
    Set<String> invocations = findFunctionInvocations(tok, sfMap, hardFunctionReferences, defd, true);
    /*if (invocations.contains("str"))
      print("==STR==" + join(tok) + "==STR==");*/
    if (!cic(definitions, "leanMode"))
      invocations.addAll(functionsToAlwaysInclude);

    //print("Functions invoked: " + structure(invocations));
    List<String> needed = diff(invocations, defd);
    for (String f : doNotIncludeFunction) needed.remove(f);
    if (needed.isEmpty())
      break;
    print("Adding functions: " + join(" " , needed));
    
    HashSet neededSet = new HashSet(needed);
    Collection<String> bad = setIntersection(neededSet, shouldNotIncludeFunction);
    if (nempty(bad)) {
      String msg = "INCLUDING BAD FUNCTIONS: " + sfu(bad);
      print(msg);
      print(join(tok));
      throw fail(msg);
    }
      
    List<String> added = new ArrayList();
    List<Future<String>> parts = new ArrayList();
    List<String> preload = new ArrayList();
    
    for (String x : needed)
      if (sfMap.containsKey(x))
        preload.add(sfMap.get(x));
    print("Preloading: " + preload);
    cachePreload(preload);
    
    for (String x : cloneList(needed)) {
      if (defd.contains(x)) continue;
      
      String id = sfMap.get(x);
      if (id == null) {
        print("Standard function " + x + " not found.");
        needed.remove(x);
        continue;
      }
      //print("Adding function: " + x + " (" + id + ")");
       
      String function = cacheGet(id);
      //if (("\n" + function).contains("\n!")) print("Warning: " + id + " contains translators.");
      
      if (cacheStdFunctions) {
        long _id = psI(id);
        CachedInclude ci = cachedIncludes.get(_id);
        if (ci == null) {
          cachedIncludes.put(_id, ci = new CachedInclude());
          //println("Caching include " + _id + ", l=" + l(cachedIncludes));
        }
        function += "\n";
        final String _function = function;
        if (neq(ci.javax, function)) {
          print("Compiling function: " + x);
          ci.javax = function;
          ci.java = executor.submit(new Callable<String>() {
            public String call() {
              // We assume that variables made with makeVar() will always be local to some block
              varCountByThread.set(null);
              return join(localStuff1(jtok(_function)));
            }
          });
          ci.realJava = null;
        }
        parts.add(ci.javaFuture());
      } else
        parts.add(nowFuture(function + "\n"));
        
      added.add(x);
      Collection<String> newFunctions = new HashSet(findFunctionDefs(javaTok(function)));
      defd.addAll(newFunctions);
      for (String f : newFunctions)
        if (!addedFunctions.add(f)) {
          printSources(tok);
          throw fail("Trying to add function " + f + " again - main class syntax broken!");
        }
    }
    
    String text = join(getAllFutures(parts));
    tok = includeInMainLoaded(tok, text);
      
    // defd = new HashSet(findFunctions(tok));
    //print("Functions added: " + joinWithSpace(added));
    
    for (String x : needed)
      if (!defd.contains(x)) {
        print(join(tok));
        throw fail("Function not defined properly: " + x);
      }
    //print("Iteration " + (i+2));
    
    tok_definitions(tok);
    tok_ifndef(tok);
    tok_ifdef(tok);
    
    if (i >= 1000) throw fail("Too many iterations");
  }
  
  return tok;
}

static List<String> findFunctions(List<String> tok) {
  //ret findFunctionDefinitions(join(findMainClass(tok)));
  return findFunctionDefs(findMainClass(tok));
}

static String cacheGet(String snippetID) {
  snippetID = formatSnippetID(snippetID);
  String text = snippetCache.get(snippetID);
  if (text == null) {
    text = loadSnippet(snippetID);
    // very early processing/checks for includes
    
    if (hasUnclosedStringLiterals(text))
      throw fail("Unclosed string literals in " + snippetID);
      
    if (regexpContains("\\bscope\\b", text)) {
      List<String> tok = javaTok(text);
      tok_scopes(tok, "autoCloseScopes" , true);
      text = join(tok);
    }
    
    snippetCache.put(snippetID, text);
  }
  return text;
}

static void cachePreload(Collection<String> ids) {
  List<String> needed = new ArrayList();
  for (String id : ids)
    if (!snippetCache.containsKey(formatSnippetID(id)))
      needed.add(formatSnippetID(id));
  if (l(needed) > 1) {
    List<String> texts = loadSnippets(needed);
    for (int i = 0; i < l(needed); i++)
      if (texts.get(i) != null)
        snippetCache.put(needed.get(i), texts.get(i));
  }
}

static List<String> jtok(List<String> tok) {
  return jtok(join(tok));
}

static List<String> jtok(String s) {
  return indexTokenList(javaTok(s));
}

static HashSet<String> haveClasses_actual(List<String> tok) {
  HashSet<String> have = new HashSet();
  for (List<String> c : innerClassesOfMain(tok))
    have.add(getClassDeclarationName(c));
  return have;
}

static HashSet<String> haveClasses_addImported(List<String> tok, HashSet<String> have) {
  have.addAll(tok_importedClassNames(tok));
  have.addAll(usualJavaClassNames()); // for S => S.class
  return have;
}

// works on Java level (no "sclass" etc)
// returns list of classes we have (useful for other processing)
static Set<String> addStandardClasses_v2(List<String> tok) {
  if (lclasses == null) {
    String sc = cacheGet("#1003674");   
    lclasses = new ArrayList();
    for (String line : tlft_j(sc)) {
      int idx = line.indexOf('/');
      lclasses.addAll(ll(line.substring(0, idx), line.substring(idx+1)));
    }
  }
  List<String> definitions = lclasses;

  for (int safety = 0; safety < 10; safety++) {
    HashSet<String> have = haveClasses_actual(tok);
    have.addAll(tok_importedClassNames(tok));

    int j;
    while ((j = jfind(tok, "should not include class *.")) >= 0) {
      String cname = tok.get(j+8);
      shouldNotIncludeClass.add(cname);
      clearAllTokens(tok.subList(j, j+12));
    }
    
    Map<String, String> need = new HashMap();
    Set<String> idx = tokenIndexWithoutIfclass_forStdClasses(tok);
    
    while ((j = jfind(tok, "please include class *.")) >= 0) {
      String cname = tok.get(j+6);
      idx.add(cname);
      clearAllTokens(tok.subList(j, j+10));
    }

    for (int i = 0; i+1 < l(definitions); i += 2) {
      String className = definitions.get(i);
      if (idx.contains(className) && !have.contains(className))
        need.put(className, definitions.get(i+1));
    }
    if (hasDef("SymbolAsString")) {
      print("Have SymbolAsString.");
      if (need.containsKey("Symbol")) {
        print("Have Symbol.");
        need.remove("Symbol");
      }
    } else
      print("No SymbolAsString.");
      
    if (empty(need)) return have;
  
    for (String className : keys(need))
      if (shouldNotIncludeClass.contains(className)) {
        String msg = "INCLUDING BAD CLASS: " + className;
        print(msg);
        print(join(tok));
        throw fail(msg);
      }
      
    cachePreload(values(need));
    
    StringBuilder buf = new StringBuilder();
    print("Adding classes: " + joinWithSpace(keys(need)));
    for (String className : keys(need)) {
      if (have.contains(className)) continue; // intermittent add
      String snippetID = need.get(className);
      //print("Adding class " + className + " / " + snippetID);
      snippetID = fsI(snippetID);
      String text = cacheGet(snippetID);
      
      assertTrue(cacheStdClasses);
      long _id = psI(snippetID);
      CachedInclude ci = cachedIncludes.get(_id);
      if (ci == null) cachedIncludes.put(_id, ci = new CachedInclude());
      if (neq(ci.javax, text)) {
        // TODO: do it in multiple threads
        print("Compiling class: " + className);
        ci.javax = text;
        ci.java = null;
        ci.realJava = join(localStuff1(jtok(text)));
      }
      buf.append(ci.java());

      List<String> ct = javaTok(ci.java());
      for (List<String> c : allClasses(ct)) {
        String name = getClassDeclarationName(c);
        have.add(name);
      }
      if (!have.contains(className))
        throw fail("Wrongly defined class: " + className + " / " + snippetID);
      if (!addedClasses.add(className)) {
        printSources(tok);
        throw fail("Trying to add class " + className + " again - main class syntax broken!");
      }
    } // for className
    
    tok = includeInMainLoaded(tok, str(buf));
  }
  throw fail("safety 10");
}

static Set<String> expandableClassNames = lithashset("BigInteger");

// no reTok - leaves tok dirty
// magically append ".class" to class name references
static boolean expandClassReferences_lazy(List<String> tok, Set<String> classNames) {
  boolean change = false;
  for (int i = 3; i+2 < l(tok); i += 2) {
    String t = tok.get(i);
    
    // skip implements/extends/throws lists
    if (eqOneOf(t, "implements", "extends", "throws")) {
      i = tok_endOfImplementsList(tok, i);
      continue;
    }
    
    if (classNames.contains(t) || expandableClassNames.contains(t)) {
      String s = tok.get(i-2); t = tok.get(i+2);
      // TODO: This whole logic ain't very good
      // (Hard to distinguish between "Int.class" as an argument
      // and "Int" as a type parameter.)
      if (eqOneOf(s, "instanceof", "new", ".", "<", ">", "/", "nu")) continue;
      if (isIdentifier(s) || isInteger(s)) continue;
      if (eq(t, ",") && isIdentifier(get(tok, i+4)) && eqGet(tok, i+6, ">")) continue; // e.g. T3<L<S>, S, S>
      if (eq(s, ",") && isIdentifier(get(tok, i-4)) && eqGet(tok, i-6, "<")) continue; // e.g. T3<S, S, S>
      if (eq(s, ",") && eqOneOf(_get(tok, i-6), "implements", "throws")) continue;
      
      // check for cast
      if (eq(s, "(") && eq(t, ")") && i >= 5) {
        if (!eqOneOf(get(tok, i+4), "{", ";")) {
          String x = tok.get(i-4);
          if (!isIdentifier(x)) continue;
          if (eqOneOf(x, "ret", "return")) continue;
        }
      }
      if (eqOneOf(t, ",", ")", ";", ":")) {
        tok.set(i, tok.get(i) + ".class");
        change = true;
      }
    }
  }
  return change;
}

static void expandClassReferences(List<String> tok, Set<String> classNames) {
  if (expandClassReferences_lazy(tok, classNames))
    reTok(tok);
}

// "<id>/<ClassName>" => "((ClassName) <id>)"
static void slashCasts(List<String> tok, final Set<String> classNames) {
  /*jreplace(tok, "<id>/<id>", "(($3) $1)", tokcondition {
    ret classNames.contains(tok.get(i+5));
  });*/
  int n = l(tok)-4;
  for (int i = 1; i < n; i += 2)
    if (tok.get(i+2).equals("/") && isIdentifier(tok.get(i))
      && classNames.contains(tok.get(i+4)))
      replaceTokens_reTok(tok, i, i+5, "((" + tok.get(i+4) + ") " + tok.get(i) + ")");
}

// "<ClassName>(...)" => "new <ClassName>(...)"
// doesn't work at beginning of statement as we can't easily
// distinguish it from a constructor declaration.
static void newWithoutNew(List<String> tok, final Set<String> classNames) {
  TokCondition cond = newWithoutNew_condition(classNames);
  jreplace(tok, "<id>(", "new $1(", cond);
  // just one case with type arg for now
  jreplace(tok, "<id><<id>>(", "new $1<$3>(", cond);
}

static TokCondition newWithoutNew_condition(final Set<String> classNames) {
  return new TokCondition() { boolean get(final List<String> tok, final int i) {
    if (!classNames.contains(tok.get(i+1))) return false;
    String prev = _get(tok, i-1);
    boolean ok = eq(prev, ">") ? eqGet(tok, i-3, "-")
      : neqOneOf(prev, "new", ";", "}", "{", "public", "protected", "private", ".");
    //print("newWithoutNew: checking " + struct(subList(tok, i-1, i+2)) + " => " + ok);
    return ok;
  }};
}

static boolean processConceptsDot(List<String> tok) {
  boolean anyChange = false, change;
  do {
    change = false;
    for (int i : jfindAll(tok, "concepts."))
      if (contains(get(tok, i+3), "\n")) {
        replaceTokens(tok, i, i+3, "!" + "include once #1004863 // Dynamic Concepts");
        reTok(tok, i, i+3);
        change = anyChange = true;
        break;
      }
  } while (change);
  return anyChange;
}

static void caseAsVariableName(List<String> tok) {
  if (!tok.contains("case")) return;
  for (int i = 1; i+2 < l(tok); i += 2) {
    String t = tok.get(i+2);
    if (tok.get(i).equals("case")
      && !(t.startsWith("'") || isInteger(t) || isIdentifier(t)))
      tok.set(i, "_case");
  }
}

static void continueAsFunctionName(List<String> tok) {
  jreplace(tok, "continue(", "_continue(");
}

// f bla => "bla" - and "please include function bla."
static void functionReferences(List<String> tok) {
  int i;
  
  jreplace_dyn(tok, "f-thread <id>", new F2<List<String>, Integer, Object>() { Object get(List<String> tok, Integer cIdx) { try { 
    return "dynamicCallableMC_thread(" + quote(tok.get(cIdx+6)) + ")";
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "\"dynamicCallableMC_thread(\" + quote(tok.get(cIdx+6)) + \")\""; }});
  
  String keyword = "f";
  while ((i = jfind(tok, keyword + " <id>", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return !eq(tok.get(i+3), "instanceof");
  }})) >= 0) {
    String f = tok.get(i+2);
    clearTokens(tok, i, i+2);
    tok.set(i+2, quote(f));
    reTok(tok, i, i+2);
    tok.set(l(tok)-1, last(tok) + "\nplease include function " + f + ".");
    reTok(tok, l(tok)-1, l(tok));
  }
  
  // r|rThread|rEnter|rThreadEnter fname => r|rThread|... { fname() }
  while ((i = jfindOneOf_cond(tok, new TokCondition() { boolean get(final List<String> tok, final int i) {
    return !eq(tok.get(i+3), "instanceof");
  }}, "r <id>", "rThread <id>", "rEnter <id>", "rThreadEnter <id>")) >= 0) {
    String f = tok.get(i+2);
    replaceTokens(tok, i, i+3, tok.get(i) + " { " + f + "(); }");
    reTok(tok, i, i+3);
  }
  
  // dm_q fname => r_dm_q(r fname)
  jreplace(tok, "dm_q <id>", "r_dm_q(r $2)");
  
  // vf<S> fname => voidfunc(S s) { fname(s) }
  jreplace(tok, "vf<<id>> <id>", "voidfunc($3 a) { $5(a) }");
  
  // vf<L<S>> fname => voidfunc(L<S> a) { fname(a) }
  jreplace(tok, "vf<<id><<id>>> <id>", "voidfunc($3<$5> a) { $8(a) }");
  
  // construct<S> Entry => func(S s) -> Entry { new Entry(s) }
  jreplace(tok, "construct<<id>> <id>", "func($3 a) -> $5 { new $5(a) }");
  
  // f<S> fname => func -> S { fname() }
  jreplace(tok, "f<<id>> <id>", "func -> $3 { $5() }");
  
  // f<S, S> fname => func(S x) -> S { fname(x) }
  jreplace(tok, "f<<id>, <id>> <id>", "func($3 x) -> $5 { $7(x) }");
  
  // f<S, L<S>> fname => func(S x) -> L<S> { fname(x) }
  jreplace(tok, "f<<id>, <id><<id>>> <id>", "func($3 x) -> $5 $6 $7 $8 { $10(x) }");
  
  // f<S, L<S>, S> fname => func(S x, L<S> y) -> S { fname(x, y) }
  jreplace(tok, "f<<id>, <id><<id>>, <id>> <id>", "func($3 x, $5 $6 $7 $8 y) -> $10 { $12(x, y) }");
  
  // if1 fname => a -> fname(a)
  jreplace_dyn(tok, "if1 <id>", new F2<List<String>, Integer, String>() { String get(List<String> tok, Integer i) { try { 
    String var = makeVar();
    return var + " -> " + tok.get(i+2) + "(" + var + ")";
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S var = makeVar();\r\n    ret var + \" -> \" + tok.get(i+2) + \"(\" + var + \")\";"; }});
}

static void quicknu(List<String> tok) {
  jreplace(tok, "nu <id>(", "nu($2.class, "); // not needed anymore
  jreplace(tok, "nu <id>", "new $2");
}

// fill variable innerClasses_list
static void innerClassesVar(List<String> tok, Set<String> have) {
  int i = jfind_check("myInnerClasses_list",tok, ">myInnerClasses_list;");
  if (i < 0) return;
  tok.set(i+4, "=litlist(\n" + joinQuoted(", ", have) + ");");
  reTok(tok, i+4, i+5);
}

// fill variable innerClasses_list
static void fillVar_transpilationDate(List<String> tok) {
  int i = jfind_check("myTranspilationDate_value",tok, "long myTranspilationDate_value;");
  if (i < 0) return;
  tok.set(i+4, " = " + now() + "L;");
  reTok(tok, i+4, i+5);
}

// process ifclass x ... endif blocks
static void tok_ifclass(List<String> tok, Set<String> have) {
  if (!tok.contains("ifclass")) return;
  
  int i = l(tok);
  while ((i = rjfind(tok, 1, i-1, "ifclass <id>")) >= 0) {
    int j = jfind(tok, i+4, "endif");
    if (j < 0) j = l(tok)-1;
    String name = tok.get(i+2);
    boolean has = have.contains(name);
    //print("ifclass " + name + " => " + has);
    if (has) {
      clearTokens_reTok(tok, j, j+1);
      clearTokens_reTok(tok, i, i+3);
    } else
      clearTokens(tok, i, j+1);
  }
}

// set flag *.
static void tok_definitions(List<String> tok) {
  int i;
  while ((i = jfind_check("flag",tok, "set flag <id>.")) >= 0) {
    String fname = tok.get(i+4);
    print("Setting flag " + fname);
    definitions.add(fname);
    clearAllTokens(tok.subList(i, i+8));
  }
  
  while ((i = jfind_check("flag",tok, "unset flag <id>.")) >= 0) {
    String fname = tok.get(i+4);
    print("Unsetting flag " + fname);
    definitions.remove(fname);
    clearAllTokens(tok.subList(i, i+8));
  }
}

static void tok_findRewrites(List<String> tok) {
  int i;
  while ((i = jfind(tok, "rewrite <id> =")) >= 0) {
    String token = tok.get(i+2);
    int repStart = i+6;
    int repEnd = smartIndexOf(tok, repStart, ".");
    String replacement = joinSubList(tok, repStart, repEnd-1);
    clearTokens(tok, i, repEnd+1);
    rewrites.put(token, replacement);
    print("Have rewrite: " + token + " => " + replacement);
  }
}

static void tok_processRewrites(List<String> tok) {
  for (String token : keys(rewrites))
    jreplace(tok, token, rewrites.get(token));
}

// extend *. (set base class of main class)
static void tok_extend(List<String> tok) {
  int i;
  while ((i = jfind(tok, "extend <id>.")) >= 0) {
    mainBaseClass = tok.get(i+2);
    clearAllTokens(tok, i, i+7);
  }
}

// process ifndef x ... endifndef blocks
static void tok_ifndef(List<String> tok) {
  if (!tok.contains("ifndef")) return;
  
  int i;
  while ((i = rjfind(tok, "ifndef <id>")) >= 0) {
    int j = jfind(tok, i+4, "endifndef");
    if (j < 0) j = l(tok)-1;
    String fname = tok.get(i+2);
    boolean has = !definitions.contains(fname);
    //print("ifndef " + fname + " => " + has);
    clearTokens(tok, i, i+3);
    clearTokens(tok, j, j+1);
    if (!has) clearTokens(tok, i+3, j);
    reTok(tok, i, j+1);
  }
}

// process ifdef x ... endifdef blocks
static void tok_ifdef(List<String> tok) {
  if (!tok.contains("ifdef")) return;
  
  int i;
  while ((i = rjfind(tok, "ifdef <id>")) >= 0) {
    int j = jfind(tok, i+4, "endifdef");
    if (j < 0) j = l(tok)-1;
    String fname = tok.get(i+2);
    boolean has = definitions.contains(fname);
    //print("ifdef " + fname + " => " + has);
    clearTokens(tok, i, i+3);
    clearTokens(tok, j, j+1);
    if (!has) clearTokens(tok, i+3, j);
    reTok(tok, i, j+1);
  }
}

static void conceptDeclarations(List<String> tok) {
  for (String kw : ll("concept", "sconcept")) {
    Object cond = new TokCondition() { boolean get(final List<String> tok, final int i) { tok_addFieldOrder(tok, i+1); return true; }};
    boolean re = false;
    if (jreplace(tok, kw + " <id> {", "static class $2 extends Concept {", cond)) re = true;
    if (jreplace(tok, kw + " <id> implements", "static class $2 extends Concept implements", cond)) re = true;
    if (jreplace(tok, kw + " <id>", "static class $2", cond)) re = true;
    if (re) reTok(tok);
  }
}

static void shortenedSubconcepts(List<String> tok) {
  jreplace(tok, "<id> > <id> {", "concept $3 extends $1 {", new TokCondition() { boolean get(final List<String> tok, final int i) {
    boolean b = (i == 0 || tok.get(i).contains("\n")) || eq(_get(tok, i-1), "abstract"); // only at beginning of line or after "abstract"
    //print("subconcept " + b + ": " + structure(subList(tok, i-1, i+5)));
    return b;
  }});
}

// -slightly experimental
// -do calculation in another thread, then return to AWT thread
// -must be placed in a block
// -transforms rest of block 
static void unswing(List<String> tok) {
  int i;
  while ((i = jfind(tok, "unswing {")) >= 0) {
    int idx = i+2;
    int closingBracket = findEndOfBracketPart(tok, idx)-1;
    int endOfOuterBlock = findEndOfBracketPart(tok, closingBracket)-1;
    tok.set(i, "thread");
    tok.set(closingBracket, " awt {");
    tok.set(endOfOuterBlock, "}}}");
    reTok(tok, closingBracket-1, endOfOuterBlock+1);
  }
}

// -Syntax: lock theLock;
// -lock a lock, unlock at end of current block with finally
static void lockBlocks(List<String> tok) {
  int i;
  while ((i = jfind(tok, "lock <id>", new TokCondition() { boolean get(final List<String> tok, final int i) { return neq(tok.get(i+3), "instanceof"); }})) >= 0) {
    int semicolon = findEndOfStatement(tok, i)-1;
    String var = makeVar();
    int endOfOuterBlock = findEndOfBracketPart(tok, semicolon)-1;
    replaceTokens(tok, i, semicolon+1,
      "Lock " + var + " = " + joinSubList(tok, i+2, semicolon-1) + "; lock(" + var + "); try {");
    tok.set(endOfOuterBlock, "} finally { unlock(" + var + "); } }");
    reTok(tok, i, endOfOuterBlock+1);
  }
}

// -Syntax: temp Bla bla = bla();
// -expands to try(Bla bla = bla()) { ... } with rest of block inside
static void tempBlocks(List<String> tok) {
  int i;
  while ((i = jfind(tok, "temp <id>")) >= 0) {
    int semicolon = findEndOfStatement(tok, i)-1;
    int endOfOuterBlock = findEndOfBracketPart(tok, semicolon)-1;
    List<String> sub = subList(tok, i-1, semicolon);
    int eq = subList(sub, 0, smartIndexOfOneOf(sub, "{", "(")).indexOf("=");
    String var;
    if (eq >= 0)
      var = sub.get(eq-2);
    else {
      // no var name, e.g. temp newThoughtSpace();
      var = makeVar();
      tok.set(i+2, "AutoCloseable " + var + " = " + tok.get(i+2));
    }
      
    //tok.set(i, "try (");
    //tok.set(semicolon, ") {";
    //tok.set(endOfOuterBlock, "}}");
    
    tok.set(i, "");
    tok.set(semicolon, "; try {");
    tok.set(endOfOuterBlock, "} finally { _close(" + var + "); }}");
    
    reTok(tok, i, endOfOuterBlock+1);
  }
}

static void forgetCachedIncludes() {
  cachedIncludes.clear();
}

// TODO: when to do this / merge contents if there are multiple transpilers?
static void cleanMeUp() {
  for (CachedInclude ci : values(cachedIncludes))
    ci.clean();
  vmKeepWithProgramMD5_save("cachedIncludes");
}

static void printSources(List<String> tok) {
  print("----");
  print(join(tok));
  print("----");
}

static void tok_quickInstanceOf(List<String> tok, final Set<String> haveClasses) {
  if (!quickInstanceOfEnabled) return;
  // "x << X" or "x >> X" => "x instanceof X"
  for (String op : ll("<<", ">>"))
    jreplace(tok, "<id> " + op + " <id>", "$1 instanceof $4", new TokCondition() { boolean get(final List<String> tok, final int i) {
      return haveClasses.contains(tok.get(i+7))
        && !eqOneOf(tok.get(i-1), "<", "extends", "implements");
    }});
}

static boolean hasDef(String s) {
  return definitions.contains(s);
}

static void expandTriple(List<String> tok) {
  jreplace(tok, "T3< <id> >", "T3<$3, $3, $3>");
  jreplace(tok, "T3< <id><<id>> >", "T3<$3<$5>, $3<$5>, $3<$5>>");
}

static void tok_shortFinals(List<String> tok) {
  jreplace(tok, "fS", "final S");
  jreplace(tok, "fO", "final O");
  jreplace(tok, "fL", "final L");
  jreplace(tok, "fMap", "final Map");
  jreplace(tok, "fRunnable", "final Runnable");
  jreplace(tok, "f int", "final int");
}

static void tok_mainClassNameAndPackage(List<String> tok) {
  int i;
  if ((i = jfind(tok, "mainClassName <id>")) >= 0) {
    mainClassName = tok.get(i+2);
    if (eqGet(tok, i+4, ".") && isIdentifier(get(tok, i+6))) {
      mainPackage = mainClassName;
      mainClassName = tok.get(i+6);
      clearTokensAndReTok(tok, i, i+7);
    } else
      clearTokensAndReTok(tok, i, i+3);
  }
  
  if ((i = jfind(tok, "mainPackage <id>")) >= 0) {
    mainPackage = tok.get(i+2);
    clearTokens(tok, i, i+3);
    reTok(tok, i, i+3);
  }
}

static void defineMapLikes(List<String> tok) {
  int i;
  while ((i = jfind(tok, "mapLike <id>")) >= 0) {
    mapLikeFunctions.add(tok.get(i+2));
    clearTokens_reTok(tok, i, i+2);
  }
}

static void defineMapMethodLikes(List<String> tok) {
  int i;
  while ((i = jfind(tok, "mapMethodLike <id>")) >= 0) {
    mapMethodLikeFunctions.add(tok.get(i+2));
    clearTokens_reTok(tok, i, i+2);
  }
}

// functions that work like "nu" syntactically (accept a class as "super-first" parameter [left of the bracket])
static void defineNuLikes(List<String> tok) {
  int i;
  while ((i = jfind(tok, "nuLike <id>")) >= 0) {
    nuLikeFunctions.add(tok.get(i+2));
    clearTokens_reTok(tok, i, i+2);
  }
}

static void defineExtraSF(List<String> tok) {
  int i;
  IntRange reTok = null;
  while ((i = jfind(tok, "function <id> is in *.")) >= 0) {
    extraStandardFunctions.put(tok.get(i+2), fsI(unquote(tok.get(i+8))));
    clearTokens(tok, i, i+12);
    reTok = combineIntRanges(reTok, intRange(i, i+12));
  }
  reTok(tok, reTok);
}

static boolean tok_applyMapLikeFunctions(List<String> tok, final Set<String> mapLikeFunctions) {
  // map funcname(...) => map(f funcname, ...)
  // filter funcname(...) => filter(f funcname, ...)
  // ...
  return jreplace(tok, "<id> <id>(", "$1(f $2,", new F2<List<String>, Integer, Boolean>() { Boolean get(List<String> tok, Integer i) { try { 
    return contains(mapLikeFunctions, tok.get(i+1));
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "contains(mapLikeFunctions, tok.get(i+1))"; }});
}

static boolean tok_applyMapMethodLikeFunctions(List<String> tok, final Set<String> mapMethodLikeFunctions) {
  // mapMethod funcname(...) => mapMethod('funcname, ...)
  // collect funcname(...) => collect('funcname, ...)
  // ...
  return jreplace_dyn(tok, "<id> <id>(",
    new F2<List<String>, Integer, String>() { String get(List<String> tok, Integer cIdx) { try {  return tok.get(cIdx) + "(" + quote(tok.get(cIdx+2)) + ",";  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "tok.get(cIdx) + \"(\" + quote(tok.get(cIdx+2)) + \",\""; }},
    new F2<List<String>, Integer, Boolean>() { Boolean get(List<String> tok, Integer i) { try { 
      return contains(mapMethodLikeFunctions, tok.get(i+1));
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "contains(mapMethodLikeFunctions, tok.get(i+1))"; }});
}

static void runMetaPostBlocks(List<String> tok) {
  for  (String code : unnull(metaPostBlocks))
    { ping(); call(hotwireCached(standardFunctionSnippet(assertIdentifier(code))), code, tok); }
    //callF(codeToFunctionOnArbitraryType(code, "LS", L, "tok"), tok);
}

static void runMetaTransformers(List<String> tok) {
  for  (String code : unnull(metaTransformers))
    { ping(); tok_runMetaTransformer(tok, code); }
}

static boolean tok_applyNuLikeFunctions(List<String> tok, final Set<String> nuLikeFunctions) {
  // nu ClassName(...) => nu(ClassName, ...)
  // ...
  return jreplace_dyn(tok, "<id> <id>(",
    new F2<List<String>, Integer, String>() { String get(List<String> tok, Integer cIdx) { try {  return tok.get(cIdx) + "(" + tok.get(cIdx+2) + ".class,";  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "tok.get(cIdx) + \"(\" + tok.get(cIdx+2) + \".class,\""; }},
    new F2<List<String>, Integer, Boolean>() { Boolean get(List<String> tok, Integer i) { try { 
      return contains(nuLikeFunctions, tok.get(i+1));
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "contains(nuLikeFunctions, tok.get(i+1))"; }});
}

static boolean metaCodeAllowed() {
  return allowMetaCode || containsIC(definitions, "allowMetaCode");
}

static List<String> indexTokenList(List<String> tok) {
  if (useIndexedList2) return indexedList2(tok);
  if (useTokenIndexedList) return tokenIndexedList3(tok);
  return tok;
}

static void tok_earlyGeneralStuff(List<String> tok) {
  tok_standardBot1(tok);
  tok_processSimplified(tok);
  tok_compactModules(tok);
}
static <A> HashSet<A> lithashset(A... items) {
  HashSet<A> set = new HashSet();
  for (A a : items) set.add(a);
  return set;
}
// supports the usual quotings (", variable length double brackets) except ' quoting
static boolean isQuoted(String s) {
  
  if (isNormalQuoted_dirty(s)) return true;
  
  
  return isMultilineQuoted(s);
}
static boolean isIdentifier(String s) {
  return isJavaIdentifier(s);
}
static boolean isInteger(String s) {
  int n = l(s);
  if (n == 0) return false;
  int i = 0;
  if (s.charAt(0) == '-')
    if (++i >= n) return false;
  while (i < n) {
    char c = s.charAt(i);
    if (c < '0' || c > '9') return false;
    ++i;
  }
  return true;
}
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 eqic(char a, char b) {
  if (a == b) return true;
  
    char u1 = Character.toUpperCase(a);
    char u2 = Character.toUpperCase(b);
    if (u1 == u2) return true;
  
  return Character.toLowerCase(u1) == Character.toLowerCase(u2);
}


static volatile StringBuffer local_log = new StringBuffer(); // not redirected


static volatile Appendable 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 boolean print_silent = false; // total mute if set

static Object print_byThread_lock = new Object();
static volatile ThreadLocal<Object> print_byThread; // special handling by thread - prefers F1<S, Bool>
static volatile Object print_allThreads;
static volatile Object print_preprocess;

static void print() {
  print("");
}

static <A> A print(String s, A o) {
  print((endsWithLetterOrDigit(s) ? s + ": " : s) + o);
  return o;
}

// slightly overblown signature to return original object...
static <A> A print(A o) {
  ping_okInCleanUp();
  if (print_silent) return o;
  String s = String.valueOf(o) + "\n";
  print_noNewLine(s);
  return o;
}

static void print_noNewLine(String s) {
  
  Object f = getThreadLocal(print_byThread_dontCreate());
  if (f == null) f = print_allThreads;
  if (f != null)
    // We do need the general callF machinery here as print_byThread is sometimes shared between modules
    if (isFalse(
      
        f instanceof F1 ? ((F1) f).get(s) :
      
      callF(f, s))) return;
  

  print_raw(s);
}

static void print_raw(String s) {
  
  if (print_preprocess != null) s = (String) callF(print_preprocess, s);
  s = fixNewLines(s);
  
  Appendable loc = local_log;
  Appendable 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_autoRotate() {
  
}
static boolean checkTokCondition(Object condition, List<String> tok, int i) {
  if (condition instanceof TokCondition)
    return ((TokCondition) condition).get(tok, i);
  return checkCondition(condition, tok, i);
}
static <A> List<A> ll(A... a) {
  ArrayList l = new ArrayList(a.length);
  if (a != null) for (A x : a) l.add(x);
  return l;
}
static TreeSet<String> ciSet() {
  return caseInsensitiveSet();
}
static <A> A getFuture(Future<A> f) { try {
  return f == null ? null : f.get();
} catch (Exception __e) { throw rethrow(__e); } }
static <A> NowFuture<A> nowFuture(A a) {
  return new NowFuture(a);
}
static long sysNow() {
  ping();
  return System.nanoTime()/1000000;
}
static void vmKeepWithProgramMD5_get(String varName) {
  String struct =  (String) (callOpt(javax(), "vmKeep_get", programID(), md5OfMyJavaSource(), varName));
  if (struct != null)
    setOpt(mc(), varName, unstructure(struct));
}
static volatile PersistableThrowable _handleException_lastException;
static List _handleException_onException = synchroList(ll("printStackTrace2"));

static void _handleException(Throwable e) {
  _handleException_lastException = persistableThrowable(e);
  
  Throwable e2 = innerException(e);
  if (e2.getClass() == RuntimeException.class && eq(e2.getMessage(), "Thread cancelled.") || e2 instanceof InterruptedException)
    return;
    
  for (Object f : cloneList(_handleException_onException)) try {
    callF(f, e);
  } catch (Throwable e3) {
    printStackTrace2(e3); // not using pcall here - it could lead to endless loops
  }
}

static volatile int numberOfCores_value;

static int numberOfCores() {
  if (numberOfCores_value == 0)
    numberOfCores_value = Runtime.getRuntime().availableProcessors();
  return numberOfCores_value;
}
static <A> A or(A a, A b) {
  return a != null ? a : b;
}
// this syntax should be removed...
static Object getThreadLocal(Object o, String name) {
  ThreadLocal t =  (ThreadLocal) (getOpt(o, name));
  return t != null ? t.get() : null;
}

static <A> A getThreadLocal(ThreadLocal<A> tl) {
  return tl == null ? null : tl.get();
}

static <A> A getThreadLocal(ThreadLocal<A> tl, A defaultValue) {
  return or(getThreadLocal(tl), defaultValue);
}
static Object getOpt(Object o, String field) {
  return getOpt_cached(o, field);
}

static Object getOpt(String field, Object o) {
  return getOpt_cached(o, field);
}

static Object getOpt_raw(Object o, String field) { try {
  Field f = getOpt_findField(o.getClass(), field);
  if (f == null) return null;
  makeAccessible(f);
  return f.get(o);
} catch (Exception __e) { throw rethrow(__e); } }

// access of static fields is not yet optimized
static Object getOpt(Class c, String field) { try {
  if (c == null) return null;
  Field f = getOpt_findStaticField(c, field);
  if (f == null) return null;
  makeAccessible(f);
  return f.get(null);
} catch (Exception __e) { throw rethrow(__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() & java.lang.reflect.Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}

static Class javax() {
  return getJavaX();
}
static ThreadLocal<Object> print_byThread() {
  synchronized(print_byThread_lock) {
    if (print_byThread == null)
      print_byThread = new ThreadLocal();
  }
  return print_byThread;
}
// f can return false to suppress regular printing
// call print_raw within f to actually print something
static AutoCloseable tempInterceptPrint(F1<String, Boolean> f) {
  return tempSetThreadLocal(print_byThread(), f);
}
// get purpose 1: access a list/array/map (safer version of x.get(y))

static <A> A get(List<A> l, int idx) {
  return l != null && idx >= 0 && idx < l(l) ? l.get(idx) : null;
}

// seems to conflict with other signatures
/*static <A, B> B get(Map<A, B> map, A key) {
  ret map != null ? map.get(key) : null;
}*/

static <A> A get(A[] l, int idx) {
  return idx >= 0 && idx < l(l) ? l[idx] : null;
}

// default to false
static boolean get(boolean[] l, int idx) {
  return idx >= 0 && idx < l(l) ? l[idx] : false;
}

// get purpose 2: access a field by reflection or a map

static Object get(Object o, String field) {
  try {
    if (o == null) return null;
    if (o instanceof Class) return get((Class) o, field);
    
    if (o instanceof Map)
      return ((Map) o).get(field);
      
    Field f = getOpt_findField(o.getClass(), field);
    if (f != null) {
      makeAccessible(f);
      return f.get(o);
    }
      
    
      if (o instanceof DynamicObject)
        return ((DynamicObject) o).fieldValues.get(field);
    
  } catch (Exception e) {
    throw asRuntimeException(e);
  }
  throw new RuntimeException("Field '" + field + "' not found in " + o.getClass().getName());
}

static Object get_raw(String field, Object o) {
  return get_raw(o, field);
}

static Object get_raw(Object o, String field) { try {
  if (o == null) return null;
  Field f = get_findField(o.getClass(), field);
  makeAccessible(f);
  return f.get(o);
} catch (Exception __e) { throw rethrow(__e); } }

static Object get(Class c, String field) {
  try {
    Field f = get_findStaticField(c, field);
    makeAccessible(f);
    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() & java.lang.reflect.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 Object get(String field, Object o) {
  return get(o, field);
}
static Map<Class, ArrayList<Method>> callF_cache = newDangerousWeakHashMap();


  static <A> A callF(F0<A> f) {
    return f == null ? null : f.get();
  }



  static <A, B> B callF(F1<A, B> f, A a) {
    return f == null ? null : f.get(a);
  }



  static <A> A callF(IF0<A> f) {
    return f == null ? null : f.get();
  }



  static <A, B> B callF(IF1<A, B> f, A a) {
    return f == null ? null : f.get(a);
  }



  static <A, B, C> C callF(F2<A, B, C> f, A a, B b) {
    return f == null ? null : f.get(a, b);
  }



  static <A, B, C> C callF(IF2<A, B, C> f, A a, B b) {
    return f == null ? null : f.get(a, b);
  }



  static <A> void callF(VF1<A> f, A a) {
    if (f != null) f.get(a);
  }


static Object callF(Object f, Object... args) { try {
  if (f instanceof String)
    return callMC((String) f, args);
  if (f instanceof Runnable) {
    ((Runnable) f).run();
    return null;
  }
  if (f == null) return null;
  
  Class c = f.getClass();
  ArrayList<Method> methods;
  synchronized(callF_cache) {
    methods = callF_cache.get(c);
    if (methods == null)
      methods = callF_makeCache(c);
  }
  
  int n = l(methods);
  if (n == 0) {
    
    throw fail("No get method in " + getClassName(c));
  }
  if (n == 1) return invokeMethod(methods.get(0), f, args);
  for (int i = 0; i < n; i++) {
    Method m = methods.get(i);
    if (call_checkArgs(m, args, false))
      return invokeMethod(m, f, args);
  }
  throw fail("No matching get method in " + getClassName(c));
} catch (Exception __e) { throw rethrow(__e); } }

// used internally
static ArrayList<Method> callF_makeCache(Class c) {
  ArrayList<Method> l = new ArrayList();
  Class _c = c;
  do {
    for (Method m : _c.getDeclaredMethods())
      if (m.getName().equals("get")) {
        makeAccessible(m);
        l.add(m);
      }
    if (!l.isEmpty()) break;
    _c = _c.getSuperclass();
  } while (_c != null);
  callF_cache.put(c, l);
  return l;
}
static String formatInt(int i, int digits) {
  return padLeft(str(i), '0', digits);
}

static String formatInt(long l, int digits) {
  return padLeft(str(l), '0', digits);
}
// f can return false to suppress regular printing
// call print_raw within f to actually print something
// f preferrably is F1<S, Bool>
static Object interceptPrintInThisThread(Object f) {
  Object old = print_byThread().get();
  print_byThread().set(f);
  return old;
}
static void _close(AutoCloseable c) {
  if (c != null) try {
    c.close();
  } catch (Throwable e) {
    // Some classes stupidly throw an exception on double-closing
    if (c instanceof javax.imageio.stream.ImageOutputStream)
      return;
    else throw rethrow(e);
  }
}
static boolean sameSnippetID(String a, String b) {
  if (!isSnippetID(a) || !isSnippetID(b)) return false;
  return parseSnippetID(a) == parseSnippetID(b);
}
static String programID() {
  return getProgramID();
}

static String programID(Object o) {
  return getProgramID(o);
}
static String defaultJavaXTranslatorID_value = "#759";

static String defaultJavaXTranslatorID() {
  return defaultJavaXTranslatorID_value;
}
static void setDefaultJavaXTranslatorID(String snippetID) {
  defaultJavaXTranslatorID_value = snippetID;
}
  static String mainJava;
  
  static String loadMainJava() throws IOException {
    if (mainJava != null) return mainJava;
    return loadTextFile("input/main.java", "");
  }
static int identityHashCode(Object o) {
  return System.identityHashCode(o);
}
static <A> HashSet<A> cloneHashSet(Collection<A> set) {
  if (set == null) return new HashSet();
  synchronized(collectionMutex(set)) {
    HashSet < A > s = new HashSet(l(set));
    s.addAll(set);
    return s;
  }
}
static Set<String> tok_mapLikeFunctions_set = asHashSet(
  splitAtSpace("map mapI mapLL filter concatMap pairMap antiFilter map_pcall mapPairB_pcall callFVerbose call_verbose map_pcall_notNull mapToCISet mapToHashSet mapVoid lazilyMap sortedByCalculatedField repF rcurry"));
  
static Set<String> tok_mapLikeFunctions() {
  return tok_mapLikeFunctions_set;
}
static Set<String> tok_mapMethodLikeFunctions_set = asHashSet(
  splitAtSpace("mapMethod uniquifyByField indexByField collect"));
  
static Set<String> tok_mapMethodLikeFunctions() {
  return tok_mapMethodLikeFunctions_set;
}
  static boolean hasCodeTokens(List<String> tok, String... tokens) {
    return findCodeTokens(tok, tokens) >= 0;
  }
static int l(Object[] a) { return a == null ? 0 : a.length; }
static int l(boolean[] a) { return a == null ? 0 : a.length; }
static int l(byte[] a) { return a == null ? 0 : a.length; }
static int l(short[] a) { return a == null ? 0 : a.length; }
static int l(long[] a) { return a == null ? 0 : a.length; }
static int l(int[] a) { return a == null ? 0 : a.length; }
static int l(float[] a) { return a == null ? 0 : a.length; }
static int l(double[] a) { return a == null ? 0 : a.length; }
static int l(char[] a) { return a == null ? 0 : a.length; }
static int l(Collection c) { return c == null ? 0 : c.size(); }
static int l(Iterator i) { return iteratorCount_int_close(i); } // consumes the iterator && closes it if possible
static int l(Map m) { return m == null ? 0 : m.size(); }
static int l(CharSequence s) { return s == null ? 0 : s.length(); }
static long l(File f) { return f == null ? 0 : f.length(); }



static int l(Object o) {
  return o == null ? 0
    : o instanceof String ? l((String) o)
    : o instanceof Map ? l((Map) o)
    : o instanceof Collection ? l((Collection) o)
    : o instanceof Object[] ? l((Object[]) o)
    : o instanceof boolean[] ? l((boolean[]) o)
    : o instanceof byte[] ? l((byte[]) o)
    : o instanceof char[] ? l((char[]) o)
    : o instanceof short[] ? l((short[]) o)
    : o instanceof int[] ? l((int[]) o)
    : o instanceof float[] ? l((float[]) o)
    : o instanceof double[] ? l((double[]) o)
    : o instanceof long[] ? l((long[]) o)
    : (Integer) call(o, "size");
}



  static int l(MultiSet ms) { return ms == null ? 0 : ms.size(); }





  static int l(IntRange r) { return r == null ? 0 : r.length(); }



static <A> List<A> singlePlusList(A a, Collection<A> l) {
  return itemPlusList(a, l);
}
static Object first(Object list) {
  return first((Iterable) list);
}

static <A> A first(List<A> list) {
  return empty(list) ? null : list.get(0);
}

static <A> A first(A[] bla) {
  return bla == null || bla.length == 0 ? null : bla[0];
}


static <A> A first(IterableIterator<A> i) {
  return first((Iterator<A>) i);
}


static <A> A first(Iterator<A> i) {
  return i == null || !i.hasNext() ? null : i.next();
}

static <A> A first(Iterable<A> i) {
  if (i == null) return null;
  Iterator<A> it = i.iterator();
  return it.hasNext() ? it.next() : null;
}

static Character first(String s) { return empty(s) ? null : s.charAt(0); }


static <A, B> A first(Pair<A, B> p) {
  return p == null ? null : p.a;
}



static String[] dropFirst(int n, String[] a) {
  return drop(n, a);
}

static String[] dropFirst(String[] a) {
  return drop(1, a);
}

static Object[] dropFirst(Object[] a) {
  return drop(1, a);
}

static <A> List<A> dropFirst(List<A> l) {
  return dropFirst(1, l);
}

static <A> List<A> dropFirst(int n, Iterable<A> i) { return dropFirst(n, toList(i)); }
static <A> List<A> dropFirst(Iterable<A> i) { return dropFirst(toList(i)); }

static <A> List<A> dropFirst(int n, List<A> l) {
  return n <= 0 ? l : new ArrayList(l.subList(Math.min(n, l.size()), l.size()));
}

static <A> List<A> dropFirst(List<A> l, int n) {
  return dropFirst(n, l);
}

static String dropFirst(int n, String s) { return substring(s, n); }
static String dropFirst(String s, int n) { return substring(s, n); }
static String dropFirst(String s) { return substring(s, 1); }
// TODO: extended multi-line strings

static int javaTok_n, javaTok_elements;
static boolean javaTok_opt = false;

static List<String> javaTok(String s) {
  ++javaTok_n;
  ArrayList<String> tok = new ArrayList();
  int l = s == null ? 0 : s.length();
  
  int i = 0, n = 0;
  while (i < l) {
    int j = i;
    char c, d;
    
    // scan for whitespace
    while (j < l) {
      c = s.charAt(j);
      d = j+1 >= l ? '\0' : s.charAt(j+1);
      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
        ++j;
      else if (c == '/' && d == '*') {
        do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
        j = Math.min(j+2, l);
      } else if (c == '/' && d == '/') {
        do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
      } else
        break;
    }
    
    tok.add(javaTok_substringN(s, i, j));
    ++n;
    i = j;
    if (i >= l) break;
    c = s.charAt(i);
    d = i+1 >= l ? '\0' : s.charAt(i+1);

    // scan for non-whitespace
    
    // Special JavaX syntax: 'identifier
    if (c == '\'' && Character.isJavaIdentifierStart(d) && i+2 < l && "'\\".indexOf(s.charAt(i+2)) < 0) {
      j += 2;
      while (j < l && Character.isJavaIdentifierPart(s.charAt(j)))
        ++j;
    } else if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        int c2 = s.charAt(j);
        if (c2 == opener || c2 == '\n' && opener == '\'') { // allow multi-line strings, but not for '
          ++j;
          break;
        } else if (c2 == '\\' && j+1 < l)
          j += 2;
        else
          ++j;
      }
    } else if (Character.isJavaIdentifierStart(c))
      do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || s.charAt(j) == '\'')); // 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 (c == '[' && d == '[') {
      do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]"));
      j = Math.min(j+2, l);
    } else if (c == '[' && d == '=' && 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(javaTok_substringC(s, i, j));
    ++n;
    i = j;
  }
  
  if ((tok.size() % 2) == 0) tok.add("");
  javaTok_elements += tok.size();
  return tok;
}

static List<String> javaTok(List<String> tok) {
  return javaTokWithExisting(join(tok), tok);
}
static void replaceTokens_reTok(List<String> tok, int i, int j, String s) {
  replaceTokens(tok, i, j, s);
  reTok(tok, i, j);
}
static String unnull(String s) {
  return s == null ? "" : s;
}

static <A> Collection<A> unnull(Collection<A> l) {
  return l == null ? emptyList() : l;
}

static <A> List<A> unnull(List<A> l) {
  return l == null ? emptyList() : l;
}

static <A, B> Map<A, B> unnull(Map<A, B> l) {
  return l == null ? emptyMap() : l;
}

static <A> Iterable<A> unnull(Iterable<A> i) {
  return i == null ? emptyList() : i;
}

static <A> A[] unnull(A[] a) {
  return a == null ? (A[]) new Object[0] : a;
}

static BitSet unnull(BitSet b) {
  return b == null ? new BitSet() : b;
}



//ifclass Symbol



static <A, B> Pair<A, B> unnull(Pair<A, B> p) {
  return p != null ? p : new Pair(null, null);
}

static List<String> tok_moveImportsUp(List<String> tok) {
  StringBuilder buf = new StringBuilder();
  
  //print("tok_moveImportsUp n=" + l(tok));
  //print("Source begins: " + quote(shorten(join(tok), 200)));
  
  // scan imports on top
  boolean change = false;
  int i;
  Set<String> have = new HashSet();
  for (i = 1; i < l(tok); i += 2)
    if (eq(tok.get(i), "import")) {
      int j = indexOf(tok, i+2, ";");
      if (j < 0) break;
      String s = joinSubList(tok, i, j+1);
      have.add(s);
      i = j;
    } else break;
  
  //print("tok_moveImportsUp have " + l(have) + " after " + i);
  
  // scan imports in text
  for (; i < l(tok); i += 2)
    if (eq(tok.get(i), "import")) {
      int j = indexOf(tok, i+2, ";");
      if (j < 0) continue; // huh?
      String s = joinSubList(tok, i, j+1);
      //print("Found import at " + i + ": " + s);
      if (!have.contains(s)) {
        buf.append(s).append("\n");
        have.add(s);
      }
      replaceTokens/*_reTok*/(tok, i, j+1, "");
      change = true;
      i -= 2;
    }
    
  if (nempty(buf)) {
    if (l(tok) == 1) addAll(tok, "", "");
    tok.set(1, str(buf)+"\n"+tok.get(1));
    change = true;
    //reTok(tok, 1, 2);
  }
    
  if (change) reTok(tok);
  return tok;
}
static boolean tok_hasTranslators(List<String> tok) {
  int n = l(tok)-2;
  for (int i = 1; i < n; i += 2)
    if (tok.get(i).equals("!") && isInteger(tok.get(i+2)))
      return true;
  return false;
}
static String defaultTranslate(String text) {
  Class javax = getJavaX();
  File x = makeTempDir();
  saveTextFile(new File(x, "main.java"), text);
  List<File> libraries_out = new ArrayList();
  List<String> libraries2_out = new ArrayList();
  File y =  (File) (call(javax, "defaultTranslate", x, libraries_out, libraries2_out));
  if (y == null) return null;
  return loadTextFile(new File(y, "main.java"));
}

public static <A> String join(String glue, Iterable<A> strings) {
  if (strings == null) return "";
  if (strings instanceof Collection) {
    if (((Collection) strings).size() == 1) return str(first(((Collection) strings)));
  }
  StringBuilder buf = new StringBuilder();
  Iterator<A> 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));
}

static <A> String join(Iterable<A> strings) {
  return join("", strings);
}

static <A> String join(Iterable<A> strings, String glue) {
  return join(glue, strings);
}

public static String join(String[] strings) {
  return join("", strings);
}


static String join(String glue, Pair p) {
  return p == null ? "" : str(p.a) + glue + str(p.b);
}

static void tok_metaTransformNow(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "meta-transformNow {")) >= 0) {
    int iOpening = indexOf(tok, i, "{");
    int iClosing = findEndOfBracketPart(tok, iOpening)-1;
    String code = joinSubList(tok, iOpening+2, iClosing-1);
    clearTokens_reTok(tok, i, iClosing+1);
    tok_runMetaTransformer(tok, code);
  }
}
static void tok_selfType(List<String> tok) {
  int i;
  mainLoop: while ((i = jfind(tok, "selfType <id>")) >= 0) {
    // Now find class name by going backwards.
    
    int j = i, level = 1;
    while (j > 0 && level > 0) {
      String t = tok.get(j);
      if (t.equals("}")) ++level;
      if (t.equals("{")) --level;
      j -= 2;
    }
    
    // search for class name
    while (j > 0) {
      String t = tok.get(j);
      if (t.equals("class")) {
        String className = tok.get(j+2);
        tok.set(i, className);
        
        continue mainLoop;
      }
      j -= 2;
    }
    tok.set(i, "Object"); // avoid endless loop
  }
}
// extra commas - e.g. ll(1, 2,) => ll(1, 2)
static void tok_dropExtraCommas(List<String> tok) {
  jreplace(tok, ",)", ")");
}
static void tok_delegateTo(List<String> tok) {
  jreplace(tok, "delegate <id> to <id>.", "replace $2 with $4.$2.");
  jreplace(tok, "delegate <id> to <id>().", "replace $2 with $4().$2.");
  // TODO: general expressions in "to" clause
}

static void tok_replaceWith(List<String> tok) {
  int i;
  while ((i = jfind(tok, "replace <id> with")) >= 0) {
    String token = tok.get(i+2);
    int repStart = i+6;
    int repEnd = repStart;
    
    // Find . with space or line break or EOF afterwards
    while (!(eqGet(tok, repEnd, ".") &&
      (nempty(get(tok, repEnd+1)) || repEnd == l(tok)-2)))
      repEnd += 2;
    //int repEnd = smartIndexOf(tok, repStart, ".");
    
    String replacement = joinSubList(tok, repStart, repEnd-1);
    clearTokens(tok, i, repEnd+1);
    print("Replacing " + token + " with " + replacement + ".");
    int end = findEndOfBlock(tok, repEnd)-1;
    for (int j = repEnd+2; j < end; j += 2)
      if (eq(tok.get(j), token)) tok.set(j, replacement);
    reTok(tok, i, end);
  }
}



static String jreplace(String s, String in, String out) {
  return jreplace(s, in, out, null);
}

static String jreplace(String s, String in, String out, Object condition) {
  List<String> tok = javaTok(s);
  return jreplace(tok, in, out, condition) ? join(tok) : s;
}

// leaves tok properly tokenized
// returns true iff anything was replaced
static boolean jreplace(List<String> tok, String in, String out) {
  return jreplace(tok, in, out, false, true, null);
}

static boolean jreplace(List<String> tok, String in, String out, Object condition) {
  return jreplace(tok, in, out, false, true, condition);
}

static boolean jreplace(List<String> tok, String in, String out, IF2<List<String>, Integer, Boolean> condition) {
  return jreplace(tok, in, out, (Object) condition);
}

static boolean jreplace(List<String> tok, String in, String out, boolean ignoreCase, boolean reTok, Object condition) {
  String[] toks = javaTokForJFind_array(in);
  int lTokin = toks.length*2+1;

  boolean anyChange = false;
  int i = -1;
  for (int n = 0; n < 10000; n++) { // TODO: don't need this check anymore
    i = findCodeTokens(tok, i+1, ignoreCase, toks, condition);
    if (i < 0)
      return anyChange;
    List<String> subList = tok.subList(i-1, i+lTokin-1); // N to N
    String expansion = jreplaceExpandRefs(out, subList);
    int end = i+lTokin-2;
    clearAllTokens(tok, i, end); // C to C
    tok.set(i, expansion);
    if (reTok) // would this ever be false??
      reTok(tok, i, end);
    i = end;
    anyChange = true;
  }
  throw fail("woot? 10000! " + quote(in) + " => " + quote(out));
}

static boolean jreplace_debug = false;
static RuntimeException rethrow(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  throw t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}

static RuntimeException rethrow(String msg, Throwable t) {
  throw new RuntimeException(msg, t);
}
static void tok_collectMetaPostBlocks(List<String> tok, List<String> postProcessBlocks_out) {
  int i = -1;
  while ((i = jfind(tok, i+1, "meta-postProcess {")) >= 0) {
    int iOpening = indexOf(tok, i, "{");
    int iClosing = findEndOfBracketPart(tok, iOpening)-1;
    add(postProcessBlocks_out, joinSubList(tok, iOpening+2, iClosing-1));
    clearTokens_reTok(tok, i, iClosing+1);
  }
}
static void tok_collectTransformers(List<String> tok, List<String> out) {
  int i = -1;
  while ((i = jfind(tok, i+1, "meta-transform {")) >= 0) {
    int iOpening = indexOf(tok, i, "{");
    int iClosing = findEndOfBracketPart(tok, iOpening)-1;
    add(out, joinSubList(tok, iOpening+2, iClosing-1));
    clearTokens_reTok(tok, i, iClosing+1);
  }
}
static long now_virtualTime;
static long now() {
  return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis();
}

static boolean tok_sameTest(List<String> tok, String text) {
  int iTok = 0, iText = 0, nTok = l(tok), nText = l(text);
  while (iTok < nTok) {
    String t = tok.get(iTok++);
    int n = l(t);
    if (n == 0) continue;
    if (!t.regionMatches(0, text, iText, n)) return false;
    iText += n;
  }
  return iText == nText;
}
static RuntimeException fail() { throw new RuntimeException("fail"); }
static RuntimeException fail(Throwable e) { throw asRuntimeException(e); }
static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); }
static RuntimeException fail(String msg) { throw new RuntimeException(msg == null ? "" : msg); }
static RuntimeException fail(String msg, Throwable innerException) { throw new RuntimeException(msg, innerException); }

static String jreplace1(String s, String in, String out) {
  return jreplace1(s, in, out, null);
}

static String jreplace1(String s, String in, String out, Object condition) {
  List<String> tok = javaTok(s);
  return jreplace1(tok, in, out, condition) ? join(tok) : s;
}

// leaves tok properly tokenized
// returns true iff anything was replaced
static boolean jreplace1(List<String> tok, String in, String out) {
  return jreplace1(tok, in, out, false, true, null);
}

static boolean jreplace1(List<String> tok, String in, String out, Object condition) {
  return jreplace1(tok, in, out, false, true, condition);
}

static boolean jreplace1(List<String> tok, String in, String out, boolean ignoreCase, boolean reTok, Object condition) {
  List<String> tokin = javaTok(in);
  jfind_preprocess(tokin);

  boolean anyChange = false;
  int i = 1;
  while ((i = findCodeTokens(tok, i, ignoreCase, toStringArray(codeTokensOnly(tokin)), condition)) >= 0) {
    List<String> subList = tok.subList(i-1, i+l(tokin)-1); // N to N
    String expansion = jreplaceExpandRefs(out, subList);
    int end = i+l(tokin)-2;
    clearAllTokens(tok, i, end); // C to C
    tok.set(i, expansion);
    if (reTok) // would this ever be false??
      reTok(tok, i, end);
    i = end;
    anyChange = true;
  }
  return anyChange;
}

static boolean jreplace1_debug = false;
// out: func(L<S> tok, int cIndex) -> S
// condition: func(L<S> tok, int nIndex) -> S  [yeah it's inconsistent]
static String jreplace_dyn(String s, String in, Object out) {
  return jreplace_dyn(s, in, out, null);
}

static String jreplace_dyn(String s, String in, Object out, Object condition) {
  List<String> tok = javaTok(s);
  jreplace_dyn(tok, in, out, condition);
  return join(tok);
}

// leaves tok properly tokenized
// returns true iff anything was replaced
static boolean jreplace_dyn(List<String> tok, String in, Object out) {
  return jreplace_dyn(tok, in, out, false, true, null);
}

static boolean jreplace_dyn(List<String> tok, String in, Object out, Object condition) {
  return jreplace_dyn(tok, in, out, false, true, condition);
}

static boolean jreplace_dyn(List<String> tok, String in, Object out, boolean ignoreCase, boolean reTok, Object condition) {
  List<String> tokin = javaTok(in);
  jfind_preprocess(tokin);
  
  String[] toks = toStringArray(codeTokensOnly(tokin));

  boolean anyChange = false;
  for (int n = 0; n < 10000; n++) {
    int i = findCodeTokens(tok, 1, ignoreCase, toks, condition);
    if (i < 0)
      return anyChange;
    String expansion =  (String) (callF(out, tok, i));
    int end = i+l(tokin)-2;
    clearAllTokens(tok, i, end); // C to C
    tok.set(i, expansion);
    if (reTok) // would this ever be false??
      reTok(tok, i, end);
    anyChange = true;
  }
  throw fail("woot? 10000! " + quote(in) + " => " + quote(out));
}
static String stringToLegalIdentifier(String s) {
  StringBuilder buf = new StringBuilder();
  s = dropTrailingSquareBracketStuff(s);
  for (int i = 0; i < l(s); i++) {
    char c = s.charAt(i);
    if (empty(buf) ? Character.isJavaIdentifierStart(c) : Character.isJavaIdentifierPart(c))
      buf.append(c);
  }
  if (empty(buf)) throw fail("Can't convert to legal identifier: " + s);
  return str(buf);
}
static String getSnippetTitle(String id) { try {
  if (id == null) return null;
  if (!isSnippetID(id)) return "?";
  
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.getSnippetTitle(id);
  

  if (isLocalSnippetID(id)) return localSnippetTitle(id);
  long parsedID = parseSnippetID(id);
  String url;
  if (isImageServerSnippet(parsedID))
    url = imageServerURL() + "title/" + parsedID + muricaCredentialsQuery();
  else if (isGeneralFileServerSnippet(parsedID))
    url = "http://butter.botcompany.de:8080/files/name/" + parsedID;
  else
    url = tb_mainServer() + "/tb-int/getfield.php?id=" + parsedID + "&field=title" + standardCredentials_noCookies();
  String title = trim(loadPageSilently(url));
  if (title != null)
    try { saveTextFileIfChanged(snippetTitle_cacheFile(id), title); } catch (Throwable __e) { print(exceptionToStringShort(__e)); }
  return or(title, "?");
} catch (Exception __e) { throw rethrow(__e); } }

static String getSnippetTitle(long id) {
  return getSnippetTitle(fsI(id));
}

static void tok_overridableFunctionDefs(List<String> tok, Set<String> overriableFunctions_out) {
  int i;
  while ((i = jfind(tok, "static overridable <id>")) >= 0) {
    int bracket = indexOf(tok, "(", i);
    int codeStart = indexOf(tok, "{", bracket);
    String fName = assertIdentifier(tok.get(bracket-2));
    String type = joinSubList(tok, i+4, bracket-3);
    String boxedType = tok_toNonPrimitiveTypes(type);
    String args = joinWithComma(map("tok_lastIdentifier",tok_parseArgsDeclList(subList(tok, i))));
    String castIt = eq(type, "Object") ? "" : ("(" + (type) + ")");

    replaceTokens(tok, i, bracket-1, ("static Object _override_" + (fName) + ";\n")
      + ("static " + (type) + " " + (fName)));
    
    tokAppend(tok, codeStart, (" if (_override_" + (fName) + " != null) return " + (castIt) + " callF(_override_" + (fName) + ", " + (args) + ");\n"));
    reTok(tok, i, codeStart+1);
    addToCollection(overriableFunctions_out, fName);
  }
}
static String sfu(Object o) { return structureForUser(o); }
static boolean containsOneOfIC(Collection<String> l, String... x) {
  Set<String> set = asCISet(x);
  for (String a : unnull(l))
    if (set.contains(a))
      return true;
  return false;
}
static boolean containsIC(Collection<String> l, String s) {
  return containsIgnoreCase(l, s);
}

static boolean containsIC(String[] l, String s) {
  return containsIgnoreCase(l, s);
}

static boolean containsIC(String s, char c) {
  return containsIgnoreCase(s, c);
}

static boolean containsIC(String a, String b) {
  return containsIgnoreCase(a, b);
}





static String javaParser_makeAllPublic(String src, Object... __) {
  CompilationUnit cu = javaParseCompilationUnit(src);
  PrettyPrinterConfiguration ppconf = new PrettyPrinterConfiguration();
  ppconf.setIndent("  ");
  ppconf.setPrintComments(false);
  javaParser_makeAllPublic_Visitor visitor = new javaParser_makeAllPublic_Visitor();
  visitor.notTopLevelClassDecl = boolOptPar("notTopLevelClassDecl",__);
  visitor.visit(cu, null);
  return cu.toString(ppconf);
}

static class javaParser_makeAllPublic_Visitor extends VoidVisitorAdapter {
  boolean notTopLevelClassDecl = false; // don't make top-level class declaration public
  
  void makePublic(Set<com.github.javaparser.ast.Modifier> modifiers) {
    modifiers.remove(com.github.javaparser.ast.Modifier.PRIVATE);
    modifiers.remove(com.github.javaparser.ast.Modifier.PROTECTED);
    modifiers.add(com.github.javaparser.ast.Modifier.PUBLIC);
  }

  public void visit(ClassOrInterfaceDeclaration n, Object arg) {
    Node parent = n.getParentNode().get();
    if (!(parent instanceof LocalClassDeclarationStmt
      || parent instanceof CompilationUnit && neq(n.getName().asString(), "main")))
      if (notTopLevelClassDecl)
        notTopLevelClassDecl = false;
      else
        makePublic(n.getModifiers());
    super.visit(n, arg);
  }
  
  public void visit(FieldDeclaration n, Object arg) {
    makePublic(n.getModifiers());
    super.visit(n, arg);
  }
  
  public void visit(ConstructorDeclaration n, Object arg) {
    Node parent = n.getParentNode().get();
    if (!(parent instanceof EnumDeclaration))
      makePublic(n.getModifiers());
    super.visit(n, arg);
  }
  
  public void visit(MethodDeclaration n, Object arg) {
    EnumSet<com.github.javaparser.ast.Modifier> m = n.getModifiers();
    //print("Method found: " + n.getName() + " with modifiers: " + m + ", position: " + n.getRange()->begin);
    if (m.contains(com.github.javaparser.ast.Modifier.PRIVATE) && !m.contains(com.github.javaparser.ast.Modifier.STATIC)) m.add(com.github.javaparser.ast.Modifier.FINAL);
    makePublic(m);
    super.visit(n, arg);
  }
}
static String javaParser_makeAllPublic_keepComments(String src) {
  //CompilationUnit cu = javaParseCompilationUnit(src);
  CompilationUnit cu = JavaParser.parse(src);
  PrettyPrinterConfiguration ppconf = new PrettyPrinterConfiguration();
  ppconf.setIndent("  ");
  new javaParser_makeAllPublic_Visitor().visit(cu, null);
  return cu.toString(ppconf);
}
static String javaParser_reparse_keepComments(String src) {
  CompilationUnit cu = JavaParser.parse(src);
  PrettyPrinterConfiguration ppconf = new PrettyPrinterConfiguration();
  ppconf.setIndent("  ");
  return cu.toString(ppconf);
}
static void extractAndPrintJavaParseError(String src, Throwable e) {
  print();
  String msg = firstLine(innerMessage(e));
  print(msg);
  int line = parseIntOpt(regexpFirstGroupIC("line (\\d+)", msg));
  print();
  if (line != 0) {
    List<String> lines = lines(src);
    for (int i = max(1, line-5); i <= min(l(lines), line+5); i++)
      print((i == line ? "* " : "  ") + "LINE " + i + ": " + lines.get(i-1));
  }
  print();
}
static File javaxCachesDir_dir; // can be set to work on different base dir

static File javaxCachesDir() {
  return javaxCachesDir_dir != null ? javaxCachesDir_dir : new File(userHome(), "JavaX-Caches");
}

static File javaxCachesDir(String sub) {
  return newFile(javaxCachesDir(), sub);
}
static void saveTextFileVerbose(File f, String text) {
  boolean exists = f.exists();
  saveTextFile(f, text);
  print((exists ? "Created" : "Updated") + " file " + f2s(f) + " (" + f.length() + " bytes)");
}
static void tokPrepend(List<String> tok, int i, String s) {
  tok.set(i, s + tok.get(i));
}
static List<String> reTok(List<String> tok) {
  replaceCollection(tok, javaTok(tok));
  return tok;
}

static List<String> reTok(List<String> tok, int i) {
  return reTok(tok, i, i+1);
}

static List<String> reTok(List<String> tok, int i, int j) {
  // extend i to an "N" token
  // and j to "C" (so j-1 is an "N" token)
  i = max(i & ~1, 0);
  j = min(l(tok), j | 1);
  if (i >= j) return tok;
  
  List<String> t = javaTok(joinSubList(tok, i, j));
  replaceListPart(tok, i, j, t);
  
  // fallback to safety
  // reTok(tok);
  
  return tok;
}


static List<String> reTok(List<String> tok, IntRange r) {
  if (r != null) reTok(tok, r.start, r.end);
  return tok;
}

static boolean nempty(Collection c) {
  return !empty(c);
}

static boolean nempty(CharSequence s) {
  return !empty(s);
}

static boolean nempty(Object[] o) { return !empty(o); }
static boolean nempty(byte[] o) { return !empty(o); }
static boolean nempty(int[] o) { return !empty(o); }

static boolean nempty(Map m) {
  return !empty(m);
}

static boolean nempty(Iterator i) {
  return i != null && i.hasNext();
}

static boolean nempty(Object o) { return !empty(o); }
static String concatMap_strings(Object f, Iterable l) {
  return join((List<String>) map(f, l));
}

static String concatMap_strings(Object f, Object[] l) {
  return join((List<String>) map(f, l));
}

static String concatMap_strings(Iterable l, Object f) {
  return concatMap_strings(f, l);
}
static String f2s(File f) {
  return f == null ? null : f.getAbsolutePath();
}

static String f2s(String s) { return f2s(newFile(s)); }


 static String f2s(java.nio.file.Path p) {
  return p == null ? null : f2s(p.toFile());
}

static File saveProgramTextFile(String name, String contents) {
  return saveTextFile(getProgramFile(name), contents);
}

static File saveProgramTextFile(String progID, String name, String contents) {
  return saveTextFile(getProgramFile(progID, name), contents);
}
static void splitJavaFiles(List<String> tok) { try {
  splitJavaFiles(tok, newFile("output"));
} catch (Exception __e) { throw rethrow(__e); } }

static void splitJavaFiles(List<String> tok, File outDir) { try {
  List<Integer> indexes = jfindAll(tok, "package");
  if (empty(indexes) || indexes.get(0) != 1)
    indexes.add(0, 1);
  for (int i = 0; i < l(indexes); i++) {
    int from = indexes.get(i);
    int to = i+1 < l(indexes) ? indexes.get(i+1) : l(tok);
    List<String> subtok = cncSubList(tok, from, to);
    String src = join(subtok);
    //print(shorten(src, 80));
    String pack = tok_packageName(subtok);
    print("Package: " + quote(pack));
    List<List<String>> classes = allClasses(subtok);
    /*for (L<S> c : classes) {
      //print("  Class: " + shorten(join(c), 80));
      print("  Class: " + quote(getClassDeclarationName(c)));
    }*/
    if (empty(classes))
      print("No classes?? " + quote(src));
    else {
      String className = getNameOfPublicClass(subtok);
      if (className == null) className = getNameOfAnyClass(subtok);

      String fileName = addSlash(pack.replace('.', '/')) + className + ".java";
      print("File name: " + fileName);
      saveTextFile(newFile(outDir, fileName), join(subtok));
    }
    print();
  }
} catch (Exception __e) { throw rethrow(__e); } }
  static void saveMainJava(String s) throws IOException {
    if (mainJava != null)
      mainJava = s;
    else
      saveTextFile("output/main.java", s);
  }
  
  static void saveMainJava(List<String> tok) throws IOException {
    saveMainJava(join(tok));
  }

// process scope x. ... end scope blocks
static void tok_scopes(List<String> tok, Object... __) {
  if (!tok.contains("scope")) return;
  boolean autoCloseScopes = boolPar("autoCloseScopes",__);
  
  int i;
  while ((i = rjfind(tok, "scope <id> .")) >= 0) {
    String scopeName = tok.get(i+2);
    int j = jfind(tok, i+6, "end scope");
    if (j < 0)
      if (autoCloseScopes)
        j = l(tok);
      else
        throw fail("Scope " + scopeName + " opened but not closed");
    else
      clearTokens(tok, j, j+3);

    HashSet<String> names = new HashSet();
    HashSet<String> functions = new HashSet();
    
    clearTokens(tok, i, i+5);
    List<String> subTok = subList(tok, i+5, j);
    
    // auto-make #lock variable
    if (jfind(subTok, "lock #lock") >= 0
      && jfind(subTok, "Lock #lock") < 0)
        tok.set(i, "static Lock " + scopeName + "_lock = lock();\n");
    
    // first pass (find # declarations)
    for (int k = i+6; k < j-2; k += 2) {
      String t = get(tok, k+2);
      if (eqGet(tok, k, "#") && isIdentifier(t)) {
        names.add(t);
        if (eqGetOneOf(tok, k+4, "(", "{", "<", "extends", "implements", ">")) // cover class declarations too
          functions.add(t); 
        replaceTokens(tok, k, k+3, scopeName + "_" + t);
      }
    }
    
    // second pass (references to scoped variables)
    for (int k = i+6; k < j; k += 2) {
      String t = get(tok, k);
      if (isIdentifier(t)) {
        if (names.contains(t)) {
          if (eqGet(tok, k-2, ".")) {}
          else if (eqGet(tok, k+2, "(") && !functions.contains(t)) {}
          // avoid lock ... and map ...
          else if (eqOneOf(t, "lock", "map") && isIdentifier(get(tok, k+2))) {}
          else
            tok.set(k, scopeName + "_" + t);
        } else if (eq(t, "__scope"))
          tok.set(k, quote(scopeName));
      }
    }
    
    reTok(tok, i, j+1);
  }
}


static boolean tok_recordDecls_autoWithToList = true;

static void tok_recordDecls(List<String> tok) {
  int i;
  boolean re = false;
  
  jreplace(tok, "record <id> {", "record $2() {");
  jreplace(tok, "record <id> implements", "record $2() implements");
  
  while ((i = jfind_any(tok, "record <id>(", "record <id><")) >= 0) {
    int argsFrom = smartIndexOf(tok, i, "(")+2;
    int argsTo = findCodeTokens(tok, argsFrom, false, ")");
    int idx = findCodeTokens(tok, argsTo, false, "{");
    
    int j = findEndOfBracketPart(tok, idx);
    String name = tok.get(i+2);
    boolean noEq = eq(get(tok, i-2), "noeq");
    if (noEq) tok.set(i-2, "");
    boolean noToString = eq(get(tok, i-2), "noToString");
    if (noToString) tok.set(i-2, "");
    boolean withToList = eq(get(tok, i-2), "withToList");
    if (withToList) tok.set(i-2, "");
    withToList = withToList || tok_recordDecls_autoWithToList;
    
    StringBuilder buf = new StringBuilder();
    
    List<String> tArgs = subList(tok, argsFrom-1, argsTo);
    List<Pair<String, String>> args = tok_typesAndNamesOfParams(tArgs, "typelessMeansObject" , true);
    List<String> contents = subList(tok, idx+1, j);
    
    for (Pair<String, String> p : args)
      buf.append("\n  " + jreplace(p.a, "...", "[]") + " " + p.b + ";");
      
    if (nempty(args)) buf.append("\n  *() {}");
    buf.append("\n  *(" + joinWithComma(map(args, new F1<Pair<String, String>, String>() { String get(Pair<String, String> p) { try { 
      return dropPrefix("new ", p.a) + " *" + p.b;  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "dropPrefix(\"new \", p.a) + \" *\" + p.b"; }})) + ") {}");
      
    if (!noToString && !(jfind(contents, "toString {") >= 0 || jfind(contents, "toString()") >= 0))
      buf.append("\n  toString { ret " 
        + quote(name + "(")
        + " + "
        + join(" + \", \" + ", secondOfPairs(args))
        + " + \")\"; }");
      
    if (!noEq) {
      //buf.append("\n  [stdEq]");
      buf.append(tok_genEqualsAndHashCode(name, args));
    }
    
    if (withToList)
      buf.append(tok_genRecordFieldsToList(args));
    
    tok.set(idx, (!withToList ? ""
      : (contains(subList(tok, argsTo, idx), "implements") ? "," : "implements") + " IFieldsToList ")
      + "{" + buf);
    
    tok.set(i, "class");
    clearTokens(tok, argsFrom-2, argsTo+1);
    //reTok(tok, i, idx+1);
    tok_addFieldOrder(tok, i);
    
    re = true;
  }
  if (re) reTok(tok);
}
static void tok_singleQuoteIdentifiersToStringConstants(List<String> tok) {
  for (int i = 1; i < l(tok); i += 2) {
    String t = tok.get(i);
    if (isSingleQuoteIdentifier(t))
      tok.set(i, quote(substring(t, 1)));
  }
}
static void tok_listComprehensions(List<String> tok) {
  if (!tok.contains("[")) return;
  for (int i = 1; i < l(tok); i += 2) {
    { if (!(eq(tok.get(i), "["))) continue; }
    int iOp = indexOfAny(tok, i+2, "?", ":", "in", "[", "]");
    if (iOp < 0) return;
    if (!eqOneOf(tok.get(iOp), ":", "in")) continue; // not a list comprehension
    
    Map<Integer, Integer> bracketMap = getBracketMap(tok); // XXX - optimize
    
    String type = joinSubList(tok, i+2, iOp-3), id = tok.get(iOp-2);
    int j = scanOverExpression(tok, bracketMap, iOp+2, "|");
    String exp = join(tok.subList(iOp+2, j));
    j += 2;
    int k = scanOverExpression(tok, bracketMap, j, "]");
    String where = join(tok.subList(j, k));
    ++k;
      
    String code = "filter(" + exp + ", func(" + type + " " + id + ") -> Bool { " + where + " })";
    replaceTokens(tok, i, k, code);
    reTok(tok, i, k);
  }
}


static void tok_for_single(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "for single (")) >= 0) {
    int iColon = indexOf(tok, ":", i);
    int iClosing = findEndOfBracketPart(tok, iColon)-1;
    tok.set(iColon, ": singletonUnlessNull(");
    tok.set(iClosing, "))");
    clearTokens(tok, i+2, i+4);
    reTok(tok, i, iClosing+1);
  }
}
static void tok_for_unpair(List<String> tok) {
  jreplace(tok, "for (unpair <id> <id>, <id> :", "for (unpair $4 $5, $4 $7 :");
  jreplace(tok, "for (<id> <id>, <id> : unpair", "for (unpair $3 $4, $3 $6 :");
  
  int i = -1;
  while ((i = jfind(tok, i+1, "for (unpair <id> <id>, <id> <id> :")) >= 0) {
    String type1 = tok.get(i+6), var1 = tok.get(i+8);
    String type2 = tok.get(i+12), var2 = tok.get(i+14);
    int iColon = indexOf(tok, ":", i);
    int iClosing = findEndOfBracketPart(tok, iColon)-1;
    if (!eqGet(tok, iClosing+2, "{"))
      throw fail("Use { with for unpair");
    String pairVar = makeVar();
    replaceTokens(tok, i+4, iColon-1, tok_toNonPrimitiveTypes("Pair<" + type1 + ", " + type2 + ">") + " " + pairVar);
    tok.set(iClosing+2, "{ "
      + type1 + " " + var1 + " = pairA(" + pairVar + "); "
      + type2 + " " + var2 + " = pairB(" + pairVar + "); ");
    reTok(tok, i, iClosing+3);
  }
}
static boolean tok_doubleFor_v2_debug = false;

static void tok_doubleFor_v2(List<String> tok) {
  for (int i : reversed(indexesOf(tok, "for"))) {
    if (neq(get(tok, i+2), "(")) continue;
    int argsFrom = i+4;
    // move loop label to proper place
    if (eqGet(tok, i-2, ":") && isIdentifier(get(tok, i-4))) i -= 4;
    int iColon = indexOfAny(tok, argsFrom, ":", ")");
    if (neq(get(tok, iColon), ":")) continue;
    if (tok_doubleFor_v2_debug) print("have colon");
    tok_typesAndNamesOfParams_keepModifiers.set(true);
    List<Pair<String, String>> args = tok_typesAndNamesOfParams(subList(tok, argsFrom-1, iColon-1));
    if (l(args) != 2) continue; // simple for or a three-argument for (out of this world!)
    
    // S a, b => S a, S b
    if (eq(second(args).a, "?")) second(args).a = first(args).a;
    
    if (tok_doubleFor_v2_debug) print("have 2 args: " + sfu(args));
    int iClosing = tok_findEndOfForExpression(tok, iColon+2);
    if (iClosing < 0) continue;
    if (tok_doubleFor_v2_debug) print("have closing");
    String exp = trimJoinSubList(tok, iColon+2, iClosing-1);
    if (tok_doubleFor_v2_debug) print("Expression: " + exp);
    int iCurly = iClosing+2;
    tok_statementToBlock(tok, iCurly);
    int iCurlyEnd = tok_findEndOfStatement(tok, iCurly)-1;
    if (iCurlyEnd < 0) continue;
    if (tok_doubleFor_v2_debug) print("have curly end");
    
    // complicated expression? evaluate to variable first
    
    if (!isIdentifier(exp)) {
      String expVar = makeVar();
      String mapType = tok_toNonPrimitiveTypes("Map<" + first(args).a + ", " + second(args).a + ">");
      tokPrepend(tok, i, "{ final " + mapType + " " + expVar + " = " + exp + "; ");
      tokAppend(tok, iCurlyEnd, "}");
      replaceTokens(tok, iColon+2, iClosing-1, expVar);
      exp = expVar;
    }
    
    tokAppend(tok, iColon, " keys(");
    tokPrepend(tok, iClosing, ")");
    
    replaceTokens(tok, argsFrom, iColon-1, joinPairWithSpace(first(args)));
    tokAppend(tok, iCurly, " " + joinPairWithSpace(second(args)) + " = " + exp + ".get(" + first(args).b + "); ");
    reTok(tok, i, iCurlyEnd+1);
  }
}
static void tok_forUnnull(List<String> tok) {
  jreplace(tok, "fOr (", "for unnull (");
  
  int i = -1;
  while ((i = jfind(tok, i+1, "for unnull (")) >= 0) {
    int argsFrom = i+4;
    int iColon = indexOf(tok, i, ":");
    int iClosing = tok_findEndOfForExpression(tok, iColon+2);
    
    clearTokens(tok, i+2, i+4);
    tokPrepend(tok, iColon+2, "unnull(");
    tokPrepend(tok, iClosing, ")");
    reTok(tok, i+2, iClosing+1);
    
  }
}
static void tok_ifCast(List<String> tok) {
  int i;
  while ((i = jfind_check("cast",tok, "if (<id> cast <id>")) >= 0) {
    int iClosing = indexOf(tok, i, ")");
    String var = tok.get(i+4), type = joinSubList(tok, i+8, iClosing-1);
    int start = iClosing+2, end = findEndOfStatement(tok, start);
    tok.set(i+6, "instanceof");
    
    // replace "var" with "((Type) var)" in enclosed block
    // unless it's another cast expression
    
    for (int j = start; j < end; j += 2)
      if (eq(tok.get(j), var) && neqGet(tok, j-2, ".")
        && neqGet(tok, j+2, "cast"))
        tok.set(j, "((" + type + ") " + var + ")");
    reTok(tok, i+6, end);
  }
}
// # 123 => "#123"
static void tok_directSnippetRefs(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "#<int>", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return !eqOneOf(_get(tok, i-1), "include", "once");
  }})) >= 0) {
    String id = tok.get(i+2);
    clearTokens(tok, i+1, i+3);
    tok.set(i, quote("#" + id));
    reTok(tok, i, i+3);
  }
}
// keyword can comprise multiple tokens now (like "p-awt"}
static List<String> replaceKeywordBlock(List<String> tok, String keyword, String beg, String end) {
  return replaceKeywordBlock(tok, keyword, beg, end, false, null);
}

static List<String> replaceKeywordBlock(List<String> tok, String keyword, String beg, String end, Object cond) {
  return replaceKeywordBlock(tok, keyword, beg, end, false, cond);
}

static List<String> replaceKeywordBlock(List<String> tok, String keyword, String beg, String end,
  boolean debug, Object cond) {
  for (int n = 0; n < 1000; n++) {
    int i = jfind(tok, keyword + " {", cond);
    if (i < 0)
      break;
    int idx = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, idx);
    
    if (debug) {
      print(toUpper(keyword) + " BEFORE\n" + join(subList(tok, i, j)));
      print("  THEN " + join(subList(tok, j, j+10)));
    }
    //assertEquals("}", tok.get(j-1));
    tok.set(j-1, end);
    replaceTokens(tok, i, idx+1, beg);
    reTok(tok, i, j);
    if (debug) print(toUpper(keyword) + "\n" + join(subList(tok, i, j)) + "\n");
  }
  return tok;
}
// finds "<keyword> <quoted> {"
// func: tok, C token index of keyword -> S[] {beg, end}
static List<String> replaceKeywordPlusQuotedBlock(List<String> tok, String keyword, Object func) {
  for (int i = 0; i < 1000; i++) {
    int idx = jfind(tok, keyword + " <quoted> {");
    if (idx < 0)
      break;
    int j = findEndOfBracketPart(tok, idx+4);
    
    String[] be = (String[]) callF(func, tok, idx);
    tok.set(idx, be[0]);
    clearTokens(tok, idx+1, idx+5);
    tok.set(j-1, be[1]);
    reTok(tok, idx, j);
  }
  return tok;
}
static int jfind(String s, String in) {
  return jfind(javaTok(s), in);
}

static int jfind(List<String> tok, String in) {
  return jfind(tok, 1, in);
}

static int jfind(List<String> tok, int startIdx, String in) {
  return jfind(tok, startIdx, in, null);
}

static int jfind(List<String> tok, String in, Object condition) {
  return jfind(tok, 1, in, condition);
}

static int jfind(List<String> tok, int startIdx, String in, Object condition) {
  //LS tokin = jfind_preprocess(javaTok(in));
  return jfind(tok, startIdx, javaTokForJFind_array(in), condition);
}

// assumes you preprocessed tokin
static int jfind(List<String> tok, List<String> tokin) {
  return jfind(tok, 1, tokin);
}

static int jfind(List<String> tok, int startIdx, List<String> tokin) {
  return jfind(tok, startIdx, tokin, null);
}

static int jfind(List<String> tok, int startIdx, String[] tokinC, Object condition) {
  return findCodeTokens(tok, startIdx, false, tokinC, condition);
}

static int jfind(List<String> tok, int startIdx, List<String> tokin, Object condition) {
  return jfind(tok, startIdx, codeTokensAsStringArray(tokin), condition);
}

static List<String> jfind_preprocess(List<String> tok) {
  for (String type : litlist("quoted", "id", "int"))
    replaceSublist(tok, ll("<", "", type, "", ">"), ll("<" + type + ">"));
  replaceSublist(tok, ll("\\", "", "*"), ll("\\*"));
  return tok;
}
// Return value is index of semicolon/curly brace+1
static int tok_findEndOfStatement(List<String> tok, int i) {
  // Is it a block?
  if (eq(get(tok, i), "{"))
    return findEndOfBlock(tok, i);
    
  // It's a regular statement.
  int j = i;
  while (j < l(tok) && neq(tok.get(j), ";"))
    if (eqOneOf(get(tok, j), "{", "("))
      j = findEndOfBracketPart(tok, j)+1;
    else
      j += 2;
  return j+1;
}
static TokCondition tokCondition_beginningOfMethodDeclaration() {
  return new TokCondition() { boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i-1), "}", ";", "{", null);
  }};
}
static boolean neqGet(List l, int i, Object o) {
  return neq(get(l, i), o);
}
static void tok_moduleClassDecls(List<String> tok) {
  jreplace(tok, "module <id> {", "module $2 extends DynModule {");
  int i = -1;
  while ((i = jfind(tok, i+1, "module <id>")) >= 0) {
    int j = findEndOfBlock(tok, indexOf(tok, "{", i))-1;
    String name = tok.get(i+2);
    tok.set(i, "sclass");
    tokAppend(tok, j, "\nsbool _moduleClass_" + name + " = true;"); // just a marker to quickly find module classes
    reTok(tok, j, j+1);
  }
}

static boolean startsWithLowerCaseOrUnderscore(String s) {
  return nempty(s) && (s.startsWith("_") || Character.isLowerCase(s.charAt(0)));
}
static boolean empty(Collection c) { return c == null || c.isEmpty(); }
static boolean empty(CharSequence s) { return s == null || s.length() == 0; }
static boolean empty(Map map) { return map == null || map.isEmpty(); }
static boolean empty(Object[] o) { return o == null || o.length == 0; }
static boolean empty(Object o) {
  if (o instanceof Collection) return empty((Collection) o);
  if (o instanceof String) return empty((String) o);
  if (o instanceof Map) return empty((Map) o);
  if (o instanceof Object[]) return empty((Object[]) o);
  if (o instanceof byte[]) return empty((byte[]) o);
  if (o == null) return true;
  throw fail("unknown type for 'empty': " + getType(o));
}

static boolean empty(Iterator i) { return i == null || !i.hasNext(); }

static boolean empty(float[] a) { return a == null || a.length == 0; }
static boolean empty(int[] a) { return a == null || a.length == 0; }
static boolean empty(long[] a) { return a == null || a.length == 0; }
static boolean empty(byte[] a) { return a == null || a.length == 0; }
static boolean empty(short[] a) { return a == null || a.length == 0; }


static boolean empty(MultiSet ms) { return ms == null || ms.isEmpty(); }


static boolean empty(File f) { return getFileSize(f) == 0; }
static <A> A _get(List<A> l, int idx) {
  return l != null && idx >= 0 && idx < l(l) ? l.get(idx) : null;
}

static Object _get(Object o, String field) {
  return get(o, field);
}

static Object _get(String field, Object o) {
  return get(o, field);
}
static <A> A _get(A[] l, int idx) {
  return idx >= 0 && idx < l(l) ? l[idx] : null;
}
static boolean eq(Object a, Object b) {
  return a == b || (a == null ? b == null : b != null && a.equals(b));
}


static boolean eqOneOf(Object o, Object... l) {
  for (Object x : l) if (eq(o, x)) return true; return false;
}
// returns index of endToken
static int scanOverExpression(List<String> tok, Map<Integer, Integer> bracketMap, int i, String endToken) {
  while (i < l(tok)) {
    if (eq(endToken, tok.get(i)))
      return i;
    Integer j = bracketMap.get(i);
    if (j != null)
      i = j+1;
    else
      i++;
  }
  return i;
}
// map: index of opening bracket -> index of closing bracket
static Map<Integer, Integer> getBracketMap(List tok) {
  return getBracketMap(tok, getBracketMap_opening, getBracketMap_closing);
}

static Map<Integer, Integer> getBracketMap(List tok, Collection<String> opening, Collection<String> closing) {
  return getBracketMap(tok, opening, closing, 0, l(tok));
}

static Map<Integer, Integer> getBracketMap(List tok, Collection<String> opening, Collection<String> closing, int from, int to) {
  TreeMap<Integer,Integer> map = new TreeMap();
  List<Integer> stack = new ArrayList();
  for (int i = from|1; i < to; i+= 2) {
    if (opening.contains(tok.get(i)))
      stack.add(i);
    else if (closing.contains(tok.get(i))) {
      if (!empty(stack))
        map.put(liftLast(stack), i);
    }
  }
  return map;
}

static Set<String> getBracketMap_opening = lithashset("{", "(");
static Set<String> getBracketMap_closing = lithashset("}", ")");
static void replaceTokens(List<String> tok, int i, int j, String s) {
  clearAllTokens(tok, i+1, j);
  tok.set(i, s);
}

static void replaceTokens(List<String> tok, String s) {
  clearAllTokens(tok, 1, l(tok));
  tok.set(0, s);
}
static int jfindOneOf(List<String> tok, String... patterns) {
  for (String in : patterns) {
    int i = jfind(tok, in);
    if (i >= 0) return i;
  }
  return -1;
}
// debug "bla" + blubb; => { if (debug) print("bla" + blubb); }
static void tok_debugStatements(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "debug <quoted>")) >= 0) {
    int j = tok_findEndOfStatement(tok, i); // index of semicolon+1
    replaceTokens(tok, i, i+2, "{ if (debug) print(");
    tok.set(j-1, "); }");
    i = j;
  }
}

static int jfind_check(String checkToken, List<String> tok, String in) {
  return jfind_check(checkToken, tok, in, null);
}

static int jfind_check(String checkToken, List<String> tok, String in, Object condition) {
  if (isIndexedList(tok) && !tok.contains(checkToken)) return -1;
  return jfind(tok, in, condition);
}
static Boolean not(Boolean b) {
  return b == null ? null : !b;
}
// i must point at the (possibly imaginary) opening bracket (any of the 2 types, not type parameters)
// index returned is index of closing bracket + 1
static int findEndOfBracketPart(List<String> cnc, int i) {
  int j = i+2, level = 1;
  while (j < cnc.size()) {
    if (eqOneOf(cnc.get(j), "{", "(")) ++level;
    else if (eqOneOf(cnc.get(j), "}", ")")) --level;
    if (level == 0)
      return j+1;
    ++j;
  }
  return cnc.size();
}
static String substring(String s, int x) {
  return substring(s, x, strL(s));
}

static String substring(String s, int x, int y) {
  if (s == null) return null;
  if (x < 0) x = 0;
  if (x >= s.length()) return "";
  if (y < x) y = x;
  if (y > s.length()) y = s.length();
  return s.substring(x, y);
}


static String substring(String s, IntRange r) {
  return r == null ? null : substring(s, r.start, r.end);
}

static int parseInt(String s) {
  return emptyString(s) ? 0 : Integer.parseInt(s);
}

static int parseInt(char c) {
  return Integer.parseInt(str(c));
}
static boolean tok_doAsMethodName_strict = false;

// Strict mode:
// Sometimes you have to make sure to omit the space after "do"
//   do(...)
// as opposed to
//   do (bla+bla).something(); while ...;  << very unusual anyway

// Non-strict mode:
//   do (bla+bla).something(); while ...;
// is not possible anymore (but who does this?)
// You can use spaces however you like

static void tok_doAsMethodName(List<String> tok) {
  if (!tok.contains("do")) return;
  for (int i = 1; i+2 < l(tok); i += 2) {
    String next = tok.get(i+2), prev = get(tok, i-2);
    if (tok.get(i).equals("do") && eq(next, "(")
      && (!tok_doAsMethodName_strict
        || eq(prev, ".") || empty(tok.get(i+1))))
      tok.set(i, "dO");
  }
}

static void tok_p_old(List<String> tok) {
  if (!containsSubList(tok, "p", "", "-")) return;
  replaceKeywordBlock(tok, "p-pcall", "p { pcall {", "}}");
  jreplace(tok, "p-autorestart", "p-autoupdate");
  jreplace(tok, "p-autoupdate {", "p { autoUpdate();");
  jreplace(tok, "p-noconsole-autoupdate {", "p-noconsole { autoUpdate();");
  jreplace(tok, "p-subst-autoupdate {", "p-subst { autoUpdate();");
  jreplace(tok, "p-subst-autorestart {", "p-subst { autoUpdate();");
  jreplace(tok, "p-pretty {", "p-noconsole {");
  replaceKeywordBlock(tok, "p-awt-noconsole", "p-awt {", ";\nhideConsole(); }");
  replaceKeywordBlock(tok, "p-hideconsole", "p {", ";\nhideConsole(); }");
  replaceKeywordBlock(tok, "p-substance-noconsole", "p-substance {", ";\nhideConsole(); }");
  replaceKeywordBlock(tok, "p-nimbus-noconsole", "p-nimbus {", ";\nhideConsole(); }");
  replaceKeywordBlock(tok, "p-subst-noconsole", "p-subst {", ";\nhideConsole(); }");
  replaceKeywordBlock(tok, "p-noconsole", "p-subst {", ";\nhideConsole(); }");
  replaceKeywordBlock(tok, "p-subst", "p-substance-thread {", "}");
  replaceKeywordBlock(tok, "p-substance-thread", "p { substance();", "}");
  replaceKeywordBlock(tok, "p-magellan-thread", "p { magellan();", "}");
  replaceKeywordBlock(tok, "p-substance", "p-awt { substance();", "}");
  replaceKeywordBlock(tok, "p-nimbus", "p-awt { nimbus();", "}");
  replaceKeywordBlock(tok, "p-center", "p { centerConsole(); ", "}");
  replaceKeywordBlock(tok, "p-experiment-tt", "p-experiment { tt(); ", "}");
  jreplace(tok, "p-exp", "p-experiment");
  replaceKeywordBlock(tok, "p-experiment", "p { pExperiment(); ", "}");
  jreplace(tok, "p-type {", "p-typewriter {");
  jreplace(tok, "p-tt {", "p-typewriter {");
  replaceKeywordBlock(tok, "p-awt", "p { swing {", "}}");
  replaceKeywordBlock(tok, "p-typewriter", "p { typeWriterConsole();", "}");
  replaceKeywordBlock(tok, "p-lowprio", "p { lowPriorityThread(r " + "{", "}); }");
  tok_p_repeatWithSleep(tok);
}
// -Syntax: switch to q();
// -rest of block is executed in Queue q()
static void tok_switchTo(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "switch to")) >= 0) {
    int semicolon = findEndOfStatement(tok, i)-1;
    int endOfOuterBlock = findEndOfBracketPart(tok, semicolon)-1;
    clearTokens(tok, i, i+4);
    tok.set(semicolon, ".add(r {");
    tok.set(endOfOuterBlock, "}); }");
    reTok(tok, i, endOfOuterBlock+1);
  }
}


static List<String> tok_equalsCast(List<String> tok) {
  if (!tok.contains("cast")) return tok;
  int iVar = -1;
  while ((iVar = jfind(tok, iVar+1, "<id> = cast")) >= 0) {
    int iTypeStart = tok_scanTypeBackwards(tok, iVar-2);
    int iCast = iVar+4;
    int j = scanToEndOfInitializer2(tok, iCast);
    String type = joinSubList(tok, iTypeStart, iVar-1);
    clearTokens(tok, iCast, iCast+1);
    if (!tok_typeIsObject(type)) {
      boolean needBrackets = j > iCast+4;
      tokPrepend(tok, iCast+2, "(" + type + ") " + (needBrackets ? "(" : ""));
      if (needBrackets) tokPrepend(tok, j+1, ")");
    }
    reTok(tok, iCast, j+2);
  }
  return tok;
}
// S s = opt cast ...;
static List<String> tok_equalsOptCast(List<String> tok) {
  if (!tok.contains("cast")) return tok;
  int iVar = -1;
  while ((iVar = jfind(tok, iVar+1, "<id> = opt cast")) >= 0) {
    int iTypeStart = tok_scanTypeBackwards(tok, iVar-2);
    int iCast = iVar+6;
    int j = scanToEndOfInitializer2(tok, iCast);
    String type = joinSubList(tok, iTypeStart, iVar-1);
    replaceTokens(tok, iCast-2, iCast+2, "optCast " + type + "(");
    tokPrepend(tok, j+1, ")");
    reTok(tok, iCast-2, j+2);
  }
  return tok;
}
static <A> List<A> subList(List<A> l, int startIndex) {
  return subList(l, startIndex, l(l));
}

static <A> List<A> subList(int startIndex, int endIndex, List<A> l) {
  return subList(l, startIndex, endIndex);
}

static <A> List<A> subList(List<A> l, int startIndex, int endIndex) {
  if (l == null) return null;
  int n = l(l);
  startIndex = Math.max(0, startIndex);
  endIndex = Math.min(n, endIndex);
  if (startIndex >= endIndex) return ll();
  if (startIndex == 0 && endIndex == n) return l;
  return l.subList(startIndex, endIndex);
}


static <A> List<A> subList(List<A> l, IntRange r) {
  return subList(l, r.start, r.end);
}

static String tok_addSemicolon(List<String> tok) {
  //assertOdd(l(tok));
  String lastToken = get(tok, (l(tok)|1)-2);
  if (eqOneOf(lastToken, "}", ";")) return join(tok);
  return join(tok) + ";";
}

static String tok_addSemicolon(String s) {
  return tok_addSemicolon(javaTok(s));
}
static String tok_autoQuineFunc(List<String> contents) {
  return tok_autoQuineFunc(contents, "toString");
}

static String tok_autoQuineFunc(List<String> contents, String funcName) {
  return "  public S " + funcName + "() { ret " + quote(shorten(maxQuineLength, trimJoin(contents))) + "; }";
}
static boolean tok_repeatWithSleep_verbose = false;

static List<String> tok_repeatWithSleep(List<String> tok) {
  int i;
  while ((i = jfind(tok, "repeat with sleep * {")) >= 0) {
    String seconds = tok.get(i+6); // int or id
    int idx = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, idx);
    if (tok_repeatWithSleep_verbose) print("idx=" + idx + ", j=" + j);
    clearTokens(tok, i, idx+1);
    tok.set(i, "repeat { pcall {");
    tok.set(j-1, "} sleepSeconds(" + seconds + "); }");
    reTok(tok, j-1, j);
    reTok(tok, i, idx+1);
  }
  
  while ((i = jfind(tok, "repeat with ms sleep * {")) >= 0) {
    String ms = tok.get(i+8); // int or id
    int idx = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, idx);
    if (tok_repeatWithSleep_verbose) print("idx=" + idx + ", j=" + j);
    clearTokens(tok, i, idx+1);
    tok.set(i, "repeat { pcall {");
    tok.set(j-1, "} sleep(" + ms + "); }");
    reTok(tok, j-1, j);
    reTok(tok, i, idx+1);
  }
  return tok;
}

static boolean tok_tokenBeforeLonelyReturnValue(List<String> tok, int i) {
  String t = get(tok, i);
  if (l(t) == 1 && "{};".contains(t) || eq(t, "else")) return true;
  if (!eq(t, ")")) return false;
  int level = 0;
  while (i > 0) {
    if (eq(tok.get(i), ")")) ++level;
    if (eq(tok.get(i), "(")) --level;
    if (level == 0) return eq(get(tok, i-2), "if");
    i -= 2;
  }
  return false;
}
static <A> ArrayList<A> litlist(A... a) {
  ArrayList l = new ArrayList(a.length);
  for (A x : a) l.add(x);
  return l;
}
static String unquote(String s) {
  if (s == null) return null;
  if (startsWith(s, '[')) {
    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.length() > 1) {
    char c = s.charAt(0);
    if (c == '\"' || c == '\'') {
      int l = endsWith(s, c) ? s.length()-1 : s.length();
      StringBuilder sb = new StringBuilder(l-1);
  
      for (int i = 1; i < l; i++) {
        char ch = s.charAt(i);
        if (ch == '\\') {
          char nextChar = (i == l - 1) ? '\\' : s.charAt(i + 1);
          // Octal escape?
          if (nextChar >= '0' && nextChar <= '7') {
              String code = "" + nextChar;
              i++;
              if ((i < l - 1) && s.charAt(i + 1) >= '0'
                      && s.charAt(i + 1) <= '7') {
                  code += s.charAt(i + 1);
                  i++;
                  if ((i < l - 1) && s.charAt(i + 1) >= '0'
                          && s.charAt(i + 1) <= '7') {
                      code += s.charAt(i + 1);
                      i++;
                  }
              }
              sb.append((char) Integer.parseInt(code, 8));
              continue;
          }
          switch (nextChar) {
          case '\"': ch = '\"'; break;
          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;
          // Hex Unicode: u????
          case 'u':
              if (i >= l - 5) {
                  ch = 'u';
                  break;
              }
              int code = Integer.parseInt(
                      "" + s.charAt(i + 2) + s.charAt(i + 3)
                         + s.charAt(i + 4) + s.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();
    }
  }
    
  return s; // not quoted - return original
}
static List<String> splitAtJavaToken(String s, String splitToken) {
  return splitByJavaToken(s, splitToken);
}
static String quote(Object o) {
  if (o == null) return "null";
  return quote(str(o));
}

static String quote(String s) {
  if (s == null) return "null";
  StringBuilder out = new StringBuilder((int) (l(s)*1.5+2));
  quote_impl(s, out);
  return out.toString();
}
  
static void quote_impl(String s, StringBuilder out) {
  out.append('"');
  int l = s.length();
  for (int i = 0; i < l; i++) {
    char c = s.charAt(i);
    if (c == '\\' || c == '"')
      out.append('\\').append(c);
    else if (c == '\r')
      out.append("\\r");
    else if (c == '\n')
      out.append("\\n");
    else if (c == '\0')
      out.append("\\0");
    else
      out.append(c);
  }
  out.append('"');
}
static String trim(String s) { return s == null ? null : s.trim(); }
static String trim(StringBuilder buf) { return buf.toString().trim(); }
static String trim(StringBuffer buf) { return buf.toString().trim(); }
static List<String> javaTokC(String s) {
  if (s == null) return null;
  int l = s.length();
  ArrayList<String> tok = new ArrayList();
  
  int i = 0;
  while (i < l) {
    int j = i;
    char c, d;
    
    // scan for whitespace
    while (j < l) {
      c = s.charAt(j);
      d = j+1 >= l ? '\0' : s.charAt(j+1);
      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
        ++j;
      else if (c == '/' && d == '*') {
        do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
        j = Math.min(j+2, l);
      } else if (c == '/' && d == '/') {
        do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
      } else
        break;
    }
    
    i = j;
    if (i >= l) break;
    c = s.charAt(i);
    d = i+1 >= l ? '\0' : s.charAt(i+1);

    // 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 (c == '[' && d == '[') {
      do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]"));
      j = Math.min(j+2, l);
    } else if (c == '[' && d == '=' && 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(javaTok_substringC(s, i, j));
    i = j;
  }
  
  return tok;
}
static boolean startsAndEndsWith(String a, char c) {
  return startsWith(a, c) && endsWith(a, c) && l(a) >= 2;
}

static boolean startsAndEndsWith(String a, String b) {
  return startsWith(a, b) && endsWith(a, b) && l(a) >= l(b)*2;
}
// func returns S[] {beg, end}
static List<String> replaceKeywordBlockDyn(List<String> tok, String keyword, Object func) {
  for (int i = 0; i < 1000; i++) {
    int idx = findCodeTokens(tok, keyword, "{");
    if (idx < 0)
      break;
    int j = findEndOfBracketPart(tok, idx+2);
    
    String[] be = (String[]) callF(func);
    tok.set(idx, be[0]);
    tok.set(idx+1, " ");
    tok.set(idx+2, "");
    tok.set(j-1, be[1]);
    reTok(tok, idx, j);
  }
  return tok;
}
// finds "<keyword> <quoted> {" and "<keyword> <id> {"
// func: tok, C token index of keyword -> S[] {beg, end}
static List<String> replaceKeywordPlusQuotedOrIDBlock(List<String> tok, String keyword, Object func) {
  for (int i = 0; i < 1000; i++) {
    int idx = jfind_any(tok, keyword + " <quoted> {", keyword + " <id> {");
    if (idx < 0)
      break;
    int j = findEndOfBracketPart(tok, idx+4);
    
    String[] be = (String[]) callF(func, tok, idx);
    tok.set(idx, be[0]);
    clearTokens(tok, idx+1, idx+5);
    tok.set(j-1, be[1]);
    reTok(tok, idx, j);
  }
  return tok;
}
static boolean neqOneOf(Object o, Object... l) {
  for (Object x : l) if (eq(o, x)) return false; return true;
}
// func : func(LS tok, int iOpening, int iClosing) -> S[] {beg, end}
static List<String> replaceKeywordBlock_dyn2_legacy(List<String> tok, String keyword, Object func) {
  for (int i = 0; i < 1000; i++) {
    //int idx = findCodeTokens(tok, keyword, "{");
    int idx = jfind(tok, keyword + " {");
    if (idx < 0)
      break;
    int idx2 = findCodeTokens(tok, idx, false, "{");
    int j = findEndOfBracketPart(tok, idx2);
    
    String[] be = (String[]) callF(func, tok, idx2, j-1);
    replaceTokens(tok, idx, idx2+2, be[0] + " ");
    tok.set(j-1, be[1]);
    reTok(tok, idx, j);
  }
  return tok;
}
static int shorten_default = 100;

static String shorten(String s) { return shorten(s, shorten_default); }

static String shorten(String s, int max) {
  return shorten(s, max, "...");
}

static String shorten(String s, int max, String shortener) {
  if (s == null) return "";
  if (max < 0) return s;
  return s.length() <= max ? s : substring(s, 0, min(s.length(), max-l(shortener))) + shortener;
}

static String shorten(int max, String s) { return shorten(s, max); }
static String trimJoin(List<String> s) {
  return trim(join(s));
}
// It's rough but should work if you don't make anonymous classes inside the statement or something...
// Also won't work with "for"
// Return value is index of semicolon/curly brace+1
static int findEndOfStatement(List<String> tok, int i) {
  int j = i;
  
  // Is it a block?
  if (eq(get(tok, i), "{"))
    return findEndOfBlock(tok, i)+1;
    
  // It's a regular statement.
  int n = l(tok);
  String t;
  while (j < n && !";".equals(t = tok.get(j)))
    if ("{".equals(t))
      j = findEndOfBlock(tok, j)+1;
    else
      j += 2;
  return j+1;
}
static boolean eqGet(List l, int i, Object o) {
  return eq(get(l, i), o);
}

static <A, B> boolean eqGet(Map<A, B> map, A key, Object o) {
  return eq(mapGet(map, key), o);
}
// Return value is C token
static int tok_findBeginningOfStatement(List<String> tok, int i) {
  i |= 1;
  int level = 0;
  
  while (i > 1) {
    String t = get(tok, i);
    if (eqOneOf(t, "{", "(")) { if (level-- < 0) break; }
    else if (eqOneOf(t, "}", ")")) level++;
    else if (level == 0 && eqGetOneOf(tok, i-2, "}", "{", ";"))
      break;
    i -= 2;
  }
  
  return i;
}
static String joinSubList(List<String> l, int i, int j) {
  return join(subList(l, i, j));
}

static String joinSubList(List<String> l, int i) {
  return join(subList(l, i));
}


static String joinSubList(List<String> l, IntRange r) {
  return r == null ? null : joinSubList(l, r.start, r.end);
}

 static String getVarDeclarationName(List<String> tok) {
    int j = 1;
    List<String> special = litlist("=", "{", ";");
    while (j < tok.size() && !special.contains(tok.get(j)))
      j += 2;
    j -= 2;
    while (j > 1 && litlist("[", "]").contains(tok.get(j)))
      j -= 2;
    return tok.get(j);
  }
static void tokPrepend_reTok(List<String> tok, int i, String s) {
  tokPrepend(tok, i, s);
  reTok(tok, i, i+1);
}
static void clearTokens(List<String> tok) {
  clearAllTokens(tok);
}

static void clearTokens(List<String> tok, int i, int j) {
  clearAllTokens(tok, i, j);
}


static void clearTokens(List<String> tok, IntRange r) {
  clearAllTokens(tok, r.start, r.end);
}

static void tok_expandLPair(List<String> tok) {
  if (!tok.contains("LPair")) return;
  int n = l(tok)-6;
  for (int i = 1; i < n; i += 2) {
    { if (!(eq(tok.get(i), "LPair") && eq(tok.get(i+2), "<"))) continue; }
    int j = findEndOfTypeArgs(tok, i+2)-1; // j = index of >
    tok.set(i, "L<Pair");
    tok.set(j, ">>");
    reTok(tok, i, j+1);
    n = l(tok)-6;
  }
}

static void tok_expandPairL(List<String> tok) {
  if (!tok.contains("PairL")) return;
  int n = l(tok)-6;
  for (int i = 1; i < n; i += 2) {
    { if (!(eq(tok.get(i), "PairL") && eq(tok.get(i+2), "<"))) continue; }
    int j = findEndOfTypeArgs(tok, i+2)-1; // j = index of >
    tok.set(i, "Pair<L");
    tok.set(j, ">>");
    reTok(tok, i, j+1);
    n = l(tok)-6;
  }
}

static void tok_expandLT3(List<String> tok) {
  if (!tok.contains("LT3")) return;
  int n = l(tok)-6;
  for (int i = 1; i < n; i += 2) {
    { if (!(eq(tok.get(i), "LT3") && eq(tok.get(i+2), "<"))) continue; }
    int j = findEndOfTypeArgs(tok, i+2)-1; // j = index of >
    tok.set(i, "L<T3");
    tok.set(j, ">>");
    reTok(tok, i, j+1);
    n = l(tok)-6;
  }
}

// +var => "var", +var
static void tok_expandVarCopies(List<String> tok) {
  for (int i = 3; i+2 < l(tok); i += 2) {
    if (!eq(tok.get(i), "+")) continue;
    if (!eqOneOf(tok.get(i-2), "(", ",", "{")) continue;
    String s = tok.get(i+2);
    if (!isIdentifier(s)) continue;
    tok.set(i, quote(s) + ", ");
    reTok(tok, i, i+1);
  }
}

static void tok_replaceColonEqualsSyntax(List<String> tok) {
  int i;
  
  // "field := value" for defining fields e.g. in "uniq"
  while ((i = jfind_check(":", tok, "<id> :=")) >= 0) {
    tok.set(i, quote(tok.get(i)));
    tok.set(i+2, ",");
    tok.set(i+4, "");
    reTok(tok, i, i+5);
  }
  
  // "quoted" := value
  while ((i = jfind_check(":", tok, "<quoted> :=")) >= 0) {
    tok.set(i, tok.get(i));
    tok.set(i+2, ",");
    tok.set(i+4, "");
    reTok(tok, i, i+5);
  }
}
static void tok_unpair(List<String> tok) {
  if (!tok.contains("unpair")) return;
  
  int i;
  
  jreplace(tok, "<id> <id>, <id> = unpair", "$1 $2, $1 $4 = unpair");
  
  while ((i = jfind(tok, "<id> <id>, <id> <id> = unpair")) >= 0) {
    int idx = indexOf(tok, "unpair", i);
    int j = findEndOfStatement(tok, idx);
    String type1 = tok.get(i), var1 = tok.get(i+2);
    String type2 = tok.get(i+6), var2 = tok.get(i+8);
    String v = makeVar();
    tok.set(i+4, ";");
    tok.set(idx-2, ";");
    tok.set(idx, "Pair<" + tok_toNonPrimitiveTypes(type1) + "," + tok_toNonPrimitiveTypes(type2) + "> " + v + "=");
    tok.set(j-1, "; " + var1 + " = " + v + ".a; " + var2 + " = " + v + ".b;");
    reTok(tok, i, j);
  }
  
  while ((i = jfind(tok, "<id> <id>, <id> < <id>,<id> > <id> = unpair")) >= 0 || (i = jfind(tok, "<id> <id>, <id><<id>> <id> = unpair")) >= 0) {
    print("unpair");
    int idx = indexOf(tok, "unpair", i);
    int j = findEndOfStatement(tok, idx);
    String type1 = tok.get(i), var1 = tok.get(i+2);
    String type2 = joinSubList(tok, i+5, idx-5), var2 = tok.get(idx-4);
    String v = makeVar();
    tok.set(i+4, ";");
    tok.set(idx-2, ";");
    tok.set(idx-1, "");
    tok.set(idx, "Pair<" + type1 + "," + type2 + "> " + v + "=");
    tok.set(j-1, "; " + var1 + " = " + v + ".a; " + var2 + " = " + v + ".b;");
    reTok(tok, i, j);
  }
}
static void tok_cachedFunctions(List<String> tok) { try {
  if (!tok.contains("cached")) return;
  int i;
  while ((i = jfind(tok, "static cached <id>")) >= 0) {
    int bracket = indexOf(tok, "(", i);
    String fName = assertIdentifier(tok.get(bracket-2));
    String type = joinSubList(tok, i+4, bracket-3);
    String boxedType = tok_toNonPrimitiveTypes(type);
    
    replaceTokens(tok, i, bracket-1, ("static Cache<" + (boxedType) + "> " + (fName) + "_cache = new Cache(f " + (fName) + "_load);\n")
      + ("static " + (type) + " " + (fName) + "() { ret " + (fName) + "_cache!; }\n\n") + ("static " + (boxedType) + " " + (fName) + "_load"));
    reTok(tok, i, bracket-3);
  }
} catch (Throwable __e) { _handleException(__e); }}
static void tok_simplyCachedFunctions(List<String> tok) { try {
  if (!tok.contains("simplyCached")) return;
  int i = -1;
  while ((i = jfind(tok, i+1, "static simplyCached <id>")) >= 0) {
    int bracket = indexOf(tok, "(", i);
    String fName = assertIdentifier(tok.get(bracket-2));
    String type = joinSubList(tok, i+4, bracket-3);
    String boxedType = tok_toNonPrimitiveTypes(type);
    
    replaceTokens(tok, i, bracket-1, ("static " + (boxedType) + " " + (fName) + "_cache;\n")
      + ("static " + (type) + " " + (fName) + "() { if (" + (fName) + "_cache == null) " + (fName) + "_cache = " + (fName) + "_load(); ret " + (fName) + "_cache; }\n\n") + ("static " + (boxedType) + " " + (fName) + "_load"));
    reTok(tok, i, bracket-3);
  }
} catch (Throwable __e) { _handleException(__e); }}
// "optPar int bla = 5;" => "int bla = optPar bla(_, 5);"
static void tok_optPar(List<String> tok) {
  if (!tok.contains("optPar")) return;
  
  int i;
  
  while ((i = jfindOneOf(tok, "optPar <id> <id>", "optPar <id> <")) >= 0) {
    int iSemicolon = tok_findEndOfStatement(tok, i)-1;
    int iEquals = indexOf_between(tok, "=", i, iSemicolon);
    clearTokens(tok, i, i+2); // drop optPar
    if (iEquals < 0) { // no initializer
      String var = tok.get(iSemicolon-2);
      String type = joinSubList(tok, i+2, iSemicolon-3);
      if (eqOneOf(type, "bool", "boolean"))
        tok.set(iSemicolon, " = boolPar " + var + "(_);");
      else
        tok.set(iSemicolon, " = cast optPar " + var + "(_);");
    } else {
      String var = tok.get(iEquals-2);
      tok.set(iEquals, "= optPar " + var + "(_, ");
      tok.set(iSemicolon, ");");
    }
    reTok(tok, i, iSemicolon+1);
  }
}
static void tok_typeAA(List<String> tok, Set<String> pairClasses) {
  int n = l(tok)-6;
  boolean change = false;
  for (int i = 1; i < n; i += 2) {
    if (!eq(get(tok, i+2), "<")) continue;
    String t = tok.get(i);
    { if (!(contains(pairClasses, t)
      || eq(t, "Entry") && eqGet(tok, i-2, ".") && eqGet(tok, i-4, "Map"))) continue; }
    if (tok_isSingleTypeArg(tok, i+2)) {
      int j = findEndOfTypeArgs(tok, i+2)-1;
      String type = joinSubList(tok, i+4, j);
      replaceTokens_reTok(tok, i+4, j, type + ", " + type);
      change = true;
    }
  }
}

static List<String> tok_expandStarConstructors(List<String> tok) {
  mainLoop: for (int i = 3; i+6 < tok.size(); i += 2) {
    String t = tok.get(i), l = tok.get(i-2);
    if (!t.equals("*"))
      continue;
    if (!tok.get(i+2).equals("("))
      continue;
    if (!eqOneOf(l, "}", "public", "private", "protected", ";", "{", "endif", ">") && neq(get(tok, i-4), "ifclass")) // is this correct...??
      continue;
      
    // ok, it seems like a constructor declaration.
    // Now find class name by going backwards.
    
    int j = i, level = 1;
    while (j > 0 && level > 0) {
      t = tok.get(j);
      if (t.equals("}")) ++level;
      if (t.equals("{")) --level;
      j -= 2;
    }
    
    // search for class name
    while (j > 0) {
      t = tok.get(j);
      if (t.equals("class")) {
        String className = tok.get(j+2);
        tok.set(i, className); // exchange constructor name!
        
        // now for the parameters.
        // Syntax: *(Learner *learner) {
        // We will surely add type inference here in time... :)
        
        j = i+2;
        while (!tok.get(j).equals("{")) j += 2;
        
        // skip calling super/this constructor
        if (eqGetOneOf(tok, j+2, "super", "this")) j = tok_findEndOfStatement(tok, j+2)-1;
        
        int block = j+1;
        for (int k = i+2; k < block-1; k += 2)
          if (tok.get(k).equals("*")) {
            tok.remove(k);
            tok.remove(k);
            block -= 2;
            String name = tok.get(k);
            tok.addAll(block, Arrays.asList(new String[] {
              "\n  ", "this", "", ".", "", name, " ", "=", " ", name, "", ";" }));
          }
        
        continue mainLoop;
      }
      j -= 2;
    }
  }
  return tok;
}
static void tok_kiloConstants(List<String> tok) {
  if (!tok.contains("K")) return;
  jreplace(tok, "<int> K", "($1*1024)");
}
// references to objects in other realm (replace with Object)
static void tok_virtualTypes(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "virtual <id>")) >= 0) {
    replaceTokens_reTok(tok, i, tok_findEndOfType(tok, i+2)-1, "Object");
  }
}
static List<String> tok_autoLongConstants(List<String> tok) {
  int n = l(tok);
  for (int i = 1 ; i < n; i += 2) {
    String t = tok.get(i);
    if (isInteger(t) && !eq(get(tok, i+2), "L")
      && !eqOneOf(get(tok, i-2), "-" /* huh? */, "." /* float/double */)
      && !longIsInt(parseLong(t)))
      tokAppend(tok, i, "L");
  }
  return tok;
}
// unimplemented S bla(); => S bla() { throw unimplemented(); }
static void tok_unimplementedMethods(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "unimplemented")) >= 0) {
    String t = get(tok, i+2);
    { if (!(isIdentifier(t) || eq(t, "<"))) continue; }
    int j = tok_findEndOfStatement(tok, i); // abuse da function
    replaceTokens(tok, i, i+2, "");
    tokReplace(tok, j-1, " { throw unimplemented(); }");
    reTok(tok, i, j);
  }
}
static void tok_switchableFields(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "switchable <id>")) >= 0) {
    String var = getVarDeclarationName(subList(tok, i+1));
    tokSet_withReTok(tok, i, "sbool _switchableField_" + var + " = true;");
  }
}
static void tok_autoDisposeFields(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "autoDispose <id>")) >= 0) {
    String var = getVarDeclarationName(subList(tok, i+1));
    tokSet_withReTok(tok, i, "");
    tokPrepend_withReTok(tok, leftScanModifiers(tok, i),
      "void cleanMeUp_" + var + "() { dispose " + var +"; } ");
  }
}
// visual bla(super); => visualize { ret bla(super.visualize()); }
static void tok_shortVisualize(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "visual <id>")) >= 0) {
    int j = tok_findEndOfStatement(tok, i);
    
    for (int k = i; k < j; k += 2)
      if (eqGet(tok, k, "super") && neqGet(tok, k+2, "."))
        tokSet(tok, k, "super.visualize()");
        
    tokSet(tok, j-1, "; }");
    tokSet(tok, i, "visualize { ret");
    reTok(tok, i, j);
  }
}
static void tok_whileGreaterThan(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, "while >= <int> (")) >= 0) {
    int iOpening = indexOf(tok, "(", i);
    int iClosing = findEndOfBracketPart(tok, iOpening)-1;
    tokSet_reTok(tok, iClosing, ") >= 0)");
    replaceTokens_reTok(tok, i+2, iOpening+1, "((");
  }
}
// if condition then code end
static void tok_ifThenEnd(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "if <id>")) >= 0) {
    int iThen = indexOf(tok, i, "then");
    if (iThen < 0) { print("No then for if"); continue; }
    int iEnd = tok_findTokenSkippingCurlyBrackets(tok, iThen, "end");
    if (iEnd < 0) { print("No end for if"); continue; }
    tokSet(tok, iEnd, "}");
    replaceTokens_reTok(tok, iThen-1, iThen+1, ") {");
    replaceTokens_reTok(tok, i, i+2, "if (");
  }
}
static void tok_autoInitVars(List<String> tok) {
  jreplace(tok, "boolean <id>;", "boolean $2 = false;",
    new TokCondition() { boolean get(final List<String> tok, final int i) { return !contains(tok_modifiersLeftOf(tok, i+1), "final"); }});
}
static void tok_fixBadTypeParameterOrder(List<String> tok) {
  // e.g. void <A> bla() => <A> void bla()
  jreplace(tok, "void <<id>>", "<$3> void");
}
static void tok_questionDot(List<String> tok) {
  jreplace(tok, "<id> ?.", "if ($1 != null) $1.");
  /*int i;
  while ((i = jfind(tok, "<id> ?.")) {
    S var = tok.get(i);
    int j = findEndOfStatement(tok, i);
    int dot = indexOf(tok, i, ".");
    tok.set(i+2, "");
    replaceTokens(tok, i, dot+1, "if (" + var + " != null) var.");
  }*/
}
static void tok_embeddedFunctions(List<String> tok) {
  int i;
  while ((i = jfind(tok, "embedded <id>")) >= 0) {
    int j = tok_findEndOfMethodDecl(tok, i); // points to N after function
    String functionName = tok_functionName(subList(tok, i));
    String className = makeVar("C"), varName = makeVar();
    
    // drop keyword, rewrite function to class, create object
    
    tok.set(i, "/* make final to optimize */ class " + className + " {");
    tokAppend(tok, j-1, " } " +
      " final " + className + " " + varName + " = new " + className + "();");
    
    // redirect calls to object
    
    int end = findEndOfBlock(tok, j)-1;
    for (j |= 1; j < end; j += 2)
      if (eq(tok.get(j), functionName) && eqGet(tok, j+2, "(")) tokPrepend(tok, j, varName + ".");
    reTok(tok, i, end);
  }
}
static void tok_once(List<String> tok) {
  replaceKeywordBlock(tok, "once",
    "do {",
    "} while (false);");
}
// Note: the variable names must match the record field names
// as we still need a way to find the field names from here
static void tok_ifRecordMatch(List<String> tok) {
  int i;
  while ((i = jfind_check("is",tok, "if <id> is <id>(")) >= 0) {
    int iOpening = indexOf(tok, i, "(");  
    int iClosing = indexOf(tok, iOpening+2, ")");
    String var = tok.get(i+2), className = tok.get(i+6);
    String cast = "((" + className + ") " + var + ")";
    int start = iClosing+2;
    tok_statementToBlock(tok, start);
    int end = findEndOfStatement(tok, start);
    StringBuilder statement = new StringBuilder(("if (" + (var) + " instanceof " + (className)));
    StringBuilder decls = new StringBuilder();
    List<Pair<String, String>> vars = tok_typesAndNamesOfParams(subList(tok, iOpening+1, iClosing));
    for (Pair<String, String> v : vars) {
      String type = v.a, name = v.b;
      String nonPrim = tok_toNonPrimitiveTypes(type);
      if (!tok_typeIsObject(type))
        statement.append((" && " + (cast) + "." + (name) + " instanceof " + (nonPrim)));
      decls.append(("\n" + (type) + " " + (name) + " = cast " + (cast) + "." + (name) + ";"));
    }
    replaceTokens_reTok(tok, i, start+1, statement + ") {" + decls);
  }
}
static void tok_processMetaBlocks(List<String> tok, boolean metaCodeAllowed) {
  for (int i = 0; i < 1000; i++) {
    int idx = findCodeTokens(tok, "meta", "{");
    if (idx < 0) break;
    if (!metaCodeAllowed) throw fail("Meta code not allowed");
    int j = findEndOfBracketPart(tok, idx+2);
    String code = joinSubList(tok, idx+4, j-2);
    print("Evaluating: " + code);
    String result = str(javaEval(code));
    replaceTokens_reTok(tok, idx, j+1, result);
  }
}
static boolean reTok_modify_check = false;

// f: func(L<S>) -> L<S>
static List<String> reTok_modify(List<String> tok, int i, int j, Object f) {
  // extend i to an "N" token
  // and j to "C" (so j-1 is an "N" token)
  i = i & ~1;
  j = j | 1;
  
  List<String> t = javaTok(join(subList(tok, i, j)));
  if (f != null) {
    t = (List<String>) callF(f, t);
    if (reTok_modify_check)
      assertEquals("Improperly tokenized", javaTok(join(t)), t);
  }
  replaceListPart(tok, i, j, t);
  
  return tok;
}

// removes invocations from src
static List<String> tok_findTranslators(List<String> tok, List<String> libsOut) {
  int i;
  while ((i = jfind(tok, "!<int>")) >= 0) {
    setAdd(libsOut, tok.get(i+2));
    clearTokens(tok, i, i+3);
    reTok(tok, i, i+3);
  }
  return tok;
}
static boolean structure_showTiming, structure_checkTokenCount;

static String structure(Object o) {
  return structure(o, new structure_Data());
}

static String structure(Object o, structure_Data d) {
  StringWriter sw = new StringWriter();
  d.out = new PrintWriter(sw);
  structure_go(o, d);
  String s = str(sw);
  if (structure_checkTokenCount) {
    print("token count=" + d.n);
    assertEquals("token count", l(javaTokC(s)), d.n);
  }
  return s;
}

static void structure_go(Object o, structure_Data d) {
  structure_1(o, d);
  while (nempty(d.stack))
    popLast(d.stack).run();
}

static void structureToPrintWriter(Object o, PrintWriter out) {
  structure_Data d = new structure_Data();
  d.out = out;
  structure_go(o, d);
}

// leave to false, unless unstructure() breaks
static boolean structure_allowShortening = false;

// info on how to serialize objects of a certain class
static class structure_ClassInfo {
  List<Field> fields;
  Method customSerializer;
  boolean special, nullInstances;
}

static class structure_Data {
  PrintWriter out;
  int stringSizeLimit;
  int shareStringsLongerThan = 20;
  boolean noStringSharing = false;

  IdentityHashMap<Object,Integer> seen = new IdentityHashMap();
  //new BitSet refd;
  HashMap<String,Integer> strings = new HashMap();
  HashSet<String> concepts = new HashSet();
  HashMap<Class, structure_ClassInfo> infoByClass = new HashMap();
  HashMap<Class,Field> persistenceInfo = new HashMap();
  int n; // token count
  List<Runnable> stack = new ArrayList();
  
  // append single token
  structure_Data append(String token) { out.print(token); ++n; return this; }
  structure_Data append(int i) { out.print(i); ++n; return this; }
  
  // append multiple tokens
  structure_Data append(String token, int tokCount) { out.print(token); n += tokCount; return this; }
  
  // extend last token
  structure_Data app(String token) { out.print(token); return this; }
  structure_Data app(int i) { out.print(i); return this; }
}

static void structure_1(final Object o, final structure_Data d) { try {
  if (o == null) { d.append("null"); return; }
  
  Class c = o.getClass();
  boolean concept = false;
  
  structure_ClassInfo info = d.infoByClass.get(c);
  if (info == null) {
    d.infoByClass.put(c, info = new structure_ClassInfo());
    if ((info.customSerializer = findMethodNamed(c, "_serialize"))
      != null) info.special = true;
  }

  List<Field> lFields = info.fields;
  if (lFields == null) {
    // these are never back-referenced (for readability)
    
    if (o instanceof Number) {
      PrintWriter out = d.out;
if (o instanceof Integer) { int i = ((Integer) o).intValue(); out.print(i); d.n += i < 0 ? 2 : 1; return; }
      if (o instanceof Long) { long l = ((Long) o).longValue(); out.print(l); out.print("L"); d.n += l < 0 ? 2 : 1; return; }
      if (o instanceof Short) { short s = ((Short) o).shortValue(); d.append("sh "); out.print(s); d.n += s < 0 ? 2 : 1; return; }
      if (o instanceof Float) { d.append("fl ", 2); quoteToPrintWriter(str(o), out); return; }
      if (o instanceof Double) { d.append("d(", 3); quoteToPrintWriter(str(o), out); d.append(")"); return; }
      if (o instanceof BigInteger) { out.print("bigint("); out.print(o); out.print(")"); d.n += ((BigInteger) o).signum() < 0 ? 5 : 4; return; }
    }
  
    if (o instanceof Boolean) {
      d.append(((Boolean) o).booleanValue() ? "t" : "f"); return;
    }
      
    if (o instanceof Character) {
      d.append(quoteCharacter((Character) o)); return;
    }
      
    if (o instanceof File) {
      d.append("File ").append(quote(((File) o).getPath())); return;
    }
      
    // referencable objects follow
    
    Integer ref = d.seen.get(o);
    if (o instanceof String && ref == null) ref = d.strings.get((String) o);
    if (ref != null) { /*d.refd.set(ref);*/ d.append("t").app(ref); return; }

    if (!(o instanceof String))
      d.seen.put(o, d.n); // record token number
    else {
      String s = d.stringSizeLimit != 0 ? shorten((String) o, d.stringSizeLimit) : (String) o;
      if (!d.noStringSharing) {
        if (d.shareStringsLongerThan == Integer.MAX_VALUE)
          d.seen.put(o, d.n);
        if (l(s) >= d.shareStringsLongerThan)
          d.strings.put(s, d.n);
      }
      quoteToPrintWriter(s, d.out); d.n++; return;
    }
      
    if (o instanceof Set) {
      /*O set2 = unwrapSynchronizedSet(o);
      if (set2 != o) {
        d.append("sync");
        o = set2;
      } TODO */
      
      if (((Set) o) instanceof TreeSet) {
        d.append(isCISet_gen(((Set) o)) ? "ciset" : "treeset");
        structure_1(new ArrayList(((Set) o)), d);
        return;
      }
      
      // assume it's a HashSet or LinkedHashSet
      d.append(((Set) o) instanceof LinkedHashSet ? "lhs" : "hashset");
      structure_1(new ArrayList(((Set) o)), d);
      return;
    }
    
    String name = c.getName();
    
    if (o instanceof Collection
      && !startsWith(name, "main$")
      /* && neq(name, "main$Concept$RefL") */) {
      
      // it's a list
    
      if (name.equals("java.util.Collections$SynchronizedList")
        || name.equals("java.util.Collections$SynchronizedRandomAccessList")) {
        d.append("sync ");
        { structure_1(unwrapSynchronizedList(((List) o)), d); return; }
      }
      else if (name.equals("java.util.LinkedList")) d.append("ll");
      d.append("[");
      final int l = d.n;
      final Iterator it = ((Collection) o).iterator();
      d.stack.add(new Runnable() {  public void run() { try { 
        if (!it.hasNext())
          d.append("]");
        else {
          d.stack.add(this);
          if (d.n != l) d.append(", ");
          structure_1(it.next(), d);
        }
      
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (!it.hasNext())\r\n          d.append(\"]\");\r\n        else {\r\n          d.sta..."; }});
      return;
    }
    
    if (o instanceof Map && !startsWith(name, "main$")) {
      if (o instanceof LinkedHashMap) d.append("lhm");
      else if (o instanceof HashMap) d.append("hm");
      else if (o instanceof TreeMap)
        d.append(isCIMap_gen(((TreeMap) o)) ? "cimap" : "tm");
      else if (name.equals("java.util.Collections$SynchronizedMap")
        || name.equals("java.util.Collections$SynchronizedSortedMap")
        || name.equals("java.util.Collections$SynchronizedNavigableMap")) {
        d.append("sync "); 
        { structure_1(unwrapSynchronizedMap(((Map) o)), d); return; }
      }
      
      d.append("{");
      final int l = d.n;
      final Iterator it = ((Map) o).entrySet().iterator();
      
      d.stack.add(new Runnable() {
        boolean v = false;
        Map.Entry e;
        
        public void run() {
          if (v) {
            d.append("=");
            v = false;
            d.stack.add(this);
            structure_1(e.getValue(), d);
          } else {
            if (!it.hasNext())
              d.append("}");
            else {
              e = (Map.Entry) it.next();
              v = true;
              d.stack.add(this);
              if (d.n != l) d.append(", ");
              structure_1(e.getKey(), d);
            }
          }
        }
      });
      return;
    }
    
    if (c.isArray()) {
      if (o instanceof byte[]) {
        d.append("ba ").append(quote(bytesToHex((byte[]) o))); return;
      }
  
      final int n = Array.getLength(o);
  
      if (o instanceof boolean[]) {
        String hex = boolArrayToHex((boolean[]) o);
        int i = l(hex);
        while (i > 0 && hex.charAt(i-1) == '0' && hex.charAt(i-2) == '0') i -= 2;
        d.append("boolarray ").append(n).app(" ").append(quote(substring(hex, 0, i))); return;
      }
      
      String atype = "array", sep = ", ";
  
      if (o instanceof int[]) {
        //ret "intarray " + quote(intArrayToHex((int[]) o));
        atype = "intarray";
        sep = " ";
      }
      
      d.append(atype).append("{");
      d.stack.add(new Runnable() {
        int i;
        public void run() {
          if (i >= n)
            d.append("}");
          else {
            d.stack.add(this);
            if (i > 0) d.append(", ");
            structure_1(Array.get(o, i++), d);
          }
        }
      });
      return;
    }
  
    if (o instanceof Class) {
      d.append("class(", 2).append(quote(((Class) o).getName())).append(")"); return;
    }
      
    if (o instanceof Throwable) {
      d.append("exception(", 2).append(quote(((Throwable) o).getMessage())).append(")"); return;
    }
      
    if (o instanceof BitSet) {
      BitSet bs = (BitSet) o;
      d.append("bitset{", 2);
      int l = d.n;
      for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
        if (d.n != l) d.append(", ");
        d.append(i);
      }
      d.append("}"); return;
    }
      
    // Need more cases? This should cover all library classes...
    if (name.startsWith("java.") || name.startsWith("javax.")) {
      d.append("j ").append(quote(str(o))); return; // Hm. this is not unstructure-able
    }
    
    
      
    /*if (name.equals("main$Lisp")) {
      fail("lisp not supported right now");
    }*/
    
    if (info.special) {
      if (info.customSerializer != null) {
        // custom serialization (_serialize method)
        Object o2 = invokeMethod(info.customSerializer, o);
        d.append("cu ");
        String shortName = dropPrefix("main$", name);
        d.append(shortName);
        d.out.append(' ');
        structure_1(o2, d);
        return;
      } else
        if (info.nullInstances) { d.append("null"); return; }
      else throw fail("unknown special type");
    }
    
    String dynName = shortDynamicClassName(o);
    if (concept && !d.concepts.contains(dynName)) {
      d.concepts.add(dynName);
      d.append("c ");
    }
    
    // serialize an object with fields.
    // first, collect all fields and values in fv.
    
    TreeSet<Field> fields = new TreeSet<Field>(new Comparator<Field>() {
      public int compare(Field a, Field b) {
        return stdcompare(a.getName(), b.getName());
      }
    });
    
    Class cc = c;
    while (cc != Object.class) {
      for (Field field : getDeclaredFields_cached(cc)) {
        String fieldName = field.getName();
        if (fieldName.equals("_persistenceInfo"))
          d.persistenceInfo.put(c, field);
        if ((field.getModifiers() & (java.lang.reflect.Modifier.STATIC | java.lang.reflect.Modifier.TRANSIENT)) != 0)
          continue;

        fields.add(field);
        
        // put special cases here...?
      }
        
      cc = cc.getSuperclass();
    }
    
    // TODO: S fieldOrder = getOpt(c, "_fieldOrder");
    lFields = asList(fields);
    
    // Render this$1 first because unstructure needs it for constructor call.
    
    int n = l(lFields);
    for (int i = 0; i < n; i++) {
      Field f = lFields.get(i);
      if (f.getName().equals("this$1")) {
        lFields.remove(i);
        lFields.add(0, f);
        break;
      }
    }
  
    
    info.fields = lFields;
  } // << if (lFields == null)
  else { // ref handling for lFields != null
    Integer ref = d.seen.get(o);
    if (ref != null) { /*d.refd.set(ref);*/ d.append("t").app(ref); return; }
    d.seen.put(o, d.n); // record token number
  }

  Field persistenceInfoField =  (Field) (d.persistenceInfo.get(c));
  Map<String, Object> persistenceInfo = persistenceInfoField == null ? null : (Map) persistenceInfoField.get(o);
  
  LinkedHashMap<String,Object> fv = new LinkedHashMap();
  for (Field f : lFields) {
    Object value;
    try {
      value = f.get(o);
    } catch (Exception e) {
      value = "?";
    }
      
    if (value != null && (persistenceInfo == null
      || !Boolean.FALSE.equals(persistenceInfo.get(f.getName()))))
      fv.put(f.getName(), value);
    
  }
  
  String name = c.getName();
  String shortName = dropPrefix("main$", name);
  if (startsWithDigit(shortName)) shortName = name; // for anonymous classes
    
  // Now we have fields & values. Process fieldValues if it's a DynamicObject.
  
  // omit field "className" if equal to class's name
  if (concept && eq(fv.get("className"), shortName))
    fv.remove("className");
          
  if (o instanceof DynamicObject) {
    putAll(fv, (Map) fv.get("fieldValues"));
    fv.remove("fieldValues");
    shortName = shortDynamicClassName(o);
    fv.remove("className");
  }
  
  String singleField = fv.size() == 1 ? first(fv.keySet()) : null;
  
  d.append(shortName);
  d.n += countDots(shortName)*2; // correct token count
  
  
  final int l = d.n;
  final Iterator it = fv.entrySet().iterator();
  
  d.stack.add(new Runnable() {  public void run() { try { 
    if (!it.hasNext()) {
      if (d.n != l)
        d.append(")");
    } else {
      Map.Entry e = (Map.Entry) it.next();
      d.append(d.n == l ? "(" : ", ");
      d.append((String) e.getKey()).append("=");
      d.stack.add(this);
      structure_1(e.getValue(), d);
    }
  
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (!it.hasNext()) {\r\n      if (d.n != l)\r\n        d.append(\")\");\r\n    } else..."; }});
} catch (Exception __e) { throw rethrow(__e); } }

static boolean tok_hasClassRef2(List<String> tok, String className) {
  int n = l(tok);
  int level = 0;
  for (int i = 1; i < n; i += 2) {
    String t = tok.get(i);
    if (eqOneOf(t, "ifclass", "ifndef")) ++level;
    else if (eqOneOf(t, "endif", "endifndef")) --level;
    else if (level <= 0 && eq(t, className)) return true;
  }
  return false;
}
static <A> A printStruct(String prefix, A a) {
  printStructure(prefix, a);
  return a;
}

static <A> A printStruct(A a) {
  printStructure(a);
  return a;
}

static boolean includeInMainLoaded_safe = false;

static List<String> includeInMainLoaded(List<String> tok, String text) {
  List<String> main = findMainClass(tok);
  if (main == null) {
    print(join(tok));
    throw fail("no main class");
  }
  int i = main.lastIndexOf("}");
  main.set(i, "\n" + text + "\n}");
  i += magicIndexOfSubList(tok, main);
  return includeInMainLoaded_safe ? includeInMainLoaded_reTok(tok, 0, l(tok)) : includeInMainLoaded_reTok(tok, i, i+1);
}
static String dropPrefix(String prefix, String s) {
  return s == null ? null : s.startsWith(prefix) ? s.substring(l(prefix)) : s;
}
static String dropSuffix(String suffix, String s) {
  return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}
static <A> int indexOf(List<A> l, A a, int startIndex) {
  if (l == null) return -1;
  int n = l(l);
  for (int i = startIndex; i < n; i++)
    if (eq(l.get(i), a))
      return i;
  return -1;
}

static <A> int indexOf(List<A> l, int startIndex, A a) {
  return indexOf(l, a, startIndex);
}

static <A> int indexOf(List<A> l, A a) {
  if (l == null) return -1;
  return l.indexOf(a);
}

static int indexOf(String a, String b) {
  return a == null || b == null ? -1 : a.indexOf(b);
}

static int indexOf(String a, String b, int i) {
  return a == null || b == null ? -1 : a.indexOf(b, i);
}

static int indexOf(String a, char b) {
  return a == null ? -1 : a.indexOf(b);
}

static int indexOf(String a, int i, char b) {
  return indexOf(a, b, i);
}

static int indexOf(String a, char b, int i) {
  return a == null ? -1 : a.indexOf(b, i);
}

static int indexOf(String a, int i, String b) {
  return a == null || b == null ? -1 : a.indexOf(b, i);
}

static <A> int indexOf(A[] x, A a) {
  int n = l(x);
  for (int i = 0; i < n; i++)
    if (eq(x[i], a))
      return i;
  return -1;
}
static void tokSet_reTok(List<String> tok, int i, String t) {
  tokSet_withReTok(tok, i, t);
}
static int getAndInc(AtomicInteger i) {
  return i.getAndIncrement();
}
static Map<String,Class> runTranslatorQuick_cache = new HashMap();

static synchronized String runTranslatorQuick(String text, String translatorID) {
  translatorID = formatSnippetID(translatorID);
  Class c = runTranslatorQuick_cache.get(translatorID);
  print("runTranslatorQuick " + programID() + " " + identityHashCode(main.class) + " CACHE " + identityHashCode(runTranslatorQuick_cache) + " " + structure(runTranslatorQuick_cache.keySet()) + " " + (c != null ? "CACHED" : "LOAD") + ": " + translatorID);
  if (c == null) {
    //printStackTrace();
    c = hotwire(translatorID);
    print("runTranslatorQuick " + programID() + " " + identityHashCode(main.class) + " CACHE " + identityHashCode(runTranslatorQuick_cache) + " " + structure(runTranslatorQuick_cache.keySet()) + " STORE: " + translatorID + " " + identityHashCode(c));
    runTranslatorQuick_cache.put(translatorID, c);
  }
  
  set(c, "mainJava", text);
  callMain(c);
  return (String) get(c, "mainJava");
}

static List<String> runTranslatorQuick(List<String> tok, String translatorID) {
  return javaTok(runTranslatorQuick(join(tok), translatorID));
}

static boolean neq(Object a, Object b) {
  return !eq(a, b);
}
static List<String> tok_findImports(List<String> tok) {
  List<String> imports = new ArrayList();
  int n = l(tok);
  for (int i = 1; i < n; i += 2)
    if (eq(tok.get(i), "import")) {
      int j = indexOf(tok, ";", i+2);
      if (j < 0) break;
      imports.add(joinCodeTokens(subList(tok, i-1, j)));
      i = j;
    }
  return imports;
}
static Class include(String progID) {
  Class c = hotwire(progID);
  setOpt(c, "programID", getProgramID());
  return c;
}

static long parseLong(String s) {
  if (empty(s)) return 0;
  return Long.parseLong(dropSuffix("L", s));
}

static long parseLong(Object s) {
  return Long.parseLong((String) s);
}
// new Type var; => Type var = new Type;
// new Type<...> var; => Type var = new Type;
static void tok_quicknew(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "new <id>")) >= 0) {
    int j = tok_findEndOfType(tok, i+2);
    if (isIdentifier(get(tok, j)) && eqGet(tok, j+2, ";")) {
      String type = tok.get(i+2);
      clearTokens(tok, i, i+2);
      tok.set(j+2, " = new " + type + ";");
      reTok(tok, i, j+3);
    }
  }
}
static List<String> findInnerClassOfMain(List<String> tok, String className) {
  for (List<String> c : innerClassesOfMain(tok)) {
    String name = getClassDeclarationName(c);
    if (eq(name, className))
      return c;
  }
  return null;
}
static <A> int indexOfSubList(List<A> x, List<A> y) {
  return indexOfSubList(x, y, 0);
}

static <A> int indexOfSubList(List<A> x, List<A> 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 <A> int indexOfSubList(List<A> x, A[] 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[j]))
        continue outer;
    return i;
  }
  return -1;
}
static void doubleReTok(List<String> tok, int i1, int j1, int i2, int j2) {
  if (i1 > i2) {
    int i = i1, j = j1;
    i1 = i2; j1 = j2;
    i2 = i; j2 = j;
  }
  if (j1 > i2) { reTok(tok); return; }
  reTok(tok, i2, j2);
  reTok(tok, i1, j1);
}
static String dropPrefixTrim(String prefix, String s) {
  return trim(dropPrefix(prefix, s));
}
  static void clearAllTokens(List<String> tok) {
    for (int i = 0; i < tok.size(); i++)
      tok.set(i, "");
  }
  
  static void clearAllTokens(List<String> tok, int i, int j) {
    for (; i < j; i++)
      tok.set(i, "");
  }
static String str(Object o) {
  return o == null ? "null" : o.toString();
}

static String str(char[] c) {
  return new String(c);
}
static int countChar(String s, char c) {
  int n = 0, l = l(s), i = 0;
  while (true) {
    int j = s.indexOf(c, i);
    if (j < 0) break;
    ++n;
    i = j+1;
  }
  return n;
}
static void tok_autosemi(List<String> tok) {
  int i;
  while (
    (i = jfind(tok, "autosemi {")) >= 0
    || (i = jfind(tok, "semiauto {")) >= 0) {
    int closing = findEndOfBlock(tok, i+2)-1;
    tok.set(i, ""); // remove autosemi keyword
    tok.set(i+1, "");
    for (int j = i+5; j < closing; j += 2)
      if (containsNewLine(tok.get(j))
        && neqOneOf(tok.get(j-1), "{", "}", ";"))
      tok.set(j-1, tok.get(j-1) + ";");
    reTok(tok, i, closing);
  }
}
static String tok_autoCloseBrackets(String s) {
  return join(tok_autoCloseBrackets(javaTok(s)));
}

// modifies tok
static List<String> tok_autoCloseBrackets(List<String> tok) {
  bigloop: for (int i = 1; i < l(tok); i += 2) {
    if (eq(tok.get(i), ";")) {
      int j, level = 0;
      for (j = i-2; j >= 0; j -= 2) {
        String t = tok.get(j);
        if (eqOneOf(t, ";", "{")) break;
        if (eq(t, "}")) break; // TODO: skip over until other end of bracket
        else if (eq(t, ")")) --level;
        else if (eq(t, "(")) {
          if (eq(get(tok, j-2), "for")) break;
          if (eq(get(tok, j-4), "for")) break; // for ping
          ++level;
        }
      }
      while (level-- > 0) {
        tok.add(i++, ")");
        tok.add(i++, "");
      }
    }
  }
  return tok;
}
static boolean jreplace_check(String checkToken, List<String> tok, String in, String out) {
  if (isIndexedList(tok) && !tok.contains(checkToken)) return false;
  return jreplace(tok, in, out);
}
static Map<String, String> tok_toNonPrimitiveTypes_map = mapFromTokens("\r\n  int Int\r\n  byte Byte\r\n  float Float\r\n  short Short\r\n  double Double\r\n  long Long\r\n  char Character\r\n  bool Bool\r\n  boolean Bool\r\n");

static String tok_toNonPrimitiveTypes(String s) {
  return join(tok_toNonPrimitiveTypes(javaTok(s)));
}

static List<String> tok_toNonPrimitiveTypes(List<String> tok) {
  for (int i = 1; i < l(tok); i += 2) {
    String t = tok.get(i);
    t = tok_toNonPrimitiveTypes_map.get(t);
    if (t != null && neq(get(tok, i+2), "[")) tok.set(i, t);
  }
  return tok;
}
static <A> List<A> cloneSubList(List<A> l, int startIndex, int endIndex) {
  return newSubList(l, startIndex, endIndex);
}

static <A> List<A> cloneSubList(List<A> l, int startIndex) {
  return newSubList(l, startIndex);
}
static List<String> tok_typesOfParams(List<String> tok) {
  try {
    List<String> types = new ArrayList();
    for (int i = 1; i < l(tok); ) {
      String t = tok.get(i);
      if (eqOneOf(t, "final")) t = get(tok, i += 2);
      
      assertTrue(isIdentifier(t));
      i += 2;
      String type = t;
      
      if (eq(t, "virtual")) {
        type += " " + tok.get(i);
        i += 2;
      }
      
      while (eq(get(tok, i), ".") && isIdentifier(get(tok, i+2))) {
        type += "." + get(tok, i+2);
        i += 4;
      }
      
      // ...
      if (eqGet(tok, i, ".") && eqGet(tok, i+2, ".") && eqGet(tok, i+2, ".")) {
        type += "[]";
        i += 6;
      }
      
      // just a parameter name, no type
      if (eqOneOf(get(tok, i), null, ","))
        t = null;
      else {
        if (eq(tok.get(i), "<")) {
          int j = findEndOfTypeArgs(tok, i)-1;
          while (eq(get(tok, j), "[") && eq(get(tok, j+2), "]")) j += 4;
          type += trimJoinSubList(tok, i, j+1);
          String id = assertIdentifier(tok.get(j+2));
          i = j+2;
        }
        while (eq(get(tok, i), "[") && eq(get(tok, i+2), "]")) {
          i += 4;
          type += "[]";
        }
        String id = assertIdentifier(tok.get(i));
        i += 2;
        while (eq(get(tok, i), "[") && eq(get(tok, i+2), "]")) {
          i += 4;
          type += "[]";
        }
        if (i < l(tok)) {
          assertEquals(get(tok, i), ",");
          i += 2;
        }
      }
      types.add(type);
    }
    return types;
  } catch (Throwable e) {
    print("Bad parameter declaration: " + join(tok));
    throw rethrow(e);
  }
}

static List<String> tok_typesOfParams(String s) {
  return tok_typesOfParams(javaTok(s));
}
static <A> String joinWithComma(Collection<A> c) {
  return join(", ", c);
}

static String joinWithComma(String... c) {
  return join(", ", c);
}


static String joinWithComma(Pair p) {
  return p == null ? "" : joinWithComma(str(p.a), str(p.b));
}

// optionally convert expression to return statement
static String tok_addReturn(List<String> tok) {
  if (tok_shouldAddReturn(tok)) {
    tokPrepend(tok, 1, "ret ");
    tokAppend(tok, l(tok)-2, ";");
  }
  return join(tok);
}

static String tok_addReturn(String s) {
  return tok_addReturn(javaTok(s));
}
static void tok_qFunctions(List<String> tok) {
  tok_qFunctions(tok, "q", "dm_q");
  tok_qFunctions(tok, "runInQAndWait", "dm_runInQAndWait");
}

static void tok_qFunctions(List<String> tok, String keyword, String function) {
  int i;
  while ((i = jfind_check(keyword, tok, "void <id> " + keyword + " {")) >= 0) {
    int bracket = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, bracket);
    clearTokens(tok, i+4, i+6);
    tok.set(bracket, "{ " + function + "(r {");
    tok.set(j-1, "}); }");
    reTok(tok, i+4, j);
  }
  
  while ((i = jfind_check(keyword, tok, ") " + keyword + " {")) >= 0) {
    int opening = findBeginningOfBracketPart(tok, i);
    String fname = assertIdentifier(get(tok, opening-2));
    int bracket = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, bracket);
    clearTokens(tok, i+2, i+4);
    tok.set(bracket, "{ " + function + "(r {");
    tok.set(j-1, "}); }");
    reTok(tok, i+4, j);
    tok_makeArgumentsFinal(tok, opening, i);
  }
}

// i must point at the (possibly imaginary) opening bracket ("{")
// index returned is index of closing bracket + 1 (or l(cnc))
static int findEndOfBlock(List<String> cnc, int i) {
  int j = i+2, level = 1, n = cnc.size();
  while (j < n) {
    String t = cnc.get(j);
    if ("{".equals(t)) ++level;
    else if ("}".equals(t)) --level;
    if (level == 0)
      return j+1;
    j += 2;
  }
  return n;
}
static boolean tok_tokenLeftOfExpression(List<String> tok, int i) {
  return eqGetOneOf(tok, i, "(", "=", ",") || isIdentifier(get(tok, i));
}
static boolean isBracketHygienic(String s) {
  return testBracketHygiene(s);
}

static boolean isBracketHygienic(String s, String op, String close, Var<String> msg) {
  return testBracketHygiene(s, op, close, msg);
}

static boolean isBracketHygienic(String s, String op, String close) {
  return testBracketHygiene(s, op, close, null);
}

static void assertTrue(Object o) {
  if (!(eq(o, true) /*|| isTrue(pcallF(o))*/))
    throw fail(str(o));
}
  
static boolean assertTrue(String msg, boolean b) {
  if (!b)
    throw fail(msg);
  return b;
}

static boolean assertTrue(boolean b) {
  if (!b)
    throw fail("oops");
  return b;
}
static <A> List<A> concatLists(Collection<A>... lists) {
  List<A> l = new ArrayList();
  if (lists != null) for (Collection<A> list : lists)
    if (list != null)
      l.addAll(list);
  return l;
}

static <A> List<A> concatLists(Collection<? extends Collection<A>> lists) {
  List<A> l = new ArrayList();
  if (lists != null) for (Collection<A> list : lists)
    if (list != null)
      l.addAll(list);
  return l;
}

static Object loadVariableDefinition(String varName) {
  return loadVariableDefinition(getProgramID(), varName);
}

// currently works with string lists ("= litlist(...)"),
// strings, longs and ints.
static Object loadVariableDefinition(String progIDOrSrc, String varName) {
  if (isSnippetID(progIDOrSrc))
    progIDOrSrc = loadSnippet(progIDOrSrc);
  return loadVariableDefinition(progIDOrSrc, javaTok(progIDOrSrc), varName);
}
    
static Object loadVariableDefinition(String src, List<String> tok, String varName) {
  int i = findCodeTokens(tok, varName, "=");
  if (i < 0) return null;
  
  i += 4;
  String t = tok.get(i);
  
  if (isQuoted(t))
    return unquote(t);
    
  if (isLongConstant(t))
    return parseLong(t);
    
  if (isInteger(t))
    return parseInt(t);
  
  if (eqOneOf(t, "litlist", "ll") && 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(src, 100) + "/" + varName);
}


static <A, B> Map<A, B> combinedMap(Map<A, B>... maps) {
  return new CombinedMap(wrapAsList(maps));
}

static <A, B, C extends Map<A, B>> Map<A, B> combinedMap(Collection<C> maps) {
  return l(maps) == 1 ? first(maps) : new CombinedMap(maps);
}
static List<String> tok_importedStaticFunctionNames(List<String> tok) {
  List<String> names = new ArrayList();
  for (int i = 1; i < l(tok); i += 2)
    if (eq(tok.get(i), "import") && eq(get(tok, i+2), "static")) {
      int j = findEndOfStatement(tok, i); // index of ;+1
      String s = get(tok, j-3);
      if (isIdentifier(s))
      names.add(s);
      i = j-1;
    }
  return names;
}
static boolean findFunctionInvocations_debug = false;

// these prefix tokens mark a non-invocation (TODO: expand)
static Set<String> findFunctionInvocations_pre = new HashSet(litlist(".", "void", "S", "String", "int", "bool", "boolean", "A", "Object", "O"));

static Set<String> findFunctionInvocations(String src, Map<String, String> sf) {
  return findFunctionInvocations(javaTok(src), sf);
}

static Set<String> findFunctionInvocations(List<String> tok, Map<String, String> sf) {
  return findFunctionInvocations(tok, sf, null);
}

// sf = set of functions to search for or null for any function
static Set<String> findFunctionInvocations(List<String> tok, Map<String, String> sf, Collection<String> hardReferences) {
  return findFunctionInvocations(tok, sf, hardReferences, null);
}

static Set<String> findFunctionInvocations(List<String> tok, Map<String, String> sf, Collection<String> hardReferences, Set<String> haveFunctions) {
  return findFunctionInvocations(tok, sf, hardReferences, haveFunctions, false);
}

static Set<String> findFunctionInvocations(List<String> tok, Map<String, String> sf, Collection<String> hardReferences, Set<String> haveFunctions, boolean includePossiblyMapLikes) {
  LinkedHashSet<String> l = new LinkedHashSet();
  for (int i : jfindAll(tok, "please include functions")) {
    int j = i + 6;
    while (licensed()) {
      String fname = tok.get(j);
      assertIdentifier("in please include functions section", get(tok, j));
      l.add(fname);
      add(hardReferences, fname);
      j += 2;
      if (eqGet(tok, j, ".")) break;
      if (!eqOneOf(get(tok, j), ",", "and"))
        throw fail("bad token 'please include functions' section: " + get(tok, j));
      j += 2;
    }
    clearAllTokens(tok.subList(i, j+2));
  }
  
  for (int i : jfindAll(tok, "please include function *.")) {
    String fname = tok.get(i+6);
    l.add(fname);
    add(hardReferences, fname);
    clearAllTokens(tok.subList(i, i+10));
  }

  int i;
  boolean result = false;
  for (i = 1; i+2 < tok.size(); i += 2) {
    String f = tok.get(i);
    if (!isIdentifier(f)) continue;
    
    // main.<A>bla()
    if ("main".equals(f) && eq(tok.get(i+2), ".") && eqGet(tok, i+4, "<")) {
      i = findEndOfTypeArgs(tok, i+4)+1;
      f = tok.get(i);
      if (!isIdentifier(f)) continue;
    }
    
    if (findFunctionInvocations_debug)
      print("Testing identifier " + f);
      
    // e.g. ... html_findLIs(LS [auto htmlTok] tok) { ... }
    if (eqGet(tok, i-4, "[") && eqGet(tok, i-2, "auto")) {
      // ok
    } else {
      if (!"(".equals(tok.get(i+2))) {
        if (!(includePossiblyMapLikes && eqGet(tok, i+4, "(") && isIdentifier(get(tok, i+2))))
          continue;
      }
    }
    
    if (i == 1
        || !findFunctionInvocations_pre.contains(tok.get(i-2))
        || eq(get(tok, i-2), ".") && eq(get(tok, i-4), "main")) {
      boolean inSF = sf == null || sf.containsKey(f);
      if (findFunctionInvocations_debug)
        print("Possible invocation: " + f + ", inSF: " + inSF);
      if (inSF && !contains(haveFunctions, f) && l.add(f))
        if (findFunctionInvocations_debug)
          print("Found reference to standard function " + f + ": " + lineAroundToken(tok, i));
    }
  }
  
  return l;
}
static boolean cic(Collection<String> l, String s) {
  return containsIgnoreCase(l, s);
}



static boolean cic(String[] l, String s) {
  return containsIgnoreCase(l, s);
}

static boolean cic(String s, char c) {
  return containsIgnoreCase(s, c);
}

static boolean cic(String a, String b) {
  return containsIgnoreCase(a, b);
}

static List<String> diff(Collection<String> a, Collection<String> b) {
  Set<String> set = asSet(b);
  List<String> l = new ArrayList();
  for (String s : a)
    if (!set.contains(s))
      l.add(s);
  return l;
}
static <A> Set<A> setIntersection(Set<A> a, Collection<A> b) {
  Set<A> set = similarEmptySet(a);
  Set<A> bSet = asSet(b);
  for (A x : a) if (bSet.contains(x)) set.add(x);
  return set;
}
static <A> ArrayList<A> cloneList(Iterable<A> l) {
  return l instanceof Collection ? cloneList((Collection) l) : asList(l);
}

static <A> ArrayList<A> cloneList(Collection<A> l) {
  if (l == null) return new ArrayList();
  synchronized(collectionMutex(l)) {
    return new ArrayList<A>(l);
  }
}
static long psI(String snippetID) {
  return parseSnippetID(snippetID);
}
static Set<String> findFunctionDefs_keywords = new HashSet(splitAtSpace("static svoid ssvoid ssynchronized sbool sS sO sL"));

static List<String> findFunctionDefs(List<String> tok) {
  int n = l(tok);
  List<String> functions = new ArrayList();
  for (int i = 1; i < n; i += 2) {
    String t = tok.get(i);
    if (findFunctionDefs_keywords.contains(t)) {
      int j = i+2;
      while (j < n && !findFunctionsDefs_checkToken(tok.get(j)))
        j += 2;
      if (isIdentifier(tok.get(j-2)) &&
        eqGet(tok, j, "(") || eqGet(tok, j, "{") && eq(t, "svoid"))
        functions.add(tok.get(j-2));
    }
  }
  return functions;
}

static boolean findFunctionsDefs_checkToken(String t) {
  if (t.length() != 1) return false;
  char c = t.charAt(0);
  return c == ';' || c == '=' || c == '(' || c == '{' || c == '#';
}
static <A> List<A> getAllFutures(Collection<Future<A>> l) {
  List<A> out = new ArrayList(l(l));
  for (Future<A> f : unnull(l))
    out.add(getFuture(f));
  return out;
}
// returns actual CNC
static List<String> findMainClass(List<String> tok) {
  for (List<String> c : reversedList(allClasses(tok))) {
    String name = getClassDeclarationName(c);
    if (eq(name, "main") || name.startsWith("x"))
      return c;
  }
  return findBlock("m {", tok);
}
static String formatSnippetID(String id) {
  return "#" + parseSnippetID(id);
}

static String formatSnippetID(long id) {
  return "#" + id;
}
static boolean preferCached = false;
static boolean loadSnippet_debug = false;
static ThreadLocal<Boolean> loadSnippet_silent = new ThreadLocal();
static int loadSnippet_timeout = 30000;


static String loadSnippet(Snippet s) {
  return loadSnippet(s.id);
}


static String loadSnippet(String snippetID) { try {
  if (snippetID == null) return null;
  return loadSnippet(parseSnippetID(snippetID), preferCached);
} catch (Exception __e) { throw rethrow(__e); } }

static String loadSnippet(String snippetID, boolean preferCached) throws IOException {
  return loadSnippet(parseSnippetID(snippetID), preferCached);
}

public static String loadSnippet(long snippetID) { try {
  return loadSnippet(snippetID, preferCached);
} catch (Exception __e) { throw rethrow(__e); } }

public static String loadSnippet(long snippetID, boolean preferCached) throws IOException {
  String text;
  
  if (isLocalSnippetID(snippetID))
    return loadLocalSnippet(snippetID);
    
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadSnippet(fsI(snippetID));
  
  
  // boss bot disabled for now for shorter transpilations
  
  /*text = getSnippetFromBossBot(snippetID);
  if (text != null) return text;*/
  
  initSnippetCache();
  text = DiskSnippetCache_get(snippetID);
  
  if (preferCached && text != null)
    return text;
  
  try {
    if (loadSnippet_debug && text != null) System.err.println("md5: " + md5(text));
    String url = tb_mainServer() + "/getraw.php?id=" + snippetID + "&utf8=1";
    if (nempty(text)) url += "&md5=" + md5(text);
    url += standardCredentials();
    
    String text2 = loadSnippet_loadFromServer(url);
    
    boolean same = eq(text2, "==*#*==");
    if (loadSnippet_debug) print("loadSnippet: same=" + same);
    if (!same) text = text2;
  } catch (RuntimeException e) {
    e.printStackTrace();
    throw new IOException("Snippet #" + snippetID + " not found or not public");
  }

  try {
    initSnippetCache();
    DiskSnippetCache_put(snippetID, text);
  } catch (IOException e) {
    System.err.println("Minor warning: Couldn't save snippet to cache ("  + DiskSnippetCache_getDir() + ")");
  }

  return text;
}

static File DiskSnippetCache_dir;

public static void initDiskSnippetCache(File dir) {
  DiskSnippetCache_dir = dir;
  dir.mkdirs();
}

public static synchronized String DiskSnippetCache_get(long snippetID) throws IOException {
  return loadTextFile(DiskSnippetCache_getFile(snippetID).getPath(), null);
}

private static File DiskSnippetCache_getFile(long snippetID) {
  return new File(DiskSnippetCache_dir, "" + snippetID);
}

public static synchronized void DiskSnippetCache_put(long snippetID, String snippet) throws IOException {
  saveTextFile(DiskSnippetCache_getFile(snippetID).getPath(), snippet);
}

public static File DiskSnippetCache_getDir() {
  return DiskSnippetCache_dir;
}

public static void initSnippetCache() {
  if (DiskSnippetCache_dir == null)
    initDiskSnippetCache(getGlobalCache());
}

static String loadSnippet_loadFromServer(String url) {
  Integer oldTimeout = setThreadLocal(loadPage_forcedTimeout_byThread, loadSnippet_timeout);
  try {
    return isTrue(loadSnippet_silent.get()) ? loadPageSilently(url) : loadPage(url);
  } finally {
    loadPage_forcedTimeout_byThread.set(oldTimeout);
  }
}

static boolean hasUnclosedStringLiterals(String s) {
  for (String t : javaTokC(s))
    if (isUnproperlyQuoted(t))
      return true;
  return false;
}
static boolean regexpContains(String pat, String s) {
  return regexpFinds(pat, s);
}

static boolean regexpContains(Pattern pat, String s) {
  return regexpFinds(pat, s);
}
static boolean loadSnippets_verbose = false;

static List<String> loadSnippets(String... ids) {
  return loadSnippets(asList(ids));
}

static List<String> loadSnippets(List<String> ids) { try {
  List<String> texts = new ArrayList();
    
  StringBuilder buf = new StringBuilder();
  Map<Long,String> cached = new HashMap();
  initSnippetCache();
  for (String id : ids) {
    long snippetID = psI(id);
    String text = DiskSnippetCache_get(snippetID);
    String md5 = text != null ? md5(text) : ".";
    if (loadSnippets_verbose)
      print(id + " => " + md5 + " - " + quote(shorten(text, 20)));
    mapPut(cached, snippetID, text);
    buf.append(snippetID).append(" ").append(md5).append(" ");
  }
  if (loadSnippets_verbose)
    print("loadSnippets post data: " + buf);

  Map<String, Object> map = jsonDecodeMap(doPost(
    "ids=" + urlencode(trim(str(buf))) + standardCredentials(),
    tb_mainServer() + "/get-multi2.php"));
    
  for (String id : ids) {
    long snippetID = psI(id);
    Object result = map.get(str(snippetID));
    if (loadSnippets_verbose)
      print(id + " => " + className(result));
    if (result instanceof String) {
      texts.add((String) result);
      DiskSnippetCache_put(snippetID, (String) result);
    } else
      texts.add(cached.get(snippetID));
  }
  return texts;
} catch (Exception __e) { throw rethrow(__e); } }
static List<List<String>> innerClassesOfMain(List<String> tok) {
  return innerClasses(findMainClass(tok));
}

static List<List<String>> innerClassesOfMain(String src) {
  return innerClassesOfMain(javaTok(src));
}
static String getClassDeclarationName(List<String> tok) {
  if (tok != null)
    for (int i = 1; i+2 < tok.size(); i += 2)
      if (allClasses_keywords.contains(tok.get(i)) && isIdentifier(tok.get(i+2))) {
        if (eqGet(tok, i+2, "noeq")) i += 2;
        return tok.get(i+2);
      }
  return null;
}

static List<String> tok_importedClassNames(List<String> tok) {
  List<String> names = new ArrayList();
  int n = l(tok);
  for (int i = 1; i < n; i += 2)
    if (eq(tok.get(i), "import") /*&& neq(get(tok, i+2), "static")*/) { // static may be OK, e.g. import static x30_pkg.x30_util.VF1;
      int j = findEndOfStatement(tok, i); // index of ;+1
      String s = get(tok, j-3);
      
      // This is to distinguish from imported functions;
      // yes it's rough.
      if (isIdentifier(s) && startsWithUpperCase(s))
        names.add(s);
      i = j-1;
    }
  return names;
}
static Set<String> usualJavaClassNames_set = asHashSet(javaTokC("\r\n  String Object Map List Integer Long Collection File Component JComponent JButton\r\n  JCheckBox Float JTextArea JTextComponent JFrame JPanel JInternalFrame\r\n  Number\r\n"));

static Set<String> usualJavaClassNames() {
  return usualJavaClassNames_set;
}
static List<String> tlft_j(String text) {
  return toLinesFullTrim_java(text);
}
static Set<String> tokenIndexWithoutIfclass_forStdClasses(List<String> tok) {
  HashSet<String> set = new HashSet();
  int n = l(tok);
  int level = 0;
  for (int i = 1; i < n; i += 2) {
    String t = tok.get(i);
    if (eqOneOf_twoNonNullStrings(t, "ifclass", "ifndef")) ++level;
    else if (eqOneOf_twoNonNullStrings(t, "endif", "endifndef")) --level;
    else if (level <= 0 && startsWithJavaIdentifierStart(t)
      && !eqOneOf_twoNonNullStrings(get(tok, i-2), ".", "virtual"))
        set.add(t);
  }
  return set;
}
static <A, B> Set<A> keys(Map<A, B> map) {
  return map == null ? new HashSet() : map.keySet();
}

static Set keys(Object map) {
  return keys((Map) map);
}


  static <A> Set<A> keys(MultiSet<A> ms) {
    return ms.keySet();
  }







static <A, B> Collection<B> values(Map<A, B> map) {
  return map == null ? emptyList() : map.values();
}




static String joinWithSpace(Collection<String> c) {
  return join(" ", c);
}

static String joinWithSpace(String... c) {
  return join(" ", c);
}

static String fsI(String id) {
  return formatSnippetID(id);
}

static String fsI(long id) {
  return formatSnippetID(id);
}
static HashSet<String> allClasses_keywords = lithashset("class", "interface", "enum", "sclass", "sinterface", "record", "srecord", "asclass");

// lists returned are actual CNC (N/C/N/.../C/N) - and connected to
// original list
// only returns the top level classes
static List<List<String>> allClasses(List<String> tok) {
  List<List<String>> l = new ArrayList();
  int n = tok.size();
  HashSet<String> _allClasses_keywords = allClasses_keywords;
  for (int i = 1; i < n; i += 2) {
    String t = tok.get(i);
    if ("{".equals(t)) // skip functions
      i = findEndOfBlock(tok, i)-1;
    else if (_allClasses_keywords.contains(t)
      && (tok_isJavaxMetaCommandLeftOf(tok, i) ||
        !eqGetOneOf(tok, i-2, ".", "include"))) {
      int j = i;
      while (j < n && !tok.get(j).equals("{"))
        j += 2;
      j = findEndOfBlock(tok, j)+1;
      i = leftScanModifiers(tok, i);
      l.add(tok.subList(i-1, Math.min(n, j)));
      i = j-2;
    }
  }
  return l;
}

static List<List<String>> allClasses(String text) {
  return allClasses(javaTok(text));
}
// i = index of "implements"/"extends"
static int tok_endOfImplementsList(List<String> tok, int i) {
  int level = 0;
  while (i < l(tok)) {
    String t = tok.get(i);
    if (eq(t, "<")) ++level;
    else if (eq(t, ">")) {
      if (level == 0) return i; else --level;
    } else if (eq(t, "{")) return i;
    i += 2;
  }
  return i;
}

static List<Integer> jfindAll(List<String> tok, String pat) {
  return jfindAll(tok, jfind_preprocess(javaTok(pat)));
}

// tokPat must be jfind_preprocess'd
static List<Integer> jfindAll(List<String> tok, List<String> tokPat) {
  String[] toks = toStringArray(codeTokensOnly(tokPat));
  
  int i = -1;
  List<Integer> l = new ArrayList();
  while ((i = findCodeTokens(tok, i+1, false, toks)) >= 0)
    l.add(i);
  return l;
}
static boolean contains(Collection c, Object o) {
  return c != null && c.contains(o);
}

static boolean contains(Object[] x, Object o) {
  if (x != null)
    for (Object a : x)
      if (eq(a, o))
        return true;
  return false;
}

static boolean contains(String s, char c) {
  return s != null && s.indexOf(c) >= 0;
}

static boolean contains(String s, String b) {
  return s != null && s.indexOf(b) >= 0;
}

static boolean contains(BitSet bs, int i) {
  return bs != null && bs.get(i);
}
static <A> A last(List<A> l) {
  return empty(l) ? null : l.get(l.size()-1);
}

static char last(String s) {
  return empty(s) ? '#' : s.charAt(l(s)-1);
}

static int last(int[] a) {
  return l(a) != 0 ? a[l(a)-1] : 0;
}

static <A> A last(A[] a) {
  return l(a) != 0 ? a[l(a)-1] : null;
}

static <A> A last(Iterator<A> it) {
  A a = null;
  while  (it.hasNext()) { ping(); a = it.next(); }
  return a;
}
static int jfindOneOf_cond(List<String> tok, Object condition, String... patterns) {
  for (String in : patterns) {
    int i = jfind(tok, in, condition);
    if (i >= 0) return i;
  }
  return -1;
}
static String joinQuoted(String sep, Collection<String> c) {
  List<String> l = new ArrayList();
  for (String s : c) l.add(quote(s));
  return join(sep, l);
}
static int rjfind(List<String> tok, String in) {
  return rjfind(tok, 1, in);
}

static int rjfind(List<String> tok, int startIdx, String in) {
  return rjfind(tok, startIdx, in, null);
}

static int rjfind(List<String> tok, int startIdx, int endIndex, String in) {
  return rjfind(tok, startIdx, endIndex, in, null);
}

static int rjfind(List<String> tok, String in, Object condition) {
  return rjfind(tok, 1, in, condition);
}

static int rjfind(List<String> tok, int startIdx, String in, Object condition) {
  List<String> tokin = javaTok(in);
  jfind_preprocess(tokin);
  return rjfind(tok, startIdx, tokin, condition);
}

static int rjfind(List<String> tok, int startIdx, int endIndex, String in, Object condition) {
  List<String> tokin = javaTok(in);
  jfind_preprocess(tokin);
  return rjfind(tok, startIdx, endIndex, tokin, condition);
}

// assumes you preprocessed tokin
static int rjfind(List<String> tok, List<String> tokin) {
  return rjfind(tok, 1, tokin);
}

static int rjfind(List<String> tok, int startIdx, List<String> tokin) {
  return rjfind(tok, startIdx, tokin, null);
}

static int rjfind(List<String> tok, int startIdx, List<String> tokin, Object condition) {
  return rfindCodeTokens(tok, startIdx, false, toStringArray(codeTokensOnly(tokin)), condition);
}

static int rjfind(List<String> tok, int startIdx, int endIndex, List<String> tokin, Object condition) {
  return rfindCodeTokens(tok, startIdx, endIndex, false, toStringArray(codeTokensOnly(tokin)), condition);
}

static void clearTokens_reTok(List<String> tok, int i, int j) {
  clearTokens(tok, i, j);
  reTok(tok, i, j);
}
// returns l(s) if not found
static int smartIndexOf(String s, String sub, int i) {
  if (s == null) return 0;
  i = s.indexOf(sub, min(i, l(s)));
  return i >= 0 ? i : l(s);
}

static int smartIndexOf(String s, int i, char c) {
  return smartIndexOf(s, c, i);
}

static int smartIndexOf(String s, char c, int i) {
  if (s == null) return 0;
  i = s.indexOf(c, min(i, l(s)));
  return i >= 0 ? i : l(s);
}

static int smartIndexOf(String s, String sub) {
  return smartIndexOf(s, sub, 0);
}

static int smartIndexOf(String s, char c) {
  return smartIndexOf(s, c, 0);
}

static <A> int smartIndexOf(List<A> l, A sub) {
  return smartIndexOf(l, sub, 0);
}

static <A> int smartIndexOf(List<A> l, int start, A sub) {
  return smartIndexOf(l, sub, start);
}

static <A> int smartIndexOf(List<A> l, A sub, int start) {
  int i = indexOf(l, sub, start);
  return i < 0 ? l(l) : i;
}
static void tok_addFieldOrder(List<String> tok, int i) {
  if (!contains(tok_modifiersLeftOf(tok, i), "static")) return; // no static fields allowed in non-static classes (for whatever reason)
  int idx = findCodeTokens(tok, i, false, "{");
  if (idx < 0) return;
  int j = findEndOfBracketPart(tok, idx);
  List<String> vars = allVarNames(subList(tok, idx+1, j-1));
  //print("addFieldOrder " + struct(vars));
  if (nempty(vars) && !vars.contains("_fieldOrder")
    && !isSortedList(vars)) {
    print("Adding field order");
    tok.set(idx+2, "static String _fieldOrder = " + quote(join(" ", vars)) + ";\n  " + tok.get(idx+2));
    // reTok has to be done by caller
  }
}
static <A> int smartIndexOfOneOf(List<A> l, int i, A... x) {
  return smartIndexOfAny(l, i, x);
}

static <A> int smartIndexOfOneOf(List<A> l, A... x) {
  return smartIndexOfAny(l, x);
}
static void vmKeepWithProgramMD5_save(String varName) {
  if (vmExiting()) return;
  String struct = struct(getOpt(mc(), varName));
  callOpt(javax(), "vmKeep_store", programID(), md5OfMyJavaSource(), varName, struct(getOpt(mc(), varName)));
}
static void clearTokensAndReTok(List<String> tok, int i, int j) {
  clearTokens_reTok(tok, i, j);
}
static IntRange combineIntRanges(IntRange a, IntRange b) {
  if (a == null) return b;
  if (b == null) return a;
  return intRange(min(a.start, b.start), max(a.end, b.end));
}
static IntRange intRange(int start, int end) {
  return new IntRange(start, end);
}
//sbool ping_actions_shareable = true;
static volatile boolean ping_pauseAll = false;
static int ping_sleep = 100; // poll pauseAll flag every 100

static volatile boolean ping_anyActions = false;
static Map<Thread, Object> ping_actions = newWeakHashMap();
static ThreadLocal<Boolean> ping_isCleanUpThread = new ThreadLocal();


// always returns true
static boolean ping() {
  if (ping_pauseAll  || ping_anyActions ) ping_impl(true /* XXX */);
  //ifndef LeanMode ping_impl(); endifndef
  return true;
}

// returns true when it slept
static boolean ping_impl(boolean okInCleanUp) { try {
  if (ping_pauseAll && !isAWTThread()) {
    do
      Thread.sleep(ping_sleep);
    while (ping_pauseAll);
    return true;
  }
  
  
  if (ping_anyActions) { // don't allow sharing ping_actions
    if (!okInCleanUp && !isTrue(ping_isCleanUpThread.get()))
      failIfUnlicensed();
    Object action = null;
    synchronized(ping_actions) {
      if (!ping_actions.isEmpty()) {
        action = ping_actions.get(currentThread());
        if (action instanceof Runnable)
          ping_actions.remove(currentThread());
        if (ping_actions.isEmpty()) ping_anyActions = false;
      }
    }
    
    if (action instanceof Runnable)
      ((Runnable) action).run();
    else if (eq(action, "cancelled"))
      throw fail("Thread cancelled.");
  }
  
  
  return false;
} catch (Exception __e) { throw rethrow(__e); } }
static Object call(Object o) {
  return callF(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) {
  //ret call_cached(o, method, args);
  return call_withVarargs(o, method, args);
}
static TreeMap<String,Class> hotwireCached_cache = new TreeMap();
static Lock hotwireCached_lock = lock();

static Class hotwireCached(String programID) {
  return hotwireCached(programID, true);
}

static Class hotwireCached(String programID, boolean runMain) {
  return hotwireCached(programID, runMain, false);
}

static Class hotwireCached(String programID, boolean runMain, boolean dependent) {
  Lock __499 = hotwireCached_lock; lock(__499); try {
  
  programID = formatSnippetID(programID);
  Class c = hotwireCached_cache.get(programID);
  if (c == null) {
    c = hotwire(programID);
    if (dependent)
      makeDependent(c);
    if (runMain)
      callMain(c);
    hotwireCached_cache.put(programID, c);
  }
  return c;
} finally { unlock(__499); } }
static String standardFunctionSnippet(String sfName) {
  return lookupStandardFunction(sfName);
}
static String assertIdentifier(String s) {
  return assertIsIdentifier(s);
}

static String assertIdentifier(String msg, String s) {
  return assertIsIdentifier(msg, s);
}
static void tok_runMetaTransformer(List<String> tok, String transformer) {
  print("META-TRANSFORM " + transformer);
  call(hotwireCached(standardFunctionSnippet(assertIdentifier(transformer))), transformer, tok);
}
static <A> IndexedList2<A> indexedList2(List<A> l) {
  return l instanceof IndexedList2 ? ((IndexedList2) l) : new IndexedList2(l);
}
static TokenIndexedList3 tokenIndexedList3(List<String> l) {
  return l instanceof TokenIndexedList3 ? ((TokenIndexedList3) l) : new TokenIndexedList3(l);
}
static void tok_standardBot1(List<String> tok) {
  int i;
  while ((i = jfind(tok, "standardBot1 <id> {")) >= 0) {
    print("Processing standardBot1");
    int iOpening = indexOf(tok, i, "{");
    String name = tok.get(iOpening-2);
    int iClosing = findEndOfBracketPart(tok, iOpening)-1;
    List<String> contents = subList(tok, iOpening+1, iClosing);
    
    String allServers = "";
    int j = jfind(contents, "allServers {");
    if (j >= 0) {
      int k = findEndOfBracketPart(contents, j+2);
      allServers = joinSubList(contents, j+4, k-2);
      clearTokens(contents, j, k);
    }

    replaceTokens_reTok(tok, i, iClosing+1,
      " cmodule " + name + " extends DynTalkBot2<" + name + ".ByServer> {\r\n        void init {\r\n          super.init();\r\n          makeByServer = () -> new ByServer;\r\n          useAGIBlueForDropPunctuation = false;\r\n          preprocessAtSelfToMyName = false;\r\n        }\r\n        \r\n        " + allServers + "\r\n\r\n        class ByServer extends DynTalkBot2.ByServer { "
        + join(contents)
        + "} }");
  }
}
static void tok_processSimplified(List<String> tok) {  
  replaceKeywordBlock(tok, "processSimplified", "\r\n    S processSimplifiedLine(S s, O... _) {\r\n      try answer super.processSimplifiedLine(s, _);\r\n      new Matches m;\r\n    ",
    " null;\r\n       } ");
}
static void tok_compactModules(List<String> tok) {
  if (jreplace_multi(tok, ll("cmodule", "compact module"), "module")) {
    print("compact modules");
    includeInMainLoaded(tok, "!include once #1020738\n");
  }
}



static Throwable printStackTrace2(Throwable e) {
  // we go to system.out now - system.err is nonsense
  print(getStackTrace2(e));
  return e;
}

static void printStackTrace2() {
  printStackTrace2(new Throwable());
}

static void printStackTrace2(String msg) {
  printStackTrace2(new Throwable(msg));
}


 // JavaParser







static CompilationUnit javaParseCompilationUnit(String java) {
  JavaParser.getStaticConfiguration().setAttributeComments(false);
  return JavaParser.parse(java);
}
static boolean isNormalQuoted_dirty(String s) {
  return startsWith(s, "\"") && endsWith(s, "\"") && l(s) >= 2;
}
static boolean isMultilineQuoted(String s) {
  if (!startsWith(s, "[")) return false;
  int i = 1;
  while (i < s.length() && s.charAt(i) == '=') ++i;
  return i < s.length() && s.charAt(i) == '[';
}
static boolean isJavaIdentifier(String s) {
  if (empty(s) || !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 asString(Object o) {
  return o == null ? null : o.toString();
}
static boolean endsWithLetterOrDigit(String s) {
  return s != null && s.length() > 0 && Character.isLetterOrDigit(s.charAt(s.length()-1));
}
static void ping_okInCleanUp() {
  if (ping_pauseAll  || ping_anyActions )
    ping_impl(true);
}
static ThreadLocal<Object> print_byThread_dontCreate() {
  return print_byThread;
}
static boolean isFalse(Object o) {
  return eq(false, o);
}
static String fixNewLines(String s) {
  int i = indexOf(s, '\r');
  if (i < 0) return s;
  int l = s.length();
  StringBuilder out = new StringBuilder(l);
  out.append(s, 0, i);
  for (; i < l; i++) {
    char c = s.charAt(i);
    if (c != '\r')
      out.append(c);
    else {
      out.append('\n');
      if (i+1 < l && s.charAt(i+1) == '\n') ++i;
    }
  }
  return out.toString();
}
static void print_append(Appendable buf, String s, int max) { try {
  synchronized(buf) {
    buf.append(s);
    if (buf instanceof StringBuffer)
      rotateStringBuffer(((StringBuffer) buf), max);
    else if (buf instanceof StringBuilder)
      rotateStringBuilder(((StringBuilder) buf), max);
  }
} catch (Exception __e) { throw rethrow(__e); } }
static boolean checkCondition(Object condition, Object... args) {
  return isTrue(callF(condition, args));
}

static <A> boolean checkCondition(IF1<A, Boolean> condition, A arg) {
  return isTrue(callF(condition, arg));
}
static TreeSet<String> caseInsensitiveSet() {
  return caseInsensitiveSet_treeSet();
}

static TreeSet<String> caseInsensitiveSet(Collection<String> c) {
  return caseInsensitiveSet_treeSet(c);
}
static Object callOpt(Object o) {
  return callF(o);
}

static <A> A callOpt(Object o, String method, Object... args) {
  return (A) callOpt_withVarargs(o, method, args);
}
static AutoInitVar < String > md5OfMyJavaSource_cache = new AutoInitVar(new F0<String>() { String get() { try {  return md5(myJavaSource());  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "ret md5(myJavaSource());"; }});

static String md5OfMyJavaSource() {
  return md5OfMyJavaSource_cache.get();
}
static Field setOpt_findField(Class c, String field) {
  HashMap<String, Field> map;
  synchronized(getOpt_cache) {
    map = getOpt_cache.get(c);
    if (map == null)
      map = getOpt_makeCache(c);
  }
  return map.get(field);
}

static void setOpt(Object o, String field, Object value) { try {
  if (o == null) return;
  
  
  
  Class c = o.getClass();
  HashMap<String, Field> map;
  
  if (getOpt_cache == null)
    map = getOpt_makeCache(c); // in class init
  else synchronized(getOpt_cache) {
    map = getOpt_cache.get(c);
    if (map == null)
      map = getOpt_makeCache(c);
  }
  
  if (map == getOpt_special) {
    if (o instanceof Class) {
      setOpt((Class) o, field, value);
      return;
    }
    
    // It's probably a subclass of Map. Use raw method
    setOpt_raw(o, field, value);
    return;
  }
  
  Field f = map.get(field);
  
  if (f != null)
    { smartSet(f, o, value); return; } // possible improvement: skip setAccessible
  
    if (o instanceof DynamicObject)
      setDyn(((DynamicObject) o), field, value);
  
} catch (Exception __e) { throw rethrow(__e); } }

static void setOpt(Class c, String field, Object value) {
  if (c == null) return;
  try {
    Field f = setOpt_findStaticField(c, field); // TODO: optimize
    if (f != null)
      smartSet(f, null, value);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
  
static Field setOpt_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0) {
        makeAccessible(f);
        return f;
      }
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}
static Class mc() {
  return main.class;
}
static Object unstructure(String text) {
  return unstructure(text, false);
}

static Object unstructure(String text, final boolean allDynamic) {
  return unstructure(text, allDynamic, null);
}

static int structure_internStringsLongerThan = 50;
static int unstructure_unquoteBufSize = 100;

static int unstructure_tokrefs; // stats

abstract static class unstructure_Receiver {
  abstract void set(Object o);
}

// classFinder: func(name) -> class (optional)
static Object unstructure(String text, boolean allDynamic,
  Object classFinder) {
  if (text == null) return null;
  return unstructure_tok(javaTokC_noMLS_iterator(text), allDynamic, classFinder);
}

static Object unstructure_reader(BufferedReader reader) {
  return unstructure_tok(javaTokC_noMLS_onReader(reader), false, null);
}

static Object unstructure_tok(final Producer<String> tok, final boolean allDynamic, final Object _classFinder) {
  final boolean debug = unstructure_debug;
  
  final class X {
    int i = -1;
    final Object classFinder = _classFinder != null ? _classFinder : _defaultClassFinder();
    HashMap<Integer,Object> refs = new HashMap();
    HashMap<Integer,Object> tokrefs = new HashMap();
    HashSet<String> concepts = new HashSet();
    HashMap<String,Class> classesMap = new HashMap();
    List<Runnable> stack = new ArrayList();
    String curT;
    char[] unquoteBuf = new char[unstructure_unquoteBufSize];
    
    String unquote(String s) {
      return unquoteUsingCharArray(s, unquoteBuf); 
    }

    // look at current token
    String t() {
      return curT;
    }
    
    // get current token, move to next
    String tpp() {
      String t = curT;
      consume();
      return t;
    }
    
    void parse(final unstructure_Receiver out) {
      String t = t();
      
      int refID = 0;
      if (structure_isMarker(t, 0, l(t))) {
        refID = parseInt(t.substring(1));
        consume();
      }
      final int _refID = refID;
      
      // if (debug) print("parse: " + quote(t));
      
      final int tokIndex = i;  
      parse_inner(refID, tokIndex, new unstructure_Receiver() {
        void set(Object o) {
          if (_refID != 0)
            refs.put(_refID, o);
          if (o != null)
            tokrefs.put(tokIndex, o);
          out.set(o);
        }
      });
    }
    
    void parse_inner(int refID, int tokIndex, final unstructure_Receiver out) {
      String t = t();
      
      // if (debug) print("parse_inner: " + quote(t));
      
      Class c = classesMap.get(t);
      if (c == null) {
        if (t.startsWith("\"")) {
          String s = internIfLongerThan(unquote(tpp()), structure_internStringsLongerThan);
          out.set(s); return;
        }
        
        if (t.startsWith("'")) {
          out.set(unquoteCharacter(tpp())); return;
        }
        if (t.equals("bigint")) {
          out.set(parseBigInt()); return;
        }
        if (t.equals("d")) {
          out.set(parseDouble()); return;
        }
        if (t.equals("fl")) {
          out.set(parseFloat()); return;
        }
        if (t.equals("sh")) {
          consume();
          t = tpp();
          if (t.equals("-")) {
            t = tpp();
            out.set((short) (-parseInt(t))); return;
          }
          out.set((short) parseInt(t)); return;
        }
        if (t.equals("-")) {
          consume();
          t = tpp();
          out.set(isLongConstant(t) ? (Object) (-parseLong(t)) : (Object) (-parseInt(t))); return;
        }
        if (isInteger(t) || isLongConstant(t)) {
          consume();
          //if (debug) print("isLongConstant " + quote(t) + " => " + isLongConstant(t));
          if (isLongConstant(t)) {
            out.set(parseLong(t)); return;
          }
          long l = parseLong(t);
          boolean isInt = l == (int) l;
          
          out.set(isInt ? (Object) Integer.valueOf((int) l) : (Object) Long.valueOf(l)); return;
        }
        if (t.equals("false") || t.equals("f")) {
          consume(); out.set(false); return;
        }
        if (t.equals("true") || t.equals("t")) {
          consume(); out.set(true); return;
        }
        if (t.equals("-")) {
          consume();
          t = tpp();
          out.set(isLongConstant(t) ? (Object) (-parseLong(t)) : (Object) (-parseInt(t))); return;
        }
        if (isInteger(t) || isLongConstant(t)) {
          consume();
          //if (debug) print("isLongConstant " + quote(t) + " => " + isLongConstant(t));
          if (isLongConstant(t)) {
            out.set(parseLong(t)); return;
          }
          long l = parseLong(t);
          boolean isInt = l == (int) l;
          
          out.set(isInt ? (Object) Integer.valueOf((int) l) : (Object) Long.valueOf(l)); return;
        }
        
        if (t.equals("File")) {
          consume();
          File f = new File(unquote(tpp()));
          out.set(f); return;
        }
        
        if (t.startsWith("r") && isInteger(t.substring(1))) {
          consume();
          int ref = Integer.parseInt(t.substring(1));
          Object o = refs.get(ref);
          if (o == null)
            throw fail("unsatisfied back reference " + ref);
          out.set(o); return;
        }
      
        if (t.startsWith("t") && isInteger(t.substring(1))) {
          consume();
          int ref = Integer.parseInt(t.substring(1));
          Object o = tokrefs.get(ref);
          if (o == null)
            throw fail("unsatisfied token reference " + ref + " at " + tokIndex);
          out.set(o); return;
        }
        
        if (t.equals("hashset")) { parseHashSet(out); return; }
        if (t.equals("lhs")) { parseLinkedHashSet(out); return; }
        if (t.equals("treeset")) { parseTreeSet(out); return; }
        if (t.equals("ciset")) { parseCISet(out); return; }
        
        if (eqOneOf(t, "hashmap", "hm")) {
          consume();
          parseMap(new HashMap(), out);
          return;
        }
        if (t.equals("lhm")) {
          consume();
          parseMap(new LinkedHashMap(), out);
          return;
        }
        if (t.equals("tm")) {
          consume();
          parseMap(new TreeMap(), out);
          return;
        }
        if (t.equals("cimap")) {
          consume();
          parseMap(ciMap(), out);
          return;
        }
        
        if (t.equals("ll")) {
          consume();
          { parseList(new LinkedList(), out); return; }
        }

        if (t.equals("syncLL")) { // legacy
          consume();
          { parseList(synchroLinkedList(), out); return; }
        }

        if (t.equals("sync")) {
          consume();
          { parse(new unstructure_Receiver() {
            void set(Object value) {
              if (value instanceof Map) {
                 // Java 7
                if (value instanceof NavigableMap)
                  { out.set(Collections.synchronizedNavigableMap((NavigableMap) value)); return; }
                
                if (value instanceof SortedMap)
                  { out.set(Collections.synchronizedSortedMap((SortedMap) value)); return; }
                { out.set(Collections.synchronizedMap((Map) value)); return; }
              } else
                { out.set(Collections.synchronizedList((List) value)); return; }
            }
          }); return; }
        }
        
        if (t.equals("{")) {
          parseMap(out); return;
        }
        if (t.equals("[")) {
          this.parseList(new ArrayList(), out); return;
        }
        if (t.equals("bitset")) {
          parseBitSet(out); return;
        }
        if (t.equals("array") || t.equals("intarray")) {
          parseArray(out); return;
        }
        if (t.equals("ba")) {
          consume();
          String hex = unquote(tpp());
          out.set(hexToBytes(hex)); return;
        }
        if (t.equals("boolarray")) {
          consume();
          int n = parseInt(tpp());
          String hex = unquote(tpp());
          out.set(boolArrayFromBytes(hexToBytes(hex), n)); return;
        }
        if (t.equals("class")) {
          out.set(parseClass()); return;
        }
        if (t.equals("l")) {
          parseLisp(out); return;
        }
        if (t.equals("null")) {
          consume(); out.set(null); return;
        }
        
        if (eq(t, "c")) {
          consume();
          t = t();
          assertTrue(isJavaIdentifier(t));
          concepts.add(t);
        }
        
        // custom deserialization (new static method method)
        if (eq(t, "cu")) {
          consume();
          t = tpp();
          assertTrue(isJavaIdentifier(t));
          String fullClassName = "main$" + t;
          Class _c = classFinder != null ? (Class) callF(classFinder, fullClassName) : findClass_fullName(fullClassName);
          if (_c == null) throw fail("Class not found: " + fullClassName);
          parse(new unstructure_Receiver() {
            void set(Object value) {
              
              out.set(call(_c, "_deserialize", value));
            }
          });
          return;
        }
      }
      
      if (eq(t, "j")) {
        consume("j");
        out.set(parseJava()); return;
      }

      if (c == null && !isJavaIdentifier(t))
        throw new RuntimeException("Unknown token " + (i+1) + ": " + t);
        
      // any other class name (or package name)
      consume();
      String className, fullClassName;
      
      // Is it a package name?
      if (eq(t(), ".")) {
        consume();
        className = fullClassName = t + "." + assertIdentifier(tpp());
      } else {
        className = t;
        fullClassName = "main$" + t;
      }
      
      if (c == null) {
        // First, find class
        if (allDynamic) c = null;
        else c = classFinder != null ? (Class) callF(classFinder, fullClassName) : findClass_fullName(fullClassName);
        if (c != null)
          classesMap.put(className, c);
      }
          
      // Check if it has an outer reference
      boolean hasBracket = eq(t(), "(");
      if (hasBracket) consume();
      boolean hasOuter = hasBracket && eq(t(), "this$1");
      
      DynamicObject dO = null;
      Object o = null;
      final String thingName = t;
      if (c != null) {
        o = hasOuter ? nuStubInnerObject(c, classFinder) : nuEmptyObject(c);
        if (o instanceof DynamicObject) dO = (DynamicObject) o;
      } else {
        if (concepts.contains(t) && (c = findClass("Concept")) != null)
          o = dO = (DynamicObject) nuEmptyObject(c);
        else
          dO = new DynamicObject();
        dO.className = className;
        
      }
      
      // Save in references list early because contents of object
      // might link back to main object
      
      if (refID != 0)
        refs.put(refID, o != null ? o : dO);
      tokrefs.put(tokIndex, o != null ? o : dO);
      
      // NOW parse the fields!
      
      final LinkedHashMap<String,Object> fields = new LinkedHashMap(); // preserve order
      final Object _o = o;
      final DynamicObject _dO = dO;
      if (hasBracket) {
        stack.add(new Runnable() {  public void run() { try { 
          
          if (eq(t(), ",")) consume();
          if (eq(t(), ")")) {
            consume(")");
            objRead(_o, _dO, fields, hasOuter);
            out.set(_o != null ? _o : _dO);
          } else {
            final String key = unquote(tpp());
            String t = tpp();
            if (!eq(t, "="))
              throw fail("= expected, got " + t + " after " + quote(key) + " in object " + thingName /*+ " " + sfu(fields)*/);
            stack.add(this);
            parse(new unstructure_Receiver() {
              void set(Object value) {
                fields.put(key, value);
                /*ifdef unstructure_debug
                  print("Got field value " + value + ", next token: " + t());
                endifdef*/
                //if (eq(t(), ",")) consume();
              }
            });
          }
        
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "ifdef unstructure_debug\r\n            print(\"in object values, token: \" + t())..."; }});
      } else {
        objRead(o, dO, fields, hasOuter);
        out.set(o != null ? o : dO);
      }
    }
    
    void objRead(Object o, DynamicObject dO, Map<String, Object> fields, boolean hasOuter) {
      
      if (o != null) {
        if (dO != null) {
          
          setOptAllDyn(dO, fields);
        } else {
          setOptAll_pcall(o, fields);
          
        }
        if (hasOuter)
          fixOuterRefs(o);
      } else for (Map.Entry<String, Object> e : fields.entrySet())
        setDynObjectValue(dO, intern(e.getKey()), e.getValue());

      if (o != null)
        pcallOpt_noArgs(o, "_doneLoading");
    }
    
    void parseSet(final Set set, final unstructure_Receiver out) {
      this.parseList(new ArrayList(), new unstructure_Receiver() {
        void set(Object o) {
          set.addAll((List) o);
          out.set(set);
        }
      });
    }
    
    void parseLisp(final unstructure_Receiver out) {
      
      
      throw fail("class Lisp not included");
    }
    
    void parseBitSet(final unstructure_Receiver out) {
      consume("bitset");
      consume("{");
      final BitSet bs = new BitSet();
      stack.add(new Runnable() {  public void run() { try { 
        if (eq(t(), "}")) {
          consume("}");
          out.set(bs);
        } else {
          stack.add(this);
          parse(new unstructure_Receiver() {
            void set(Object o) {
              bs.set((Integer) o);
              if (eq(t(), ",")) consume();
            }
          });
        }
      
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (eq(t(), \"}\")) {\r\n          consume(\"}\");\r\n          out.set(bs);\r\n       ..."; }});
    }
    
    void parseList(final List list, final unstructure_Receiver out) {
      consume("[");
      stack.add(new Runnable() {  public void run() { try { 
        if (eq(t(), "]")) {
          consume();
          
          out.set(list);
        } else {
          stack.add(this);
          parse(new unstructure_Receiver() {
            void set(Object o) {
              //if (debug) print("List element type: " + getClassName(o));
              list.add(o);
              if (eq(t(), ",")) consume();
            }
          });
        }
      
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (eq(t(), \"]\")) {\r\n          consume();\r\n          ifdef unstructure_debug\r..."; }});
    }
    
    void parseArray(final unstructure_Receiver out) {
      final String type = tpp();
      consume("{");
      final List list = new ArrayList();
      
      stack.add(new Runnable() {  public void run() { try { 
        if (eq(t(), "}")) {
          consume("}");
          out.set(type.equals("intarray") ? toIntArray(list) : list.toArray());
        } else {
          stack.add(this);
          parse(new unstructure_Receiver() {
            void set(Object o) {
              list.add(o);
              if (eq(t(), ",")) consume();
            }
          });
        }
      
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (eq(t(), \"}\")) {\r\n          consume(\"}\");\r\n          out.set(type.equals(\"..."; }});
    }
    
    Object parseClass() {
      consume("class");
      consume("(");
      String name = unquote(tpp());
      consume(")");
      Class c = allDynamic ? null : classFinder != null ? (Class) callF(classFinder, name) : findClass_fullName(name);
      if (c != null) return c;
      DynamicObject dO = new DynamicObject();
      dO.className = "java.lang.Class";
      name = dropPrefix("main$", name);
      dO.fieldValues.put("name", name);
      return dO;
    }
    
    Object parseBigInt() {
      consume("bigint");
      consume("(");
      String val = tpp();
      if (eq(val, "-"))
        val = "-" + tpp();
      consume(")");
      return new BigInteger(val);
    }
    
    Object parseDouble() {
      consume("d");
      consume("(");
      String val = unquote(tpp());
      consume(")");
      return Double.parseDouble(val);
    }
    
    Object parseFloat() {
      consume("fl");
      String val;
      if (eq(t(), "(")) {
        consume("(");
        val = unquote(tpp());
        consume(")");
      } else {
        val = unquote(tpp());
      }
      return Float.parseFloat(val);
    }
    
    void parseHashSet(unstructure_Receiver out) {
      consume("hashset");
      parseSet(new HashSet(), out);
    }
    
    void parseLinkedHashSet(unstructure_Receiver out) {
      consume("lhs");
      parseSet(new LinkedHashSet(), out);
    }
    
    void parseTreeSet(unstructure_Receiver out) {
      consume("treeset");
      parseSet(new TreeSet(), out);
    }
    
    void parseCISet(unstructure_Receiver out) {
      consume("ciset");
      parseSet(ciSet(), out);
    }
    
    void parseMap(unstructure_Receiver out) {
      parseMap(new TreeMap(), out);
    }
    
    Object parseJava() {
      String j = unquote(tpp());
      Matches m = new Matches();
      if (jmatch("java.awt.Color[r=*,g=*,b=*]", j, m))
        return nuObject("java.awt.Color", parseInt(m.unq(0)), parseInt(m.unq(1)), parseInt(m.unq(2)));
      else {
        warn("Unknown Java object: " + j);
        return null;
      }
    }
    
    void parseMap(final Map map, final unstructure_Receiver out) {
      consume("{");
      stack.add(new Runnable() {
        boolean v = false;
        Object key;
        
        public void run() { 
          if (v) {
            v = false;
            stack.add(this);
            if (!eq(tpp(), "="))
              throw fail("= expected, got " + t() + " in map of size " + l(map));

            parse(new unstructure_Receiver() {
              void set(Object value) {
                map.put(key, value);
                
                if (eq(t(), ",")) consume();
              }
            });
          } else {
            if (eq(t(), "}")) {
              consume("}");
              out.set(map);
            } else {
              v = true;
              stack.add(this);
              parse(new unstructure_Receiver() {
                void set(Object o) {
                  key = o;
                }
              });
            }
          } // if v else
        } // run()
      });
    }
    
    /*void parseSub(unstructure_Receiver out) {
      int n = l(stack);
      parse(out);
      while (l(stack) > n)
        stack
    }*/
    
    void consume() { curT = tok.next(); ++i; }
    
    void consume(String s) {
      if (!eq(t(), s)) {
        /*S prevToken = i-1 >= 0 ? tok.get(i-1) : "";
        S nextTokens = join(tok.subList(i, Math.min(i+2, tok.size())));
        fail(quote(s) + " expected: " + prevToken + " " + nextTokens + " (" + i + "/" + tok.size() + ")");*/
        throw fail(quote(s) + " expected, got " + quote(t()));
      }
      consume();
    }
    
    // outer wrapper function getting first token and unwinding the stack
    void parse_initial(unstructure_Receiver out) {
      consume(); // get first token
      parse(out);
      while (nempty(stack))
        popLast(stack).run();
    }
  }
  
  Boolean b = DynamicObject_loading.get();
  DynamicObject_loading.set(true);
  try {
    final Var v = new Var();
    X x = new X();
    x.parse_initial(new unstructure_Receiver() {
      void set(Object o) { v.set(o); }
    });
    unstructure_tokrefs = x.tokrefs.size();
    return v.get();
  } finally {
    DynamicObject_loading.set(b);
  }
}

static boolean unstructure_debug = false;
static <A> List<A> synchroList() {
  return Collections.synchronizedList(new ArrayList<A>());
}

static <A> List<A> synchroList(List<A> l) {
  return Collections.synchronizedList(l);
}

static PersistableThrowable persistableThrowable(Throwable e) {
  return e == null ? null : new PersistableThrowable(e);
}
static Throwable innerException(Throwable e) {
  return getInnerException(e);
}
//static final Map<Class, HashMap<S, Field>> getOpt_cache = newDangerousWeakHashMap(f getOpt_special_init);

static class getOpt_Map extends WeakHashMap {
  getOpt_Map() {
    if (getOpt_special == null) getOpt_special = new HashMap();
    clear();
  }
  
  public void clear() {
    super.clear();
    //print("getOpt clear");
    put(Class.class, getOpt_special);
    put(String.class, getOpt_special);
  }
}

static final Map<Class, HashMap<String, Field>> getOpt_cache = _registerDangerousWeakMap(synchroMap(new getOpt_Map()));
//static final Map<Class, HashMap<S, Field>> getOpt_cache = _registerWeakMap(synchroMap(new getOpt_Map));
static HashMap getOpt_special; // just a marker

/*static void getOpt_special_init(Map map) {
  map.put(Class.class, getOpt_special);
  map.put(S.class, getOpt_special);
}*/

static Object getOpt_cached(Object o, String field) { try {
  if (o == null) return null;

  Class c = o.getClass();
  HashMap<String, Field> map;
  synchronized(getOpt_cache) {
    map = getOpt_cache.get(c);
    if (map == null)
      map = getOpt_makeCache(c);
  }
  
  if (map == getOpt_special) {
    if (o instanceof Class)
      return getOpt((Class) o, field);
    /*if (o instanceof S)
      ret getOpt(getBot((S) o), field);*/
    if (o instanceof Map)
      return ((Map) o).get(field);
  }
    
  Field f = map.get(field);
  if (f != null) return f.get(o);
  
    if (o instanceof DynamicObject)
      return mapGet2(((DynamicObject) o).fieldValues, field);
  
  return null;
} catch (Exception __e) { throw rethrow(__e); } }

// used internally - we are in synchronized block
static HashMap<String, Field> getOpt_makeCache(Class c) {
  HashMap<String, Field> map;
  if (isSubtypeOf(c, Map.class))
    map = getOpt_special;
  else {
    map = new HashMap();
    if (!reflection_classesNotToScan().contains(c.getName())) {
      Class _c = c;
      do {
        for (Field f : _c.getDeclaredFields()) {
          makeAccessible(f);
          String name = f.getName();
          if (!map.containsKey(name))
            map.put(name, f);
        }
        _c = _c.getSuperclass();
      } while (_c != null);
    }
  }
  if (getOpt_cache != null) getOpt_cache.put(c, map);
  return map;
}
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 Field makeAccessible(Field f) {
  try {
    f.setAccessible(true);
  } catch (Throwable e) {
    // Note: The error reporting only works with Java VM option --illegal-access=deny
    vmBus_send("makeAccessible_error",e, f);
  }
  return f;
}

static Method makeAccessible(Method m) {
  try {
    m.setAccessible(true);
  } catch (Throwable e) {
    vmBus_send("makeAccessible_error",e, m);
  }
  return m;
}

static Constructor makeAccessible(Constructor c) {
  try {
    c.setAccessible(true);
  } catch (Throwable e) {
    vmBus_send("makeAccessible_error",e, c);
  }
  return c;
}
static Class __javax;

static Class getJavaX() { try {
  
  return __javax;
} catch (Exception __e) { throw rethrow(__e); } }
static <A> AutoCloseable tempSetThreadLocal(final ThreadLocal<A> tl, A a) {
  if (tl == null) return null;
  final A prev = setThreadLocal(tl, a);
  return new AutoCloseable() { public String toString() { return "tl.set(prev);"; } public void close() throws Exception { tl.set(prev); }};
}
static RuntimeException asRuntimeException(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}
static <A, B> Map<A, B> newDangerousWeakHashMap() {
  return _registerDangerousWeakMap(synchroMap(new WeakHashMap()));
}

// initFunction: voidfunc(Map) - is called initially, and after clearing the map
static <A, B> Map<A, B> newDangerousWeakHashMap(Object initFunction) {
  return _registerDangerousWeakMap(synchroMap(new WeakHashMap()), initFunction);
}
static HashMap<String, List<Method>> callMC_cache = new HashMap();
static String callMC_key;
static Method callMC_value;

// varargs assignment fixer for a single string array argument
static Object callMC(String method, String[] arg) {
  return callMC(method, new Object[] {arg});
}

static Object callMC(String method, Object... args) { try {
  Method me;
  if (callMC_cache == null) callMC_cache = new HashMap(); // initializer time workaround
  synchronized(callMC_cache) {
    me = method == callMC_key ? callMC_value : null;
  }
  if (me != null) try {
    return invokeMethod(me, null, args);
  } catch (IllegalArgumentException e) {
    throw new RuntimeException("Can't call " + me + " with arguments " + classNames(args), e);
  }

  List<Method> m;
  synchronized(callMC_cache) {
    m = callMC_cache.get(method);
  }
  if (m == null) {
    if (callMC_cache.isEmpty()) {
      callMC_makeCache();
      m = callMC_cache.get(method);
    }
    if (m == null) throw fail("Method named " + method + " not found in main");
  }
  int n = m.size();
  if (n == 1) {
    me = m.get(0);
    synchronized(callMC_cache) {
      callMC_key = method;
      callMC_value = me;
    }
    try {
      return invokeMethod(me, null, args);
    } catch (IllegalArgumentException e) {
      throw new RuntimeException("Can't call " + me + " with arguments " + classNames(args), e);
    }
  }
  for (int i = 0; i < n; i++) {
    me = m.get(i);
    if (call_checkArgs(me, args, false))
      return invokeMethod(me, null, args);
  }
  throw fail("No method called " + method + " with arguments (" + joinWithComma(getClasses(args)) + ") found in main");
} catch (Exception __e) { throw rethrow(__e); } }

static void callMC_makeCache() {
  synchronized(callMC_cache) {
    callMC_cache.clear();
    Class _c = (Class) mc(), c = _c;
    while (c != null) {
      for (Method m : c.getDeclaredMethods())
        if ((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0) {
          makeAccessible(m);
          multiMapPut(callMC_cache, m.getName(), m);
        }
      c = c.getSuperclass();
    }
  }
}
static String getClassName(Object o) {
  return o == null ? "null" : o instanceof Class ? ((Class) o).getName() : o.getClass().getName();
}
static Object invokeMethod(Method m, Object o, Object... args) { try {
  try {
    return m.invoke(o, args);
  } catch (InvocationTargetException e) {
    throw rethrow(getExceptionCause(e));
  } catch (IllegalArgumentException e) {
    throw new IllegalArgumentException(e.getMessage() + " - was calling: " + m + ", args: " + joinWithSpace(classNames(args)));
  }
} catch (Exception __e) { throw rethrow(__e); } }
static boolean call_checkArgs(Method m, Object[] args, boolean debug) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length) {
    if (debug)
      print("Bad parameter length: " + args.length + " vs " + types.length);
    return false;
  }
  for (int i = 0; i < types.length; i++) {
    Object arg = args[i];
    if (!(arg == null ? !types[i].isPrimitive()
      : isInstanceX(types[i], arg))) {
      if (debug)
        print("Bad parameter " + i + ": " + arg + " vs " + types[i]);
      return false;
    }
  }
  return true;
}
static String padLeft(String s, char c, int n) {
  return rep(c, n-l(s)) + s;
}

// default to space
static String padLeft(String s, int n) {
  return padLeft(s, ' ', n);
}
  public static boolean isSnippetID(String s) {
    try {
      parseSnippetID(s);
      return true;
    } catch (RuntimeException e) {
      return false;
    }
  }
public static long parseSnippetID(String snippetID) {
  long id = Long.parseLong(shortenSnippetID(snippetID));
  if (id == 0) throw fail("0 is not a snippet ID");
  return id;
}
static String programID;

static String getProgramID() {
  return nempty(programID) ? formatSnippetIDOpt(programID) : "?";
}


// TODO: ask JavaX instead
static String getProgramID(Class c) {
  String id = (String) getOpt(c, "programID");
  if (nempty(id))
    return formatSnippetID(id);
  return "?";
}


static String getProgramID(Object o) {
  return getProgramID(getMainClass(o));
}
static String loadTextFile(String fileName) {
  return loadTextFile(fileName, null);
}

static String loadTextFile(File f, String defaultContents) { try {
  
  checkFileNotTooBigToRead(f);
  
  if (f == null || !f.exists()) return defaultContents;

  FileInputStream fileInputStream = new FileInputStream(f);
  InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");
  return loadTextFile(inputStreamReader);
} catch (Exception __e) { throw rethrow(__e); } }

public static String loadTextFile(File fileName) {
  return loadTextFile(fileName, null);
}

static String loadTextFile(String fileName, String defaultContents) {
  return fileName == null ? defaultContents : loadTextFile(newFile(fileName), defaultContents);
}

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 str(builder);
}
static Object collectionMutex(List l) {
  return l;
}

static Object collectionMutex(Object o) {
  if (o instanceof List) return o;
  
  String c = className(o);
  if (eq(c, "java.util.TreeMap$KeySet"))
    c = className(o = getOpt(o, "m"));
  else if (eq(c, "java.util.HashMap$KeySet"))
    c = className(o = get_raw(o, "this$0"));

  
  
  if (eqOneOf(c, "java.util.TreeMap$AscendingSubMap", "java.util.TreeMap$DescendingSubMap"))
    c = className(o = get_raw(o, "m"));
    
  
    
  return o;
}
static <A> HashSet<A> asHashSet(Collection<A> c) {
  synchronized(collectionMutex(c)) {
    return new HashSet(c);
  }
}

static <A> HashSet<A> asHashSet(A[] a) {
  return a == null ? null : new HashSet(Arrays.asList(a));
}
static List<String> splitAtSpace(String s) {
  return empty(s) ? emptyList() : asList(s.split("\\s+"));
}
static <A> int iteratorCount_int_close(Iterator<A> i) { try {
  int n = 0;
  if (i != null) while (i.hasNext()) { i.next(); ++n; }
  if (i instanceof AutoCloseable) ((AutoCloseable) i).close();
  return n;
} catch (Exception __e) { throw rethrow(__e); } }
static <A> List<A> itemPlusList(A a, Collection<A> l) {
  return concatLists(ll(a), l);
}
static String[] drop(int n, String[] a) {
  n = Math.min(n, a.length);
  String[] b = new String[a.length-n];
  System.arraycopy(a, n, b, 0, b.length);
  return b;
}

static Object[] drop(int n, Object[] a) {
  n = Math.min(n, a.length);
  Object[] b = new Object[a.length-n];
  System.arraycopy(a, n, b, 0, b.length);
  return b;
}
static <A> ArrayList<A> toList(A[] a) { return asList(a); }
static ArrayList<Integer> toList(int[] a) { return asList(a); }
static <A> ArrayList<A> toList(Set<A> s) { return asList(s); }
static <A> ArrayList<A> toList(Iterable<A> s) { return asList(s); }
static String javaTok_substringN(String s, int i, int j) {
  if (i == j) return "";
  if (j == i+1 && s.charAt(i) == ' ') return " ";
  return s.substring(i, j);
}
static String javaTok_substringC(String s, int i, int j) {
  return s.substring(i, j);
}
static List<String> javaTokWithExisting(String s, List<String> existing) {
  ++javaTok_n;
  int nExisting = javaTok_opt && existing != null ? existing.size() : 0;
  ArrayList<String> tok = existing != null ? new ArrayList(nExisting) : new ArrayList();
  int l = s.length();
  
  int i = 0, n = 0;
  while (i < l) {
    int j = i;
    char c, d;
    
    // scan for whitespace
    while (j < l) {
      c = s.charAt(j);
      d = j+1 >= l ? '\0' : s.charAt(j+1);
      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
        ++j;
      else if (c == '/' && d == '*') {
        do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
        j = Math.min(j+2, l);
      } else if (c == '/' && d == '/') {
        do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
      } else
        break;
    }
    
    if (n < nExisting && javaTokWithExisting_isCopyable(existing.get(n), s, i, j))
      tok.add(existing.get(n));
    else
      tok.add(javaTok_substringN(s, i, j));
    ++n;
    i = j;
    if (i >= l) break;
    c = s.charAt(i);
    d = i+1 >= l ? '\0' : s.charAt(i+1);

    // scan for non-whitespace
    
    // Special JavaX syntax: 'identifier
    if (c == '\'' && Character.isJavaIdentifierStart(d) && i+2 < l && "'\\".indexOf(s.charAt(i+2)) < 0) {
      j += 2;
      while (j < l && Character.isJavaIdentifierPart(s.charAt(j)))
        ++j;
    } else if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        if (s.charAt(j) == opener /*|| s.charAt(j) == '\n'*/) { // allow multi-line strings
          ++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 (c == '[' && d == '[') {
      do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]"));
      j = Math.min(j+2, l);
    } else if (c == '[' && d == '=' && i+2 < l && s.charAt(i+2) == '[') {
      do ++j; while (j+2 < l && !s.substring(j, j+3).equals("]=]"));
      j = Math.min(j+3, l);
    } else
      ++j;
      
    if (n < nExisting && javaTokWithExisting_isCopyable(existing.get(n), s, i, j))
      tok.add(existing.get(n));
    else
      tok.add(javaTok_substringC(s, i, j));
    ++n;
    i = j;
  }
  
  if ((tok.size() % 2) == 0) tok.add("");
  javaTok_elements += tok.size();
  return tok;
}

static boolean javaTokWithExisting_isCopyable(String t, String s, int i, int j) {
  return t.length() == j-i
    && s.regionMatches(i, t, 0, j-i); // << could be left out, but that's brave
}
static ArrayList emptyList() {
  return new ArrayList();
  //ret Collections.emptyList();
}

static ArrayList emptyList(int capacity) {
  return new ArrayList(max(0, capacity));
}

// Try to match capacity
static ArrayList emptyList(Iterable l) {
  return l instanceof Collection ? emptyList(((Collection) l).size()) : emptyList();
}

static ArrayList emptyList(Object[] l) {
  return emptyList(l(l));
}

// get correct type at once
static <A> ArrayList<A> emptyList(Class<A> c) {
  return new ArrayList();
}
static Map emptyMap() {
  return new HashMap();
}
static <A, B extends A> void addAll(Collection<A> c, Iterable<B> b) {
  if (c != null && b != null) for (A a : b) c.add(a);
}

static <A, B extends A> boolean addAll(Collection<A> c, Collection<B> b) {
  return c != null && b != null && c.addAll(b);
}

static <A, B extends A> boolean addAll(Collection<A> c, B... b) {
  return c != null && c.addAll(Arrays.asList(b));
}



static <A, B> Map<A, B> addAll(Map<A, B> a, Map<? extends A,? extends B> b) {
  if (a != null) a.putAll(b);
  return a;
}
  static File makeTempDir() {
    return (File) call(getJavaX(), "TempDirMaker_make");
  }
/** writes safely (to temp file, then rename) */
static File saveTextFile(String fileName, String contents) throws IOException {
  CriticalAction action = beginCriticalAction("Saving file " + fileName + " (" + l(contents) + " chars)");
  try {
    File file = new File(fileName);
    mkdirsForFile(file);
    String tempFileName = fileName + "_temp";
    File tempFile = new File(tempFileName);
    if (contents != null) {
      if (tempFile.exists()) try {
        String saveName = tempFileName + ".saved." + now();
        copyFile(tempFile, new File(saveName));
      } catch (Throwable e) { printStackTrace(e); }
      FileOutputStream fileOutputStream = newFileOutputStream(tempFile.getPath());
      OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");
      PrintWriter printWriter = new PrintWriter(outputStreamWriter);
      printWriter.print(contents);
      printWriter.close();
    }
    
    if (file.exists() && !file.delete())
      throw new IOException("Can't delete " + fileName);
  
    if (contents != null)
      if (!tempFile.renameTo(file))
        throw new IOException("Can't rename " + tempFile + " to " + file);
        
    vmBus_send("wroteFile", file);
    return file;
  } finally {
    action.done();
  }
}

static File saveTextFile(File fileName, String contents) { try {
  saveTextFile(fileName.getPath(), contents);
  return fileName;
} catch (Exception __e) { throw rethrow(__e); } }


static Map<String, String[]> javaTokForJFind_array_cache = synchronizedMRUCache(1000);

static String[] javaTokForJFind_array(String s) {
  String[] tok = javaTokForJFind_array_cache.get(s);
  if (tok == null)
    javaTokForJFind_array_cache.put(s, tok = codeTokensAsStringArray(jfind_preprocess(javaTok(s))));
  return tok;
}


// "$1" is first code token, "$2" second code token etc.
static String jreplaceExpandRefs(String s, List<String> tokref) {
  List<String> tok = javaTok(s);
  for (int i = 1; i < l(tok); i += 2) {
    String t = tok.get(i);
    if (t.startsWith("$") && isInteger(t.substring(1))) {
      String x = tokref.get(-1+parseInt(t.substring(1))*2);
      tok.set(i, x);
    } else if (t.equals("\\")) {
      tok.set(i, "");
      i += 2;
    }
  }
  return join(tok);
}

static void _handleError(Error e) {
  call(javax(), "_handleError", e);
}
static void add(BitSet bs, int i) {
  bs.set(i);
}

static <A> boolean add(Collection<A> c, A a) {
  return c != null && c.add(a);
}


static void add(Container c, Component x) {
  addToContainer(c, x);
}

static String[] toStringArray(Collection<String> c) {
  String[] a = new String[l(c)];
  Iterator<String> it = c.iterator();
  for (int i = 0; i < l(a); i++)
    a[i] = it.next();
  return a;
}

static String[] toStringArray(Object o) {
  if (o instanceof String[])
    return (String[]) o;
  else if (o instanceof Collection)
    return toStringArray((Collection<String>) o);
  else
    throw fail("Not a collection or array: " + getClassName(o));
}

static List<String> codeTokensOnly(List<String> tok) {
  int n = tok.size();
  List<String> l = emptyList(n/2);
  for (int i = 1; i < n; i += 2)
    l.add(tok.get(i));
  return l;
}
static String dropTrailingSquareBracketStuff(String s) {
  if (!endsWith(s, "]")) return s;
  return trimSubstring(s, 0, smartLastIndexOf(s, '['));
}
static IResourceLoader vm_getResourceLoader() {
  return proxy(IResourceLoader.class, vm_generalMap_get("_officialResourceLoader"));
}
static boolean isLocalSnippetID(String snippetID) {
  return isSnippetID(snippetID) && isLocalSnippetID(psI(snippetID));
}

static boolean isLocalSnippetID(long snippetID) {
  return snippetID >= 1000 && snippetID <= 9999;
}
static String localSnippetTitle(String snippetID) {
  if (!isLocalSnippetID(snippetID)) return null;
  File f = localSnippetFile(snippetID);
  if (!f.exists()) return null;
  return or2(getFileInfoField(dropExtension(f), "Title"), "Unnamed");
}
static boolean isImageServerSnippet(long id) {
  return id >= 1100000 && id < 1200000;
}
static String imageServerURL() {
  return or2(trim(loadTextFile(javaxDataDir("image-server-url.txt"))), "http://botcompany.de/images/raw/");
}
static String muricaCredentialsQuery() {
  return htmlQuery(muricaCredentials());
}
static boolean isGeneralFileServerSnippet(long id) {
  return id >= 1400000 && id < 1500000;
}
static String tb_mainServer_default = "http://code.botcompany.de:8081";
static Object tb_mainServer_override; // func -> S

static String tb_mainServer() {
  if (tb_mainServer_override != null) return (String) callF(tb_mainServer_override);
  return trim(loadTextFile(tb_mainServer_file(),
    tb_mainServer_default));
}

static File tb_mainServer_file() {
  return getProgramFile("#1001638", "mainserver.txt");
}

static boolean tb_mainServer_isDefault() {
  return eq(tb_mainServer(), tb_mainServer_default);
}
static String standardCredentials_noCookies() {
  return standardCredentials() + "&noCookies=1";
}
static int loadPage_defaultTimeout = 60000;
static ThreadLocal<String> loadPage_charset = new ThreadLocal();
static boolean loadPage_allowGzip = true, loadPage_debug;
static boolean loadPage_anonymous = false; // don't send computer ID
static int loadPage_verboseness = 100000;
static int loadPage_retries = 1; //60; // seconds
static ThreadLocal<Boolean> loadPage_silent = new ThreadLocal();
static volatile int loadPage_forcedTimeout; // ms
static ThreadLocal<Integer> loadPage_forcedTimeout_byThread = new ThreadLocal(); // ms
static ThreadLocal<Map<String, List<String>>> loadPage_responseHeaders = new ThreadLocal();
static ThreadLocal<Map<String, String>> loadPage_extraHeaders = new ThreadLocal();

public static String loadPageSilently(String url) { try {
  return loadPageSilently(new URL(loadPage_preprocess(url)));
} catch (Exception __e) { throw rethrow(__e); } }

public static String loadPageSilently(URL url) { try {
  if (url.getProtocol().equals("https"))
    disableCertificateValidation();
    
  if (!networkAllowanceTest(str(url))) throw fail("Not allowed: " + url);
    
  IOException e = null;
  for (int tries = 0; tries < loadPage_retries; tries++)
    try {
      URLConnection con = loadPage_openConnection(url);
      return loadPage(con, url);
    } catch (IOException _e) {
      e = _e;
      if (loadPageThroughProxy_enabled) {
        print("Trying proxy because of: " + e);
        try {
          return loadPageThroughProxy(str(url));
        } catch (Throwable e2) {
          print("  " + exceptionToStringShort(e2));
        }
      } else if (loadPage_debug)
        print(exceptionToStringShort(e));
      if (tries < loadPage_retries-1) sleepSeconds(1);
    }
  throw e;
} catch (Exception __e) { throw rethrow(__e); } }

static String loadPage_preprocess(String url) {  
  if (url.startsWith("tb/")) // don't think we use this anymore
    url = tb_mainServer() + "/" + url;
  if (url.indexOf("://") < 0)
    url = "http://" + url;
  return url;
}

static String loadPage(String url) { try {
  url = loadPage_preprocess(url);
  if (!isTrue(loadPage_silent.get()))
    printWithTime("Loading: " + hideCredentials(url));
  return loadPageSilently(new URL(url));
} catch (Exception __e) { throw rethrow(__e); } }

static String loadPage(URL url) {
  return loadPage(url.toExternalForm());
}

static String loadPage(URLConnection con, URL url) throws IOException {
  return loadPage(con, url, true);
}

static String loadPage(URLConnection con, URL url, boolean addHeaders) throws IOException {
  Map<String, String> extraHeaders = getAndClearThreadLocal(loadPage_extraHeaders);
  if (addHeaders) try {
    if (!loadPage_anonymous)
      setHeaders(con);
    if (loadPage_allowGzip)
      con.setRequestProperty("Accept-Encoding", "gzip");
    con.setRequestProperty("X-No-Cookies", "1");
    for (String key : keys(extraHeaders))
      con.setRequestProperty(key, extraHeaders.get(key));
  } catch (Throwable e) {} // fails if within doPost
  
  
  vm_generalSubMap("URLConnection per thread").put(currentThread(), con);
  
  loadPage_responseHeaders.set(con.getHeaderFields());
  InputStream in = null;
  try {
    in = urlConnection_getInputStream(con);
  //vm_generalSubMap("InputStream per thread").put(currentThread(), in);
  if (loadPage_debug)
    print("Put stream in map: " + currentThread());
    String contentType = con.getContentType();
    if (contentType == null) {
      //printStruct("Headers: ", con.getHeaderFields());
      throw new IOException("Page could not be read: " + hideCredentials(url));
    }
    //print("Content-Type: " + contentType);
    String charset = loadPage_charset == null ? null : loadPage_charset.get();
    if (charset == null) charset = loadPage_guessCharset(contentType);
    
    if ("gzip".equals(con.getContentEncoding())) {
      if (loadPage_debug)
        print("loadPage: Using gzip.");
      in = newGZIPInputStream(in);
    }
    Reader r;
    try {
      r = new InputStreamReader(in, unquote(charset));
    } catch (UnsupportedEncodingException e) {
      print(toHex(utf8(charset)));
      throw e;
    }
    
    boolean silent = isTrue(loadPage_silent.get());
    StringBuilder buf = new StringBuilder();
    int n = 0;
    while (true) {
      int ch = r.read();
      if (ch < 0)
        break;
      buf.append((char) ch);
      ++n;
      if (!silent && (n % loadPage_verboseness) == 0)
        print("  " + n + " chars read");
    }
    return buf.toString();
  } finally {
    if (loadPage_debug)
      print("loadPage done");
    //vm_generalSubMap("InputStream per thread").remove(currentThread());
    
    vm_generalSubMap("URLConnection per thread").remove(currentThread());
    
    if (in != null) in.close();
  }
}

static String loadPage_guessCharset(String contentType) {
  Matcher m = regexpMatcher("text/[a-z]+;\\s*charset=([^\\s]+)\\s*", contentType);
  String match = m.matches() ? m.group(1) : null;
  if (loadPage_debug)
    print("loadPage: contentType=" + contentType + ", match: " + match);
  /* If Content-Type doesn't match this pre-conception, choose default and hope for the best. */
  //return or(match, "ISO-8859-1");
  return or(match, "UTF-8");
}

static URLConnection loadPage_openConnection(URL url) {
  URLConnection con = openConnection(url);
  int timeout = toInt(loadPage_forcedTimeout_byThread.get());
  if (timeout == 0) timeout = loadPage_forcedTimeout;
  if (timeout != 0)
    setURLConnectionTimeouts(con, loadPage_forcedTimeout);
  else
    setURLConnectionDefaultTimeouts(con, loadPage_defaultTimeout);
  return con;
}
static boolean saveTextFileIfChanged(File f, String contents) {
  return saveTextFileIfDifferent(f, contents);
}
static File snippetTitle_cacheFile(String snippetID) {
  return javaxCachesDir("Snippet Titles/" + psI(snippetID));
}
static String exceptionToStringShort(Throwable e) {
  lastException(e);
  e = getInnerException(e);
  String msg = hideCredentials(unnull(e.getMessage()));
  if (msg.indexOf("Error") < 0 && msg.indexOf("Exception") < 0)
    return baseClassName(e) + prependIfNempty(": ", msg);
  else
    return msg;
}
static List map(Iterable l, Object f) { return map(f, l); }

static List map(Object f, Iterable l) {
  List x = emptyList(l);
  if (l != null) for (Object o : l)
    x.add(callF(f, o));
  return x;
}


  static <A, B> List<B> map(Iterable<A> l, F1<A, B> f) { return map(f, l); }

  static <A, B> List<B> map(F1<A, B> f, Iterable<A> l) {
    List x = emptyList(l);
    if (l != null) for (A o : l)
      x.add(callF(f, o));
    return x;
  }


static <A, B> List<B> map(IF1<A, B> f, Iterable<A> l) { return map(l, f); }
static <A, B> List<B> map(Iterable<A> l, IF1<A, B> f) {
  List x = emptyList(l);
  if (l != null) for (A o : l)
    x.add(f.get(o));
  return x;
}
  
static <A, B> List<B> map(IF1<A, B> f, A[] l) { return map(l, f); }
static <A, B> List<B> map(A[] l, IF1<A, B> f) {
  List x = emptyList(l);
  if (l != null) for (A o : l)
    x.add(f.get(o));
  return x;
}
  
static List map(Object f, Object[] l) { return map(f, asList(l)); }
static List map(Object[] l, Object f) { return map(f, l); }

static List map(Object f, Map map) {
  return map(map, f);
}

// map: func(key, value) -> list element
static List map(Map map, Object f) {
  List x = new ArrayList();
  if (map != null) for (Object _e : map.entrySet()) {
    Map.Entry e = (Map.Entry) _e;
    x.add(callF(f, e.getKey(), e.getValue()));
  }
  return x;
}

static <A, B, C> List<C> map(Map<A, B> map, IF2<A, B, C> f) {
  return map(map, (Object) f);
}
static String tok_lastIdentifier(String s) {
  return lastIdentifier(javaTokC(s));
}
static List<String> tok_parseArgsDeclList(List<String> tok) {
  return tok_parseArgsList(tok, getBracketMapIncludingAngleBrackets(tok));
}
static void tokAppend(List<String> tok, int i, String s) {
  tok.set(i, tok.get(i)+s);
}
static <A> boolean addToCollection(Collection<A> c, A a) {
  return c != null && c.add(a);
}
static String structureForUser(Object o) {
  return beautifyStructure(struct_noStringSharing(o));
}
static TreeSet<String> asCISet(Iterable<String> c) {
  return toCaseInsensitiveSet(c);
}

static TreeSet<String> asCISet(String... x) {
  return toCaseInsensitiveSet(x);
}
static boolean containsIgnoreCase(Collection<String> l, String s) {
  if (l != null) for (String x : l)
    if (eqic(x, s))
      return true;
  return false;
}

static boolean containsIgnoreCase(String[] l, String s) {
  if (l != null) for (String x : l)
    if (eqic(x, s))
      return true;
  return false;
}

static boolean containsIgnoreCase(String s, char c) {
  return indexOfIgnoreCase(s, String.valueOf(c)) >= 0;
}

static boolean containsIgnoreCase(String a, String b) {
  return indexOfIgnoreCase(a, b) >= 0;
}
static boolean boolOptPar(ThreadLocal<Boolean> tl) {
  return boolOptParam(tl);
}



// defaults to false
static boolean boolOptPar(Object[] __, String name) {
  return boolOptParam(__, name);
}

static boolean boolOptPar(String name, Object[] __) {
  return boolOptParam(__, name);
}
static String firstLine(String text) {
  if (text == null) return null;
  int i = text.indexOf('\n');
  return i >= 0 ? text.substring(0, i) : text;
}
static String innerMessage(Throwable e) {
  return getInnerMessage(e);
}
static int parseIntOpt(String s) {
  return isInteger(s) ? parseInt(s) : 0;
}
static String regexpFirstGroupIC(String pat, String s) {
  Matcher m = regexpIC(pat, s);
  if (m.find()) return m.group(1); else return null;
}
static String lines(Iterable lines) { return fromLines(lines); }
static String lines(Object[] lines) { return fromLines(asList(lines)); }
static List<String> lines(String s) { return toLines(s); }
static int max(int a, int b) { return Math.max(a, b); }
static int max(int a, int b, int c) { return max(max(a, b), c); }
static long max(int a, long b) { return Math.max((long) a, b); }
static long max(long a, long b) { return Math.max(a, b); }
static double max(int a, double b) { return Math.max((double) a, b); }
static float max(float a, float b) { return Math.max(a, b); }
static double max(double a, double b) { return Math.max(a, b); }

static int max(Collection<Integer> 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 float max(float[] c) {
  if (c.length == 0) return Float.MAX_VALUE;
  float x = c[0];
  for (int i = 1; i < c.length; i++) x = Math.max(x, c[i]);
  return x;
}

static byte max(byte[] c) {
  byte x = -128;
  for (byte d : c) if (d > x) x = d;
  return x;
}

static short max(short[] c) {
  short x = -0x8000;
  for (short d : c) if (d > x) x = d;
  return x;
}

static int max(int[] c) {
  int x = Integer.MIN_VALUE;
  for (int d : c) if (d > x) x = d;
  return x;
}
static int min(int a, int b) {
  return Math.min(a, b);
}

static long min(long a, long b) {
  return Math.min(a, b);
}

static float min(float a, float b) { return Math.min(a, b); }
static float min(float a, float b, float c) { return min(min(a, b), c); }

static double min(double a, double b) {
  return Math.min(a, b);
}

static double min(double[] c) {
  double x = Double.MAX_VALUE;
  for (double d : c) x = Math.min(x, d);
  return x;
}

static float min(float[] c) {
  float x = Float.MAX_VALUE;
  for (float d : c) x = Math.min(x, d);
  return x;
}

static byte min(byte[] c) {
  byte x = 127;
  for (byte d : c) if (d < x) x = d;
  return x;
}

static short min(short[] c) {
  short x = 0x7FFF;
  for (short d : c) if (d < x) x = d;
  return x;
}

static int min(int[] c) {
  int x = Integer.MAX_VALUE;
  for (int d : c) if (d < x) x = d;
  return x;
}
static String _userHome;
static String userHome() {
  if (_userHome == null)
    return actualUserHome();
  return _userHome;
}

static File userHome(String path) {
  return new File(userDir(), path);
}
static File newFile(File base, String... names) {
  for (String name : names) base = new File(base, name);
  return base;
}

static File newFile(String name) {
  return name == null ? null : new File(name);
}

static File newFile(String base, String... names) {
  return newFile(newFile(base), names);
}
static <A> void replaceCollection(Collection<A> dest, Collection<A> src) {
  dest.clear();
  dest.addAll(src);
}
static void replaceListPart(List l, int i, int j, List l2) {
  replaceSublist(l, i, j, l2);
}
static File getProgramFile(String progID, String fileName) {
  if (new File(fileName).isAbsolute())
    return new File(fileName);
  return new File(getProgramDir(progID), fileName);
}

static File getProgramFile(String fileName) {
  return getProgramFile(getProgramID(), fileName);
}

static List<String> cncSubList(List<String> tok, int from, int to) {
  from &= ~1;
  to |= 1;
  return subList(tok, from, to);
}
static String tok_packageName(List<String> tok) {
  int i = jfind(tok, "package");
  if (i < 0) return "";
  i += 2;
  int j = jfind(tok, i, ";");
  if (j < 0) return "";
  return join(codeTokensOnly(subList(tok, i-1, j)));
}
static String getNameOfPublicClass(List<String> tok) {
  for (List<String> c : allClasses(tok))
    if (hasModifier(c, "public"))
      return getClassDeclarationName(c);
  return null;
}

static String getNameOfAnyClass(List<String> tok) {
  return getClassDeclarationName(first(allClasses(tok)));
}

static String addSlash(String s) {
  return empty(s) || s.endsWith("/") ? s : s + "/";
}
static boolean boolPar(ThreadLocal<Boolean> tl) {
  return boolOptParam(tl);
}



// defaults to false
static boolean boolPar(Object[] __, String name) {
  return boolOptParam(__, name);
}

static boolean boolPar(String name, Object[] __) {
  return boolOptParam(__, name);
}

static boolean boolPar(String name, Map __) {
  return boolOptParam(name, __);
}

static boolean boolPar(String name, Object[] params, boolean defaultValue) {
  return optParam(params, name, defaultValue);
}

static <A> boolean eqGetOneOf(List<A> l, int i, A... options) {
  return eqOneOf(get(l, i), options);
}
static int jfind_any(String src, String... patterns) {
  return jfind_any(javaTok(src), patterns);
}
  
static int jfind_any(List<String> tok, String... patterns) {
  int i = -1;
  for (String pat : patterns)
    i = minUnlessMinus1(i, jfind(tok, pat));
  return i;
}
static ThreadLocal<Boolean> tok_typesAndNamesOfParams_keepModifiers = new ThreadLocal();

static List<Pair<String, String>> tok_typesAndNamesOfParams(List<String> tok, Object... __) {
  boolean keepModifiers = boolPar(tok_typesAndNamesOfParams_keepModifiers);
  boolean typelessMeansObject = boolPar("typelessMeansObject",__);

  try {
    List<Pair<String, String>> out = new ArrayList();
    for (int i = 1; i < l(tok); ) {
      String t = tok.get(i);
      String pre = "";
      
      
      
      // yeah, the typeless handling is a mess...
      if (typelessMeansObject && isIdentifier(t) && eqGetOneOf(tok, i+2, ",", null)) {
        i += 4;
        { out.add(pair("Object", t)); continue; }
      }
      
      if (eq(t, "final")) {
        if (keepModifiers) pre += "final ";
        t = get(tok, i += 2);
      }
      
      if (eq(t, "virtual")) {
        pre += "virtual ";
        t = get(tok, i += 2);
      }
      
      if (eq(t, "new")) { pre += "new "; t = get(tok, i += 2); }
      
      assertTrue(isIdentifier(t));
      i += 2;
      String type = t, name = "?";
      
      while (eq(get(tok, i), ".") && !eqGet(tok, i+2, ".")) {
        type += "." + assertIdentifier(get(tok, i+2));
        i += 4;
      }
      
      // just a parameter name, no type
      if (eqOneOf(get(tok, i), null, ",")) {
        name = type; type = "?";
      } else {
        if (eq(tok.get(i), "<")) {
          int j = findEndOfTypeArgs(tok, i)-1;
          while (eq(get(tok, j), "[") && eq(get(tok, j+2), "]")) j += 4;
          type += trimJoinSubList(tok, i, j+1);
          String id = assertIdentifier(tok.get(j+2));
          i = j+2;
        }
        while (eq(get(tok, i), "[") && eq(get(tok, i+2), "]")) {
          i += 4;
          type += "[]";
        }
        if (eqGet(tok, i, ".") && eqGet(tok, i+2, ".") && eqGet(tok, i+4, ".")) {
          type += "...";
          i += 6;
        }
        name = assertIdentifier(tok.get(i));
        i += 2;
        while (eq(get(tok, i), "[") && eq(get(tok, i+2), "]")) {
          i += 4;
          type += "[]";
        }
        if (i < l(tok)) {
          assertEquals(get(tok, i), ",");
          i += 2;
        }
      }
      out.add(pair(pre + type, name));
    }
    return out;
  } catch (Throwable e) {
    print("Bad parameter declaration: " + sfu(tok));
    throw rethrow(e);
  }
}
static <A, B> List<B> secondOfPairs(Collection<Pair<A, B>> l) {
  return map("secondOfPair", l);
}

static String tok_genEqualsAndHashCode(String className, List<Pair<String, String>> fields) {
  StringBuilder buf = new StringBuilder("\n\npublic bool equals(O o) {\n");
  if (empty(fields))
    buf.append("ret o instanceof " + className + ";\n");
  else
    buf.append("if (!o instanceof " + className + ") false;\r\n    " + className + " x = cast o;\r\n    ret " + join(" && ", map(fields, new F1<Pair<String, String>, String>() { String get(Pair<String, String> p) { try { 
      // TODO: use == for primitive fields
      return "eq(" + p.b + ", x." + p.b + ")";
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "// TODO: use == for primitive fields\r\n      \"eq(\" + p.b + \", x.\" + p.b + \")\""; }})) + ";\n");
  buf.append("}\n");
  
  buf.append("\r\n  public int hashCode() {\r\n    int h = " + hashCode(className) + ";\r\n    " + concatMap_strings(fields, new F1<Pair<String, String>, Object>() { Object get(Pair<String, String> p) { try { 
      return "h = boostHashCombine(h, _hashCode(" + p.b + "));\r\n    "; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "\"h = boostHashCombine(h, _hashCode(\" + p.b + [[));\r\n    ]]"; }}) + "ret h;\r\n  }\r\n  ");
  return str(buf);
}
static String tok_genRecordFieldsToList(List<Pair<String, String>> fields) {
  return "public O[] _fieldsToList() { " + (empty(fields) ? "null;" : "ret new O[] {" + joinWithComma(pairsB(fields)) + "};") + " }\n";
}
static boolean isSingleQuoteIdentifier(String s) {
  if (l(s) < 2 || s.charAt(0) != '\'' || !Character.isJavaIdentifierStart(s.charAt(1))) return false;
  for (int i = 2; i < l(s); i++)
    if (!Character.isJavaIdentifierPart(s.charAt(i))) return false;
  return true;
}
static <A> int indexOfAny(List<A> l, int i, A... x) {
  while (i < l(l))
    if (eqOneOf(l.get(i), x)) return i; else ++i;
  return -1;
}

static <A> int indexOfAny(List<A> l, Collection<A> x) {
  return indexOfAny(l, 0, x);
}

static <A> int indexOfAny(List<A> l, int i, Collection<A> x) {
  if (nempty(x))
    while (i < l(l))
      if (x.contains(l.get(i))) return i; else ++i;
  return -1;
}

static int indexOfAny(String s, int i, String chars) {
  for (; i < l(s); i++)
    if (chars.indexOf(s.charAt(i)) >= 0)
      return i;
  return -1;
}

static int indexOfAny(String s, String chars) { return indexOfAny(s, 0, chars); }
static <A> List<A> reversed(Collection<A> l) {
  return reversedList(l);
}

static <A> List<A> reversed(A[] l) {
  return reversedList(asList(l));
}

static String reversed(String s) {
  return reversedString(s);
}
static <A> List<Integer> indexesOf(List<A> l, A a) {
  return indicesOf(l, a);
}
static <A> A second(List<A> l) {
  return get(l, 1);
}

static <A> A second(Iterable<A> l) {
  if (l == null) return null;
  Iterator<A> it = iterator(l);
  if (!it.hasNext()) return null;
  it.next();
  return it.hasNext() ? it.next() : null;
}

static <A> A second(A[] bla) {
  return bla == null || bla.length <= 1 ? null : bla[1];
}


static <A, B> B second(Pair<A, B> p) {
  return p == null ? null : p.b;
}





static <A> A second(Producer<A> p) {
  if (p == null) return null;
  if (p.next() == null) return null;
  return p.next();
}


static char second(String s) {
  return charAt(s, 1);
}
// Return value is index of ")"
static int tok_findEndOfForExpression(List<String> tok, int i) {
  int level = 1;
  
  while (i < l(tok)) {
    String t = tok.get(i);
    if (eq(t, "(")) ++level;
    else if (eq(t, ")"))
      if (--level == 0) return i;
    i += 2;
  }
  return i;
}
static String trimJoinSubList(List<String> l, int i, int j) {
  return trim(join(subList(l, i, j)));
}

static String trimJoinSubList(List<String> l, int i) {
  return trim(join(subList(l, i)));
}
// i = beginning of statement or block
static void tok_statementToBlock(List<String> tok, int i) {
  if (neqGet(tok, i, "{")) {
    int iEnd = tok_findEndOfStatement(tok, i)-1;
    tokAppend_reTok(tok, iEnd, " }");
    tokPrepend_reTok(tok, i, "{ ");
  }
}
static String joinPairWithSpace(Pair p) {
  return str(p.a) + " " + str(p.b);
}
static String toUpper(String s) {
  return s == null ? null : s.toUpperCase();
}

static List<String> toUpper(Collection<String> s) {
  return allToUpper(s);
}
static String[] codeTokensAsStringArray(List<String> tok) {
  int n = max(0, (l(tok)-1)/2);
  String[] out = new String[n];
  for (int i = 0; i < n; i++)
    out[i] = tok.get(i*2+1);
  return out;
}
static <A> List<A> replaceSublist(List<A> l, List<A> x, List<A> y) {
  if (x == null) return l;
  
  int i = 0;
  while (true) {
    i = indexOfSubList(l, x, i);
    if (i < 0) break;
    
    replaceSublist(l, i, i+l(x), y);
    i += l(y);
  }
  return l;
}

static <A> List<A> replaceSublist(List<A> l, int fromIndex, int toIndex, List<A> y) {
  // TODO: optimize more
  int n = y.size(), toIndex_new = fromIndex+n;
  if (toIndex_new < toIndex) {
    removeSubList(l, toIndex_new, toIndex);
    copyListPart(y, 0, l, fromIndex, n);
  } else {
    copyListPart(y, 0, l, fromIndex, toIndex-fromIndex);
    if (toIndex_new > toIndex)
      l.addAll(toIndex, subList(y, toIndex-fromIndex));
  }
  return l;
}


static <A> List<A> replaceSublist(List<A> l, IntRange r, List<A> y) {
  return replaceSublist(l, r.start, r.end, y);
}

static String getType(Object o) {
  return getClassName(o);
}
static long getFileSize(String path) {
  return path == null ? 0 : new File(path).length();
}

static long getFileSize(File f) {
  return f == null ? 0 : f.length();
}
static <A> A liftLast(List<A> l) {
  if (empty(l)) return null;
  int i = l(l)-1;
  A a = l.get(i);
  l.remove(i);
  return a;
}

static <A> List<A> liftLast(int n, List<A> l) {
  int i = l(l)-n;
  List<A> part = cloneSubList(l, i);
  removeSubList(l, i);
  return part;
}
static boolean isIndexedList(List l) {
  
  if (l instanceof IndexedList2) return true;
  
  
  if (l instanceof TokenIndexedList3) return true;
  
  return false;
}
static int strL(String s) {
  return s == null ? 0 : s.length();
}
static boolean emptyString(String s) {
  return s == null || s.length() == 0;
}
static <A> boolean containsSubList(List<A> x, List<A> y) {
  return indexOfSubList(x, y) >= 0;
}

static <A> boolean containsSubList(List<A> x, A... y) {
  return indexOfSubList(x, y, 0) >= 0;
}

static void tok_p_repeatWithSleep(List<String> tok) {
  int i;
  while ((i = jfind(tok, "p-repeat with sleep * {")) >= 0) {
    String seconds = tok.get(i+6); // int or id
    int idx = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, idx);
    tok.set(i+2, " { ");
    tok.set(j-1, "} }");
    reTok(tok, j-1);
    reTok(tok, i+2);
  }
}

// i = last C token of type
// returns first C token of type
static int tok_scanTypeBackwards(List<String> tok, int i) {
  if (i < 0) return i;
  int level = 0;
  while (i > 0) {
    String t = tok.get(i);
    if (isIdentifier(t) && level == 0) {
      while (eqGet(tok, i-2, ".") && isIdentifier(get(tok, i-4)))
        i -= 4;
      return i;
    }
    if (eq(t, ">")) ++level;
    else if (eq(t, "<")) --level;
    i -= 2;
  }
  return i;
}
// returns index of trailing N token
static int scanToEndOfInitializer2(List<String> tok, int i) {
  int level = 0;
  while (i < l(tok)) {
    String t = tok.get(i);
    if (eqOneOf(t, "(", "{")) ++level;
    else if (level > 0 && eqOneOf(t, ")", "}")) --level;
    else if (level == 0 && eqOneOf(tok.get(i), ";", ",", ")", "}"))
      return i-1;
    i++;
  }
  return i;
}
static boolean tok_typeIsObject(String type) {
  return eqOneOf(type, "O", "Object");
}
static boolean startsWith(String a, String b) {
  return a != null && a.startsWith(b);
}

static boolean startsWith(String a, char c) {
  return nemptyString(a) && a.charAt(0) == c;
}


  static boolean startsWith(String a, String b, Matches m) {
    if (!startsWith(a, b)) return false;
    m.m = new String[] {substring(a, strL(b))};
    return true;
  }


static boolean startsWith(List a, List b) {
  if (a == null || listL(b) > listL(a)) return false;
  for (int i = 0; i < listL(b); i++)
    if (neq(a.get(i), b.get(i)))
      return false;
  return true;
}


static boolean endsWith(String a, String b) {
  return a != null && a.endsWith(b);
}

static boolean endsWith(String a, char c) {
  return nempty(a) && lastChar(a) == c;
}


  static boolean endsWith(String a, String b, Matches m) {
    if (!endsWith(a, b)) return false;
    m.m = new String[] {dropLast(l(b), a)};
    return true;
  }


static List<String> splitByJavaToken(String s, String splitToken) {
  List<String> tok = javaTok(s);
  List<String> l = new ArrayList();
  int i = 1;
  while (i < l(tok)) {
    int j = smartIndexOf(tok, splitToken, i);
    l.add(join(subList(tok, i, j-1)));
    i = j+2;
  }
  return l;
}
static <A, B> B mapGet(Map<A, B> map, A a) {
  return map == null || a == null ? null : map.get(a);
}

static <A, B> B mapGet(A a, Map<A, B> map) {
  return map == null || a == null ? null : map.get(a);
}
// i must point at the opening bracket ("<")
// index returned is index of closing bracket + 1
static int findEndOfTypeArgs(List<String> cnc, int i) {
  int j = i+2, level = 1;
  while (j < cnc.size()) {
    if (cnc.get(j).equals("<")) ++level;
    else if (cnc.get(j).equals(">")) --level;
    if (level == 0)
      return j+1;
    ++j;
  }
  return cnc.size();
}

static <A> int indexOf_between(List<A> l, A a, int startIndex, int endIndex) {
  if (l == null) return -1;
  for (int i = startIndex; i < endIndex; i++)
    if (eq(l.get(i), a))
      return i;
  return -1;
}
static boolean tok_isSingleTypeArg(List<String> tok, int iOpeningBracket) {
  int j = tok_findEndOfTypeArg(tok, iOpeningBracket);
  return neqGet(tok, j, ",");
}
static int tok_findEndOfType(List<String> tok) {
  return tok_findEndOfType(tok, 1);
}

// i = beginning of type (identifier)
// index returned C index after type
// TODO: packages/inner classes
static int tok_findEndOfType(List<String> tok, int i) {
  if (!eqGet(tok, i+2, "<")) return i+2;
  return findEndOfTypeArgs(tok, i+2)+1;
}
static boolean longIsInt(long l) {
  return l == (int) l;
}
static void tokReplace(List<String> tok, int i, String t) {
  put(tok, i, t);
}
static void tokSet_withReTok(List<String> tok, int i, String t) {
  put(tok, i, t);
  reTok(tok, i, i+1);
}
static void tokPrepend_withReTok(List<String> tok, int i, String s) {
  tokPrepend_reTok(tok, i, s);
}
static int leftScanModifiers(List<String> tok, int i) {
  List<String> mod = getJavaModifiers();
  while (i > 1 && mod.contains(tok.get(i-2)))
    i -= 2;
  return i;
}

static void tokSet(List<String> tok, int idx, String token) {
  tok.set(idx, token);
}
static int tok_findTokenSkippingCurlyBrackets(List<String> tok, int i, String t) {
  while (i < l(tok)) {
    if (eqGet(tok, i, t)) return i;
    if (eqGet(tok, i, "{"))
      i = findEndOfBracketPart(tok, i)+1;
    else
      i += 2;
  }
  return -1;
}
static Collection<String> tok_modifiersLeftOf(List<String> tok, int i) {
  int j = leftScanModifiers(tok, i);
  List<String> modifiers = new ArrayList();
  while (j < i) {
    modifiers.add(tok.get(j));
    j += 2;
  }
  return modifiers;
}
// index returned is index of closing bracket or semicolon + 1 (or l(cnc))
static int tok_findEndOfMethodDecl(List<String> tok, int i) {
  i = tok_findEndOfMethodHeader(tok, i);
  return findEndOfBlock(tok, i);
}
static String tok_functionName(List<String> tok) {
  return get(tok, indexOfOneOf(tok, 0, "(", "{")-2);
}
static boolean is(String a, String b) {
  
  return false;
}
static Object javaEval(String code) {
  return evalJava(code);
}
static <A> A assertEquals(Object x, A y) {
  return assertEquals(null, x, y);
}

static <A> A assertEquals(String msg, Object x, A y) {
  if (assertVerbose()) return assertEqualsVerbose(msg, x, y);
  if (!(x == null ? y == null : x.equals(y)))
    throw fail((msg != null ? msg + ": " : "") + y + " != " + x);
  return y;
}
static <A> boolean setAdd(Collection<A> c, A a) {
  if (c == null || c.contains(a)) return false;
  c.add(a);
  return true;
}
static <A> A popLast(List<A> l) {
  return liftLast(l);
}

static <A> List<A> popLast(int n, List<A> l) {
  return liftLast(n, l);
}
// This is a bit rough... finds static and non-static methods.

static Method findMethodNamed(Object obj, String method) {
  if (obj == null) return null;
  if (obj instanceof Class)
    return findMethodNamed((Class) obj, method);
  return findMethodNamed(obj.getClass(), method);
}

static Method findMethodNamed(Class c, String method) {
  while (c != null) {
    for (Method m : c.getDeclaredMethods())
      if (m.getName().equals(method)) {
        makeAccessible(m);
        return m;
      }
    c = c.getSuperclass();
  }
  return null;
}
static void quoteToPrintWriter(String s, PrintWriter out) {
  if (s == null) { out.print("null"); return; }
  out.print('"');
  int l = s.length();
  for (int i = 0; i < l; i++) {
    char c = s.charAt(i);
    if (c == '\\' || c == '"') {
      out.print('\\'); out.print(c);
    } else if (c == '\r')
      out.print("\\r");
    else if (c == '\n')
      out.print("\\n");
    else if (c == '\0')
      out.print("\\0");
    else
      out.print(c);
  }
  out.print('"');
}
static String quoteCharacter(char c) {
  if (c == '\'') return "'\\''";
  if (c == '\\') return "'\\\\'";
  if (c == '\r') return "'\\r'";
  if (c == '\n') return "'\\n'";
  if (c == '\t') return "'\\t'";
  return "'" + c + "'";
}

static boolean isCISet_gen(Iterable<String> l) {
  return l instanceof TreeSet && className(((TreeSet) l).comparator()).contains("CIComp");
}
static <A> List<A> unwrapSynchronizedList(List<A> l) {
  if (eqOneOf(className(l),
    "java.util.Collections$SynchronizedList",
    "java.util.Collections$SynchronizedRandomAccessList"))
    return (List) get_raw(l, "list");
  return l;
}
static boolean isCIMap_gen(Map map) {
  return map instanceof TreeMap && className(((TreeMap) map).comparator()).contains("CIComp");
}
static <A, B> Map<A, B> unwrapSynchronizedMap(Map<A, B> map) {
  if (eqOneOf(className(map),
    "java.util.Collections$SynchronizedMap",
    "java.util.Collections$SynchronizedSortedMap",
    "java.util.Collections$SynchronizedNavigableMap"))
    return (Map) get_raw(map, "m");
  return map;
}
  public static String bytesToHex(byte[] bytes) {
    return bytesToHex(bytes, 0, bytes.length);
  }

  public static String bytesToHex(byte[] bytes, int ofs, int len) {
    StringBuilder stringBuilder = new StringBuilder(len*2);
    for (int i = 0; i < len; i++) {
      String s = "0" + Integer.toHexString(bytes[ofs+i]);
      stringBuilder.append(s.substring(s.length()-2, s.length()));
    }
    return stringBuilder.toString();
  }

static String boolArrayToHex(boolean[] a) {
  return bytesToHex(boolArrayToBytes(a));
}
static Iterator emptyIterator() {
  return Collections.emptyIterator();
}
static String shortDynamicClassName(Object o) {
 if (o instanceof DynamicObject && ((DynamicObject) o).className != null)
    return ((DynamicObject) o).className;
  return shortClassName(o);
}
static int stdcompare(Number a, Number b) {
  return cmp(a, b);
}

static int stdcompare(String a, String b) {
  return cmp(a, b);
}

static int stdcompare(long a, long b) {
  return a < b ? -1 : a > b ? 1 : 0;
}

static int stdcompare(Object a, Object b) {
  return cmp(a, b);
}

static Map<Class, Field[]> getDeclaredFields_cache = newDangerousWeakHashMap();

static Field[] getDeclaredFields_cached(Class c) {
  Field[] fields;
  synchronized(getDeclaredFields_cache) {
    fields = getDeclaredFields_cache.get(c);
    if (fields == null) {
      getDeclaredFields_cache.put(c, fields = c.getDeclaredFields());
      for (Field f : fields)
        makeAccessible(f);
    }
  }
  return fields;
}
static <A> ArrayList<A> asList(A[] a) {
  return a == null ? new ArrayList<A>() : new ArrayList<A>(Arrays.asList(a));
}

static ArrayList<Integer> asList(int[] a) {
  if (a == null) return null;
  ArrayList<Integer> l = emptyList(a.length);
  for (int i : a) l.add(i);
  return l;
}

static ArrayList<Float> asList(float[] a) {
  if (a == null) return null;
  ArrayList<Float> l = emptyList(a.length);
  for (float i : a) l.add(i);
  return l;
}
static <A> ArrayList<A> asList(Iterable<A> s) {
  if (s instanceof ArrayList) return (ArrayList) s;
  ArrayList l = new ArrayList();
  if (s != null)
    for (A a : s)
      l.add(a);
  return l;
}


static <A> ArrayList<A> asList(Producer<A> p) {
  ArrayList l = new ArrayList();
  A a;
  if (p != null) while ((a = p.next()) != null)
    l.add(a);
  return l;
}


static <A> ArrayList<A> asList(Enumeration<A> e) {
  ArrayList l = new ArrayList();
  if (e != null)
    while (e.hasMoreElements())
      l.add(e.nextElement());
  return l;
}
static boolean startsWithDigit(String s) {
  return nempty(s) && isDigit(s.charAt(0));
}
static <A, B> Map<A, B> putAll(Map<A, B> a, Map<? extends A,? extends B> b) {
  if (a != null && b != null) a.putAll(b);
  return a;
}
static int countDots(String s) {
  int n = l(s), count = 0;
  for (int i = 0; i < n; i++) if (s.charAt(i) == '.') ++count;
  return count;
}
static void printStructure(String prefix, Object o) {
  if (endsWithLetter(prefix)) prefix += ": ";
  print(prefix + structureForUser(o));
}

static void printStructure(Object o) {
  print(structureForUser(o));
}

static int lastIndexOf(String a, String b) {
  return a == null || b == null ? -1 : a.lastIndexOf(b);
}

static int lastIndexOf(String a, char b) {
  return a == null ? -1 : a.lastIndexOf(b);
}
static <A> A set(A o, String field, Object value) {
  if (o == null) return null;
  if (o instanceof Class) set((Class) o, field, value);
  else try {
    Field f = set_findField(o.getClass(), field);
    makeAccessible(f);
    smartSet(f, o, value);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
  return o;
}

static void set(Class c, String field, Object value) {
  if (c == null) return;
  try {
    Field f = set_findStaticField(c, field);
    makeAccessible(f);
    smartSet(f, null, value);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
  
static Field set_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  throw new RuntimeException("Static field '" + field + "' not found in " + c.getName());
}

static Field set_findField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field))
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  throw new RuntimeException("Field '" + field + "' not found in " + c.getName());
}
// Finds out the index of a sublist in the original list
// Works with nested (grand-parent) sublists.
// Does not work with lists not made with subList() (returns -1)

static <A> int magicIndexOfSubList(List<A> list, List<A> sublist) {
  if (sublist == list) return 0;
  Integer o2 =  (Integer) (get(sublist, "offset"));
  if (o2 == null) return -1;
  Integer o1 =  (Integer) (getOpt(list, "offset"));
  return o2-(o1 != null ? o1 : 0);
}
static Class<?> hotwire(String src) {
  assertFalse(_inCore());
  Class j = getJavaX();
  if (isAndroid()) {
    synchronized(j) { // hopefully this goes well...
      List<File> libraries = new ArrayList<File>();
      File srcDir = (File) call(j, "transpileMain", src, libraries);
      if (srcDir == null)
        throw fail("transpileMain returned null (src=" + quote(src) + ")");
    
      Object androidContext = get(j, "androidContext");
      return (Class) call(j, "loadx2android", srcDir, src);
    }
  } else {
    
    
    Class c =  (Class) (call(j, "hotwire", src));
    hotwire_copyOver(c);
    return c;
    
  }
}
static <A> A callMain(A c, String... args) {
  callOpt(c, "main", new Object[] {args});
  return c;
}

static void callMain() {
  callMain(mc());
}
static String joinCodeTokens(List<String> tok) {
  StringBuilder buf = new StringBuilder();
  int n = tok.size();
  for (int i = 1; i < n; i += 2)
    buf.append(tok.get(i));
  return buf.toString();
}
static boolean containsNewLine(String s) {
  return contains(s, '\n'); // screw \r, nobody needs it
}
static HashMap<String, String> mapFromTokens(String s) {
  List<String> tok = javaTok(s);
  HashMap map = new HashMap();
  for (int i = 1; i+2 < l(tok); i += 4)
    map.put(tok.get(i), tok.get(i+2));
  return map;
}
static <A> List<A> newSubList(List<A> l, int startIndex, int endIndex) {
  return cloneList(subList(l, startIndex, endIndex));
}

static <A> List<A> newSubList(List<A> l, int startIndex) {
  return cloneList(subList(l, startIndex));
}
static boolean tok_shouldAddReturn(List<String> tok) {
  String lastToken = get(tok, l(tok)-2);
  return !eqOneOf(lastToken, "}", ";");
}

static boolean tok_shouldAddReturn(String s) {
  return tok_shouldAddReturn(javaTok(s));
}
// i must point at the (possibly imaginary) closing bracket (any of the 2 types, not type parameters)
// index returned is index of opening bracket
static int findBeginningOfBracketPart(List<String> cnc, int i) {
  int j = i-2, level = 1;
  while (j > 0) {
    if (eqOneOf(cnc.get(j), "}", ")")) ++level;
    else if (eqOneOf(cnc.get(j), "{", "(")) --level;
    if (level == 0)
      return j;
    j -= 2;
  }
  return -1;
}
// iOpening, iClosing = position of () brackets
static List<String> tok_makeArgumentsFinal(List<String> tok, int iOpening, int iClosing) {
  Map<Integer, Integer> bracketMap = getBracketMapIncludingAngleBrackets(tok, iOpening+2, iClosing);
  boolean change = false;
  int i = iOpening+2;
  while (i < iClosing) {
    if (neqGet(tok, i, "final")) { tokPrepend(tok, i, "final "); change = true; }
    while (i < iClosing && neqGet(tok, i, ","))
      i = or(bracketMap.get(i), i)+2;
    i += 2;
  }
  if (change) reTok(tok, iOpening, iClosing);
  return tok;
}
// not including <> as they are ambiguous (< is also a comparison operator)
static String testBracketHygiene_op    = "([{";
static String testBracketHygiene_close = ")]}";

static boolean testBracketHygiene(String s) {
  return testBracketHygiene(s, testBracketHygiene_op, testBracketHygiene_close, null);
}

static boolean testBracketHygiene(String s, Var<String> msg) {
  return testBracketHygiene(s, testBracketHygiene_op, testBracketHygiene_close, msg);
}

static boolean testBracketHygiene(String s, String op, String close, Var<String> msg) {
  Pair<Integer, String> p = testBracketHygiene2(s, op, close);
  if (p == null) {
    if (msg != null) msg.set("Hygiene OK!");
    return true;
  }
  if (msg != null) msg.set(p.b);
  return false;
}
static boolean isLongConstant(String s) {
  if (!s.endsWith("L")) return false;
  s = s.substring(0, l(s)-1);
  return isInteger(s);
}
static <A> List<A> wrapAsList(A[] a) {
  return wrapArrayAsList(a);
}
static volatile boolean licensed_yes = true;

static boolean licensed() {
  if (!licensed_yes) return false;
  ping_okInCleanUp();
  return true;
}

static void licensed_off() {
  licensed_yes = false;
}
static String lineAroundToken(List<String> tok, int tokenIndex) {
  int i = tokenIndex, j = tokenIndex;
  while (i > 1 && !containsNewLine(get(tok, i-1)))
    i -= 2;
  while (j+2 < l(tok) && !containsNewLine(get(tok, j+1)))
    j += 2;
  return afterLineBreak(get(tok, i-1)) + joinSubList(tok, i, j+1) + beforeLineBreak(get(tok, j+1));
}
static Set asSet(Object[] array) {
  HashSet set = new HashSet();
  for (Object o : array)
    if (o != null)
      set.add(o);
  return set;
}

static Set<String> asSet(String[] array) {
  TreeSet<String> set = new TreeSet();
  for (String o : array)
    if (o != null)
      set.add(o);
  return set;
}

static <A> Set<A> asSet(Iterable<A> l) {
  if (l instanceof Set) return (Set) l;
  HashSet<A> set = new HashSet();
  for (A o : unnull(l))
    if (o != null)
      set.add(o);
  return set;
}
static Set similarEmptySet(Collection m) {
  if (m instanceof TreeSet) return new TreeSet(((TreeSet) m).comparator());
  if (m instanceof LinkedHashSet) return new LinkedHashSet();
  
  return new HashSet();
}
static <A> List<A> reversedList(Collection<A> l) {
  List<A> x = cloneList(l);
  Collections.reverse(x);
  return x;
}
static List<String> findBlock(String pat, List<String> tok) {
  List<String> tokpat = javaTok(pat);
  int i = findCodeTokens(tok, toStringArray(codeTokensOnly(tokpat)));
  //print("index of block " + quote(pat) + ": " + i);
  if (i < 0) return null;
  int bracketIdx = i+tokpat.size()-3;
  assertEquals("{", tok.get(bracketIdx));
  int endIdx = findEndOfBlock(tok, bracketIdx);
  return subList(tok, i-1, endIdx+1); // make it actual CNC
}
static String loadLocalSnippet(String snippetID) {
  return loadLocalSnippet(psI(snippetID));
}

static String loadLocalSnippet(long snippetID) {
  return loadTextFile(localSnippetFile(snippetID));
}
static String md5(String text) { try {
  if (text == null) return "-";
  return bytesToHex(md5_impl(toUtf8(text))); // maybe different than the way PHP does it...
} catch (Exception __e) { throw rethrow(__e); } }

static String md5(byte[] data) {
  if (data == null) return "-";
  return bytesToHex(md5_impl(data));
}

static byte[] md5_impl(byte[] data) { try {
  return MessageDigest.getInstance("MD5").digest(data);
} catch (Exception __e) { throw rethrow(__e); } }

static String md5(File file) {
  return md5OfFile(file);
}
static String standardCredentials() {
  String user = standardCredentialsUser();
  String pass = standardCredentialsPass();
  if (nempty(user) && nempty(pass))
    return "&_user=" + urlencode(user) + "&_pass=" + urlencode(pass);
  return "";
}
static File getGlobalCache() {
  File file = new File(javaxCachesDir(), "Binary Snippets");
  file.mkdirs();
  return file;
}

static <A> A setThreadLocal(ThreadLocal<A> tl, A value) {
  if (tl == null) return null;
  A old = tl.get();
  tl.set(value);
  return old;
}
static boolean isTrue(Object o) {
  if (o instanceof Boolean)
    return ((Boolean) o).booleanValue();
  if (o == null) return false;
  if (o instanceof ThreadLocal) // TODO: remove this
    return isTrue(((ThreadLocal) o).get());
  throw fail(getClassName(o));
}
static boolean isUnproperlyQuoted(String s) {
  return startsWith(s, '"') && !isProperlyQuoted(s);
}
static boolean regexpFinds(String pat, String s) {
  return regexp(pat, s).find();
}

static boolean regexpFinds(Pattern pat, String s) {
  return regexp(pat, s).find();
}
static <A, B> void mapPut(Map<A, B> map, A key, B value) {
  if (map != null && key != null && value != null) map.put(key, value);
}
static Map jsonDecodeMap(String s) {
  Object o = jsonDecode(s);
  if (o instanceof List && empty((List) o))
    return new HashMap();
  if (o instanceof Map)
    return (Map) o;
  else
    throw fail("Not a JSON map: " + s);
}
static ThreadLocal<Boolean> doPost_silently = new ThreadLocal();
static ThreadLocal<Long> doPost_timeout = new ThreadLocal();
static ThreadLocal<Map<String, String>> doPost_extraHeaders = new ThreadLocal();

static String doPost(Map urlParameters, String url) {
  return doPost(makePostData(urlParameters), url);
}

static String doPost(String urlParameters, String url) { try {
  URL _url = new URL(url);
  ping();
  return doPost(urlParameters, _url.openConnection(), _url);
} catch (Exception __e) { throw rethrow(__e); } }

static String doPost(String urlParameters, URLConnection conn, URL url) { try {
  boolean silently = isTrue(optParam(doPost_silently));
  Long timeout = optParam(doPost_timeout);
  Map<String, String> extraHeaders = optPar(doPost_extraHeaders);
  setHeaders(conn);
  for (String key : keys(extraHeaders))
    conn.setRequestProperty(key, extraHeaders.get(key));

  int l = lUtf8(urlParameters);
  if (!silently)
    print("Sending POST request: " + hideCredentials(url) + " (" + l + " bytes)");
      
  // connect and do POST
  if (timeout != null) setURLConnectionTimeouts(conn, timeout);
  ((HttpURLConnection) conn).setRequestMethod("POST");
  conn.setDoOutput(true);
  conn.setRequestProperty("Content-Length", str(l));

  OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
  writer.write(urlParameters);
  writer.flush();

  String contents = loadPage_utf8(conn, url, false);
  writer.close();
  return contents;
} catch (Exception __e) { throw rethrow(__e); } }
static String urlencode(String x) {
  try {
    return URLEncoder.encode(unnull(x), "UTF-8");
  } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); }
}
static String className(Object o) {
  return getClassName(o);
}
static List<List<String>> innerClasses(List<String> tok) {
  if (tok == null) return emptyList();
  int i = 1;
  while (i < tok.size() && !tok.get(i).equals("{"))
    i += 2;
  return allClasses(tok.subList(i+1, tok.size()));
}
static boolean startsWithUpperCase(String s) {
  return nempty(s) && Character.isUpperCase(s.charAt(0));
}
static List<String> toLinesFullTrim_java(String text) {
  // TODO: shouldn't it be tlft(javaDropComments(text))?
  return tlft(joinLines(map("javaDropComments",tlft(text))));
}
static boolean eqOneOf_twoNonNullStrings(String s, String a, String b) {
  return a.equals(s) || b.equals(s);
}
static boolean startsWithJavaIdentifierStart(String s) {
  return nempty(s) && Character.isJavaIdentifierStart(s.charAt(0));
}
// i points at C token after suspected meta command
static boolean tok_isJavaxMetaCommandLeftOf(List<String> tok, int i) {
  int j = i-2*4-1;
  if (j >= 0 && jfind(subList(tok, j, i), "set flag <id> .") == 1) return true;
  return false;
}
static int rfindCodeTokens(List<String> tok, String... tokens) {
  return rfindCodeTokens(tok, 1, false, tokens);
}

static int rfindCodeTokens(List<String> tok, boolean ignoreCase, String... tokens) {
  return rfindCodeTokens(tok, 1, ignoreCase, tokens);
}

static int rfindCodeTokens(List<String> tok, int startIdx, boolean ignoreCase, String... tokens) {
  return rfindCodeTokens(tok, startIdx, ignoreCase, tokens, null);
}

static List<String> rfindCodeTokens_specials = litlist("*", "<quoted>", "<id>", "<int>", "\\*");
static boolean rfindCodeTokens_debug = false;
static int rfindCodeTokens_indexed, rfindCodeTokens_unindexed;
static int rfindCodeTokens_bails, rfindCodeTokens_nonbails;

static int rfindCodeTokens(List<String> tok, int startIdx, boolean ignoreCase, String[] tokens, Object condition) {
  return rfindCodeTokens(tok, startIdx, l(tok), ignoreCase, tokens, condition);
}

static int rfindCodeTokens(List<String> tok, int startIdx, int endIndex, boolean ignoreCase, String[] tokens, Object condition) {
  if (rfindCodeTokens_debug) {
    if (eq(getClassName(tok), "main$IndexedList2"))
      rfindCodeTokens_indexed++;
    else
      rfindCodeTokens_unindexed++;
  }
  // bail out early if first token not found (works great with IndexedList)
  if (!rfindCodeTokens_specials.contains(tokens[0])
    && !tok.contains(tokens[0] /*, startIdx << no signature in List for this, unfortunately */)) {
      ++rfindCodeTokens_bails;
      return -1;
    }
  ++rfindCodeTokens_nonbails;
  
  outer: for (int i = min(endIndex, tok.size()-tokens.length*2) | 1; i >= startIdx; i -= 2) {
    for (int j = 0; j < tokens.length; j++) {
      String p = tokens[j], t = tok.get(i+j*2);
      boolean match = false;
      if (eq(p, "*")) match = true;
      else if (eq(p, "<quoted>")) match = isQuoted(t);
      else if (eq(p, "<id>")) match = isIdentifier(t);
      else if (eq(p, "<int>")) match = isInteger(t);
      else if (eq(p, "\\*")) match = eq("*", t);
      else match = ignoreCase ? eqic(p, t) : eq(p, t);
      
      if (!match)
        continue outer;
    }
    
    if (condition == null || checkTokCondition(condition, tok, i-1)) // pass N index
      return i;
  }
  return -1;
}
static boolean allVarNames_debug = false;

static List<String> allVarNames(List<String> tok) {
  boolean debug = allVarNames_debug;
  Map<Integer, Integer> bracketMap = getBracketMap(tok);
  List<String> varNames = new ArrayList();
  for (int i = 3; i < tok.size(); i += 2) {
    String t = tok.get(i);
    if (eqOneOf(t, "=", ";", ",")) {
      String v = tok.get(i-2);
      if (isIdentifier(v))
        varNames.add(v);
      if (eq(t, "=")) {
        i = scanToEndOfInitializer(tok, bracketMap, i)+1;
        assertTrue(odd(i));
      }
    } else if (eq(t, "<")) {
      i = findEndOfTypeArgs(tok, i)+1;
      if (debug)
        print("Skipped type args: " + struct(subList(tok, i)));
    } else if (eqOneOf(t, "{", "("))
      i = findEndOfBracketPart(tok, i)-1; // skip function/class bodies
  }
  return varNames;
}

static List<String> allVarNames(String text) {
  return allVarNames(javaTok(text));
}
static boolean isSortedList(List l) {
  for (int i = 0; i < l(l)-1; i++)
    if (cmp(l.get(i), l.get(i+1)) > 0)
      return false;
  return true;
}
static <A> int smartIndexOfAny(List<A> l, int i, A... x) {
  while (i < l(l))
    if (eqOneOf(l.get(i), x)) return i; else ++i;
  return l(l);
}

static <A> int smartIndexOfAny(List<A> l, A... x) {
  return smartIndexOfAny(l, 0, x);
}
static boolean vmExiting() {
  return isTrue(getOpt(javax(), "killing"));
}
static String struct(Object o) {
  return structure(o);
}

static String struct(Object o, structure_Data data) {
  return structure(o, data);
}
static <A, B> Map<A, B> newWeakHashMap() {
  return _registerWeakMap(synchroMap(new WeakHashMap()));
}
// TODO: test if android complains about this
static boolean isAWTThread() {
  if (isAndroid()) return false;
  if (isHeadless()) return false;
  return isAWTThread_awt();
}

static boolean isAWTThread_awt() {
  return SwingUtilities.isEventDispatchThread();
}
static void failIfUnlicensed() {
  assertTrue("license off", licensed());
}
static Thread currentThread() {
  return Thread.currentThread();
}
static Object call_withVarargs(Object o, String method, Object... args) { try {
  if (o == null) return null;
  
  if (o instanceof Class) {
    Class c = (Class) o;
    _MethodCache cache = callOpt_getCache(c);
    
    Method me = cache.findStaticMethod(method, args);
    if (me != null)
      return invokeMethod(me, null, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(method);
    if (methods != null) methodSearch: for (Method m : methods) {
      { if (!(m.isVarArgs())) continue; }
      { if (!(isStaticMethod(m))) continue; }
      Object[] newArgs = massageArgsForVarArgsCall(m, args);
      if (newArgs != null)
        return invokeMethod(m, null, newArgs);
    }
    
    throw fail("Method " + c.getName() + "." + method + "(" + joinWithComma(classNames(args)) + ") not found");
  } else {
    Class c = o.getClass();
    _MethodCache cache = callOpt_getCache(c);

    Method me = cache.findMethod(method, args);
    if (me != null)
      return invokeMethod(me, o, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(method);
    if (methods != null) methodSearch: for (Method m : methods) {
      { if (!(m.isVarArgs())) continue; }
      Object[] newArgs = massageArgsForVarArgsCall(m, args);
      if (newArgs != null)
        return invokeMethod(m, o, newArgs);
    }
    
    throw fail("Method " + c.getName() + "." + method + "(" + joinWithComma(classNames(args)) + ") not found in " + c);
  }
} catch (Exception __e) { throw rethrow(__e); } }
static void lock(Lock lock) { try {
  ping();
  if (lock == null) return;
  try {
    lock.lockInterruptibly();
  } catch (InterruptedException e) {
    print("Locking interrupted! I probably deadlocked, oops.");
    printStackTrace(e);
    rethrow(e);
  }
  ping();
} catch (Exception __e) { throw rethrow(__e); } }

static void lock(Lock lock, String msg) {
  print("Locking: " + msg);
  lock(lock);
}

static void lock(Lock lock, String msg, long timeout) {
  print("Locking: " + msg);
  lockOrFail(lock, timeout);
}

static ReentrantLock lock() {
  return fairLock();
}
static Object makeDependent_postProcess;

static void makeDependent(Object c) {
  if (c == null) return;
  assertTrue("Not a class", c instanceof Class);
  dependentClasses(); // cleans up the list
  hotwire_classes.add(new WeakReference(c));
  
  Object local_log = getOpt(mc(), "local_log");
  if (local_log != null)
    setOpt(c, "local_log", local_log);
    
  /*if (isTrue(getOpt(c, 'ping_actions_shareable)))
    setOpt(c, +ping_actions);*/
    
  Object print_byThread = getOpt(mc(), "print_byThread");
  if (print_byThread != null)
    setOpt(c, "print_byThread", print_byThread);
    
  callF(makeDependent_postProcess, c);
}

static void unlock(Lock lock, String msg) {
  if (lock == null) return;
  print("Unlocking: " + msg);
  lock.unlock();
}

static void unlock(Lock lock) {
  if (lock == null) return;
  lock.unlock();
}
static String lookupStandardFunction(String sfName) {
  return stdFunctions_cached().get(sfName);
}
static String assertIsIdentifier(String s) {
  if (!isIdentifier(s))
    throw fail("Not an identifier: " + quote(s));
  return s;
}

static String assertIsIdentifier(String msg, String s) {
  if (!isIdentifier(s))
    throw fail(msg + " - Not an identifier: " + quote(s));
  return s;
}
static boolean jreplace_multi(List<String> tok, List<String> ins, String out) {
  boolean change = false;
  for (String in : ins)
    if (jreplace(tok, in, out))
      change = true;
  return change;
}

// doesn't loop
static String jreplace_multi(String s, String... replacements) {
  List<String> tok = javaTok(s);
  boolean change = false;
  for (int i = 0; i < l(replacements); i += 2)
    if (jreplace(tok, replacements[i], replacements[i+1]))
      change = true;
  return change ? join(tok) : s;
}


static <A, B> B secondOfPair(Pair<A, B> p) {
  return p == null ? null : p.b;
}
static String getStackTrace2(Throwable e) {
  return hideCredentials(getStackTrace(unwrapTrivialExceptionWraps(e)) + replacePrefix("java.lang.RuntimeException: ", "FAIL: ",
    hideCredentials(str(innerException2(e)))) + "\n");
}
static void rotateStringBuffer(StringBuffer buf, int max) { try {
  if (buf == null) return;
  synchronized(buf) {
    if (buf.length() <= max) return;
    
    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);
    }
    buf.trimToSize();
  }
} catch (Exception __e) { throw rethrow(__e); } }
static void rotateStringBuilder(StringBuilder buf, int max) { try {
  if (buf == null) return;
  synchronized(buf) {
    if (buf.length() <= max) return;
    
    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);
    }
    buf.trimToSize();
  }
} catch (Exception __e) { throw rethrow(__e); } }
static TreeSet<String> caseInsensitiveSet_treeSet() {
  return new TreeSet(caseInsensitiveComparator());
}

static TreeSet<String> caseInsensitiveSet_treeSet(Collection<String> c) {
  return toCaseInsensitiveSet_treeSet(c);
}
static Object callOpt_withVarargs(Object o, String method, Object... args) { try {
  if (o == null) return null;
  
  if (o instanceof Class) {
    Class c = (Class) o;
    _MethodCache cache = callOpt_getCache(c);
    
    Method me = cache.findMethod(method, args);
    if (me == null) {
      // TODO: varargs
      return null;
    }
    if ((me.getModifiers() & Modifier.STATIC) == 0)
      return null;
    return invokeMethod(me, null, args);
  } else {
    Class c = o.getClass();
    _MethodCache cache = callOpt_getCache(c);

    Method me = cache.findMethod(method, args);
    if (me != null)
      return invokeMethod(me, o, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(method);
    if (methods != null) methodSearch: for (Method m : methods) {
      { if (!(m.isVarArgs())) continue; }
      Object[] newArgs = massageArgsForVarArgsCall(m, args);
      if (newArgs != null)
        return invokeMethod(m, o, newArgs);
    }
    
    return null;
  }
} catch (Exception __e) { throw rethrow(__e); } }
static String myJavaSource_code;

static String myJavaSource() {
  return myJavaSource_code;
}
static void setOpt_raw(Object o, String field, Object value) { try {
  if (o == null) return;
  if (o instanceof Class) setOpt_raw((Class) o, field, value);
  else {
    Field f = setOpt_raw_findField(o.getClass(), field);
    if (f != null) {
      makeAccessible(f);
      smartSet(f, o, value);
    }
  }
} catch (Exception __e) { throw rethrow(__e); } }

static void setOpt_raw(Class c, String field, Object value) { try {
  if (c == null) return;
  Field f = setOpt_raw_findStaticField(c, field);
  if (f != null) {
    makeAccessible(f);
    smartSet(f, null, value);
  }
} catch (Exception __e) { throw rethrow(__e); } }
  
static Field setOpt_raw_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}

static Field setOpt_raw_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 void smartSet(Field f, Object o, Object value) throws Exception {
  try {
    f.set(o, value);
  } catch (Exception e) {
    Class type = f.getType();
    
    // take care of common case (long to int)
    if (type == int.class && value instanceof Long)
      value = ((Long) value).intValue();
    
    if (type == LinkedHashMap.class && value instanceof Map)
      { f.set(o, asLinkedHashMap((Map) value)); return; }
    
    
    throw e;
  }
}
static <A extends DynamicObject> A setDyn(A o, String key, Object value) {
  if (o == null) return o;
  setDynObjectValue(o, key, value);
  return o;
}
static Producer<String> javaTokC_noMLS_iterator(final String s) {
  return javaTokC_noMLS_iterator(s, 0);
}

static Producer<String> javaTokC_noMLS_iterator(final String s, final int startIndex) {
  return new Producer<String>() {
    final int l = s.length();
    int i = startIndex;
    
    public String next() {
      if (i >= l) return null;
      
      int j = i;
      char c, d;
      
      // scan for whitespace
      while (j < l) {
        c = s.charAt(j);
        d = j+1 >= l ? '\0' : s.charAt(j+1);
        if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
          ++j;
        else if (c == '/' && d == '*') {
          do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
          j = Math.min(j+2, l);
        } else if (c == '/' && d == '/') {
          do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
        } else
          break;
      }
      
      i = j;
      if (i >= l) return null;
      c = s.charAt(i);
      d = i+1 >= l ? '\0' : s.charAt(i+1);
  
      // 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)));
      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
        ++j;
        
      String t = quickSubstring(s, i, j);
      i = j;
      return t;
    }
  };
}

static Producer<String> javaTokC_noMLS_onReader(final BufferedReader r) {
  final class X implements Producer<String> {
    StringBuilder buf = new StringBuilder(); // stores from "i"
    char c, d, e = 'x'; // just not '\0'
    
    X() {
      // fill c, d and e
      nc();
      nc();
      nc();
    }
    
    // get next character(s) into c, d and e
    void nc() { try {
      c = d;
      d = e;
      if (e == '\0') return;
      int i = r.read();
      e = i < 0 ? '\0'
        : i == '\0' ? '_' // shouldn't happen anymore
        : (char) i;
    } catch (Exception __e) { throw rethrow(__e); } }
    
    void ncSave() {
      if (c != '\0') {
        buf.append(c);
        nc();
      }
    }
    
    public String next() {
      // scan for whitespace
      while (c != '\0') {
        if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
          nc();
        else if (c == '/' && d == '*') {
          do nc(); while (c != '\0' && !(c == '*' && d == '/'));
          nc(); nc();
        } else if (c == '/' && d == '/') {
          do nc(); while (c != '\0' && "\r\n".indexOf(c) < 0);
        } else
          break;
      }
      
      if (c == '\0') return null;

      // scan for non-whitespace
      if (c == '\'' || c == '"') {
        char opener = c;
        ncSave();
        while (c != '\0') {
          if (c == opener || c == '\n') { // end at \n to not propagate unclosed string literal errors
            ncSave();
            break;
          } else if (c == '\\') {
            ncSave();
            ncSave();
          } else
            ncSave();
        }
      } else if (Character.isJavaIdentifierStart(c))
        do ncSave(); while (Character.isJavaIdentifierPart(c) || c == '\''); // for stuff like "don't"
      else if (Character.isDigit(c)) {
        do ncSave(); while (Character.isDigit(c));
        if (c == 'L') ncSave(); // Long constants like 1L
      } else
        ncSave();
        
      String t = buf.toString();
      buf.setLength(0);
      return t;
    }
  }
  
  return new X();
}

static Object _defaultClassFinder_value = defaultDefaultClassFinder();

static Object _defaultClassFinder() {
  return _defaultClassFinder_value;
}
static String unquoteUsingCharArray(String s, char[] buf) {
  if (s == null) return null;
  if (startsWith(s, '[')) {
    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.length() > 1) {
    char c = s.charAt(0);
    if (c == '\"' || c == '\'') {
      int l = endsWith(s, c) ? s.length()-1 : s.length();
      if (l > buf.length) return unquote(s); // fallback
      int n = 0;
  
      for (int i = 1; i < l; i++) {
        char ch = s.charAt(i);
        if (ch == '\\') {
          char nextChar = (i == l - 1) ? '\\' : s.charAt(i + 1);
          // Octal escape?
          if (nextChar >= '0' && nextChar <= '7') {
              String code = "" + nextChar;
              i++;
              if ((i < l - 1) && s.charAt(i + 1) >= '0'
                      && s.charAt(i + 1) <= '7') {
                  code += s.charAt(i + 1);
                  i++;
                  if ((i < l - 1) && s.charAt(i + 1) >= '0'
                          && s.charAt(i + 1) <= '7') {
                      code += s.charAt(i + 1);
                      i++;
                  }
              }
              buf[n++] = (char) Integer.parseInt(code, 8);
              continue;
          }
          switch (nextChar) {
          case '\"': ch = '\"'; break;
          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;
          // Hex Unicode: u????
          case 'u':
              if (i >= l - 5) {
                  ch = 'u';
                  break;
              }
              int code = Integer.parseInt(
                      "" + s.charAt(i + 2) + s.charAt(i + 3)
                         + s.charAt(i + 4) + s.charAt(i + 5), 16);
              char[] x = Character.toChars(code);
              int lx = x.length;
              for (int j = 0; j < lx; j++)
                buf[n++] = x[j];
              i += 5;
              continue;
          default:
            ch = nextChar; // added by Stefan
          }
          i++;
        }
        buf[n++] = ch;
      }
      return new String(buf, 0, n);
    }
  }
    
  return s; // not quoted - return original
}
static boolean structure_isMarker(String s, int i, int j) {
  if (i >= j) return false;
  if (s.charAt(i) != 'm') return false;
  ++i;
  while (i < j) {
    char c = s.charAt(i);
    if (c < '0' || c > '9') return false;
    ++i;
  }
  return true;
}
static String internIfLongerThan(String s, int l) {
  return s == null ? null : l(s) >= l ? intern(s) : s;
}
static char unquoteCharacter(String s) {
  assertTrue(s.startsWith("'") && s.length() > 1);
  return unquote("\"" + s.substring(1, s.endsWith("'") ? s.length()-1 : s.length()) + "\"").charAt(0);
}
static BigInteger parseBigInt(String s) {
  return new BigInteger(s);
}
static double parseDouble(String s) {
  return Double.parseDouble(s);
}
static float parseFloat(String s) {
  return Float.parseFloat(s);
}
static <A> TreeMap<String, A> ciMap() {
  return caseInsensitiveMap();
}
static List parseList(String s) {
  return (List) safeUnstructure(s);
}
static <A> List<A> synchroLinkedList() {
  return Collections.synchronizedList(new LinkedList<A>());
}

static byte[] hexToBytes(String s) {
  if (odd(l(s))) throw fail("Hex string has odd length: " + quote(shorten(10, s)));
  int n = l(s) / 2;
  byte[] bytes = new byte[n];
  for (int i = 0; i < n; i++) {
    int a = parseHexChar(s.charAt(i*2));
    int b = parseHexChar(s.charAt(i*2+1));
    if (a < 0 || b < 0)
      throw fail("Bad hex byte: " + quote(substring(s, i*2, i*2+2)) + " at " + i*2 + "/" + l(s));
    bytes[i] = (byte) ((a << 4) | b);
  }
  return bytes;
}
static boolean[] boolArrayFromBytes(byte[] a, int n) {
  boolean[] b = new boolean[n];
  int m = min(n, l(a)*8);
  for (int i = 0; i < m; i++)
    b[i] = (a[i/8] & 1 << (i & 7)) != 0;
  return b;
}
static HashMap<String,Class> findClass_fullName_cache = new HashMap();

// returns null on not found
// this is the simple version that is not case-tolerant
static Class findClass_fullName(String name) {
  synchronized(findClass_fullName_cache) {
    if (findClass_fullName_cache.containsKey(name))
      return findClass_fullName_cache.get(name);
      
    Class c;
    try {
      c = Class.forName(name);
    } catch (ClassNotFoundException e) {
      c = null;
    }
    findClass_fullName_cache.put(name, c);
    return c;
  }
}
static <A> A nuStubInnerObject(Class<A> c) {
  return nuStubInnerObject(c, null);
}

static <A> A nuStubInnerObject(Class<A> c, Object classFinder) { try {
  Class outerType = getOuterClass(c, classFinder);
  Constructor m = c.getDeclaredConstructor(outerType);
  makeAccessible(m);
  return (A) m.newInstance(new Object[] {null});
} catch (Exception __e) { throw rethrow(__e); } }
static Map<Class, Constructor> nuEmptyObject_cache = newDangerousWeakHashMap();

static <A> A nuEmptyObject(Class<A> c) { try {
  Constructor ctr;
  
  synchronized(nuEmptyObject_cache) {
    ctr = nuEmptyObject_cache.get(c);
    if (ctr == null) {
      nuEmptyObject_cache.put(c, ctr = nuEmptyObject_findConstructor(c));
      makeAccessible(ctr);
    }
  }

  try {
    return (A) ctr.newInstance();
  } catch (InstantiationException e) {
    if (empty(e.getMessage()))
      if ((c.getModifiers() & Modifier.ABSTRACT) != 0)
        throw fail("Can't instantiate abstract class " + className(c), e);
      else
        throw fail("Can't instantiate " + className(c), e);  
    else throw rethrow(e);
  }
} catch (Exception __e) { throw rethrow(__e); } }

static Constructor nuEmptyObject_findConstructor(Class c) {
  for (Constructor m : c.getDeclaredConstructors())
    if (m.getParameterTypes().length == 0)
      return m;
  throw fail("No default constructor declared in " + c.getName());
}

static HashMap<String,Class> findClass_cache = new HashMap();

// 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) {
  synchronized(findClass_cache) {
    if (findClass_cache.containsKey(name))
      return findClass_cache.get(name);
      
    if (!isJavaIdentifier(name)) return null;
    
    Class c;
    try {
      c = Class.forName("main$" + name);
    } catch (ClassNotFoundException e) {
      c = null;
    }
    findClass_cache.put(name, c);
    return c;
  }
}
static void setOptAllDyn(DynamicObject o, Map<String, Object> fields) {
  if (fields == null) return;
  HashMap<String, Field> fieldMap = instanceFieldsMap(o);
  for (Map.Entry<String, Object> e : fields.entrySet()) {
    String field = e.getKey();
    Object val = e.getValue();
    boolean has = fieldMap.containsKey(field);
    if (has)
      setOpt(o, field, val);
    else {
      o.fieldValues = syncMapPut2_createLinkedHashMap(o.fieldValues, intern(field), val);
      
    }
  }
}
static void setOptAll_pcall(Object o, Map<String, Object> fields) {
  if (fields == null) return;
  for (String field : keys(fields))
    try { setOpt(o, field, fields.get(field)); } catch (Throwable __e) { print(exceptionToStringShort(__e)); }
}

static void setOptAll_pcall(Object o, Object... values) {
  //values = expandParams(c.getClass(), values);
  warnIfOddCount(values);
  for (int i = 0; i+1 < l(values); i += 2) {
    String field = (String) values[i];
    Object value = values[i+1];
    try { setOpt(o, field, value); } catch (Throwable __e) { print(exceptionToStringShort(__e)); }
  }
}
static void fixOuterRefs(Object o) { try {
  if (o == null) return;
  Field[] l = thisDollarOneFields(o.getClass());
  if (l.length <= 1) return;
  Object father = null;
  for (Field f : l) {
    father = f.get(o);
    if (father != null) break;
  }
  if (father == null) return;
  for (Field f : l)
    f.set(o, father);
} catch (Exception __e) { throw rethrow(__e); } }
static void setDynObjectValue(DynamicObject o, String field, Object value) {
  o.fieldValues = syncMapPut2_createLinkedHashMap(o.fieldValues, field, value);
}
static String intern(String s) {
  return fastIntern(s);
}
static void pcallOpt_noArgs(Object o, String method) {
  try { callOpt_noArgs(o, method); } catch (Throwable __e) { _handleException(__e); }
}
static int[] toIntArray(Collection<Integer> l) {
  int[] a = new int[l(l)];
  int i = 0;
  if (a.length != 0) for (int x : l)
    a[i++] = x;
  return a;
}
static boolean jmatch(String pat, String s) {
  return jmatch(pat, s, null);
}

static boolean jmatch(String pat, String s, Matches matches) {
  if (s == null) return false;
  return jmatch(pat, javaTok(s), matches);
}

static boolean jmatch(String pat, List<String> toks) {
  return jmatch(pat, toks, null);
}

static boolean jmatch(String pat, List<String> toks, Matches matches) {
  List<String> tokpat = javaTok(pat);
  String[] m = match2(tokpat, toks);
  //print(structure(tokpat) + " on " + structure(toks) + " => " + structure(m));
  if (m == null)
    return false;
  else {
    if (matches != null) matches.m = m;
    return true;
  }
}
static Object nuObject(String className, Object... args) { try {
  return nuObject(classForName(className), args);
} catch (Exception __e) { throw rethrow(__e); } }

// too ambiguous - maybe need to fix some callers
/*static O nuObject(O realm, S className, O... args) {
  ret nuObject(_getClass(realm, className), args);
}*/

static <A> A nuObject(Class<A> c, Object... args) { try {
  if (args.length == 0) return nuObjectWithoutArguments(c); // cached!
  
  Constructor m = nuObject_findConstructor(c, args);
  makeAccessible(m);
  return (A) m.newInstance(args);
} catch (Exception __e) { throw rethrow(__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 fail("Constructor " + c.getName() + getClasses(args) + " not found"
    + (args.length == 0 && (c.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0 ? " - hint: it's a non-static class!" : ""));
}

 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 boolean warn_on = true;
static ThreadLocal<List<String>> warn_warnings = new ThreadLocal();

static void warn(String s) {
  if (warn_on)
    print("Warning: " + s);
}

static void warn(String s, List<String> warnings) {
  warn(s);
  if (warnings != null)
    warnings.add(s);
  addToCollection(warn_warnings.get(), s);
}
static Throwable getInnerException(Throwable e) {
  if (e == null) return null;
  while (e.getCause() != null)
    e = e.getCause();
  return e;
}

static Throwable getInnerException(Runnable r) {
  return getInnerException(getException(r));
}
static void clear(Collection c) {
  if (c != null) c.clear();
}
static <A, B> void put(Map<A, B> map, A a, B b) {
  if (map != null) map.put(a, b);
}

static <A> void put(List<A> l, int i, A a) {
  if (l != null && i >= 0 && i < l(l)) l.set(i, a);
}
static List<Pair> _registerDangerousWeakMap_preList;

static <A> A _registerDangerousWeakMap(A map) {
  return _registerDangerousWeakMap(map, null);
}

static <A> A _registerDangerousWeakMap(A map, Object init) {
  
  callF(init, map);
  
  if (init instanceof String) {
    final String f =  (String) init;
    init = new VF1<Map>() { public void get(Map map) { try {  callMC(f, map) ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "callMC(f, map)"; }};
  }
    
  if (javax() == null) {
    // We're in class init
    if (_registerDangerousWeakMap_preList == null) _registerDangerousWeakMap_preList = synchroList();
    _registerDangerousWeakMap_preList.add(pair(map, init));
    return map;
  }
  
  call(javax(), "_registerDangerousWeakMap", map, init);
  
  return map;
}

static void _onLoad_registerDangerousWeakMap() {
  
  assertNotNull(javax());
  if (_registerDangerousWeakMap_preList == null) return;
  for (Pair p : _registerDangerousWeakMap_preList)
    _registerDangerousWeakMap(p.a, p.b);
  _registerDangerousWeakMap_preList = null;
  
}
static Map synchroMap() {
  return synchroHashMap();
}

static <A, B> Map<A, B> synchroMap(Map<A, B> map) {
  return Collections.synchronizedMap(map);
}
static <A, B> B mapGet2(Map<A, B> map, A a) {
  return map == null ? null : map.get(a);
}

static <A, B> B mapGet2(A a, Map<A, B> map) {
  return map == null ? null : map.get(a);
}
static boolean isSubtypeOf(Class a, Class b) {
  return b.isAssignableFrom(a); // << always hated that method, let's replace it!
}
static Set<String> reflection_classesNotToScan_value = litset(
  "jdk.internal.loader.URLClassPath"
);

static Set<String> reflection_classesNotToScan() {
  return reflection_classesNotToScan_value;
}
static void vmBus_send(String msg, Object... args) {
  Object arg = vmBus_wrapArgs(args);
  pcallFAll(vm_busListeners_live(), msg, arg);
  pcallFAll(vm_busListenersByMessage_live().get(msg), msg, arg);
}

static void vmBus_send(String msg) {
  vmBus_send(msg, (Object) null);
}
static List<String> classNames(Collection l) {
  return getClassNames(l);
}

static List<String> classNames(Object[] l) {
  return getClassNames(Arrays.asList(l));
}
static List<Class> getClasses(Object[] array) {
  List<Class> l = emptyList(l(array));
  for (Object o : array) l.add(_getClass(o));
  return l;
}
static <A, B> void multiMapPut(Map<A, List<B>> map, A a, B b) {
  List<B> l = map.get(a);
  if (l == null)
    map.put(a, l = new ArrayList());
  l.add(b);
}


static Throwable getExceptionCause(Throwable e) {
  Throwable c = e.getCause();
  return c != null ? c : e;
}
static boolean isInstanceX(Class type, Object arg) {
  if (type == boolean.class) return arg instanceof Boolean;
  if (type == int.class) return arg instanceof Integer;
  if (type == long.class) return arg instanceof Long;
  if (type == float.class) return arg instanceof Float;
  if (type == short.class) return arg instanceof Short;
  if (type == char.class) return arg instanceof Character;
  if (type == byte.class) return arg instanceof Byte;
  if (type == double.class) return arg instanceof Double;
  return type.isInstance(arg);
}
static String rep(int n, char c) {
  return repeat(c, n);
}

static String rep(char c, int n) {
  return repeat(c, n);
}

static <A> List<A> rep(A a, int n) {
  return repeat(a, n);
}

static <A> List<A> rep(int n, A a) {
  return repeat(n, a);
}

static String shortenSnippetID(String snippetID) {
  if (snippetID.startsWith("#"))
    snippetID = snippetID.substring(1);
  String httpBlaBla = "http://tinybrain.de/";
  if (snippetID.startsWith(httpBlaBla))
    snippetID = snippetID.substring(httpBlaBla.length());
  return "" + parseLong(snippetID);
}
static String formatSnippetIDOpt(String s) {
  return isSnippetID(s) ? formatSnippetID(s) : s;
}
static Class getMainClass() {
  return mc();
}

static Class getMainClass(Object o) { try {
  if (o == null) return null;
  if (o instanceof Class && eq(((Class) o).getName(), "x30")) return (Class) o;
  return (o instanceof Class ? (Class) o : o.getClass()).getClassLoader().loadClass("main");
} catch (Exception __e) { throw rethrow(__e); } }
static ThreadLocal<VF1<File>> checkFileNotTooBigToRead_tl = new ThreadLocal();

static void checkFileNotTooBigToRead(File f) {
  callF(checkFileNotTooBigToRead_tl.get(), f);
}
static List<CriticalAction> beginCriticalAction_inFlight = synchroList();

static class CriticalAction {
  String description;
  
  CriticalAction() {}
  CriticalAction(String description) {
  this.description = description;}
  
  void done() {
    beginCriticalAction_inFlight.remove(this);
  }
}

static CriticalAction beginCriticalAction(String description) {
  ping();
  CriticalAction c = new CriticalAction(description);
  beginCriticalAction_inFlight.add(c);
  return c;
}

static void cleanMeUp_beginCriticalAction() {
  int n = 0;
  while (nempty(beginCriticalAction_inFlight)) {
    int m = l(beginCriticalAction_inFlight);
    if (m != n) {
      n = m;
      try {
        print("Waiting for " + n2(n, "critical actions") + ": " + join(", ", collect(beginCriticalAction_inFlight, "description")));
      } catch (Throwable __e) { _handleException(__e); }
    }
    sleepInCleanUp(10);
  }
}
public static File mkdirsForFile(File file) {
  File dir = file.getParentFile();
  if (dir != null) { // is null if file is in current dir
    dir.mkdirs();
    if (!dir.isDirectory())
      if (dir.isFile()) throw fail("Please delete the file " + f2s(dir) + " - it is supposed to be a directory!");
      else throw fail("Unknown IO exception during mkdirs of " + f2s(file));
  }
  return file;
}

public static String mkdirsForFile(String path) {
  mkdirsForFile(new File(path));
  return path;
}
static File copyFile(File src, File dest) { try {
  FileInputStream inputStream = new FileInputStream(src.getPath());
  FileOutputStream outputStream = newFileOutputStream(dest.getPath());
  try {
    copyStream(inputStream, outputStream);
    inputStream.close();
  } finally {
    outputStream.close();
  }
  return dest;
} catch (Exception __e) { throw rethrow(__e); } }
static <A extends Throwable> A printStackTrace(A e) {
  // we go to system.out now - system.err is nonsense
  print(getStackTrace(e));
  return e;
}

static void printStackTrace() {
  printStackTrace(new Throwable());
}

static void printStackTrace(String msg) {
  printStackTrace(new Throwable(msg));
}

static void printStackTrace(String msg, Throwable e) {
  printStackTrace(new Throwable(msg, e));
}
static FileOutputStream newFileOutputStream(File path) throws IOException {
  return newFileOutputStream(path.getPath());
}

static FileOutputStream newFileOutputStream(String path) throws IOException {
  return newFileOutputStream(path, false);
}

static FileOutputStream newFileOutputStream(File path, boolean append) throws IOException {
  return newFileOutputStream(path.getPath(), append);
}

static FileOutputStream newFileOutputStream(String path, boolean append) throws IOException {
  mkdirsForFile(path);
  FileOutputStream f = new FileOutputStream(path, append);
  
  _registerIO(f, path, true);
  
  return f;
}
static <A, B> Map<A, B> synchronizedMRUCache(int maxSize) {
  return synchroMap(new MRUCache(maxSize));
}
static void addToContainer(final Container a, final Component b) {
  if (a != null && b != null) { swing(new Runnable() {  public void run() { try {  a.add(b); 
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "a.add(b);"; }}); }
}
static String trimSubstring(String s, int x) {
  return trim(substring(s, x));
}

static String trimSubstring(String s, int x, int y) {
  return trim(substring(s, x, y));
}


static String trimSubstring(String s, IntRange r) {
  return trim(substring(s, r));
}

static int smartLastIndexOf(String s, char c) {
  if (s == null) return 0;
  int i = s.lastIndexOf(c);
  return i >= 0 ? i : l(s);
}
static <A> A proxy(Class<A> intrface, final Object target) {
  if (target == null) return null;
  if (isInstance(intrface, target)) return (A) target;
  return (A) java.lang.reflect.Proxy.newProxyInstance(intrface.getClassLoader(),
   new Class[] { intrface },
   new InvocationHandler() {
     public Object invoke(Object proxy, Method method, Object[] args) {
       return call(target, method.getName(), unnull(args));
     }
   });
}

static <A> A proxy(Object target, Class<A> intrface) {
  return proxy(intrface, target);
}
static Object vm_generalMap_get(Object key) {
  return vm_generalMap().get(key);
}
static File localSnippetFile(long snippetID) {
  return localSnippetsDir(snippetID + ".text");
}

static File localSnippetFile(String snippetID) {
  return localSnippetFile(parseSnippetID(snippetID));
}
static String or2(String a, String b) {
  return nempty(a) ? a : b;
}

static String or2(String a, String b, String c) {
  return or2(or2(a, b), c);
}
static String getFileInfoField(File f, String field) {
  return getOneLineFileInfoField(f, field);
}
static File dropExtension(File f) {
  return f == null ? null : fileInSameDir(f, dropExtension(f.getName()));
}

static String dropExtension(String s) {
  return takeFirst(s, smartLastIndexOf(s, '.'));
}
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 File javaxDataDir(String... subs) {
  return newFile(javaxDataDir(), subs);
}
static String htmlQuery(Map params) {
  return empty(params) ? "" : "?" + makePostData(params);
}

static String htmlQuery(Object... data) {
  return empty(data) ? "" : "?" + makePostData(data);
}
static Object[] muricaCredentials() {
  String pass = muricaPassword();
  return nempty(pass) ? new Object[] {"_pass", pass } : new Object[0];
}




static volatile boolean disableCertificateValidation_attempted = false;

static void disableCertificateValidation() { try {
  if (disableCertificateValidation_attempted) return;
  disableCertificateValidation_attempted = true;
  
  try {
    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] { 
      new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { 
          return new X509Certificate[0]; 
        }
        public void checkClientTrusted(X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(X509Certificate[] certs, String authType) {}
    }};
  
    // Ignore differences between given hostname and certificate hostname
    HostnameVerifier hv = new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) { return true; }
    };
  
    // Install the all-trusting trust manager
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    HttpsURLConnection.setDefaultHostnameVerifier(hv);
  } catch (Throwable __e) { _handleException(__e); }
} catch (Exception __e) { throw rethrow(__e); } }
static boolean networkAllowanceTest(String url) {
  
  
  return isAllowed("networkAllowanceTest", url);
  
}
static final boolean loadPageThroughProxy_enabled = false;

static String loadPageThroughProxy(String url) {
  return null;
}
static void sleepSeconds(double s) {
  if (s > 0) sleep(round(s*1000));
}
static <A> A printWithTime(A a) {
  return printWithTime("", a);
}

static <A> A printWithTime(String s, A a) {
  print(hmsWithColons() + ": " + s, a);
  return a;
}
static String hideCredentials(URL url) { return url == null ? null : hideCredentials(str(url)); }

static String hideCredentials(String url) {
  try {
    if (startsWithOneOf(url, "http://", "https://") && isAGIBlueDomain(hostNameFromURL(url))) return url;
  } catch (Throwable e) {
    print("HideCredentials", e);
  }
  return url.replaceAll("([&?])(_pass|key)=[^&\\s\"]*", "$1$2=<hidden>");
}

static String hideCredentials(Object o) {
  return hideCredentials(str(o));
}
static <A> A getAndClearThreadLocal(ThreadLocal<A> tl) {
  A a = tl.get();
  tl.set(null);
  return a;
}
static void setHeaders(URLConnection con) throws IOException {
  
  String computerID = getComputerID_quick();
  if (computerID != null) try {
    con.setRequestProperty("X-ComputerID", computerID);
    con.setRequestProperty("X-OS", System.getProperty("os.name") + " " + System.getProperty("os.version"));
  } catch (Throwable e) {
    //printShortException(e);
  }
  
}
static Map vm_generalSubMap(Object name) {
  synchronized(get(javax(), "generalMap")) {
    Map map =  (Map) (vm_generalMap_get(name));
    if (map == null)
      vm_generalMap_put(name, map = synchroMap());
    return map;
  }
}

static InputStream urlConnection_getInputStream(URLConnection con) throws IOException {
  UnknownHostException lastException = null;
  for (int _repeat_1134 = 0; _repeat_1134 < 2; _repeat_1134++)  {
    try {
      if (con instanceof HttpURLConnection)
        if (((HttpURLConnection) con).getResponseCode() == 500)
          throw new IOException(joinNemptiesWithColonSpace("Server code 500", tryToReadErrorStreamFromURLConnection(((HttpURLConnection) con))));
      return con.getInputStream();
    } catch (UnknownHostException e) {
      lastException = e;
      print("Retrying because of: " + e);
      continue;
    }
  }
  throw lastException;
}
static GZIPInputStream newGZIPInputStream(File f) {
  return gzInputStream(f);
}

static GZIPInputStream newGZIPInputStream(InputStream in) {
  return gzInputStream(in);
}
static String toHex(byte[] bytes) {
  return bytesToHex(bytes);
}

static String toHex(byte[] bytes, int ofs, int len) {
  return bytesToHex(bytes, ofs, len);
}

static byte[] utf8(String s) {
  return toUtf8(s);
}
static Matcher regexpMatcher(String pat, String s) {
  return compileRegexp(pat).matcher(unnull(s));
}
static URLConnection openConnection(String url) { try {
  return openConnection(new URL(url));
} catch (Exception __e) { throw rethrow(__e); } }

static URLConnection openConnection(URL url) { try {
  ping();
  
  callOpt(javax(), "recordOpenURLConnection", str(url));
  
  return url.openConnection();
} catch (Exception __e) { throw rethrow(__e); } }
static int toInt(Object o) {
  if (o == null) return 0;
  if (o instanceof Number)
    return ((Number) o).intValue();
  if (o instanceof String)
    return parseInt((String) o);
  throw fail("woot not int: " + getClassName(o));
}

static int toInt(long l) {
  if (l != (int) l) throw fail("Too large for int: " + l);
  return (int) l;
}
static URLConnection setURLConnectionTimeouts(URLConnection con, long timeout) {
  con.setConnectTimeout(toInt(timeout));
  con.setReadTimeout(toInt(timeout));
  if (con.getConnectTimeout() != timeout || con.getReadTimeout() != timeout)
    print("Warning: Timeouts not set by JDK.");
  return con;
}
static URLConnection setURLConnectionDefaultTimeouts(URLConnection con, long timeout) {
  if (con.getConnectTimeout() == 0) {
    con.setConnectTimeout(toInt(timeout));
    if (con.getConnectTimeout() != timeout)
      print("Warning: URL connect timeout not set by JDK.");
  }
  if (con.getReadTimeout() == 0) {
    con.setReadTimeout(toInt(timeout));
    if (con.getReadTimeout() != timeout)
      print("Warning: URL read timeout not set by JDK.");
  }
  return con;
}
static boolean saveTextFileIfDifferent(File f, String contents) {
  if (eq(loadTextFile(f), contents)) return false;
  { saveTextFile(f, contents); return true; }
}
// PersistableThrowable doesn't hold GC-disturbing class references in backtrace
static volatile PersistableThrowable lastException_lastException;

static PersistableThrowable lastException() {
  return lastException_lastException;
}

static void lastException(Throwable e) {
  lastException_lastException = persistableThrowable(e);
}
static String baseClassName(String className) {
  return substring(className, className.lastIndexOf('.')+1);
}

static String baseClassName(Object o) {
  return baseClassName(getClassName(o));
}
static String prependIfNempty(String prefix, String s) {
  return empty(s) ? s : prefix + s;
}
static String lastIdentifier(List<String> l) {
  for (int i = l(l)-1; i >= 0; i--) {
    String t = l.get(i);
    if (isIdentifier(t)) return t;
  }
  return null;
}
static List<String> tok_parseArgsList(String s) {
  return tok_parseArgsList(javaTok(s));
}

static List<String> tok_parseArgsList(List<String> tok) {
  return tok_parseArgsList(tok, getBracketMap(tok));
}
  
static List<String> tok_parseArgsList(List<String> tok, Map<Integer, Integer> bracketMap) {
  int i = indexOfAny(tok, 0, "(", "{");
  if (i < 0) return null;
  if (eq(get(tok, i), "{")) return ll(); // e.g. void bla { ... }
  int argStart = (i += 2);
  List<String> args = new ArrayList();
  while (i < l(tok)-2 && neq(get(tok, i), ")")) {
    Integer j = bracketMap.get(i);
    if (j != null) { i = j+2; continue; }
    if (eqGetOneOf(tok, i, ",")) {
      if (i > argStart) args.add(trimJoinSubList(tok, argStart, i));
      argStart = i+2;
    }
    i += 2;
  }
  if (i > argStart) args.add(trimJoinSubList(tok, argStart, i));
  return args;
}
static HashSet<String> getBracketMapIncludingAngleBrackets_opening = lithashset("(", "{", "<");
static HashSet<String> getBracketMapIncludingAngleBrackets_closing = lithashset(")", "}", ">");

static Map<Integer, Integer> getBracketMapIncludingAngleBrackets(List tok) {
  return getBracketMap(tok, getBracketMapIncludingAngleBrackets_opening, getBracketMapIncludingAngleBrackets_closing);
}

static Map<Integer, Integer> getBracketMapIncludingAngleBrackets(List tok, int from, int to) {
  return getBracketMap(tok, getBracketMapIncludingAngleBrackets_opening, getBracketMapIncludingAngleBrackets_closing, from, to);
}
static String beautifyStructure(String s) {
  List<String> tok = javaTokForStructure(s);
  structure_addTokenMarkers(tok);
  jreplace(tok, "lhm", "");
  return join(tok);
}
static String struct_noStringSharing(Object o) {
  structure_Data d = new structure_Data();
  d.noStringSharing = true;
  return structure(o, d);
}
static TreeSet<String> toCaseInsensitiveSet(Iterable<String> c) {
  if (isCISet(c)) return (TreeSet) c;
  TreeSet<String> set = caseInsensitiveSet();
  addAll(set, c);
  return set;
}

static TreeSet<String> toCaseInsensitiveSet(String... x) {
  TreeSet<String> set = caseInsensitiveSet();
  addAll(set, x);
  return set;
}
// works on lists and strings and null

static int indexOfIgnoreCase(List<String> a, String b) {
  return indexOfIgnoreCase(a, b, 0);
}

static int indexOfIgnoreCase(List<String> a, String b, int i) {
  int n = a == null ? 0 : a.size();
  for (; i < n; i++)
    if (eqic(a.get(i), b)) return i;
  return -1;
}

static int indexOfIgnoreCase(String a, String b) {
  return indexOfIgnoreCase_manual(a, b);
  /*Matcher m = Pattern.compile(b, Pattern.CASE_INSENSITIVE + Pattern.LITERAL).matcher(a);
  if (m.find()) return m.start(); else ret -1;*/
}
static boolean boolOptParam(ThreadLocal<Boolean> tl) {
  return isTrue(optPar(tl));
}

// defaults to false
static boolean boolOptParam(Object[] __, String name) {
  return isTrue(optParam(__, name));
}

static boolean boolOptParam(String name, Object[] __) {
  return boolOptParam(__, name);
}

static boolean boolOptParam(String name, Map __) {
  return isTrue(optPar(name, __));
}
static String getInnerMessage(Throwable e) {
  if (e == null) return null;
  return getInnerException(e).getMessage();
}
static Matcher regexpIC(Pattern pat, String s) {
  return pat.matcher(unnull(s));
}

static Matcher regexpIC(String pat, String s) {
  return compileRegexpIC(pat).matcher(unnull(s));
}

static Pattern regexpIC(String pat) {
  return compileRegexpIC(pat);
}
// usually L<S>
static String fromLines(Iterable lines) {
  StringBuilder buf = new StringBuilder();
  if (lines != null)
    for (Object line : lines)
      buf.append(str(line)).append('\n');
  return buf.toString();
}

static String fromLines(String... lines) {
  return fromLines(asList(lines));
}
static IterableIterator<String> toLines(File f) {
  return linesFromFile(f);
}

static List<String> toLines(String s) {
  List<String> lines = new ArrayList<String>();
  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;
}

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 actualUserHome_value;
static String actualUserHome() {
  if (actualUserHome_value == null) {
    if (isAndroid())
      actualUserHome_value = "/storage/emulated/0/";
    else
      actualUserHome_value = System.getProperty("user.home");
  }
  return actualUserHome_value;
}

static File actualUserHome(String sub) {
  return newFile(new File(actualUserHome()), sub);
}
static File userDir() {
  return new File(userHome());
}

static File userDir(String path) {
  return new File(userHome(), path);
}
static File getProgramDir() {
  return programDir();
}

static File getProgramDir(String snippetID) {
  return programDir(snippetID);
}
// scans a Java construct (class, method) and checks its modifiers
static boolean hasModifier(List<String> tok, String modifier) {
  for (int i = 1; i < tok.size() && getJavaModifiers().contains(tok.get(i)); i += 2)
    if (tok.get(i).equals(modifier))
      return true;
  return false;
}

static <A> A optParam(ThreadLocal<A> tl, A defaultValue) {
  return optPar(tl, defaultValue);
}

static <A> A optParam(ThreadLocal<A> tl) {
  return optPar(tl);
}

static Object optParam(String name, Map params) {
  return mapGet(params, name);
}

// now also takes a map as single array entry
static <A> A optParam(Object[] opt, String name, A defaultValue) {
  int n = l(opt);
  if (n == 1 && opt[0] instanceof Map) {
    Map map =  (Map) (opt[0]);
    return map.containsKey(name) ? (A) map.get(name) : defaultValue;
  }
  if (!even(l(opt))) throw fail("Odd parameter length");
  for (int i = 0; i < l(opt); i += 2)
    if (eq(opt[i], name))
      return (A) opt[i+1];
  return defaultValue;
}

static Object optParam(Object[] opt, String name) {
  return optParam(opt, name, null);
}
static int minUnlessMinus1(int a, int b) {
  return a == -1 ? b : b == -1 ? a : min(a, b);
}
static <A, B> Pair<A, B> pair(A a, B b) {
  return new Pair(a, b);
}

static <A> Pair<A, A> pair(A a) {
  return new Pair(a, a);
}
static int hashCode(Object a) {
  return a == null ? 0 : a.hashCode();
}
static <A, B> List<B> pairsB(Collection<Pair<A, B>> l) {
  return secondOfPairs(l);
}
static String reversedString(String s) {
  return reverseString(s);
}
static <A> List<Integer> indicesOf(List<A> l, A a) {
  if (l == null) return null;
  
  
  if (l instanceof IContentsIndexedList)
    return intArrayToList(((IContentsIndexedList) l).indicesOf(a));
  
  
  List<Integer> x = new ArrayList();
  for (int i = 0; i < l(l); i++)
    if (eq(l.get(i), a))
      x.add(i);
  return x;
}
static <A> Iterator<A> iterator(Iterable<A> c) {
  return c == null ? emptyIterator() : c.iterator();
}
static char charAt(String s, int i) {
  return s != null && i >= 0 && i < s.length() ? s.charAt(i) : '\0';
}
static void tokAppend_reTok(List<String> tok, int i, String s) {
  tok.set(i, tok.get(i)+s);
  reTok(tok, i, i+1);
}
static List<String> allToUpper(Collection<String> l) {
  List<String> x = new ArrayList(l(l));
  if (l != null) for (String s : l) x.add(upper(s));
  return x;
}
static void removeSubList(List l, int from, int to) {
  if (l != null) subList(l, from, to).clear();
}

static void removeSubList(List l, int from) {
  if (l != null) subList(l, from).clear();
}
static <A, B extends A> void copyListPart(List<B> a, int i1, List<A> b, int i2, int n) {
  if (a == null || b == null) return;
  for (int i = 0; i < n; i++)
    b.set(i2+i, a.get(i1+i));
}
static boolean nemptyString(String s) {
  return s != null && s.length() > 0;
}
static int listL(Collection l) {
  return l == null ? 0 : l.size();
}
static char lastChar(String s) {
  return empty(s) ? '\0' : s.charAt(l(s)-1);
}
static String[] dropLast(String[] a, int n) {
  n = Math.min(n, a.length);
  String[] b = new String[a.length-n];
  System.arraycopy(a, 0, b, 0, b.length);
  return b;
}

static <A> List<A> dropLast(List<A> l) {
  return subList(l, 0, l(l)-1);
}

static <A> List<A> dropLast(int n, List<A> l) {
  return subList(l, 0, l(l)-n);
}

static <A> List<A> dropLast(Iterable<A> l) {
  return dropLast(asList(l));
}

static String dropLast(String s) {
  return substring(s, 0, l(s)-1);
}

static String dropLast(String s, int n) {
  return substring(s, 0, l(s)-n);
}

static String dropLast(int n, String s) {
  return dropLast(s, n);
}

// i must point at the opening bracket ("<")
// index returned is index of closing bracket or comma 
static int tok_findEndOfTypeArg(List<String> cnc, int i) {
  int j = i+2, level = 1;
  while (j < cnc.size()) {
    String t = cnc.get(j);
    if (t.equals("<")) ++level;
    else if (t.equals(">")) --level;
    if (level == 0 || level == 1 && eq(t, ","))
      return j;
    ++j;
  }
  return cnc.size();
}

static List<String> getJavaModifiers_list = litlist("static", "abstract", "public", "private", "protected", "final", "native", "volatile", "synchronized", "transient");

static List<String> getJavaModifiers() {
  return getJavaModifiers_list;
}
// returns index of "{" or ";"
static int tok_findEndOfMethodHeader(List<String> tok, int i) {
  i |= 1;
  int level = 0;
  while (i < l(tok)) {
    String t = tok.get(i);
    if (eq(t, "(")) ++level;
    else if (eq(t, ")")) --level;
    else if (level == 0 && eqOneOf(t, "{", ";")) return i;
    i += 2;
  }
  return i;
}
static <A> int indexOfOneOf(List<A> l, int i, A... x) {
  return indexOfAny(l, i, x);
}



static <A> int indexOfOneOf(List<A> l, Collection<A> x) {
  return indexOfAny(l, x);
}



static <A> int indexOfOneOf(List<A> l, int i, Collection<A> x) {
  return indexOfAny(l, i, x);
}



static int indexOfOneOf(String s, int i, String chars) {
  return indexOfAny(s, i, chars);
}
static Object evalJava(String code) {
  return evalJava_main(evalJava_prep(code));
}
static ThreadLocal<Boolean> assertVerbose_value = new ThreadLocal();

static void assertVerbose(boolean b) {
  assertVerbose_value.set(b);
}

static boolean assertVerbose() { return isTrue(assertVerbose_value.get()); }
static <A> A assertEqualsVerbose(Object x, A y) {
  assertEqualsVerbose((String) null, x, y);
  return y;
}

static <A> A assertEqualsVerbose(String msg, Object x, A y) {
  if (!eq(x, y)) {
    

    throw fail((msg != null ? msg + ": " : "") + /*sfu*/(y) + " != " + /*sfu*/(x));
  } else
    print("OK: " + /*sfu*/(x));
  return y;
}


static byte[] boolArrayToBytes(boolean[] a) {
  byte[] b = new byte[(l(a)+7)/8];
  for (int i = 0; i < l(a); i++)
    if (a[i])
      b[i/8] |= 1 << (i & 7);
  return b;
}
static String shortClassName(Object o) {
  if (o == null) return null;
  Class c = o instanceof Class ? (Class) o : o.getClass();
  String name = c.getName();
  return shortenClassName(name);
}
static int cmp(Number a, Number b) {
  return a == null ? b == null ? 0 : -1 : cmp(a.doubleValue(), b.doubleValue());
}

static int cmp(double a, double b) {
  return a < b ? -1 : a == b ? 0 : 1;
}

static int cmp(Object a, Object b) {
  if (a == null) return b == null ? 0 : -1;
  if (b == null) return 1;
  return ((Comparable) a).compareTo(b);
}
static boolean isDigit(char c) {
  return Character.isDigit(c);
}
static boolean endsWithLetter(String s) {
  return nempty(s) && isLetter(last(s));
}
static void assertFalse(Object o) {
  if (!(eq(o, false) /*|| isFalse(pcallF(o))*/))
    throw fail(str(o));
}
  
static boolean assertFalse(boolean b) {
  if (b) throw fail("oops");
  return b;
}

static boolean assertFalse(String msg, boolean b) {
  if (b) throw fail(msg);
  return b;
}

static boolean _inCore() {
  return false;
}
static int isAndroid_flag;

static boolean isAndroid() {
  if (isAndroid_flag == 0)
    isAndroid_flag = System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0 ? 1 : -1;
  return isAndroid_flag > 0;
}

static List hotwire_copyOver_after = synchroList();

static void hotwire_copyOver(Class c) {
  // TODO: make a mechanism for making such "inheritable" fields
  for (String field : ll("print_log", "print_silent", "androidContext", "_userHome"))
    setOptIfNotNull(c, field, getOpt(mc(), field));
    
  
  
  setOptIfNotNull(c, "mainBot" , getMainBot());
  setOpt(c, "creator_class" , new WeakReference(mc()));
  pcallFAll(hotwire_copyOver_after, c);
}
static Pair<Integer, String> testBracketHygiene2(String s) {
  return testBracketHygiene2(s, testBracketHygiene_op, testBracketHygiene_close);
}

static Pair<Integer, String> testBracketHygiene2(String s, String op, String close) {
  List<String> tok = javaTok(s);
  Map<Integer,Integer> map = new HashMap();
  List<Integer> stack = getBracketMap2(tok, map, op, close);
  
  for (int i = 1; i < l(tok); i += 2)
    if (isUnproperlyQuoted(tok.get(i))) {
      int lineNr = tokenIndexToUserLandLineNr(tok, i);
      return pair(tokenToCharIndex(tok, i), "Unclosed string literal at line " + lineNr);
    }

  for (int i : keys(map)) {
    int j = map.get(i);
    String a = tok.get(i), b = tok.get(j);
    int ai = op.indexOf(a), bi = close.indexOf(b);
    if (ai != bi) {
      int lineNr1 = tokenIndexToUserLandLineNr(tok, i);
      int lineNr2 = tokenIndexToUserLandLineNr(tok, j);
      return pair(tokenToCharIndex(tok, /*tossCoin() ?*/ i /*: j*/),
        "Bad hygiene - brackets don't match (" + quote(a) + " vs " + quote(b) + ") at lines " + lineNr1 + " and " + lineNr2);
    }
  }
  
  if (nempty(stack)) {
    int lineNr = tokenIndexToUserLandLineNr(tok, last(stack));
    return pair(tokenIndexToCharIndex(tok, last(stack)), "Line " + lineNr + ": "
      + "Bad hygiene - " + n(l(stack), "bracket") + " not closed");
  }

  if (map.containsKey(0)) {
    int lineNr = tokenIndexToUserLandLineNr(tok, map.get(0));
    return pair(tokenToCharIndex(tok, map.get(0)),
      "Bad hygiene - bracket not opened (" + quote(tok.get(map.get(0))) + ") at line " + lineNr);
  }
      
  return null;
}
static <A> List<A> wrapArrayAsList(A[] a) {
  return a == null ? null : Arrays.asList(a);
}
static String afterLineBreak(String s) {
  return substring(s, lastIndexOf(s, '\n')+1);
}
static String beforeLineBreak(String s) {
  int i = smartIndexOf(s, '\n');
  if (i > 0 && s.charAt(i-1) == '\r') --i;
  return substring(s, 0, i);
}
static byte[] toUtf8(String s) { try {
  return s.getBytes("UTF-8");
} catch (Exception __e) { throw rethrow(__e); } }
static boolean md5OfFile_verbose = false;

static String md5OfFile(String path) {
  return md5OfFile(newFile(path));
}

static String md5OfFile(File f) { try {
  if (!f.exists()) return "-";
  
  if (md5OfFile_verbose)
    print("Getting MD5 of " + f);
  
  MessageDigest md5 = MessageDigest.getInstance("MD5");
   FileInputStream in = new FileInputStream(f); try {

  byte buf[] = new byte[65536];
  int l;
  while (true) {
    l = in.read(buf);
    if (l <= 0) break;
    md5.update(buf, 0, l);
  }
  
  return bytesToHex(md5.digest());
} finally { _close(in); }} catch (Exception __e) { throw rethrow(__e); } }
static String standardCredentialsUser() {
  return trim(loadTextFile(
    oneOfTheFiles(
      javaxSecretDir("tinybrain-username"),
      userDir(".tinybrain/username"))));
}
static String standardCredentialsPass() {
  return trim(loadTextFile(
    oneOfTheFiles(
      javaxSecretDir("tinybrain-userpass"),
      userDir(".tinybrain/userpass"))));
}
static boolean isProperlyQuoted(String s) {
  return s.length() >= 2
    && s.startsWith("\"")
    && s.endsWith("\"")
    && (!s.endsWith("\\\"") || s.endsWith("\\\\\""))
    && !containsNewLine(s);
}
static Matcher regexp(String pat, String s) {
  return regexp(compileRegexp(pat), unnull(s));
}

static Matcher regexp(Pattern pat, String s) {
  return pat.matcher(unnull(s));
}

static Pattern regexp(String pat) {
  return compileRegexp(pat);
}
static boolean jsonDecode_useOrderedMaps = true;

static Object jsonDecode(final String text) {
  final List<String> tok = jsonTok(text);
  if (l(tok) == 1) return null;
  
  class Y {
    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("{"))
        return parseMap();
      if (t.equals("["))
        return this.parseList(); // avoid loading standard function "parseList"
      if (t.equals("null")) {
        i += 2; return null;
      }
      if (t.equals("false")) {
        i += 2; return false;
      }
      if (t.equals("true")) {
        i += 2; return true;
      }
      boolean minus = false;
      if (t.equals("-")) {
        minus = true;
        i += 2;
        t = get(tok, i);
      }
      if (isInteger(t)) {
        i += 2;
        if (eq(get(tok, i), ".")) {
          String x = t + "." + get(tok, i+2);
          i += 4;
          double d = parseDouble(x);
          if (minus) d = -d;
          return d;
        } else {
          long l = parseLong(t);
          if (minus) l = -l;
          return l != (int) l ? new Long(l) : new Integer((int) l);
        }
      }
      
      throw new RuntimeException("Unknown token " + (i+1) + ": " + t + ": " + text);
    }
    
    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 parseMap() {
      consume("{");
      Map map = jsonDecode_useOrderedMaps ? new LinkedHashMap() : new TreeMap();
      while (!tok.get(i).equals("}")) {
        String key = unquote(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())));
        throw fail(quote(s) + " expected: " + prevToken + " " + nextTokens + " (" + i + "/" + tok.size() + ")");
      }
      i += 2;
    }
  }
  
  return new Y().parse();
}
static String makePostData(Map<Object, Object> map) {
  StringBuilder buf = new StringBuilder();
  for (Map.Entry<Object, Object> e : map.entrySet()) {
    String key =  (String) (e.getKey());
    Object val = e.getValue();
    if (val != null) {
      String value = str(val);
      if (nempty(buf)) buf.append("&");
      buf.append(urlencode(key)).append("=").append(urlencode(/*escapeMultichars*/(value)));
    }
  }
  return str(buf);
}

static String makePostData(Object... params) {
  StringBuilder buf = new StringBuilder();
  int n = l(params);
  for (int i = 0; i+1 < n; i += 2) {
    String key =  (String) (params[i]);
    Object val = params[i+1];
    if (val != null) {
      String value = str(val);
      if (nempty(buf)) buf.append("&");
      buf.append(urlencode(key)).append("=").append(urlencode(/*escapeMultichars*/(value)));
    }
  }
  return str(buf);

}

static <A> A optPar(ThreadLocal<A> tl, A defaultValue) {
  A a = tl.get();
  if (a != null) {
    tl.set(null);
    return a;
  }
  return defaultValue;
}

static <A> A optPar(ThreadLocal<A> tl) {
  return optPar(tl, null);
}

static Object optPar(Object[] params, String name) {
  return optParam(params, name);
}

static Object optPar(String name, Object[] params) {
  return optParam(params, name);
}

static Object optPar(String name, Map params) {
  return optParam(name, params);
}

static <A> A optPar(Object[] params, String name, A defaultValue) {
  return optParam(params, name, defaultValue);
}

static <A> A optPar(String name, Object[] params, A defaultValue) {
  return optParam(params, name, defaultValue);
}
static int lUtf8(String s) {
  return l(utf8(s));
}
static String loadPage_utf8(URL url) {
  return loadPage_utf8(url.toString());
}

static String loadPage_utf8(String url) {
   AutoCloseable __1290 = tempSetTL(loadPage_charset, "UTF-8"); try {
  return loadPage(url);
} finally { _close(__1290); }}

static String loadPage_utf8(URLConnection con, URL url, boolean addHeaders) throws IOException {
   AutoCloseable __1292 = tempSetTL(loadPage_charset, "UTF-8"); try {
  return loadPage(con, url, addHeaders);
} finally { _close(__1292); }}
static List<String> tlft(String s) { return toLinesFullTrim(s); }
static List<String> tlft(File f) { return toLinesFullTrim(f); }

static String joinLines(List<String> lines) {
  return fromLines(lines);
}

static String joinLines(String glue, String text) {
  return join(glue, toLines(text));
}
static String javaDropComments(String s) {
  return javaDropAllComments(s);
}
// returns index of trailing N token
static int scanToEndOfInitializer(List<String> tok, Map<Integer, Integer> bracketMap, int i) {
  while (i < l(tok)) {
    if (eqOneOf(tok.get(i), ";", ",", ")", "}"))
      return i-1;
    Integer j = bracketMap.get(i);
    if (j != null)
      i = j+1;
    else
      i++;
  }
  return i;
}
static boolean odd(int i) {
  return (i & 1) != 0;
}

static boolean odd(long i) {
  return (i & 1) != 0;
}

static boolean odd(BigInteger i) { return odd(toInt(i)); }
static List _registerWeakMap_preList;

static <A> A _registerWeakMap(A map) {
  if (javax() == null) {
    // We're in class init
    if (_registerWeakMap_preList == null) _registerWeakMap_preList = synchroList();
    _registerWeakMap_preList.add(map);
    return map;
  }
  
  try {
    call(javax(), "_registerWeakMap", map);
  } catch (Throwable e) {
    printException(e);
    print("Upgrade JavaX!!");
  }
  return map;
}

static void _onLoad_registerWeakMap() {
  assertNotNull(javax());
  if (_registerWeakMap_preList == null) return;
  for (Object o : _registerWeakMap_preList)
    _registerWeakMap(o);
  _registerWeakMap_preList = null;
}
static Boolean isHeadless_cache;

static boolean isHeadless() {
  if (isHeadless_cache != null) return isHeadless_cache;
  if (isAndroid()) return isHeadless_cache = true;
  if (GraphicsEnvironment.isHeadless()) return isHeadless_cache = true;
  
  // Also check if AWT actually works.
  // If DISPLAY variable is set but no X server up, this will notice.
  
  try {
    SwingUtilities.isEventDispatchThread();
    return isHeadless_cache = false;
  } catch (Throwable e) { return isHeadless_cache = true; }
}
static final Map<Class, _MethodCache> callOpt_cache = newDangerousWeakHashMap();

static Object callOpt_cached(Object o, String methodName, Object... args) { try {
  if (o == null) return null;
  
  if (o instanceof Class) {
    Class c = (Class) o;
    _MethodCache cache = callOpt_getCache(c);
    
    // TODO: (super-rare) case where method exists static and non-static
    // with different args
    
    Method me = cache.findMethod(methodName, args);
    if (me == null || (me.getModifiers() & Modifier.STATIC) == 0) return null;
    return invokeMethod(me, null, args);
  } else {
    Class c = o.getClass();
    _MethodCache cache = callOpt_getCache(c);

    Method me = cache.findMethod(methodName, args);
    if (me == null) return null;
    return invokeMethod(me, o, args);
  }
} catch (Exception __e) { throw rethrow(__e); } }

static _MethodCache callOpt_getCache(Class c) {
  synchronized(callOpt_cache) {
    _MethodCache cache = callOpt_cache.get(c);
    if (cache == null)
      callOpt_cache.put(c, cache = new _MethodCache(c));
    return cache;
  }
}
static boolean isStaticMethod(Method m) {
  return methodIsStatic(m);
}
static Object[] massageArgsForVarArgsCall(Method m, Object[] args) {
  Class<?>[] types = m.getParameterTypes();
  int n = types.length-1, nArgs = args.length;
  if (nArgs < n) return null;
  for (int i = 0; i < n; i++)
    if (!argumentCompatibleWithType(args[i], types[i]))
      return null;
  Class varArgType = types[n].getComponentType();
  for (int i = n; i < nArgs; i++)
    if (!argumentCompatibleWithType(args[i], varArgType))
      return null;
  Object[] newArgs = new Object[n+1];
  arraycopy(args, 0, newArgs, 0, n);
  Object[] varArgs = arrayOfType(varArgType, nArgs-n);
  arraycopy(args, n, varArgs, 0, nArgs-n);
  newArgs[n] = varArgs;
  return newArgs;
}
static void lockOrFail(Lock lock, long timeout) { try {
  ping();
  if (!lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
    String s = "Couldn't acquire lock after " + timeout + " ms.";
    if (lock instanceof ReentrantLock) {
      ReentrantLock l =  (ReentrantLock) lock;
      s += " Hold count: " + l.getHoldCount() + ", owner: " + call(l, "getOwner");
    }
    throw fail(s);
  }
  ping();
} catch (Exception __e) { throw rethrow(__e); } }
static ReentrantLock fairLock() {
  return new ReentrantLock(true);
}
static List<Class> dependentClasses() {
  return cleanUpAndGetWeakReferencesList(hotwire_classes);
}
static Map<String, String> stdFunctions_cached_map; // name -> snippet ID
static Lock stdFunctions_cached_lock = lock();

static Map<String, String> stdFunctions_cached() {
  Lock __1318 = stdFunctions_cached_lock; lock(__1318); try {
  if (stdFunctions_cached_map == null)
    stdFunctions_cached_map = stdFunctions_uncached();
  return stdFunctions_cached_map;
} finally { unlock(__1318); } }

static synchronized void stdFunctions_clearCache() {
  stdFunctions_cached_map = null;
}


static List<WeakReference<Class>> hotwire_classes = synchroList();

static Class<?> hotwireDependent(String src) {
  Class c = hotwire(src);
  makeDependent(c);
  return c;
}

static String getStackTrace(Throwable throwable) {
  lastException(throwable);
  return getStackTrace_noRecord(throwable);
}

static String getStackTrace_noRecord(Throwable throwable) {
  StringWriter writer = new StringWriter();
  throwable.printStackTrace(new PrintWriter(writer));
  return hideCredentials(writer.toString());
}

static String getStackTrace() {
  return getStackTrace_noRecord(new Throwable());
}
static Throwable unwrapTrivialExceptionWraps(Throwable e) {
  if (e == null) return e;
  while (e.getClass() == RuntimeException.class
    && e.getCause() != null && eq(e.getMessage(), str(e.getCause())))
    e = e.getCause();
  return e;
}
static String replacePrefix(String prefix, String replacement, String s) {
  if (!startsWith(s, prefix)) return s;
  return replacement + substring(s, l(prefix));
}
static Throwable innerException2(Throwable e) {
  if (e == null) return null;
  while (empty(e.getMessage()) && e.getCause() != null)
    e = e.getCause();
  return e;
}
static Comparator<String> caseInsensitiveComparator() {
  
  
  return betterCIComparator();
  
}
static TreeSet<String> toCaseInsensitiveSet_treeSet(Iterable<String> c) {
  if (isCISet(c)) return (TreeSet) c;
  TreeSet<String> set = caseInsensitiveSet_treeSet();
  addAll(set, c);
  return set;
}

static TreeSet<String> toCaseInsensitiveSet_treeSet(String... x) {
  TreeSet<String> set = caseInsensitiveSet_treeSet();
  addAll(set, x);
  return set;
}
static <A, B> LinkedHashMap<A, B> asLinkedHashMap(Map<A, B> map) {
  if (map instanceof LinkedHashMap) return (LinkedHashMap) map;
  LinkedHashMap<A,B> m = new LinkedHashMap();
  if (map != null) synchronized(collectionMutex(map)) {
    m.putAll(map);
  }
  return m;
}
static String quickSubstring(String s, int i, int j) {
  if (i == j) return "";
  return s.substring(i, j);
}
static Object defaultDefaultClassFinder() {
  return new F1<String, Class>() { Class get(String name) { try { 
    Class c = findClass_fullName(name);
    if (c != null) return c;
    if (startsWith(name, "loadableUtils.utils$"))
      return findClass_fullName("main" + substring(name, 19));
    return null;
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "Class c = findClass_fullName(name);\r\n    if (c != null) ret c;\r\n    if (start..."; }};
}
static <A> TreeMap<String, A> caseInsensitiveMap() {
  return new TreeMap(caseInsensitiveComparator());
}
static Object safeUnstructure(String s) {
  return unstructure(s, true);
}
static int parseHexChar(char c) {
  if (c >= '0' && c <= '9') return charDiff(c, '0');
  if (c >= 'a' && c <= 'f') return charDiff(c, 'a')+10;
  if (c >= 'A' && c <= 'F') return charDiff(c, 'A')+10;
  return -1;
}
static Class getOuterClass(Class c) {
  return getOuterClass(c, null);
}

static Class getOuterClass(Class c, Object classFinder) { try {
  String s = c.getName();
  int i = s.lastIndexOf('$');
  String name = substring(s, 0, i);
  if (classFinder != null) return (Class) callF(classFinder, name);
  return Class.forName(name);
} catch (Exception __e) { throw rethrow(__e); } }
static HashMap<String, Field> instanceFieldsMap(Object o) {
  Class c = o.getClass();
  HashMap<String, Field> map;
  synchronized(getOpt_cache) {
    map = getOpt_cache.get(c);
    if (map == null)
      map = getOpt_makeCache(c);
  }
  return map;
}

static <A, B> LinkedHashMap<A, B> syncMapPut2_createLinkedHashMap(LinkedHashMap<A, B> map, A key, B value) {
  if (key != null)
    if (value != null) {
      if (map == null) map = new LinkedHashMap();
      synchronized(collectionMutex(map)) { map.put(key, value); }
    } else if (map != null) synchronized(collectionMutex(map)) { map.remove(key); }
  return map;
}
static void warnIfOddCount(Object... list) {
  if (odd(l(list)))
    printStackTrace("Odd list size: " + list);
}


static Map<Class, Field[]> thisDollarOneFields_cache = newDangerousWeakHashMap();

static Field[] thisDollarOneFields(Class c) {
  synchronized(thisDollarOneFields_cache) {
    Field[] l = thisDollarOneFields_cache.get(c);
    if (l == null)
      thisDollarOneFields_cache.put(c, l = thisDollarOneFields_uncached(c));
    return l;
  }
}

static Field[] thisDollarOneFields_uncached(Class c) {
  List<Field> fields = new ArrayList();
  do {
    for (Field f : c.getDeclaredFields())
      if (f.getName().equals("this$1"))
        fields.add(makeAccessible(f));
    c = c.getSuperclass();
  } while (c != null);
  return toArray(new Field[l(fields)], fields);
}


static Method fastIntern_method;

static String fastIntern(String s) { try {
  if (s == null) return null;
  if (fastIntern_method == null) {
    fastIntern_method = findMethodNamed(javax(), "internPerProgram");
    if (fastIntern_method == null) upgradeJavaXAndRestart();
  }
    
  return (String) fastIntern_method.invoke(null, s);
} catch (Exception __e) { throw rethrow(__e); } }
static Map<Class, HashMap<String, Method>> callOpt_noArgs_cache = newDangerousWeakHashMap();

static Object callOpt_noArgs(Object o, String method) { try {
  if (o == null) return null;
  if (o instanceof Class)
    return callOpt(o, method); // not optimized
  
  Class c = o.getClass();
  HashMap<String, Method> map;
  synchronized(callOpt_noArgs_cache) {
    map = callOpt_noArgs_cache.get(c);
    if (map == null)
      map = callOpt_noArgs_makeCache(c);
  }

  Method m = map.get(method);
  return m != null ? m.invoke(o) : null;
} catch (Exception __e) { throw rethrow(__e); } }

// used internally - we are in synchronized block
static HashMap<String, Method> callOpt_noArgs_makeCache(Class c) {
  HashMap<String,Method> map = new HashMap();
  Class _c = c;
  do {
    for (Method m : c.getDeclaredMethods())
      if (m.getParameterTypes().length == 0 
        && !reflection_isForbiddenMethod(m)) {
        makeAccessible(m);
        String name = m.getName();
        if (!map.containsKey(name))
          map.put(name, m);
      }
    _c = _c.getSuperclass();
  } while (_c != null);
  callOpt_noArgs_cache.put(c, map);
  return map;
}
// match2 matches multiple "*" (matches a single token) wildcards and zero or one "..." wildcards (matches multiple tokens)

static String[] match2(List<String> pat, List<String> tok) {
  // standard case (no ...)
  int i = pat.indexOf("...");
  if (i < 0) return match2_match(pat, tok);
  
  pat = new ArrayList<String>(pat); // We're modifying it, so copy first
  pat.set(i, "*");
  while (pat.size() < tok.size()) {
    pat.add(i, "*");
    pat.add(i+1, ""); // doesn't matter
  }
  
  return match2_match(pat, tok);
}

static String[] match2_match(List<String> pat, List<String> tok) {
  List<String> result = new ArrayList<String>();
  if (pat.size() != tok.size()) {
    
    return null;
  }
  for (int i = 1; i < pat.size(); i += 2) {
    String p = pat.get(i), t = tok.get(i);
    
    if (eq(p, "*"))
      result.add(t);
    else if (!equalsIgnoreCase(unquote(p), unquote(t))) // bold change - match quoted and unquoted now
      return null;
  }
  return result.toArray(new String[result.size()]);
}

static Map<String, Class> classForName_cache = synchroHashMap();

static Class classForName(String name) { try {
  if (classForName_cache == null) return Class.forName(name); // in class init
  Class c = classForName_cache.get(name);
  if (c == null)
    classForName_cache.put(name, c = Class.forName(name));
  return c;
} catch (Exception __e) { throw rethrow(__e); } }
static Map<Class, Constructor> nuObjectWithoutArguments_cache = newDangerousWeakHashMap();

static Object nuObjectWithoutArguments(String className) { try {
  return nuObjectWithoutArguments(classForName(className));
} catch (Exception __e) { throw rethrow(__e); } }

static <A> A nuObjectWithoutArguments(Class<A> c) { try {
  if (nuObjectWithoutArguments_cache == null)
    // in class init
    return (A) nuObjectWithoutArguments_findConstructor(c).newInstance();
    
  Constructor m = nuObjectWithoutArguments_cache.get(c);
  if (m == null)
    nuObjectWithoutArguments_cache.put(c, m = nuObjectWithoutArguments_findConstructor(c));
  return (A) m.newInstance();
} catch (Exception __e) { throw rethrow(__e); } }

static Constructor nuObjectWithoutArguments_findConstructor(Class c) {
  for (Constructor m : c.getDeclaredConstructors())
    if (empty(m.getParameterTypes())) {
      makeAccessible(m);
      return m;
    }
  throw fail("No default constructor found in " + c.getName());
}

static Throwable getException(Runnable r) {
  try {
    callF(r);
    return null;
  } catch (Throwable e) {
    return e;
  }
}
static <A> A assertNotNull(A a) {
  assertTrue(a != null);
  return a;
}

static <A> A assertNotNull(String msg, A a) {
  assertTrue(msg, a != null);
  return a;
}
static Map synchroHashMap() {
  return Collections.synchronizedMap(new HashMap());
}

static <A> HashSet<A> litset(A... items) {
  return lithashset(items);
}
static Object vmBus_wrapArgs(Object... args) {
  return empty(args) ? null
    : l(args) == 1 ? args[0]
    : args;
}
static void pcallFAll(Collection l, Object... args) {
  if (l != null) for (Object f : cloneList(l)) pcallF(f, args);
}

static void pcallFAll(Iterator it, Object... args) {
  while (it.hasNext()) pcallF(it.next(), args);
}
static Set vm_busListeners_live_cache;
static Set vm_busListeners_live() { if (vm_busListeners_live_cache == null) vm_busListeners_live_cache = vm_busListeners_live_load(); return vm_busListeners_live_cache; }

static Set vm_busListeners_live_load() {
  return vm_generalIdentityHashSet("busListeners");
}
static Map<String, Set> vm_busListenersByMessage_live_cache;
static Map<String, Set> vm_busListenersByMessage_live() { if (vm_busListenersByMessage_live_cache == null) vm_busListenersByMessage_live_cache = vm_busListenersByMessage_live_load(); return vm_busListenersByMessage_live_cache; }

static Map<String, Set> vm_busListenersByMessage_live_load() {
  return vm_generalHashMap("busListenersByMessage");
}
static List<String> getClassNames(Collection l) {
  List<String> out = new ArrayList();
  if (l != null) for (Object o : l)
    out.add(o == null ? null : getClassName(o));
  return out;
}
static Class<?> _getClass(String name) {
  try {
    return Class.forName(name);
  } catch (ClassNotFoundException e) {
    return null; // could optimize this
  }
}

static Class _getClass(Object o) {
  return o == null ? null
    : o instanceof Class ? (Class) o : o.getClass();
}

static Class _getClass(Object realm, String name) {
  try {
    return getClass(realm).getClassLoader().loadClass(classNameToVM(name));
  } catch (ClassNotFoundException e) {
    return null; // could optimize this
  }
}
static String repeat(char c, int n) {
  n = Math.max(n, 0);
  char[] chars = new char[n];
  for (int i = 0; i < n; i++)
    chars[i] = c;
  return new String(chars);
}

static <A> List<A> repeat(A a, int n) {
  n = Math.max(n, 0);
  List<A> l = new ArrayList(n);
  for (int i = 0; i < n; i++)
    l.add(a);
  return l;
}

static <A> List<A> repeat(int n, A a) {
  return repeat(a, n);
}
static String n2(long l) { return formatWithThousands(l); }
static String n2(Collection l) { return n2(l(l)); }

static String n2(double l, String singular) {
  return n2(l, singular, singular + "s");
}

static String n2(double l, String singular, String plural) {
  if (fraction(l) == 0)
    return n2((long) l, singular, plural);
  else
    return l + " " + plural;
}

static String n2(long l, String singular, String plural) {
  return n_fancy2(l, singular, plural);
}

static String n2(long l, String singular) {
  return n_fancy2(l, singular, singular + "s");
}

static String n2(Collection l, String singular) {
  return n2(l(l), singular);
}

static String n2(Collection l, String singular, String plural) {
  return n_fancy2(l, singular, plural);
}

static String n2(Map m, String singular, String plural) {
  return n_fancy2(m, singular, plural);
}

static String n2(Map m, String singular) {
  return n2(l(m), singular);
}

static String n2(Object[] a, String singular) { return n2(l(a), singular); }
static String n2(Object[] a, String singular, String plural) { return n_fancy2(a, singular, plural); }


  static String n2(MultiSet ms, String singular, String plural) {
    return n_fancy2(ms, singular, plural);
  }

static List collect(Collection c, String field) {
  return collectField(c, field);
}

static List collect(String field, Collection c) {
  return collectField(c, field);
}

/*ifclass Concept
static L collect(Class c, S field) {
  ret collect(list(c), field);
}
endif
TODO: make translator ignore stuff in ifclass until resolved
*/
static void sleepInCleanUp(long ms) { try {
  if (ms < 0) return;
  Thread.sleep(ms);
} catch (Exception __e) { throw rethrow(__e); } }
static void copyStream(InputStream in, OutputStream out) { try {
  byte[] buf = new byte[65536];
  while (true) {
    int n = in.read(buf);
    if (n <= 0) return;
    out.write(buf, 0, n);
  }
} catch (Exception __e) { throw rethrow(__e); } }
static void _registerIO(Object object, String path, boolean opened) {
}
static Object swing(Object f) {
  return swingAndWait(f);
}

static <A> A swing(F0<A> f) {
  return (A) swingAndWait(f);
}

static <A> A swing(IF0<A> f) {
  return (A) swingAndWait(f);
}
static boolean isInstance(Class type, Object arg) {
  return type.isInstance(arg);
}
static Map vm_generalMap_map;

static Map vm_generalMap() {
  if (vm_generalMap_map == null)
    vm_generalMap_map = (Map) get(javax(), "generalMap");
  return vm_generalMap_map;
}
static File localSnippetsDir() {
  return javaxDataDir("Personal Programs");
}

static File localSnippetsDir(String sub) {
  return newFile(localSnippetsDir(), sub);
}
static String getOneLineFileInfoField(File f, String field) {
  File infoFile = associatedInfosFile(f);
  List<String> lines = lines(loadTextFile(infoFile));
  return firstStartingWithIC_drop(lines, field + ": ");
}
static File fileInSameDir(File f, String newName) {
  return newFile(parentFile(f), newName);
}
static <A> List<A> takeFirst(List<A> l, int n) {
  return l(l) <= n ? l : newSubListOrSame(l, 0, n);
}

static <A> List<A> takeFirst(int n, List<A> l) {
  return takeFirst(l, n);
}

static String takeFirst(int n, String s) { return substring(s, 0, n); }
static String takeFirst(String s, int n) { return substring(s, 0, n); }

static <A> List<A> takeFirst(int n, Iterable<A> i) {
  List l = new ArrayList();
  Iterator<A> it = i.iterator();
  for (int _repeat_1416 = 0; _repeat_1416 < n; _repeat_1416++)  { if (it.hasNext()) l.add(it.next()); else break; }
  return l;
}

static volatile boolean muricaPassword_pretendNotAuthed = false;

static String muricaPassword() {
  if (muricaPassword_pretendNotAuthed) return null;
  return trim(loadTextFile(muricaPasswordFile()));
}
static volatile Object isAllowed_function; // func(S, O[]) -> bool
static volatile boolean isAllowed_all = true;

static boolean isAllowed(String askingMethod, Object... args) {
  // check on VM level
  Object f = vm_generalMap_get("isAllowed_function");
  if (f != null && !isTrue(callF(f, askingMethod, args))) return false;
  
  // check locally
  return isAllowed_all || isTrue(callF(isAllowed_function, askingMethod, args));
}
static volatile boolean sleep_noSleep = false;

static void sleep(long ms) {
  ping();
  if (ms < 0) return;
  // allow spin locks
  if (isAWTThread() && ms > 100) throw fail("Should not sleep on AWT thread");
  try {
    Thread.sleep(ms);
  } catch (Exception e) { throw new RuntimeException(e); }
}

static void sleep() { try {
  if (sleep_noSleep) throw fail("nosleep");
  print("Sleeping.");
  sleepQuietly();
} catch (Exception __e) { throw rethrow(__e); } }
static long round(double d) {
  return Math.round(d);
}


static String hmsWithColons() {
  return hmsWithColons(now());
}

static String hmsWithColons(long time) {
  return new SimpleDateFormat("HH:mm:ss").format(time);
}

static boolean startsWithOneOf(String s, String... l) {
  for (String x : l) if (startsWith(s, x)) return true; return false;
}
static boolean isAGIBlueDomain(String domain) {
  return domainIsUnder(domain, theAGIBlueDomain());
}
static String hostNameFromURL(String url) { try {
  return new URL(url).getHost();
} catch (Exception __e) { throw rethrow(__e); } }
static String getComputerID_quick() {
  return computerID();
}
static Object vm_generalMap_put(Object key, Object value) {
  return mapPutOrRemove(vm_generalMap(), key, value);
}
static String joinNemptiesWithColonSpace(String... strings) {
  return joinNempties(": ", strings);
}

static String joinNemptiesWithColonSpace(Collection<String> strings) {
  return joinNempties(": ", strings);
}
static String tryToReadErrorStreamFromURLConnection(URLConnection conn) { try {
  if (conn instanceof HttpURLConnection)
    return stream2string(((HttpURLConnection) conn).getErrorStream()); // TODO: ensure some max length
  return null;
} catch (Throwable __e) { return null; } }
static int gzInputStream_defaultBufferSize = 65536;

static GZIPInputStream gzInputStream(File f) { try {
  return gzInputStream(new FileInputStream(f));
} catch (Exception __e) { throw rethrow(__e); } }

static GZIPInputStream gzInputStream(File f, int bufferSize) { try {
  return gzInputStream(new FileInputStream(f), bufferSize);
} catch (Exception __e) { throw rethrow(__e); } }

static GZIPInputStream gzInputStream(InputStream in) {
  return gzInputStream(in, gzInputStream_defaultBufferSize);
}

static GZIPInputStream gzInputStream(InputStream in, int bufferSize) { try {
  return _registerIOWrap(new GZIPInputStream(in, gzInputStream_defaultBufferSize), in);
} catch (Exception __e) { throw rethrow(__e); } }
static Map<String, java.util.regex.Pattern> compileRegexp_cache = syncMRUCache(10);

static java.util.regex.Pattern compileRegexp(String pat) {
  java.util.regex.Pattern p = compileRegexp_cache.get(pat);
  if (p == null) {
    
    compileRegexp_cache.put(pat, p = java.util.regex.Pattern.compile(pat));
  }
  return p;
}
static List<String> javaTokForStructure(String s) {
  return javaTok_noMLS(s);
}
static String structure_addTokenMarkers(String s) {
  return join(structure_addTokenMarkers(javaTokForStructure(s)));
}
  
static List<String> structure_addTokenMarkers(List<String> tok) {
  // find references
  
  TreeSet<Integer> refs = new TreeSet();
  for (int i = 1; i < l(tok); i += 2) {
    String t = tok.get(i);
    if (t.startsWith("t") && isInteger(t.substring(1)))
      refs.add(parseInt(t.substring(1)));
  }
  
  if (empty(refs)) return tok;
  
  // add markers
  for (int i : refs) {
    int idx = i*2+1;
    if (idx >= l(tok)) continue; // broken structure
    String t = "";
    if (endsWithLetterOrDigit(tok.get(idx-1))) t = " ";
    tok.set(idx, t + "m" + i + " " + tok.get(idx));
  }
  
  return tok;
}
static boolean isCISet(Iterable<String> l) {
  return l instanceof TreeSet && ((TreeSet) l).comparator() == caseInsensitiveComparator();
}
static int indexOfIgnoreCase_manual(String a, String b) {
  int la = strL(a), lb = strL(b);
  if (la < lb) return -1;
  int n = la-lb;
  
  loop: for (int i = 0; i <= n; i++) {
    for (int j = 0; j < lb; j++) {
      char c1 = a.charAt(i+j), c2 = b.charAt(j);
      if (!eqic(c1, c2))
        continue loop;
    }
    return i;
  }
  return -1;
}
static Map<String, java.util.regex.Pattern> compileRegexpIC_cache = syncMRUCache(10);

static java.util.regex.Pattern compileRegexpIC(String pat) {
  java.util.regex.Pattern p = compileRegexpIC_cache.get(pat);
  if (p == null) {
    
    try {
      compileRegexpIC_cache.put(pat, p = java.util.regex.Pattern.compile(pat, Pattern.CASE_INSENSITIVE));
    } catch (PatternSyntaxException e) {
      throw rethrow(wrapPatternSyntaxException(e));
    }
  }
  return p;
}
static CloseableIterableIterator<String> linesFromFile(File f) { try {
  if (!f.exists()) return emptyCloseableIterableIterator();
  
  if (ewic(f.getName(), ".gz"))
    return linesFromReader(utf8bufferedReader(newGZIPInputStream(f)));
  
  return linesFromReader(utf8bufferedReader(f));
} catch (Exception __e) { throw rethrow(__e); } }

static CloseableIterableIterator<String> linesFromFile(String path) {
  return linesFromFile(newFile(path));
}
static File programDir_mine; // set this to relocate program's data

static File programDir() {
  return programDir(getProgramID());
}

static File programDir(String snippetID) {
  boolean me = sameSnippetID(snippetID, programID());
  if (programDir_mine != null && me)
    return programDir_mine;
  File dir = new File(javaxDataDir(), formatSnippetIDOpt(snippetID));
  if (me) {
    String c = caseID();
    if (nempty(c)) dir = newFile(dir, c);
  }
  return dir;
}

static File programDir(String snippetID, String subPath) {
  return new File(programDir(snippetID), subPath);
}
static boolean even(int i) {
  return (i & 1) == 0;
}

static boolean even(long i) {
  return (i & 1) == 0;
}
static String reverseString(String s) {
  return new StringBuilder(s).reverse().toString();
}
static <A> ArrayList<Integer> intArrayToList(int[] a) {
  if (a == null) return null;
  ArrayList < Integer > l = new ArrayList(a.length);
  for (int i : a) l.add(i);
  return l;
}
static String upper(String s) {
  return s == null ? null : s.toUpperCase();
}

static char upper(char c) {
  return Character.toUpperCase(c);
}
static Object evalJava_main(String main) {
  return callCalc(evalJava_prep2(main));
}
static String evalJava_prep(String code) {
  return evalJava_prep(code, "calc");
}

static String evalJava_prep(String code, String mainName) {
  return evalJava_prep(code, mainName, "");
}

static ThreadLocal<String> evalJava_prep_args = new ThreadLocal();
static ThreadLocal<Boolean> evalJava_prep_voidMagic = new ThreadLocal(); // set to false to avoid looking up standard function return types

static String evalJava_prep(String code, String mainName, String preCode) {
  List<String> tok = javaTok(trim(code));
  
  if (!isFalse(evalJava_prep_voidMagic.get()) && tok_shouldAddReturn(tok) && eqGet(tok, 3, "(") && isIdentifier(get(tok, 1)) && isKnownVoidFunction(get(tok, 1)))
    tokAppend_reTok(tok, l(tok)-2, ";");
    
  code = tok_addReturn(tok);
  String returnType = containsReturnWithArgument(code) ? "O" : "void";
  String main =
    "static " + returnType + " " + mainName + "(" + unnull(evalJava_prep_args.get()) + ") throws Exception {\n" 
      + preCode + code + "\n" + "}";
  return main;
}
static String shortenClassName(String name) {
  if (name == null) return null;
  int i = lastIndexOf(name, "$");
  if (i < 0) i = lastIndexOf(name, ".");
  return i < 0 ? name : substring(name, i+1);
}
static boolean isLetter(char c) {
  return Character.isLetter(c);
}
static void setOptIfNotNull(Object o, String field, Object value) {
  if (value != null) setOpt(o, field, value);
}
static Object mainBot;

static Object getMainBot() {
  return mainBot;
}
static List<Integer> getBracketMap2(List<String> tok, Map<Integer, Integer> map, String openingBrackets, String closingBrackets) {
  map.clear();
  List<Integer> stack = new ArrayList();
  for (int i = 1; i < l(tok); i+= 2) {
    String t = tok.get(i);
    if (l(t) == 1)
      if (openingBrackets.contains(t))
        stack.add(i);
      else if (closingBrackets.contains(tok.get(i)))
        map.put(empty(stack) ? 0 : liftLast(stack), i);
  }
  return stack;
}
static int tokenIndexToUserLandLineNr(List<String> tok, int tokenIndex) {
  return charIndexToUserLandLineNr(join(tok), tokenIndexToCharIndex(tok, tokenIndex));
}
static int tokenToCharIndex(List<String> tok, int tokenIndex) {
  if (tokenIndex < 0) return -1;
  int idx = 0;
  tokenIndex = min(tokenIndex, l(tok));
  for (int i = 0; i < tokenIndex; i++) idx += l(tok.get(i));
  return idx;
}
static int tokenIndexToCharIndex(List<String> tok, int tokenIndex) {
  return tokenToCharIndex(tok, tokenIndex);
}
static String n(long l, String name) {
  return l + " " + trim(l == 1 ? singular(name) : getPlural(name));
}

static String n(Collection l, String name) {
  return n(l(l), name);
}

static String n(Map m, String name) {
  return n(l(m), name);
}

static String n(Object[] a, String name) {
  return n(l(a), name);
}


  static String n(MultiSet ms, String name) {
    return n(l(ms), name);
  }

static File oneOfTheFiles(String... paths) {
  if (paths != null) for (String path : paths)
    if (fileExists(path))
      return newFile(path);
  return null;
}

static File oneOfTheFiles(File... files) {
  if (files != null) for (File f : files)
    if (fileExists(f))
      return f;
  return null;
}
static File javaxSecretDir_dir; // can be set to work on different base dir

static File javaxSecretDir() {
  return javaxSecretDir_dir != null ? javaxSecretDir_dir : new File(userHome(), "JavaX-Secret");
}

static File javaxSecretDir(String sub) {
  return newFile(javaxSecretDir(), sub);
}
static List<String> jsonTok(String s) {
  List<String> tok = new ArrayList();
  int l = l(s);
  
  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

    // scan for non-whitespace (json strings, "null" identifier, numbers. everything else automatically becomes a one character token.)
    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.isLetter(c))
      do ++j; while (j < l && Character.isLetter(s.charAt(j)));
    else if (Character.isDigit(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 <A> AutoCloseable tempSetTL(ThreadLocal<A> tl, A a) {
  return tempSetThreadLocal(tl, a);
}
static List<String> toLinesFullTrim(String s) {
  
  List<String> l = new ArrayList();
  for (String line : toLines(s)) if (nempty(line = trim(line))) l.add(line);
  return l;
}

static List<String> toLinesFullTrim(File f) {
  List<String> l = new ArrayList();
  for (String line : linesFromFile(f)) if (nempty(line = trim(line))) l.add(line);
  return l;
}

static String javaDropAllComments(String s) {
  return join(javaDropAllComments(javaTok(s)));
}

static List<String> javaDropAllComments(List<String> tok) {
  for (int i = 0; i < l(tok); i += 2)
    tok.set(i, tok_javaDropCommentsFromWhitespace(tok.get(i)));
  return tok;
}
static <A extends Throwable> A printException(A e) {
  printStackTrace(e);
  return e;
}
static boolean methodIsStatic(Method m) {
  return (m.getModifiers() & Modifier.STATIC) != 0;
}
static boolean argumentCompatibleWithType(Object arg, Class type) {
  return arg == null ? !type.isPrimitive() : isInstanceX(type, arg);
}
static void arraycopy(Object[] a, Object[] b) {
  if (a != null && b != null)
    arraycopy(a, 0, b, 0, min(a.length, b.length));
}

static void arraycopy(Object src, int srcPos, Object dest, int destPos, int n) {
  if (n != 0)
    System.arraycopy(src, srcPos, dest, destPos, n);
}
static <A> A[] arrayOfType(Class<A> type, int n) {
  return makeArray(type, n);
}

static <A> A[] arrayOfType(int n, Class<A> type) {
  return arrayOfType(type, n);
}
static <A> List<A> cleanUpAndGetWeakReferencesList(List<WeakReference<A>> l) {
  if (l == null) return null;
  synchronized(l) {
    List<A> out = new ArrayList();
    for (int i = 0; i < l(l); i++) {
      A a = l.get(i).get();
      if (a == null)
        l.remove(i--);
      else
        out.add(a);
    }
    return out;
  }
}
static Map<String, String> stdFunctions_uncached() {
  return stdFunctions_uncached(new HashMap());
}

static Map<String, String> stdFunctions_uncached(Map<String, String> map) {
  parseStdFunctionsList(loadSnippetSilently("#1006654"), map);
  parseStdFunctionsList(loadSnippetSilently("#761"), map);
  return map;
}


static betterCIComparator_C betterCIComparator_instance;

static betterCIComparator_C betterCIComparator() {
  if (betterCIComparator_instance == null)
    betterCIComparator_instance = new betterCIComparator_C();
  return betterCIComparator_instance;
}

static class betterCIComparator_C implements Comparator<String> {
  public int compare(String s1, String s2) {
    if (s1 == null) return s2 == null ? 0 : -1;
    if (s2 == null) return 1;
  
    int n1 = s1.length();
    int n2 = s2.length();
    int min = Math.min(n1, n2);
    for (int i = 0; i < min; i++) {
        char c1 = s1.charAt(i);
        char c2 = s2.charAt(i);
        if (c1 != c2) {
            c1 = Character.toUpperCase(c1);
            c2 = Character.toUpperCase(c2);
            if (c1 != c2) {
                c1 = Character.toLowerCase(c1);
                c2 = Character.toLowerCase(c2);
                if (c1 != c2) {
                    // No overflow because of numeric promotion
                    return c1 - c2;
                }
            }
        }
    }
    return n1 - n2;
  }
}
static int charDiff(char a, char b) {
  return (int) a-(int) b;
}

static int charDiff(String a, char b) {
  return charDiff(stringToChar(a), b);
}
static Object[] toArray(Collection c) {
  return toObjectArray(c);
}

static <A> A[] toArray(Collection c, Class<A> type) {
  A[] a = arrayOfType(l(c), type);
  if (a.length == 0) return a;
  asList(c).toArray(a);
  return a;
}

// array must have correct length and will be filled
static <A> A[] toArray(A[] array, Collection c) {
  if (array == null || c == null) return null;
  asList(c).toArray(array);
  return array;
}
static void upgradeJavaXAndRestart() {
  
    run("#1001639");
    restart();
    sleep();
  
  
}
static boolean reflection_isForbiddenMethod(Method m) {
  return m.getDeclaringClass() == Object.class
    && eqOneOf(m.getName(), "finalize", "clone", "registerNatives");
}
static boolean equalsIgnoreCase(String a, String b) {
  return eqic(a, b);
}

static boolean equalsIgnoreCase(char a, char b) {
  return eqic(a, b);
}
static Object pcallF(Object f, Object... args) {
  return pcallFunction(f, args);
}


static <A> A pcallF(F0<A> f) { try {
  return f == null ? null : f.get();
} catch (Throwable __e) { return null; } }



static <A, B> B pcallF(F1<A, B> f, A a) { try {
  return f == null ? null : f.get(a);
} catch (Throwable __e) { return null; } }



static <A> void pcallF(VF1<A> f, A a) {
  try {
    if (f != null) f.get(a);
  } catch (Throwable __e) { _handleException(__e); }
}

static Set vm_generalIdentityHashSet(Object name) {
  synchronized(get(javax(), "generalMap")) {
    Set set =  (Set) (vm_generalMap_get(name));
    if (set == null)
      vm_generalMap_put(name, set = syncIdentityHashSet());
    return set;
  }
}

static Map vm_generalHashMap(Object name) {
  synchronized(get(javax(), "generalMap")) {
    Map m =  (Map) (vm_generalMap_get(name));
    if (m == null)
      vm_generalMap_put(name, m = syncHashMap());
    return m;
  }
}

static Class<?> getClass(String name) {
  try {
    return Class.forName(name);
  } catch (ClassNotFoundException e) {
    return null;
  }
}

static Class getClass(Object o) {
  return o instanceof Class ? (Class) o : o.getClass();
}

static Class getClass(Object realm, String name) { try {
  try {
    return getClass(realm).getClassLoader().loadClass(classNameToVM(name));
  } catch (ClassNotFoundException e) {
    return null;
  }
} catch (Exception __e) { throw rethrow(__e); } }
static String classNameToVM(String name) {
  return name.replace(".", "$");
}
static String formatWithThousands(long l) {
  return formatWithThousandsSeparator(l);
}
static double fraction(double d) {
  return d % 1;
}
static String n_fancy2(long l, String singular, String plural) {
  return formatWithThousandsSeparator(l) + " " + trim(l == 1 ? singular : plural);
}

static String n_fancy2(Collection l, String singular, String plural) {
  return n_fancy2(l(l), singular, plural);
}

static String n_fancy2(Map m, String singular, String plural) {
  return n_fancy2(l(m), singular, plural);
}

static String n_fancy2(Object[] a, String singular, String plural) {
  return n_fancy2(l(a), singular, plural);
}


  static String n_fancy2(MultiSet ms, String singular, String plural) {
    return n_fancy2(l(ms), singular, plural);
  }

static List collectField(Collection c, String field) {
  List l = new ArrayList();
  if (c != null) for (Object a : c)
    l.add(getOpt(a, field));
  return l;
}

static List collectField(String field, Collection c) {
  return collectField(c, field);
}
static void swingAndWait(Runnable r) { try {
  if (isAWTThread())
    r.run();
  else
    EventQueue.invokeAndWait(addThreadInfoToRunnable(r));
} catch (Exception __e) { throw rethrow(__e); } }

static Object swingAndWait(final Object f) {
  if (isAWTThread())
    return callF(f);
  else {
    final Var result = new Var();
    swingAndWait(new Runnable() {  public void run() { try { 
      result.set(callF(f));
    
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "result.set(callF(f));"; }});
    return result.get();
  }
}
static File associatedInfosFile(File f) {
  return replaceExtension(f, ".infos");
}
static String firstStartingWithIC_drop(Collection<String> l, final String prefix) {
  for (String s : unnull(l))
    if (swic(s, prefix))
      return substring(s, l(prefix));
  return null;
}

static String firstStartingWithIC_drop(String prefix, Collection<String> l) {
  return firstStartingWithIC_drop(l, prefix);
}
static File parentFile(File f) {
  return dirOfFile(f);
}
static <A> List<A> newSubListOrSame(List<A> l, int startIndex) {
  return newSubListOrSame(l, startIndex, l(l));
}

static <A> List<A> newSubListOrSame(List<A> l, int startIndex, int endIndex) {
  if (l == null) return null;
  int n = l(l);
  startIndex = max(0, startIndex);
  endIndex = min(n, endIndex);
  if (startIndex >= endIndex) return ll();
  if (startIndex == 0 && endIndex == n) return l;
  return cloneList(l.subList(startIndex, endIndex));
}


static <A> List<A> newSubListOrSame(List<A> l, IntRange r) {
  return newSubListOrSame(l, r.start, r.end);
}

static File muricaPasswordFile() {
  return new File(javaxSecretDir(), "murica/muricaPasswordFile");
}
static Object sleepQuietly_monitor = new Object();

static void sleepQuietly() { try {
  assertFalse(isAWTThread());
  synchronized(sleepQuietly_monitor) { sleepQuietly_monitor.wait(); }
} catch (Exception __e) { throw rethrow(__e); } }
static boolean domainIsUnder(String domain, String mainDomain) {
  return eqic(domain, mainDomain) || ewic(domain, "." + mainDomain);
}
static String theAGIBlueDomain() {
  return "agi.blue";
}
static String _computerID;
static Lock computerID_lock = lock();

public static String computerID() {
  if (_computerID == null) {
    Lock __1561 = computerID_lock; lock(__1561); try {
    if (_computerID != null) return _computerID;
    File file = computerIDFile();
    _computerID = loadTextFile(file.getPath());
    if (_computerID == null) {
      // legacy load
      _computerID = loadTextFile(userDir(".tinybrain/computer-id"));
      if (_computerID == null)
        _computerID = makeRandomID(12, new SecureRandom());
      saveTextFile(file, _computerID);
    }
  } finally { unlock(__1561); } }
  return _computerID;
}
static <A, B> B mapPutOrRemove(Map<A, B> map, A key, B value) {
  if (map != null && key != null)
    if (value != null) return map.put(key, value);
    else return map.remove(key);
  return null;
}
static String joinNempties(String sep, String... strings) {
  return joinStrings(sep, strings);
}

static String joinNempties(String sep, Collection<String> strings) {
  return joinStrings(sep, strings);
}
static String stream2string(InputStream in) {
  return utf8streamToString(in);
}
static <A> A _registerIOWrap(A wrapper, Object wrapped) {
  return wrapper;
}
static <A, B> Map<A, B> syncMRUCache(int size) {
  return synchroMap(new MRUCache(size));
}
static List<String> javaTok_noMLS(String s) {
  ArrayList<String> tok = new ArrayList();
  int l = s == null ? 0 : s.length();
  
  int i = 0, n = 0;
  while (i < l) {
    int j = i;
    char c, d;
    
    // scan for whitespace
    while (j < l) {
      c = s.charAt(j);
      d = j+1 >= l ? '\0' : s.charAt(j+1);
      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
        ++j;
      else if (c == '/' && d == '*') {
        do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
        j = Math.min(j+2, l);
      } else if (c == '/' && d == '/') {
        do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
      } else
        break;
    }
    
    tok.add(javaTok_substringN(s, i, j));
    ++n;
    i = j;
    if (i >= l) break;
    c = s.charAt(i);
    d = i+1 >= l ? '\0' : s.charAt(i+1);

    // scan for non-whitespace
    
    if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        int c2 = s.charAt(j);
        if (c2 == opener || c2 == '\n' && opener == '\'') { // allow multi-line strings, but not for '
          ++j;
          break;
        } else if (c2 == '\\' && 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
      ++j;
      
    tok.add(javaTok_substringC(s, i, j));
    ++n;
    i = j;
  }
  
  if ((tok.size() % 2) == 0) tok.add("");
  return tok;
}
static RuntimeException wrapPatternSyntaxException(PatternSyntaxException e) {
  if (e == null) return null;
  String pat = e.getPattern();
  int i = e.getIndex();
  return new RuntimeException("Regular expression error between " + multiLineQuoteWithSpaces(substring(pat, 0, i)) + " and " + multiLineQuoteWithSpaces(substring(pat, i)) + " - " + e.getMessage());
}
static CloseableIterableIterator emptyCloseableIterableIterator_instance = new CloseableIterableIterator() {
  public Object next() { throw fail(); }
  public boolean hasNext() { return false; }
};

static <A> CloseableIterableIterator<A> emptyCloseableIterableIterator() {
  return emptyCloseableIterableIterator_instance; 
}
static boolean ewic(String a, String b) {
  return endsWithIgnoreCase(a, b);
}


static boolean ewic(String a, String b, Matches m) {
  return endsWithIgnoreCase(a, b, m);
}

static CloseableIterableIterator<String> linesFromReader(Reader r) {
  final BufferedReader br = bufferedReader(r);
  return iteratorFromFunction_f0_autoCloseable(new F0<String>() { String get() { try {  return readLineFromReaderWithClose(br);  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "ret readLineFromReaderWithClose(br);"; }}, _wrapIOCloseable(r));
}
static BufferedReader utf8bufferedReader(InputStream in) { try {
  return bufferedReader(_registerIOWrap(new InputStreamReader(in, "UTF-8"), in));
} catch (Exception __e) { throw rethrow(__e); } }

static BufferedReader utf8bufferedReader(File f) { try {
  return utf8bufferedReader(newFileInputStream(f));
} catch (Exception __e) { throw rethrow(__e); } }
static volatile String caseID_caseID;

static String caseID() { return caseID_caseID; }

static void caseID(String id) {
  caseID_caseID = id;
}
static Object callCalc(Object o, Object... args) {
  return call(o, "calc", args);
}


// flag is per thread, cache is per program (in-memory cache of JavaX source -> loaded Class)
static ThreadLocal<Boolean> evalJava_prep2_useCacheInThread = new ThreadLocal();
static volatile boolean evalJava_prep2_useCache = false;

// cached main classes, used only when you enable it
static Map<String, Object> evalJava_prep2_cache = synchronizedMRUCache(100);

static Object evalJava_prep2(String main) {
  boolean _useCache = evalJava_prep2_useCache || isTrue(evalJava_prep2_useCacheInThread.get());
  if (_useCache) {
    Object obj = evalJava_prep2_cache.get(main);
    if (obj != null) return obj;
  }
  Object obj = veryQuickJava(main);
  if (_useCache)
    evalJava_prep2_cache.put(main, obj);
  makeDependent(obj);
  setOpt(obj, "getProgramName_cache", "User Code");
  return obj;
}


static boolean isKnownVoidFunction(String name) {
  //ret mL_localCopy("Void Standard Functions").contains(name);
  return standardFunctionAlwaysReturnsVoid(name);
}
// We'd be really fancy if we filtered out return statements in
// inner blocks.
static boolean containsReturnWithArgument(List<String> tok) {
  for (int i = 1; i+2 < l(tok); i += 2)
    if (eqOneOf(tok.get(i), "ret", "return") && neqOneOf(tok.get(i+2), ";", "if", "unless", "with"))
      return true;
  return false;
}

static boolean containsReturnWithArgument(String code) {
  return containsReturnWithArgument(javaTok(code));
}
static int charIndexToUserLandLineNr(String text, int charIndex) {
  int i = 0, row = 1;
  charIndex = min(charIndex, l(text));
  while (i < charIndex) {
    i = smartIndexOf(text, '\n', i)+1;
    if (charIndex < i) break;
    ++row;
    if (charIndex == i) break;
  }
  return row;
}
static Map<String, String> singular_specials = litmap(
  "children", "child", "images", "image", "chess", "chess");
  
static Set<String> singular_specials2 = litset("time", "machine", "line");

static String singular(String s) {
  if (s == null) return null;
  { String __1598 = singular_specials.get(s); if (!empty(__1598)) return __1598; }
  //try answer hippoSingulars().get(lower(s));
  if (singular_specials2.contains(dropSuffix("s", afterLastSpace(s))))
    return dropSuffix("s", s);
  if (s.endsWith("ness")) return s;
  if (s.endsWith("ges")) return dropSuffix("s", s);
  if (endsWith(s, "bases")) return dropLast(s);
  s = dropSuffix("es", s);
  s = dropSuffix("s", s);
  return s;
}
static List<String> getPlural_specials = ll("sheep", "fish");

static String getPlural(String s) {
  if (containsIgnoreCase(getPlural_specials, s)) return s;
  if (ewic(s, "y")) return dropSuffixIgnoreCase("y", s) + "ies";
  if (ewic(s, "ss")) return s + "es";
  if (ewic(s, "s")) return s;
  return s + "s";
}
static boolean fileExists(String path) {
  return path != null && new File(path).exists();
}

static boolean fileExists(File f) {
  return f != null && f.exists();
}
static String tok_javaDropCommentsFromWhitespace(String s) {
  int l = l(s), j = 0;
  StringBuilder buf = new StringBuilder();
  while (j < l) {
    char c = s.charAt(j);
    char d = j+1 >= l ? '\0' : s.charAt(j+1);
    if (c == '/' && d == '*') {
      do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
      j = Math.min(j+2, l);
    } else if (c == '/' && d == '/') {
      do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
    } else {
      buf.append(c); ++j;
    }
  }
  return str(buf);
}
static <A> A[] makeArray(Class<A> type, int n) {
  return (A[]) Array.newInstance(type, n);
}
// map contains function name -> snippet id
static Map<String, String> parseStdFunctionsList(String snippetSrc) {
  return parseStdFunctionsList(snippetSrc, new LinkedHashMap());
}

static Map<String, String> parseStdFunctionsList(String snippetSrc, Map<String, String> map) {
  List<String> tok = javaTok(snippetSrc);
  int i = findCodeTokens(tok, "standardFunctions", "=", "litlist", "(");
  int opening = i+6;
  int closing = indexOf(tok, ")", opening)-1;
  for (i = opening+2; i < closing; i += 4) {
    String[] f = unquote(tok.get(i)).split("/");
    map.put(f[1], f[0]);
  }
  return map;
}

static String loadSnippetSilently(Snippet s) {
  return loadSnippetQuietly(s);
}



static String loadSnippetSilently(String snippetID) {
  return loadSnippetQuietly(snippetID);
}


static char stringToChar(String s) {
  if (l(s) != 1) throw fail("bad stringToChar: " + s);
  return firstChar(s);
}
static Object[] toObjectArray(Collection c) {
  List l = asList(c);
  return l.toArray(new Object[l.size()]);
}

static Class run(String progID, String... args) {
  Class main = hotwire(progID);
  callMain(main, args);
  return main;
}
static void restart() {
  Object j = getJavaX();
  call(j, "cleanRestart", get(j, "fullArgs"));
}
static Object pcallFunction(Object f, Object... args) {
  try { return callFunction(f, args); } catch (Throwable __e) { _handleException(__e); }
  return null;
}
static <A> Set<A> syncIdentityHashSet() {
  return (Set) synchronizedSet(identityHashSet());
}
static Map syncHashMap() {
  return synchroHashMap();
}


static String formatWithThousandsSeparator(long l) {
  return NumberFormat.getInstance(new Locale("en_US")).format(l);
}
static Runnable addThreadInfoToRunnable(final Object r) {
  final Object info = _threadInfo();
  return info == null ? asRunnable(r) : new Runnable() {  public void run() { try {  _inheritThreadInfo(info); callF(r); 
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "_inheritThreadInfo(info); callF(r);"; }};
}
static File replaceExtension(File f, String extOld, String extNew) {
  return newFile(replaceExtension(f2s(f), extOld, extNew));
}

static File replaceExtension(File f, String extNew) {
  return replaceExtension(f, fileExtension(f), extNew);
}

static String replaceExtension(String s, String extOld, String extNew) {
  s = dropSuffixIC(addPrefixOptIfNempty(".", extOld), s);
  return s + addPrefixOptIfNempty(".", extNew);
}

static String replaceExtension(String name, String extNew) {
  return replaceExtension(name, fileExtension(name), extNew);
}
static boolean swic(String a, String b) {
  return startsWithIgnoreCase(a, b);
}


  static boolean swic(String a, String b, Matches m) {
    if (!swic(a, b)) return false;
    m.m = new String[] {substring(a, l(b))};
    return true;
  }

static File dirOfFile(File f) {
  return f == null ? null : f.getParentFile();
}
static File computerIDFile() {
  return javaxDataDir("Basic Info/computer-id.txt");
}
static String makeRandomID(int length) {
  return makeRandomID(length, defaultRandomGenerator());
}

static String makeRandomID(int length, Random 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 makeRandomID(Random r, int length) {
  return makeRandomID(length, r);
}
static String joinStrings(String sep, String... strings) {
  return joinStrings(sep, Arrays.asList(strings));
}

static String joinStrings(String sep, Collection<String> strings) {
  StringBuilder buf = new StringBuilder();
  for (String s : unnull(strings)) if (nempty(s)) {
    if (nempty(buf)) buf.append(sep);
    buf.append(s);
  }
  return str(buf);
}
static String utf8streamToString(InputStream in) {
  return readerToString(utf8bufferedReader(in));
}
static String multiLineQuoteWithSpaces(String s) {
  return multiLineQuote(" " + s + " ");
}
static boolean endsWithIgnoreCase(String a, String b) {
  int la = l(a), lb = l(b);
  return la >= lb && regionMatchesIC(a, la-lb, b, 0, lb);
}


static boolean endsWithIgnoreCase(String a, String b, Matches m) {
  if (!endsWithIgnoreCase(a, b)) return false;
  m.m = new String[] { substring(a, 0, l(a)-l(b)) };
  return true;
}

static BufferedReader bufferedReader(Reader r) {
  return r instanceof BufferedReader ? (BufferedReader) r : _registerIOWrap(new BufferedReader(r), r);
}
static <A> CloseableIterableIterator<A> iteratorFromFunction_f0_autoCloseable(final F0<A> f, final AutoCloseable closeable) {
  class IFF2 extends CloseableIterableIterator<A> {
    A a;
    boolean done = false;
    
    public boolean hasNext() {
      getNext();
      return !done;
    }
    
    public A next() {
      getNext();
      if (done) throw fail();
      A _a = a;
      a = null;
      return _a;
    }
    
    void getNext() {
      if (done || a != null) return;
      a = f.get();
      done = a == null;
    }
    
    public void close() throws Exception {
      if (closeable != null) closeable.close();
    }
  };
  return new IFF2();
}
static String readLineFromReaderWithClose(BufferedReader r) { try {
  String s = r.readLine();
  if (s == null) r.close();
  return s;
} catch (Exception __e) { throw rethrow(__e); } }
static AutoCloseable _wrapIOCloseable(final AutoCloseable c) {
  return c == null ? null : new AutoCloseable() { public String toString() { return "c.close();\r\n    _registerIO(c, null, false);"; } public void close() throws Exception { c.close();
    _registerIO(c, null, false);
  }};
}

static FileInputStream newFileInputStream(File path) throws IOException {
  return newFileInputStream(path.getPath());
}

static FileInputStream newFileInputStream(String path) throws IOException {
  FileInputStream f = new FileInputStream(path);
  _registerIO(f, path, true);
  return f;
}
// mainJava is a complete program, but without the !752/!759 at the top
// returns link to main class
static Class veryQuickJava(CharSequence mainJava) {
  return veryQuickJava3(str(mainJava)); // Latest version with internal compiler bot.
}
static boolean standardFunctionAlwaysReturnsVoid(String name) {
  return tok_staticFunctionAlwaysReturnsVoid(javaTok(textOfStandardFunction_cached(name)), name);
}
static HashMap litmap(Object... x) {
  HashMap map = new HashMap();
  litmap_impl(map, x);
  return map;
}

static void litmap_impl(Map map, Object... x) {
  if (x != null) for (int i = 0; i < x.length-1; i += 2)
    if (x[i+1] != null)
      map.put(x[i], x[i+1]);
}
static String afterLastSpace(String s) {
  return s == null ? null : substring(s, s.lastIndexOf(' ')+1);
}
static String dropSuffixIgnoreCase(String suffix, String s) {
  return ewic(s, suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}

static String loadSnippetQuietly(Snippet s) {
  return loadSnippetQuietly(s.id);
}


static String loadSnippetQuietly(String snippetID) {
  loadSnippet_silent.set(true);
  try {
    return loadSnippet(snippetID);
  } finally {
    loadSnippet_silent.set(null);
  }
}


static char firstChar(String s) {
  return s.charAt(0);
}
static Object callFunction(Object f, Object... args) {
  return callF(f, args);
}
static <A> Set<A> synchronizedSet() {
  return synchroHashSet();
}

static <A> Set<A> synchronizedSet(Set<A> set) {
  return Collections.synchronizedSet(set);
}
static <A> Set<A> identityHashSet() {
  return Collections.newSetFromMap(new IdentityHashMap());
}
static List<VF1<Map>> _threadInfo_makers = synchroList();

static Object _threadInfo() {
  if (empty(_threadInfo_makers)) return null;
  HashMap map = new HashMap();
  pcallFAll(_threadInfo_makers, map);
  return map;
}
static Runnable asRunnable(Object o) {
  return toRunnable(o);
}


static void _inheritThreadInfo(Object info) {
  _threadInheritInfo(info);
}
static String fileExtension(File f) {
  if (f == null) return null;
  return fileExtension(f.getName());
}

static String fileExtension(String s) {
  return substring(s, smartLastIndexOf(s, '.'));
}
static String dropSuffixIC(String suffix, String s) {
  return s == null ? null : ewic(s, suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}
static String addPrefixOptIfNempty(String prefix, String s) {
  return addPrefixIfNotEmpty2(prefix, s);
}
static boolean startsWithIgnoreCase(String a, String b) {
  return regionMatchesIC(a, 0, b, 0, b.length());
}
static Random defaultRandomGenerator() {
  return ThreadLocalRandom.current();
}
static String readerToString(Reader r) { try {
  try {
    StringBuilder buf = new StringBuilder();
    int n = 0;
    while (true) {
      int ch = r.read();
      if (ch < 0)
        break;
      buf.append((char) ch);
      ++n;
      //if ((n % loadPage_verboseness) == 0) print("  " + n + " chars read");
    }
    return buf.toString();
  } finally {
    r.close();
  }  
} catch (Exception __e) { throw rethrow(__e); } }
static String multiLineQuote(String s) {
  for (int i = 0; ; i++) {
    String closer = "]" + rep('=', i) + "]";
    if (!contains(s, closer))
      return "[" + rep('=', i) + "[" + s + closer;
  }
}
static boolean regionMatchesIC(String a, int offsetA, String b, int offsetB, int len) {
  
  
    return a != null && a.regionMatches(true, offsetA, b, offsetB, len);
  
}
static boolean veryQuickJava_silent = true;
static boolean veryQuickJava_useCompilerBot = true; // we always use it now
static ThreadLocal<String> veryQuickJava_transpiled = new ThreadLocal();
static Object veryQuickJava3_cacheFunction; // func(S, LS) -> Class

// mainJava is a complete program, but without the !752/!759 at the top
// returns link to main class
static Class veryQuickJava3(String mainJava) {
  return veryQuickJava3(mainJava, emptyList());
}

static Class veryQuickJava3(String mainJava, List<String> libs) {
  Class c =  (Class) (callF(veryQuickJava3_cacheFunction, mainJava, libs));
  if (c != null) return c;
  transpileRaw_silent = veryQuickJava_silent;
  String src = transpileRaw(mainJava); // transpiled, with lib references
  if (veryQuickJava_transpiled.get() != null)
    veryQuickJava_transpiled.set(src);
  return veryQuickJava_finish(src, libs);
}
static boolean tok_staticFunctionAlwaysReturnsVoid(List<String> tok, String name) {
  Set<String> types = tok_returnTypesOfStaticFunction_uncleaned(tok, name);
  if (empty(types)) return false;
  for (String type : types)
    if (!containsOneOf(javaTokC(type), "void", "svoid")) return false;
  return true;
}
static String textOfStandardFunction_cached(String sfName) {
  return textOfStandardFunction(sfName);
}


static <A> Set<A> synchroHashSet() {
  return Collections.synchronizedSet(new HashSet<A>());
}

static Runnable toRunnable(final Object o) {
  if (o instanceof Runnable) return (Runnable) o;
  return new Runnable() {  public void run() { try {  callF(o) ;
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "callF(o)"; }};
}
static List<VF1<Map>> _threadInheritInfo_retrievers = synchroList();

static void _threadInheritInfo(Object info) {
  if (info == null) return;
  pcallFAll(_threadInheritInfo_retrievers, (Map) info);
}
static String addPrefixIfNotEmpty2(String prefix, String s) {
  return empty(s) ? "" : addPrefix(prefix, s);
}
static boolean transpileRaw_silent = true;
static boolean transpileRaw_useDiskCache = false;




static Class transpileRaw_trans;
static boolean transpileRaw_verySilent = false;
static boolean transpileRaw_dontCopyFromCreator = false;
static Lock transpileRaw_lock = lock();

static ThreadLocal<Boolean> transpileRaw_asInclude = new ThreadLocal();

static String transpileRaw(String mainJava) {
  return transpileRaw(mainJava, false, transpileRaw_useDiskCache);
}

static String transpileRaw(String mainJava, boolean fragment) {
  return transpileRaw(mainJava, fragment, transpileRaw_useDiskCache);
}

static String transpileRaw(String mainJava, boolean fragment, boolean useDiskCache) {
  mainJava = dropTranslators(mainJava);
  if (!transpileRaw_dontCopyFromCreator)
    transpileRaw_copyFromCreator();
  Lock __1726 = transpileRaw_lock; lock(__1726); try {
  
  File cacheFile = null;
  if (useDiskCache) {
    cacheFile = new File(javaxCodeDir(), "Transpilations/" + uniqueFileNameUsingMD5_80_v2(mainJava) + ".java");
    { String __1732 = loadTextFile(cacheFile); if (!empty(__1732)) return __1732; }
  }
  
   AutoCloseable __1729 = transpileRaw_verySilent ? null : tempLoadingAnim("Transpiling..."); try {
  
  transpileRaw_translator();
 
  //setOpt(transpileRaw_trans, dontPrintSource := true);
  setOpt(transpileRaw_trans, "localStuffOnly" , fragment);
  setOpt(transpileRaw_trans, "asInclude" , isTrue(transpileRaw_asInclude.get()));
  set(transpileRaw_trans, "mainJava", mainJava);
  // TODO: setOpt(transpileRaw_trans, transpilingSnippetID := );
  set(transpileRaw_trans, "print_byThread", print_byThread);
  if (!transpileRaw_verySilent) {
    print("Running translator " + getOpt(transpileRaw_trans, "programID"));
    if (!transpileRaw_silent) {
      print("Translator ID: " + systemHashCode(transpileRaw_trans) + ", print_byThread=" + getTL((ThreadLocal) getOpt(transpileRaw_trans, "print_byThread")));
      call(transpileRaw_trans, "print", "test");
    }
  }
  callMain(transpileRaw_trans);
  //print("Ran translator " + identityHashCode(transpileRaw_trans));
  String main =  (String) (get(transpileRaw_trans, "mainJava"));
  if (useDiskCache) {
    saveTextFile(new File(cacheFile.getPath() + "x"), mainJava);
    saveTextFile(cacheFile, main);
  }
  return main;
} finally { _close(__1729); }} finally { unlock(__1726); } }

static Class transpileRaw_translator() {
  if (transpileRaw_trans == null) {
    //print("Loading translator.");
    transpileRaw_trans = hotwireSharingLibraries_silently(defaultJavaXTranslatorID());
    makeDependent(transpileRaw_trans);
    //print("Loaded translator: " + identityHashCode(transpileRaw_trans));
  }
  setOpt(transpileRaw_trans, "print_silent", transpileRaw_silent);
  return transpileRaw_trans;
}

static void transpileRaw_copyFromCreator() {
  Lock __1727 = transpileRaw_lock; lock(__1727); try {
  if (transpileRaw_trans != null) return;
  Object c = creator();
  if (c == null) return;
  Class trans =  (Class) (getOpt(c, "transpileRaw_trans"));
  Lock lock =  (Lock) (getOpt(c, "transpileRaw_lock"));
  if (trans != null && lock != null) {
    print("Using creator's transpiler: " + getProgramID(c) + " => " + programID());
    transpileRaw_lock = lock;
    transpileRaw_trans = trans;
  }
} finally { unlock(__1727); } }

static void cleanMeUp_transpileRaw() {
  transpileRaw_trans = null; // release proactively (it's big)
}

static Class veryQuickJava_finish(String src, List<String> libs) {
  libs = cloneList(libs);
  src = findTranslators2(src, libs);
  //print("Libs found: " + struct(libs));
  
  String dehlibs = join(" ", libs);
  File bytecode = null;
  //try {
    bytecode = javaCompile_overInternalBot(src, dehlibs);
  /*} on fail {
    print("Was compiling: " + src + "\n");
  }*/

  return hotwireCore(concatLists(ll(bytecode), loadLibraries(libs)));
}
static Set<String> tok_returnTypesOfStaticFunction_uncleaned(List<String> tok, String functionName) {
  List<List<String>> funcs = findFullFunctionDefs(tok, true);
  TreeSet<String> out = new TreeSet();
  for (List<String> tokF : funcs) {
    int i = indexOfAny(tokF, 0, "(", "{");
    if (i < 0) continue;
    String fname = get(tokF, i-2);
    if (!eq(fname, functionName)) continue;
    out.add(joinSubList(tokF, 1, i-3));
  }
  return out;
}
static <A> boolean containsOneOf(Collection<A> l, A... x) {
  for (A a : unnull(l))
    if (eqOneOf(a, x))
      return true;
  return false;
}

static <A> boolean containsOneOf(Collection<A> l, Set<A> set) {
  if (set == null) return false;
  for (A a : unnull(l))
    if (set.contains(a))
      return true;
  return false;
}

static boolean containsOneOf(String s, String... x) {
  for (String o : x)
    if (contains(s, o)) return true;
  return false;
}
static String textOfStandardFunction(String sfName) {
  return loadSnippet_cached(stdFunctions_cached().get(sfName));
}


static String addPrefix(String prefix, String s) {
  return s.startsWith(prefix) ? s : prefix + s;
}
static String dropTranslators(String src) {
  return findTranslators2(src, null);
}

// modifies original tok
static List<String> dropTranslators(List<String> tok) {
  return findTranslators2(tok, null);
}
static File javaxCodeDir_dir; // can be set to work on different base dir

static File javaxCodeDir() {
  return javaxCodeDir_dir != null ? javaxCodeDir_dir : new File(userHome(), "JavaX-Code");
}

static File javaxCodeDir(String sub) {
  return newFile(javaxCodeDir(), sub);
}
static String uniqueFileNameUsingMD5_80_v2(String fullName) {
  return uniqueFileNameUsingMD5_80_v2(fullName, md5(fullName));
}

static String uniqueFileNameUsingMD5_80_v2(String fullName, String md5) {
  return takeFirst(80-33, fileNameEncode(fullName)) + " - " + md5;
}
static <A> AutoCloseable tempLoadingAnim(String msg) {
  return tempDisposeWindow(loadingAnim(msg));
}
static int systemHashCode(Object o) {
  return identityHashCode(o);
}
static Object getTL(Object o, String name) {
  return getThreadLocal(o, name);
}

static <A> A getTL(ThreadLocal<A> tl) {
  return getThreadLocal(tl);
}

static <A> A getTL(ThreadLocal<A> tl, A defaultValue) {
  return getThreadLocal(tl, defaultValue);
}
static Class<?> hotwireSharingLibraries_silently(String progID) {
   AutoCloseable __1759 = temp_loadPage_silent(); try {
  return hotwireSharingLibraries(progID);
} finally { _close(__1759); }}
static WeakReference<Object> creator_class;

static Object creator() {
  return creator_class == null ? null : creator_class.get();
}
// probably better than findTranslators (uses tokens)
// removes invocations from src
static String findTranslators2(String src, List<String> libsOut) {
  return join(findTranslators2(javaTok(src), libsOut));
}

// modifies original tok
static List<String> findTranslators2(List<String> tok, List<String> libsOut) {
  int i;
  while ((i = jfind(tok, "!<int>")) >= 0) {
    setAdd(libsOut, tok.get(i+2));
    clearTokens(tok, i, i+3);
  }
  return tok;
}
static File javaCompile_overInternalBot(String src) {
  return javaCompile_overInternalBot(src, "");
}

// returns path to jar
static synchronized File javaCompile_overInternalBot(String src, String dehlibs) {
  return CompilerBot.compile(src, dehlibs);
}

static Class hotwireCore(List<File> files) { try {
  // make class loader
  JavaXClassLoader classLoader = hotwire_makeClassLoader(files);

  // load & return main class
  Class<?> theClass = classLoader.loadClass("main");
  
  setOpt(theClass, "__javax", getJavaX());
  if (getOpt(theClass, "programID") == null)
    setOpt(theClass, "programID", "#3999999");
  
  if (!_inCore())
    hotwire_copyOver(theClass);
  
  return theClass;
} catch (Exception __e) { throw rethrow(__e); } }
static List<File> loadLibraries(List<String> snippetIDs) {
  return map("loadLibrary", snippetIDs);
}
static Set<String> findFullFunctionDefs_keywords = new HashSet(splitAtSpace("static svoid ssvoid ssynchronized sbool sS sO sL"));

// returns actual CNC
static List<List<String>> findFullFunctionDefs(List<String> tok, boolean topLevelOnly) {
  int n = l(tok);
  List<List<String>> functions = new ArrayList();
  for (int i = 1; i < n; i += 2) {
    String t = tok.get(i);
    if (topLevelOnly && eq(t, "{")) i = findEndOfBlock(tok, i)-1;
    else if (findFullFunctionDefs_keywords.contains(t)) {
      
      int j = i+2;
      while (j < n && !eqOneOf(tok.get(j), ";", "=", "(", "{"))
        j += 2;
      if ((eqGet(tok, j, "(") || eq(t, "svoid") && eqGet(tok, j, "{"))
        && isIdentifier(tok.get(j-2))) {
        int k = smartIndexOf(tok, "{", j);
        if (k < l(tok)) {
          k = findEndOfBlock(tok, k)+1;
          functions.add(subList(tok, i-1, k));
          i = k-2;
        }
      }
    }
  }
  return functions;
}

static List<List<String>> findFullFunctionDefs(String s, boolean topLevelOnly) {
  return findFullFunctionDefs(javaTok(s), topLevelOnly);
}
static String loadSnippet_cached(String id) {
  return loadSnippet_simpleCache(id);
}


static File loadLibrary(String snippetID) {
  return loadBinarySnippet(snippetID);
}
static String fileNameEncode_safeChars = " ()[]#,!";

static String fileNameEncode(String s) {
  StringBuilder buf = new StringBuilder();
  int n = l(s);
  for (int i = 0; i < n; i++) {
    char c = s.charAt(i);
    if (contains(fileNameEncode_safeChars, c))
      buf.append(c);
    else
      buf.append(urlencode(str(c)));
  }
  return str(buf);
}
static <A> AutoCloseable tempDisposeWindow(final Window w) {
  return new AutoCloseable() {
    public void close() {
      disposeWindow(w);
    }
  };
}
static JWindow loadingAnim() {
  return showLoadingAnimation();
}

static JWindow loadingAnim(String text) {
  return showLoadingAnimation(text);
}
static AutoCloseable temp_loadPage_silent() {
  return tempSetThreadLocal(loadPage_silent, true);
}
static Class<?> hotwireSharingLibraries(String progID) { try {
  Pair<File, String> p = CompilerBot.compileSnippet2(progID);
  File jar = p.a;
  assertTrue(f2s(jar), jar.isFile());
  
  // collect files (program + libraries)
  
  List<File> files = ll(jar);
  String dehlibs = unnull(loadTextFileFromZip(jar, "libraries"));
  
  List<File> myLibraries = myLibraryFiles();
  //print("My libraries: " + myLibraries);
  
  Matcher matcher = Pattern.compile("\\d+").matcher(dehlibs);
  while (matcher.find()) {
    String libID = matcher.group();
    File lib = loadLibrary(libID);
    if (myLibraries.contains(lib)) {
      //print("Skipping lib " + lib);
    } else {
      //print("Adding lib " + lib);
      files.add(lib);
    }
  }

  // make class loader
  JavaXClassLoaderWithParent classLoader = new JavaXClassLoaderWithParent(progID, files, myClassLoader());
  
  return hotwire_finish(classLoader, progID, p.b);
} catch (Exception __e) { throw rethrow(__e); } }
static JavaXClassLoader hotwire_makeClassLoader(List<File> files) {
  Collection<String> toShare = hotwire_classesToShare();
  return nempty(toShare)
    ? new JavaXClassLoaderWithParent2(null, files, myClassLoader(), cloneList(toShare))
    : new JavaXClassLoader(null, files);
}
static ExpiringMap2<String, String> loadSnippet_simpleCache_map = new ExpiringMap2(10000);
static Lock loadSnippet_simpleCache_lock = lock();

// timeout for loadPage
static int loadSnippet_simpleCache_timeout = 60000;

static String loadSnippet_simpleCache(String id) {
  if (id == null) return null;
  Lock __1784 = loadSnippet_simpleCache_lock; lock(__1784); try {
  id = fsI(id);
  
  // get from cache
  String src = loadSnippet_simpleCache_map.get(id);
  if (src != null) return src;
  
  // load & put in cache
  Integer oldTimeout = setThreadLocal(loadPage_forcedTimeout_byThread, loadSnippet_simpleCache_timeout);
  try {
    src = loadSnippet(id);
    if (src != null)
      loadSnippet_simpleCache_map.put(id, src);
    return src;
  } finally {
    loadPage_forcedTimeout_byThread.set(oldTimeout);
  }
} finally { unlock(__1784); } }


static File loadBinarySnippet(String snippetID) { try {
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadLibrary(snippetID);
  
  
  long id = parseSnippetID(snippetID);
  if (isImageServerSnippet(id)) return loadImageAsFile(snippetID);
  File f = DiskSnippetCache_getLibrary(id);
  if (fileSize(f) == 0)
    f = loadDataSnippetToFile(snippetID);
  return f;
} catch (Exception __e) { throw rethrow(__e); } }
static void disposeWindow(final Window window) {
  if (window != null) { swing(new Runnable() {  public void run() { try { 
    window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING)); // call listeners
    myFrames_list.remove(window);
    window.dispose();
  
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING)); //..."; }}); }
}

static void disposeWindow(final Component c) {
  disposeWindow(getWindow(c));
}

static void disposeWindow(Object o) {
  if (o != null) disposeWindow(((Component) o));
}

static void disposeWindow() {
  disposeWindow(heldInstance(Component.class));
}
static JWindow showLoadingAnimation() {
  return showLoadingAnimation("Hold on user...");
}

static JWindow showLoadingAnimation(String text) { try {
  return showAnimationInTopRightCorner("#1003543", text);
} catch (Throwable __e) { return null; } }
static String loadTextFileFromZip(File inZip, String fileName) {
  return loadTextFileFromZipFile(inZip, fileName);
}
static List<File> myLibraryFiles() {
  return (List<File>) get(myClassLoader(), "files");
}
static ClassLoader myClassLoader() {
  return _getClass(mc()).getClassLoader();
}
static Class hotwire_finish(ClassLoader classLoader, String progID, String javaSource) {
  return hotwire_finish(classLoader, progID, javaSource, "main");
}

static Class hotwire_finish(ClassLoader classLoader, String progID, String javaSource, String mainClass) { try {
  // load & return main class
  Class<?> theClass = classLoader.loadClass(mainClass);
  
  Class j = getJavaX();
  
  setOpt(theClass, "myJavaSource_code", javaSource);
  
  synchronized(j) { // hopefully this goes well...
    call(j, "setVars", theClass, progID);
    callOpt(j, "addInstance", progID, theClass);
  }
  
  hotwire_copyOver(theClass);
  vmBus_send("hotwireFinished",theClass, mc());
  return theClass;
} catch (Exception __e) { throw rethrow(__e); } }
static Set<String> hotwire_classesToShare = synchroSet();

static Set<String> hotwire_classesToShare() {
  return hotwire_classesToShare;
}


static Map<JFrame, Boolean> myFrames_list = weakHashMap();

static List<JFrame> myFrames() {
  return swing(new F0<List<JFrame>>() { List<JFrame> get() { try {  return keysList(myFrames_list);  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "ret keysList(myFrames_list);"; }});
}


static File loadImageAsFile(String snippetIDOrURL) { try {
  if (isURL(snippetIDOrURL))
    throw fail("not implemented");

  if (!isSnippetID(snippetIDOrURL)) throw fail("Not a URL or snippet ID: " + snippetIDOrURL);
  String snippetID = "" + parseSnippetID(snippetIDOrURL);
  
  File file = imageSnippetCacheFile(snippetID);
  if (fileSize(file) > 0) return file;

  String imageURL = snippetImageURL_noHttps(snippetID);
  System.err.println("Loading image: " + imageURL);
  byte[] data = loadBinaryPage(imageURL);

  saveBinaryFile(file, data);
  return file;
} catch (Exception __e) { throw rethrow(__e); } }

// If you change this, also change DiskSnippetCache_fileToLibID
static File DiskSnippetCache_file(long snippetID) {
  return new File(getGlobalCache(), "data_" + snippetID + ".jar");
}
  
  // Data files are immutable, use centralized cache
public static File DiskSnippetCache_getLibrary(long snippetID) throws IOException {
  File file = DiskSnippetCache_file(snippetID);
  return file.exists() ? file : null;
}

public static void DiskSnippetCache_putLibrary(long snippetID, byte[] data) throws IOException {
  saveBinaryFile(DiskSnippetCache_file(snippetID), data);
}

static byte[] loadDataSnippetImpl(String snippetID) throws IOException {
  byte[] data;
  try {
    URL url = new URL(dataSnippetLink(snippetID));
    print("Loading library: " + hideCredentials(url));
    try {
      data = loadBinaryPage(url.openConnection());
    } catch (RuntimeException e) {
      data = null;
    }
    
    if (data == null || data.length == 0) {
      url = new URL(tb_mainServer() + "/blobs/" + parseSnippetID(snippetID));
      print("Loading library: " + hideCredentials(url));
      data = loadBinaryPage(url.openConnection());
    }
    print("Bytes loaded: " + data.length);
  } catch (FileNotFoundException e) {
    throw new IOException("Binary snippet #" + snippetID + " not found or not public");
  }
  return data;
}
static long fileSize(String path) { return getFileSize(path); }
static long fileSize(File f) { return getFileSize(f); }

static File loadDataSnippetToFile(String snippetID) { try {
  snippetID = fsI(snippetID);
  
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadLibrary(snippetID);
  
  
  File f = DiskSnippetCache_file(parseSnippetID(snippetID));
  List<URL> urlsTried = new ArrayList();
  List<Throwable> errors = new ArrayList();
  try {
    URL url = addAndReturn(urlsTried, new URL(dataSnippetLink(snippetID)));
    print("Loading library: " + hideCredentials(url));
    try {
      loadBinaryPageToFile(openConnection(url), f);
      if (fileSize(f) == 0) throw fail();
    } catch (Throwable e) {
      errors.add(e);
      url = addAndReturn(urlsTried, new URL(tb_mainServer() + "/blobs/" + psI(snippetID)));
      print("Trying other server: " + hideCredentials(url));
      loadBinaryPageToFile(openConnection(url), f);
      print("Got bytes: " + fileSize(f));
    }
    // TODO: check if we hit the "LOADING" message
    if (fileSize(f) == 0) throw fail();
    System.err.println("Bytes loaded: " + fileSize(f));
  } catch (Throwable e) {
    //printStackTrace(e);
    errors.add(e);
    throw fail("Binary snippet " + snippetID + " not found or not public. URLs tried: " + allToString(urlsTried) + ", errors: " + allToString(errors));
  }
  return f;
} catch (Exception __e) { throw rethrow(__e); } }
static Window getWindow(Object o) {
  if (!(o instanceof Component)) return null;
  Component c = (Component) o;
  while (c != null) {
    if (c instanceof Window) return (Window) c;
    c = c.getParent();
  }
  return null;
}
static <A> A heldInstance(Class<A> c) {
  List<Object> l = holdInstance_l.get();
  for (int i = l(l)-1; i >= 0; i--) {
    Object o = l.get(i);
    if (isInstanceOf(o, c))
      return (A) o;
  }
  throw fail("No instance of " + className(c) + " held");
}
static boolean showAnimationInTopRightCorner_alwaysOnTop = true;
static boolean showAnimationInTopRightCorner_on = true;

// automatically switches to AWT thread for you
// text is optional text below image
static JWindow showAnimationInTopRightCorner(String imageID, String text) {
  if (isHeadless() || !showAnimationInTopRightCorner_on) return null;
  return showAnimationInTopRightCorner(imageIcon(imageID), text);
}

static JWindow showAnimationInTopRightCorner(final Image image, final String text) {
  if (image == null || isHeadless() || !showAnimationInTopRightCorner_on) return null;
  return showAnimationInTopRightCorner(imageIcon(image), text);
}

static JWindow showAnimationInTopRightCorner(final ImageIcon imageIcon, final String text) {
  if (isHeadless() || !showAnimationInTopRightCorner_on) return null;
  return (JWindow) swingAndWait(new F0<Object>() { Object get() { try { 
    JLabel label = new JLabel(imageIcon);
    if (nempty(text)) {
      label.setText(text);
      label.setVerticalTextPosition(SwingConstants.BOTTOM);
      label.setHorizontalTextPosition(SwingConstants.CENTER);
    }
    final JWindow window = showInTopRightCorner(label);
    onClick(label, new Runnable() {  public void run() { try {  window.dispose() ;
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "window.dispose()"; }});
    if (showAnimationInTopRightCorner_alwaysOnTop)
      window.setAlwaysOnTop(true);
    return window;
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "JLabel label = new JLabel(imageIcon);\r\n    if (nempty(text)) {\r\n      label.s..."; }});
}

static JWindow showAnimationInTopRightCorner(final String imageID) {
  return showAnimationInTopRightCorner(imageID, "");
}

static JWindow showAnimationInTopRightCorner(String imageID, double seconds) {
  return showAnimationInTopRightCorner(imageID, "", seconds);
}

static JWindow showAnimationInTopRightCorner(String imageID, String text, double seconds) {
  if (isHeadless()) return null;
  return disposeWindowAfter(iround(seconds*1000), showAnimationInTopRightCorner(imageID, text));
}

static JWindow showAnimationInTopRightCorner(BufferedImage img, String text, double seconds) {
  return disposeWindowAfter(iround(seconds*1000), showAnimationInTopRightCorner(img, text));
}

static String loadTextFileFromZipFile(File inZip, String fileName) { try {
  if (!fileExists(inZip)) return null;
  try {
     ZipFile zip = new ZipFile(inZip); try {
    return loadTextFileFromZipFile(zip, fileName);
  } finally { _close(zip); }} catch (Throwable e) {
    throw fail(f2s(inZip), e);
  }
} catch (Exception __e) { throw rethrow(__e); } }
    
static String loadTextFileFromZipFile(ZipFile zip, String fileName) { try {
  ZipEntry entry = zip.getEntry(fileName);
  if (entry == null) return null;
   InputStream fin = zip.getInputStream(entry); try {
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  copyStream(fin, baos);
  return fromUTF8(baos.toByteArray());
} finally { _close(fin); }} catch (Exception __e) { throw rethrow(__e); } }
static <A> Set<A> synchroSet() {
  return synchroHashSet();
}

static <A> Set<A> synchroSet(Set<A> set) {
  return Collections.synchronizedSet(set);
}


static boolean loadBufferedImage_useImageCache = true;

static BufferedImage loadBufferedImage(String snippetIDOrURLOrFile) { try {
  ping();
  if (snippetIDOrURLOrFile == null) return null;
  if (isURL(snippetIDOrURLOrFile))
    return imageIO_readURL(snippetIDOrURLOrFile);

  if (isAbsolutePath(snippetIDOrURLOrFile)) 
    return loadBufferedImage(new File(snippetIDOrURLOrFile));
  
  if (!isSnippetID(snippetIDOrURLOrFile))
    throw fail("Not a URL or snippet ID or file: " + snippetIDOrURLOrFile);
  String snippetID = "" + parseSnippetID(snippetIDOrURLOrFile);
  
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return loadBufferedImage(rl.loadLibrary(snippetID));
  
  
  File dir = imageSnippetsCacheDir();
  if (loadBufferedImage_useImageCache) {
    dir.mkdirs();
    File file = new File(dir, snippetID + ".png");
    if (file.exists() && file.length() != 0)
      try {
        return ImageIO.read(file);
      } catch (Throwable e) {
        e.printStackTrace();
        // fall back to loading from sourceforge
      }
  }

  String imageURL = snippetImageURL_http(snippetID);
  print("Loading image: " + imageURL);
  BufferedImage image = imageIO_readURL(imageURL);

  if (loadBufferedImage_useImageCache) {
    File tempFile = new File(dir, snippetID + ".tmp." + System.currentTimeMillis());
    ImageIO.write(image, "png", tempFile);
    tempFile.renameTo(new File(dir, snippetID + ".png"));
    //Log.info("Cached image.");
  }

  //Log.info("Loaded image.");
  return image;
} catch (Exception __e) { throw rethrow(__e); } }

static BufferedImage loadBufferedImage(File file) { try {
  return file.isFile() ? ImageIO.read(file) : null;
} catch (Exception __e) { throw rethrow(__e); } }
static ThreadLocal<List<Object>> holdInstance_l = new ThreadLocal();

static AutoCloseable holdInstance(Object o) {
  if (o == null) return null;
  listThreadLocalAdd(holdInstance_l, o);
  return new AutoCloseable() {
    public void close() {
      listThreadLocalPopLast(holdInstance_l);
    }
  };
}
static <A, B> Map<A, B> weakHashMap() {
  return newWeakHashMap();
}
static <A, B> List<A> keysList(Map<A, B> map) {
  return cloneListSynchronizingOn(keys(map), map);
}


  static <A> List<A> keysList(MultiSet<A> ms) {
    return ms == null ? null : keysList(ms.map);
  }

static boolean isURL(String s) {
  return startsWithOneOf(s, "http://", "https://", "file:");
}
static File imageSnippetCacheFile(String snippetID) {
  File dir = imageSnippetsCacheDir();
  
  if (!loadBufferedImage_useImageCache) return null;
  
  return new File(dir, parseSnippetID(snippetID) + ".png");
}
static String snippetImageURL_noHttps(String snippetID) {
  return snippetImageURL_noHttps(snippetID, "png");
}

static String snippetImageURL_noHttps(String snippetID, String contentType) {
  return snippetImageURL(snippetID, contentType)
    .replace("https://www.botcompany.de:8443/", "http://www.botcompany.de:8080/")
    .replace("https://botcompany.de/", "http://botcompany.de/");
}
static ThreadLocal<Map<String, List<String>>> loadBinaryPage_responseHeaders = new ThreadLocal();
static ThreadLocal<Map<String, String>> loadBinaryPage_extraHeaders = new ThreadLocal();

static byte[] loadBinaryPage(String url) { try {
  print("Loading " + url);
  return loadBinaryPage(loadPage_openConnection(new URL(url)));
} catch (Exception __e) { throw rethrow(__e); } }

static byte[] loadBinaryPage(URLConnection con) { try {
  Map<String, String> extraHeaders = getAndClearThreadLocal(loadBinaryPage_extraHeaders);
  setHeaders(con);
  for (String key : keys(extraHeaders))
    con.setRequestProperty(key, extraHeaders.get(key));
  return loadBinaryPage_noHeaders(con);
} catch (Exception __e) { throw rethrow(__e); } }

static byte[] loadBinaryPage_noHeaders(URLConnection con) { try {
  ByteArrayOutputStream buf = new ByteArrayOutputStream();
  InputStream inputStream = con.getInputStream();
  loadBinaryPage_responseHeaders.set(con.getHeaderFields());
  long len = 0;
  try { len = con.getContentLength/*Long*/(); } catch (Throwable e) { printStackTrace(e); }
int n = 0;
  while (true) {
    int ch = inputStream.read();
    if (ch < 0)
      break;
    buf.write(ch);
    if (++n % 100000 == 0)
      println("  " + n + (len != 0 ? "/" + len : "") + " bytes loaded.");
  }
  inputStream.close();
  return buf.toByteArray();
} catch (Exception __e) { throw rethrow(__e); } }

/** writes safely (to temp file, then rename) */
public static byte[] saveBinaryFile(String fileName, byte[] contents) { try {
  File file = new File(fileName);
  File parentFile = file.getParentFile();
  if (parentFile != null)
    parentFile.mkdirs();
  String tempFileName = fileName + "_temp";
  FileOutputStream fileOutputStream = newFileOutputStream(tempFileName);
  fileOutputStream.write(contents);
  fileOutputStream.close();
  if (file.exists() && !file.delete())
    throw new IOException("Can't delete " + fileName);

  if (!new File(tempFileName).renameTo(file))
    throw new IOException("Can't rename " + tempFileName + " to " + fileName);
    
  vmBus_send("wroteFile", file);
  return contents;
} catch (Exception __e) { throw rethrow(__e); } }

static byte[] saveBinaryFile(File fileName, byte[] contents) {
  return saveBinaryFile(fileName.getPath(), contents);
}
static String dataSnippetLink(String snippetID) {
  long id = parseSnippetID(snippetID);
  if (id >= 1100000 && id < 1200000)
    return imageServerURL() + id;
  if (id >= 1400000 && id < 1500000)
    return "http://butter.botcompany.de:8080/files/" + id + "?_pass=" + muricaPassword();
  if (id >= 1200000 && id < 1300000) { // Woody files, actually
    String pw = muricaPassword();
    if (empty(pw)) throw fail("Please set 'murica password by running #1008829");
    return "http://butter.botcompany.de:8080/1008823/raw/" + id + "?_pass=" + pw; // XXX, although it typically gets hidden when printing
  } else
    return "http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_"
      + id + "&contentType=application/binary";
}
static <B, A extends B> A addAndReturn(Collection<B> c, A a) {
  if (c != null) c.add(a);
  return a;
}
static void loadBinaryPageToFile(String url, File file) { try {
  print("Loading " + url);
  loadBinaryPageToFile(openConnection(new URL(url)), file);
} catch (Exception __e) { throw rethrow(__e); } }

static void loadBinaryPageToFile(URLConnection con, File file) { try {
  setHeaders(con);
  loadBinaryPageToFile_noHeaders(con, file);
} catch (Exception __e) { throw rethrow(__e); } }

static void loadBinaryPageToFile_noHeaders(URLConnection con, File file) { try {
  File ftemp = new File(f2s(file) + "_temp");
  FileOutputStream buf = newFileOutputStream(mkdirsFor(ftemp));
  try {
    InputStream inputStream = con.getInputStream();
    long len = 0;
    try { len = con.getContentLength/*Long*/(); } catch (Throwable e) { printStackTrace(e); }
    String pat = "  {*}" + (len != 0 ? "/" + len : "") + " bytes loaded.";
    copyStreamWithPrints(inputStream, buf, pat);
    inputStream.close();
    buf.close();
    file.delete();
    renameFile_assertTrue(ftemp, file);
  } finally {
    if (buf != null) buf.close();
  }
} catch (Exception __e) { throw rethrow(__e); } }

static List<String> allToString(Iterable c) {
  List<String> l = new ArrayList();
  for (Object o : unnull(c)) l.add(str(o));
  return l;
}

static List<String> allToString(Object[] c) {
  List<String> l = new ArrayList();
  for (Object o : unnull(c)) l.add(str(o));
  return l;
}
static boolean isInstanceOf(Object o, Class type) {
  return type.isInstance(o);
}
static int imageIcon_cacheSize = 10;
static boolean imageIcon_verbose = false;
static Map<String, ImageIcon> imageIcon_cache;
static Lock imageIcon_lock = lock();
static ThreadLocal<Boolean> imageIcon_fixGIF = new ThreadLocal();

// not going through BufferedImage preserves animations
static ImageIcon imageIcon(String imageID) { try {
  if (imageID == null) return null;
  Lock __1855 = imageIcon_lock; lock(__1855); try {
  if (imageIcon_cache == null)
    imageIcon_cache = new MRUCache(imageIcon_cacheSize);
  imageID = fsI(imageID);
  ImageIcon ii = imageIcon_cache.get(imageID);
  if (ii == null) {
    if (imageIcon_verbose) print("Loading image icon: " + imageID);
    File f = loadBinarySnippet(imageID);
    
      Boolean b = imageIcon_fixGIF.get();
      if (!isFalse(b))
        ii = new ImageIcon(loadBufferedImageFixingGIFs(f));
      else
    
    ii = new ImageIcon(f.toURI().toURL());
  } else
    imageIcon_cache.remove(imageID); // move to front of cache on access
  imageIcon_cache.put(imageID, ii);
  return ii;
} finally { unlock(__1855); } } catch (Exception __e) { throw rethrow(__e); } }

// doesn't fix GIFs
static ImageIcon imageIcon(File f) { try {
  return new ImageIcon(f.toURI().toURL());
} catch (Exception __e) { throw rethrow(__e); } }

static ImageIcon imageIcon(Image img) {
  return new ImageIcon(img);
}


static JWindow showInTopRightCorner(Component c) {
  return swing(() -> {
    JWindow w = new JWindow();
    w.add(c);
    w.pack();
    moveToTopRightCorner(w);
    w.setVisible(true);
    return w;
  });
}
static <A extends JComponent> A onClick(final A c, final Object runnable) {
  if (c != null) { swing(new Runnable() {  public void run() { try { 
    c.addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        callF(runnable, e);
      }
    });
  
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "c.addMouseListener(new MouseAdapter {\r\n      public void mouseClicked(MouseEv..."; }}); }
  return c;
}

// re-interpreted for buttons
static void onClick(JButton btn, final Object runnable) {
  onEnter(btn, runnable);
}
static <A extends Window> A disposeWindowAfter(int delay, final A w) {
  if (w != null)
    swingLater(delay, new Runnable() {  public void run() { try { 
      w.dispose();
    
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "w.dispose();"; }});
  return w;
}

static <A extends Window> A disposeWindowAfter(A w, double seconds) {
  return disposeWindowAfter(toMS_int(seconds), w);
}

static <A extends Window> A disposeWindowAfter(double seconds, A w) {
  return disposeWindowAfter(w, seconds);
}
static int iround(double d) {
  return (int) Math.round(d);
}

static int iround(Number n) {
  return iround(toDouble(n));
}
static String fromUTF8(byte[] bytes) {
  return fromUtf8(bytes);
}


static BufferedImage imageIO_readURL(String url) { try {
  if (startsWith(url, "https:")) // Java is still buggy there :(
    disableCertificateValidation();
  return ImageIO.read(new URL(url));
} catch (Exception __e) { throw rethrow(__e); } }

static boolean isAbsolutePath(String s) {
  return s != null && new File(s).isAbsolute();
}

static boolean isAbsolutePath(File f) {
  return f != null && f.isAbsolute();
}
static File imageSnippetsCacheDir() {
  return javaxCachesDir("Image-Snippets");
}
static String snippetImageURL_http(String snippetID) {
  return snippetImageURL_http(snippetID, "png");
}

static String snippetImageURL_http(String snippetID, String contentType) {
  return replacePrefix("https://", "http://", snippetImageURL(snippetID, contentType)).replace(":8443", ":8080");
}
static <A> void listThreadLocalAdd(ThreadLocal<List<A>> tl, A a) {
  List<A> l = tl.get();
  if (l == null) tl.set(l = new ArrayList());
  l.add(a);
}
static <A> A listThreadLocalPopLast(ThreadLocal<List<A>> tl) {
  List<A> l = tl.get();
  if (l == null) return null;
  A a = popLast(l);
  if (empty(l)) tl.set(null);
  return a;
}
static <A> ArrayList<A> cloneListSynchronizingOn(Collection<A> l, Object mutex) {
  if (l == null) return new ArrayList();
  synchronized(mutex) {
    return new ArrayList<A>(l);
  }
}
static String snippetImageURL(long snippetID) {
  return snippetImageURL(fsI(snippetID));
}

static String snippetImageURL(String snippetID) {
  return snippetImageURL(snippetID, "png");
}

static String snippetImageURL(String snippetID, String contentType) {
  if (isURL(snippetID)) return snippetID;
  long id = parseSnippetID(snippetID);
  String url;
  if (id == 1000010 || id == 1000012)
    url = "http://tinybrain.de:8080/tb/show-blobimage.php?id=" + id;
  else if (isImageServerSnippet(id))
    url = imageServerLink(id);
  else
    //url = "http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_" + id + "&contentType=image/" + contentType;
    url = "https://www.botcompany.de:8443/img/" + id;
  return url;
}
static <A> A println(A a) {
  return print(a);
}
public static File mkdirsFor(File file) {
  return mkdirsForFile(file);
}

static void copyStreamWithPrints(InputStream in, OutputStream out, String pat) { try {
  byte[] buf = new byte[65536];
  int total = 0;
  while (true) {
    int n = in.read(buf);
    if (n <= 0) return;
    out.write(buf, 0, n);
    if ((total+n)/100000 > total/100000)
      print(pat.replace("{*}", str(roundDownTo(total, 100000))));
    total += n;
  }
} catch (Exception __e) { throw rethrow(__e); } }
static void renameFile_assertTrue(File a, File b) { try {
  if (!a.exists()) throw fail("Source file not found: " + f2s(a));
  if (b.exists()) throw fail("Target file exists: " + f2s(b));
  mkdirsForFile(b);
  
  
  if (!a.renameTo(b))
    throw fail("Can't rename " + f2s(a) + " to " + f2s(b));
  
} catch (Exception __e) { throw rethrow(__e); } }





static boolean loadBufferedImageFixingGIFs_debug = false;
static ThreadLocal<Var<byte[]>> loadBufferedImageFixingGIFs_output = new ThreadLocal();

static Image loadBufferedImageFixingGIFs(File file) { try {
  if (!file.exists()) return null;

  // Load anything but GIF the normal way
  if (!isGIF(file))
    return ImageIO.read(file);
    
  if (loadBufferedImageFixingGIFs_debug) print("loadBufferedImageFixingGIFs" + ": checking gif");

  // Get GIF reader
  ImageReader reader = ImageIO.getImageReadersByFormatName("gif").next();
  // Give it the stream to decode from
  reader.setInput(ImageIO.createImageInputStream(file));

  int numImages = reader.getNumImages(true);

  // Get 'metaFormatName'. Need first frame for that.
  IIOMetadata imageMetaData = reader.getImageMetadata(0);
  String metaFormatName = imageMetaData.getNativeMetadataFormatName();

  // Find out if GIF is bugged
  boolean foundBug = false;
  for (int i = 0; i < numImages && !foundBug; i++) {
      // Get metadata
      IIOMetadataNode root = (IIOMetadataNode)reader.getImageMetadata(i).getAsTree(metaFormatName);

      // Find GraphicControlExtension node
      int nNodes = root.getLength();
      for (int j = 0; j < nNodes; j++) {
          org.w3c.dom.Node node = root.item(j);
          if (node.getNodeName().equalsIgnoreCase("GraphicControlExtension")) {
              // Get delay value
              String delay = ((IIOMetadataNode)node).getAttribute("delayTime");

              // Check if delay is bugged
              if (Integer.parseInt(delay) == 0) {
                  foundBug = true;
              }

              break;
          }
      }
  }

  if (loadBufferedImageFixingGIFs_debug) print("loadBufferedImageFixingGIFs" + ": " + f2s(file) + " foundBug=" + foundBug);
  
  // Load non-bugged GIF the normal way
  Image image;
  if (!foundBug) {
    image = Toolkit.getDefaultToolkit().createImage(f2s(file));
  } else {
    // Prepare streams for image encoding
    ByteArrayOutputStream baoStream = new ByteArrayOutputStream();
    {
       ImageOutputStream ios = ImageIO.createImageOutputStream(baoStream); try {
      // Get GIF writer that's compatible with reader
      ImageWriter writer = ImageIO.getImageWriter(reader);
      // Give it the stream to encode to
      writer.setOutput(ios);

      writer.prepareWriteSequence(null);

      for (int i = 0; i < numImages; i++) {
          // Get input image
          BufferedImage frameIn = reader.read(i);

          // Get input metadata
          IIOMetadataNode root = (IIOMetadataNode)reader.getImageMetadata(i).getAsTree(metaFormatName);

          // Find GraphicControlExtension node
          int nNodes = root.getLength();
          for (int j = 0; j < nNodes; j++) {
              org.w3c.dom.Node node = root.item(j);
              if (node.getNodeName().equalsIgnoreCase("GraphicControlExtension")) {
                  // Get delay value
                  String delay = ((IIOMetadataNode)node).getAttribute("delayTime");

                  // Check if delay is bugged
                  if (Integer.parseInt(delay) == 0) {
                      // Overwrite with a valid delay value
                      ((IIOMetadataNode)node).setAttribute("delayTime", "10");
                  }

                  break;
              }
          }

          // Create output metadata
          IIOMetadata metadata = writer.getDefaultImageMetadata(new ImageTypeSpecifier(frameIn), null);
          // Copy metadata to output metadata
          metadata.setFromTree(metadata.getNativeMetadataFormatName(), root);

          // Create output image
          IIOImage frameOut = new IIOImage(frameIn, null, metadata);

          // Encode output image
          writer.writeToSequence(frameOut, writer.getDefaultWriteParam());
      }

      writer.endWriteSequence();
    } finally { _close(ios); }}

    // Create image using encoded data
    byte[] data = baoStream.toByteArray();
    setVar(loadBufferedImageFixingGIFs_output.get(), data);
    if (loadBufferedImageFixingGIFs_debug) print("Data size: " + l(data));
    image = Toolkit.getDefaultToolkit().createImage(data);
  }

  return image;
} catch (Exception __e) { throw rethrow(__e); } }


static int moveToTopRightCorner_inset = 20;

static <A extends Component> A moveToTopRightCorner(A a) {
  return moveToTopRightCorner(moveToTopRightCorner_inset, moveToTopRightCorner_inset, a);
}

static <A extends Component> A moveToTopRightCorner(int insetX, int insetY, A a) {
  Window w = getWindow(a);
  if (w != null)
    w.setLocation(getScreenSize().width-w.getWidth()-insetX, insetY);
  return a;
}

static JTextField onEnter(final JTextField tf, final Object action) {
  if (action == null || tf == null) return tf;
  tf.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent _evt) { try {
    tf.selectAll();
    callF(action);
  } catch (Throwable __e) { messageBox(__e); }}});
  return tf;
}

static JButton onEnter(JButton btn, final Object action) {
  if (action == null || btn == null) return btn;
  btn.addActionListener(actionListener(action));
  return btn;
}

static JList onEnter(JList list, Object action) {
  list.addKeyListener(enterKeyListener(rCallOnSelectedListItem(list, action)));
  return list;
}

static JComboBox onEnter(final JComboBox cb, final Object action) {
  { swing(new Runnable() {  public void run() { try { 
    if (cb.isEditable()) {
      JTextField text = (JTextField) cb.getEditor().getEditorComponent();
      onEnter(text, action);
    } else {
      cb.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "enter");
      cb.getActionMap().put("enter", abstractAction("", new Runnable() {  public void run() { try {  cb.hidePopup(); callF(action); 
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "cb.hidePopup(); callF(action);"; }}));
    }
  
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (cb.isEditable()) {\r\n      JTextField text = (JTextField) cb.getEditor().g..."; }}); }
  return cb;
}

static JTable onEnter(final JTable table, final Object action) {  
  table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
    .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "Enter");
    
  table.getActionMap().put("Enter", new AbstractAction() {
    public void actionPerformed(ActionEvent e) {
      callF(action, table.getSelectedRow());
    }
  });
  return table;
}

/*static JTextArea onEnter(final JTextArea ta, fO action) {
  addKeyListener(ta, enterKeyListener(action));
  ret ta;
}*/

static JTextField onEnter(Object action, JTextField tf) {
  return onEnter(tf, action);
}
static void swingLater(long delay, final Object r) {
  javax.swing.Timer timer = new javax.swing.Timer(toInt(delay), actionListener(wrapAsActivity(r)));
  timer.setRepeats(false);
  timer.start();
}

static void swingLater(Object r) {
  SwingUtilities.invokeLater(toRunnable(r));
}

static int toMS_int(double seconds) {
  return toInt_checked((long) (seconds*1000));
}
static double toDouble(Object o) {
  if (o instanceof Number)
    return ((Number) o).doubleValue();
  if (o instanceof BigInteger)
    return ((BigInteger) o).doubleValue();
  if (o == null) return 0.0;
  throw fail(o);
}
static String fromUtf8(byte[] bytes) { try {
  return bytes == null ? null : new String(bytes, "UTF-8");
} catch (Exception __e) { throw rethrow(__e); } }


static String imageServerLink(String md5OrID) {
  if (possibleMD5(md5OrID))
    return "https://botcompany.de/images/md5/" + md5OrID;
  return imageServerLink(parseSnippetID(md5OrID));
}

static String imageServerLink(long id) {
  return "https://botcompany.de/images/" + id;
}
static int roundDownTo(int x, int n) {
  return x/n*n;
}

static long roundDownTo(long x, long n) {
  return x/n*n;
}
static byte[] isGIF_magic = bytesFromHex("47494638"); // Actual signature is longer, but we're lazy

static boolean isGIF(byte[] data) {
  return byteArrayStartsWith(data, isGIF_magic);
}

static boolean isGIF(File f) {
  return isGIF(loadBeginningOfBinaryFile(f, l(isGIF_magic)));
}
static <A> void setVar(IVar<A> v, A value) {
  if (v != null) v.set(value);
}
static Dimension getScreenSize() {
  return Toolkit.getDefaultToolkit().getScreenSize();
}
static void messageBox(final String msg) {
  if (headless()) print(msg);
  else { swing(new Runnable() {  public void run() { try { 
    JOptionPane.showMessageDialog(null, msg, "JavaX", JOptionPane.INFORMATION_MESSAGE);
  
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "JOptionPane.showMessageDialog(null, msg, \"JavaX\", JOptionPane.INFORMATION_MES..."; }}); }
}

static void messageBox(Throwable e) {
  //showConsole();
  printStackTrace(e);
  messageBox(hideCredentials(innerException2(e)));
}
static ActionListener actionListener(final Object runnable) {
  return actionListener(runnable, null);
}

static ActionListener actionListener(final Object runnable, final Object instanceToHold) {
  if (runnable instanceof ActionListener) return (ActionListener) runnable;
  final Object info = _threadInfo();
  return new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent _evt) { try {
    _threadInheritInfo(info);
     AutoCloseable __1921 = holdInstance(instanceToHold); try {
    callF(runnable);
  } finally { _close(__1921); }} catch (Throwable __e) { messageBox(__e); }}};
}
static KeyListener enterKeyListener(final Object action) {
  return new KeyAdapter() {
    public void keyPressed(KeyEvent ke) {
      if (ke.getKeyCode() == KeyEvent.VK_ENTER)
        pcallF(action);
    }
  };
}
static Runnable rCallOnSelectedListItem(final JList list, final Object action) {
  return new Runnable() {  public void run() { try {  pcallF(action, getSelectedItem(list)) ;
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "pcallF(action, getSelectedItem(list))"; }};
}
static AbstractAction abstractAction(String name, final Object runnable) {
  return new AbstractAction(name) {
    public void actionPerformed(ActionEvent evt) {
      pcallF(runnable);
    }
  };
}
static Runnable wrapAsActivity(Object r) {
  return toRunnable(r);
}
static int toInt_checked(long l) {
  if (l != (int) l) throw fail("Too large for int: " + l);
  return (int) l;
}


static boolean possibleMD5(String s) { return isMD5(s); }
static byte[] bytesFromHex(String s) {
  return hexToBytes(s);
}
static boolean byteArrayStartsWith(byte[] a, byte[] b) {
  if (a == null || b == null) return false;
  if (a.length < b.length) return false;
  for (int i = 0; i < b.length; i++)
    if (a[i] != b[i])
      return false;
  return true;
}
static byte[] loadBeginningOfBinaryFile(File file, int maxBytes) {
  return loadBinaryFilePart(file, 0, maxBytes);
}
static boolean headless() {
  return isHeadless();
}
static String getSelectedItem(JList l) {
  return (String) l.getSelectedValue();
}

static String getSelectedItem(JComboBox cb) {
  return strOrNull(cb.getSelectedItem());
}


static boolean isMD5(String s) {
  return l(s) == 32 && isLowerHexString(s);
}
static byte[] loadBinaryFilePart(File file, long start, long end) { try {
  RandomAccessFile raf = new RandomAccessFile(file, "r");
  int n = toInt(min(raf.length(), end-start));
  byte[] buffer = new byte[n];
  try {
    raf.seek(start);
    raf.readFully(buffer, 0, n);
    return buffer;
  } finally {
    raf.close();
  }
} catch (Exception __e) { throw rethrow(__e); } }
static String strOrNull(Object o) {
  return o == null ? null : str(o);
}


static boolean isLowerHexString(String s) {
  for (int i = 0; i < l(s); i++) {
    char c = s.charAt(i);
    if (c >= '0' && c <= '9' || c >= 'a' && c <= 'f') {
      // ok
    } else
      return false;
  }
  return true;
}


// immutable, has strong refs
final static class _MethodCache {
  final Class c;
  final HashMap<String, List<Method>> cache = new HashMap();
  
  _MethodCache(Class c) {
  this.c = c; _init(); }
  
  void _init() {
    Class _c = c;
    while (_c != null) {
      for (Method m : _c.getDeclaredMethods())
        if (!reflection_isForbiddenMethod(m))
          multiMapPut(cache, m.getName(), makeAccessible(m));
      _c = _c.getSuperclass();
    }
    
    // add default methods - this might lead to a duplication
    // because the overridden method is also added, but it's not
    // a problem except for minimal performance loss.
    for (Class intf : allInterfacesImplementedBy(c))
      for (Method m : intf.getDeclaredMethods())
        if (m.isDefault() && !reflection_isForbiddenMethod(m))
          multiMapPut(cache, m.getName(), makeAccessible(m));

    
  }
  
  // Returns only matching methods
  Method findMethod(String method, Object[] args) { try {
    List<Method> m = cache.get(method);
    
    if (m == null) return null;
    int n = m.size();
    for (int i = 0; i < n; i++) {
      Method me = m.get(i);
      if (call_checkArgs(me, args, false))
        return me;
    }
    return null;
  } catch (Exception __e) { throw rethrow(__e); } }
  
  Method findStaticMethod(String method, Object[] args) { try {
    List<Method> m = cache.get(method);
    if (m == null) return null;
    int n = m.size();
    for (int i = 0; i < n; i++) {
      Method me = m.get(i);
      if (isStaticMethod(me) && call_checkArgs(me, args, false))
        return me;
    }
    return null;
  } catch (Exception __e) { throw rethrow(__e); } }
}static abstract class VF1<A> implements IVF1<A> {
  public abstract void get(A a);
}static class Matches {
  String[] m;
  
  Matches() {}
  Matches(String... m) {
  this.m = m;}
  
  String get(int i) { return i < m.length ? m[i] : null; }
  String unq(int i) { return unquote(get(i)); }
  String fsi(int i) { return formatSnippetID(unq(i)); }
  String fsi() { return fsi(0); }
  String tlc(int i) { return unq(i).toLowerCase(); }
  boolean bool(int i) { return "true".equals(unq(i)); }
  String rest() { return m[m.length-1]; } // for matchStart
  int psi(int i) { return Integer.parseInt(unq(i)); }
  
  public String toString() { return "Matches(" + joinWithComma(quoteAll(asList(m))) + ")"; }
  
  public int hashCode() { return _hashCode(toList(m)); }
  public boolean equals(Object o) { return o instanceof Matches && arraysEqual(m, ((Matches) o).m); }
}
static class NowFuture<T> implements Future<T> {
  T result;
  NowFuture() {}
  NowFuture(T result) {
  this.result = result;}

  public boolean cancel(final boolean b) { return false; }
  public boolean isCancelled() { return false; }
  public boolean isDone() { return true; }
  public T get() { return result; }
  public T get(long l, TimeUnit timeUnit) { return result; }
}static class Var<A> implements IVar<A> {
  Var() {}
  Var(A v) {
  this.v = v;}

  
  A v; // you can access this directly if you use one thread
  
  public synchronized void set(A a) {
    if (v != a) {
      v = a;
      notifyAll();
    }
  }
  
  public synchronized A get() { return v; }
  public synchronized boolean has() { return v != null; }
  public synchronized void clear() { v = null; }
  
  public String toString() { return str(get()); }
}static final class IndexedList2<A> extends AbstractList<A> {
  ArrayList<A> l = new ArrayList();
  MultiSet<A> index = new MultiSet(false); // HashMap
  static boolean debug = false;
  static int instances;
  
  IndexedList2() {
    ++instances;
  }
  
  IndexedList2(List<A> x) {
    this();
    l.ensureCapacity(l(x));
    addAll(x);
  }
  
  // required immutable list methods
  
  @Override
  public A get(int i) {
    return l.get(i);
  }
  
  @Override
  public int size() {
    return l.size();
  }
  
  // required mutable list methods
  
  @Override
  public A set(int i, A a) {
    A prev = l.get(i);
    if (prev != a) {
      index.remove(prev);
      index.add(a);
    }
    l.set(i, a);
    return prev;
  }
  
  @Override
  public void add(int i, A a) {
    index.add(a);
    l.add(i, a);
  }
  
  @Override
  public A remove(int i) {
    A a = l.get(i);
    index.remove(a);
    l.remove(i);
    return a;
  }
  
  // speed up methods

  @Override  
  protected void removeRange(int fromIndex, int toIndex) {
    for (int i = fromIndex; i < toIndex; i++)
      unindex(i);
    l.subList(fromIndex, toIndex).clear();
  }
  
  @Override
  public int indexOf(Object a) {
    if (!contains(a)) return -1;
    return l.indexOf(a);
  }
  
  @Override
  public boolean contains(Object a) {
    boolean b = index.contains((A) a);
    if (debug) print("IndexedList2.contains " + a + " => " + b);
    return b;
  }
  
  @Override
  public void clear() {
    index.clear();
    l.clear();
  }
  
  @Override
  public boolean addAll(int i, Collection<? extends A> c) {
    index.addAll((Collection) c);
    return l.addAll(i, c);
  }
  
  // indexing methods
  
  void unindex(int i) {
    index.remove(l.get(i));
  }
  
  void index(int i) {
    index.add(l.get(i));
  }
  
  // static methods
  
  static <A> IndexedList2<A> ensureIndexed(List<A> l) {
    return l instanceof IndexedList2 ? (IndexedList2) l : new IndexedList2(l);
  }
}static class AutoInitVar<A> extends Var<A> {
  Object make;
  boolean has = false;
  Lock lock = lock();
  
  AutoInitVar() {}
  AutoInitVar(Object make) {
  this.make = make;}
  
  public A get() {
    if (has) return super.get();
    Lock __1969 = lock; lock(__1969); try {
    if (has) return super.get();
    set((A) pcallF(make));
    make = null;
    has = true;
    return super.get();
  } finally { unlock(__1969); } }
}static abstract class TokCondition {
  abstract boolean get(List<String> tok, int i); // i = N Index
}static interface IResourceLoader {
  String loadSnippet(String snippetID);
  String getTranspiled(String snippetID); // with libs
  int getSnippetType(String snippetID);
  String getSnippetTitle(String snippetID);
  File loadLibrary(String snippetID);
  File pathToJavaXJar();
  File getSnippetJar(String snippetID, String transpiledSrc);
}static class CombinedMap<A, B> extends AbstractMap<A, B> {
  List < Map < A , B > > maps = new ArrayList();

  CombinedMap() {}
  CombinedMap(Map<A, B>... maps) { addAllNonNulls(this.maps, maps); }
  <C extends Map<A, B>> CombinedMap(Collection<C> maps) { addAllNonNulls(this.maps, maps); }

  public int size() { return lengthLevel2_maps(maps); }
  public Set<Map.Entry<A, B>> entrySet() { throw fail(); }
  public boolean containsKey(Object o) {
    for (Map<A, B> map : maps)
      if (map.containsKey(o))
        return true;
    return false;
  }
  
  @Override
  public B get(Object o) {
    for (Map<A, B> map : maps)
      if (map.containsKey(o))
        return map.get(o);
    return null;
  }
}
// optimized for search - O(1) for searching for a token
// set is O(m) with m = number of occurrences of token
final static class TokenIndexedList3 extends RandomAccessAbstractList<String> implements IContentsIndexedList<String>, IContentsIndexedList2<String> {
  Map < String , TreeSet < Token > > index = new HashMap(); // tokens by contents, sorted by index
  List<Token> list = new ArrayList();

  static class Token extends HasIndex {
    String s; // actual token
    
    public String toString() { return "Token " + quote(s) + "@" + idx; }
  }
  
  TokenIndexedList3() {}
  TokenIndexedList3(Collection<String> l) { addAll(l); }
  
  public String get(int i) { return list.get(i).s; }
  public int size() { return list.size(); }
  
  public String set(int i, String s) {
    Token t = list.get(i);
    String old = t.s;
    if (eq(old, s)) return old;
    removeFromIdx(t);
    t.s = s;
    addToIdx(t);
    return old;
  }
  
  public boolean add(String s) {
    ++modCount;
    Token t = new Token();
    t.s = s;
    t.idx = size();
    list.add(t);
    addToIdx(t);
    return true;
  }
  
  public void add(int i, String s) {
    ++modCount;
    Token t = new Token();
    t.s = s;
    t.idx = i;
    list.add(i, t);
    reorder(i+1);
    addToIdx(t);
  }
  
  public boolean addAll(int i, Collection<? extends String> l) {
    int n = l.size();
    if (n == 0) return false;
    ++modCount;
    List<Token> l2 = emptyList(n);
    int j = i;
    for (String s : l) {
      Token t = new Token();
      t.s = s;
      t.idx = j++;
      l2.add(t);
    }
    list.addAll(i, l2);
    reorder(i+n);
    for (Token t : l2)
      addToIdx(t);
    return true;
  }
  
  public String remove(int i) {
    ++modCount;
    Token t = list.get(i);
    removeFromIdx(t);
    list.remove(i);
    reorder(i);
    return t.s;
  }
  
  void reorder(int fromIdx) {
    int n = size();
    for (int i = fromIdx; i < n; i++)
      list.get(i).idx = i;
  }
  
  void removeFromIdx(Token t) {
    TreeSet<Token> idx = index.get(t.s);
    idx.remove(t);
    if (idx.isEmpty()) index.remove(t.s);
  }
  
  void addToIdx(Token t) {
    TreeSet<Token> idx = index.get(t.s);
    if (idx == null)
      index.put(t.s, idx = new TreeSet());
    idx.add(t);
  }
  
  Comparator<Token> comparator() {
    return (Comparator<Token>) (a, b) -> b.idx-a.idx;
  }
  
  public int indexOf(String s) {
    TreeSet<Token> l = index.get(s);
    return l == null ? -1 : first(l).idx;
  }
  
  public boolean contains(Object s) {
    return index.containsKey(s);
  }
  
  public void clear() {
    ++modCount;
    index.clear();
    list.clear();
  }
  
  protected void removeRange(int fromIndex, int toIndex) {
    if (fromIndex == toIndex) return;
    ++modCount;
    
    // TODO: this can be optimized
    for (int i = fromIndex; i < toIndex; i++)
      removeFromIdx(list.get(i));
      
    list.subList(fromIndex, toIndex).clear();
    reorder(fromIndex);
  }
  
  public int[] indicesOf(Object o) {
    TreeSet<Token> idx = index.get(o);
    if (idx == null) return emptyIntArray();
    int[] a = new int[idx.size()];
    int i = 0;
    for (Token t : idx) a[i++] = t.idx;
    return a;
  }
  
  public TreeSet<HasIndex> indicesOf_treeSetOfHasIndex(Object o) {
    return (TreeSet) index.get(o);
  }
}static class CompilerBot {
  static boolean verbose = false;

  static File compileSnippet(String snippetID) {
    return compileSnippet(snippetID, "");
  }
  
  static Pair<File, String> compileSnippet2(String snippetID) {
    return compileSnippet2(snippetID, "");
  }
  
  // returns jar path
  static File compileSnippet(String snippetID, String javaTarget) {
    return compileSnippet2(snippetID, javaTarget).a;
  }
  
  // returns jar path, Java source
  static Pair<File, String> compileSnippet2(String snippetID, String javaTarget) {
    String transpiledSrc = getServerTranspiled2(snippetID);
    int i = transpiledSrc.indexOf('\n');
    String libs = transpiledSrc.substring(0, Math.max(0, i));
    if (verbose)
      print("Compiling snippet: " + snippetID + ". Libs: " + libs);
    transpiledSrc = transpiledSrc.substring(i+1);
    return pair(compile(transpiledSrc, libs, javaTarget, snippetID), transpiledSrc);
  }

  static File compile(String src) {
    return compile(src, "");
  }
  
  static File compile(String src, String libs) {
    return compile(src, libs, null);
  }

  static File compile(String src, String dehlibs, String javaTarget) {
    return compile(src, dehlibs, javaTarget, null);
  }
  
  static File compile(String src, String dehlibs, String javaTarget, String progID) {
    if (verbose)
      print("Compiling " + l(src) + " chars");
      
    // Note: This is different from the calculation in x30
    // (might lead to programs being compiled twice)
    String md5 = md5(dehlibs + "\n" + src + "\n" + fsIOpt(progID));
    File jar = getJarFile(md5);
    if (jar == null || jar.length() <= 22) {
      // have to compile
      
      print("Have to compile: " + progID + " / " + md5);
      
      List<String> mainClass = findMainClass(javaTok(src));
      boolean canRename = mainClass != null && useDummyMainClasses() && isSnippetID(progID) && !tok_classHasModifier(mainClass, "public");
      if (verbose)
        print("useRenaming: " + useDummyMainClasses() + ", canRename: " + canRename + ", progID: " + progID);

      javaCompileToJar_optionalRename(src, dehlibs, jar, canRename ? progID : null, progID);
    } else {
      if (verbose)
        print("Getting classes from cache (" + jar.getAbsolutePath() + ", " + jar.length() + " bytes)");
      touchFile(jar); // so we can find the unused ones easier
    }
    
    return jar;
  }

  // look in non-virtual JavaX-Caches first (important for packaged programs)
  static File getJarFile(String md5) {
    assertTrue(isMD5(md5));
    String sub = "JavaX-Caches/#1002203/" + md5 + ".jar";
    File f = actualUserDir(sub);
    return fileExists(f) ? f : userDir(sub);
  }
}static class Pair<A, B> implements Comparable<Pair<A, B>> {
  A a;
  B b;

  Pair() {}
  Pair(A a, B b) {
  this.b = b;
  this.a = a;}
  
  public int hashCode() {
    return hashCodeFor(a) + 2*hashCodeFor(b);
  }
  
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof Pair)) return false;
    Pair t = (Pair) o;
    return eq(a, t.a) && eq(b, t.b);
  }
  
  public String toString() {
    return "<" + a + ", " + b + ">";
  }
  
  public int compareTo(Pair<A, B> p) {
    if (p == null) return 1;
    int i = ((Comparable<A>) a).compareTo(p.a);
    if (i != 0) return i;
    return ((Comparable<B>) b).compareTo(p.b);
  }
}static class JavaXClassLoader extends URLClassLoader {
  String progID;
  List<File> files;
  Set<String> triedToLoad = synchroSet();
  Set<Class> loadedClasses = synchroSet();
  boolean retired = false;
  Object retiredMarker;
  
  JavaXClassLoader(String progID, List<File> files) {
    this(progID, files, getSystemClassLoader());
  }
  
  JavaXClassLoader(String progID, List<File> files, ClassLoader parent) {
    super(new URL[0], parent);
    this.progID = progID;
    this.files = files;
    
    { try {
      for (File f : files)
        addURL(f.toURI().toURL());
    } catch (Exception __e) { throw rethrow(__e); } }
    
    fixACCInClassLoader(this);
  }
  
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    boolean triedBefore = !triedToLoad.add(name);
    try {
      Class<?> c = super.findClass(name);
      loadedClasses.add(c);
      if (eq(name, "main"))
        callOpt(javax(), "registerAMainClass", c);
      return c;
    } catch (ClassNotFoundException e) {
      throw new ClassNotFoundException("Class " + name + " not found in " + joinWithComma(map("f2s", files)) + " (progID=" + progID + ")"
        + (triedBefore ? ", tried to load before" : ""), e);
    }
  }
  
  public String toString() {
    return "JavaXClassLoader[" + systemHashCodeHex(this) + "] - " + progID;
  }
}
static class JavaXClassLoaderWithParent extends JavaXClassLoader {
  ClassLoader virtualParent;

  JavaXClassLoaderWithParent(String progID, List<File> files, ClassLoader virtualParent) {
    super(progID, files);
    this.virtualParent = virtualParent;
  }
  
   protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (virtualParent != null && !eq(name, "main") && !name.startsWith("main$")) {
      try {
        return virtualParent.loadClass(name);
      } catch (ClassNotFoundException e) {}
    }
    return super.findClass(name);
  }
}
    final static class IntRange {
  int start, end;
  
  IntRange() {}
  IntRange(int start, int end) {
  this.end = end;
  this.start = start;}
  
  public boolean equals(Object o) { return stdEq2(this, o); }
public int hashCode() { return stdHash2(this); }
  
  int length() { return end-start; }
  
  static String _fieldOrder = "start end";
  
  public String toString() { return "[" + start + ";" + end + "]"; }
}static abstract class F0<A> {
  abstract A get();
}static abstract class F1<A, B> {
  abstract B get(A a);
}// you still need to implement hasNext() and next()
static abstract class IterableIterator<A> implements Iterator<A>, Iterable<A> {
  public Iterator<A> iterator() {
    return this;
  }
  
  public void remove() {
    unsupportedOperation();
  }
}static class Snippet {
  String id, title, md5, type, text;
  
  Snippet() {}
  Snippet(String id, String title) {
  this.title = title;
  this.id = id;}
  Snippet(String id, String title, String md5) {
  this.md5 = md5;
  this.title = title;
  this.id = id;}
  
  public String toString() { return id + " - " + title; }
  
  public boolean equals(Object o) { return stdEq2(this, o); }
public int hashCode() { return stdHash2(this); }
}static abstract class F2<A, B, C> {
  abstract C get(A a, B b);
}// elements are put to front when added (not when accessed)
static class MRUCache<A, B> extends LinkedHashMap<A, B> {
  int maxSize = 10;

  MRUCache() {}
  MRUCache(int maxSize) {
  this.maxSize = maxSize;}
  
  protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > maxSize;
  }
  
  Object _serialize() {
    return ll(maxSize, cloneLinkedHashMap(this));
  }
  
  static MRUCache _deserialize(List l) {
    MRUCache m = new MRUCache();
    m.maxSize = (int) first(l);
    m.putAll((LinkedHashMap) second(l));
    return m;
  }
}static interface IF0<A> {
  A get();
}static interface IContentsIndexedList2<A> extends List<A> {
  TreeSet<HasIndex> indicesOf_treeSetOfHasIndex(Object o);
}static abstract class CloseableIterableIterator<A> extends IterableIterator<A> implements AutoCloseable {
  public void close() throws Exception {}
}static class ExpiringMap2<A, B> extends AbstractMap<A, B> {
  Map<A, Pair<Long, B>> byKey = new HashMap();      // key -> pair(expiry sys time, value)
  PriorityBlockingQueue < Pair < Long , A > > queue = new PriorityBlockingQueue(); // queue(pair(expiry sys time, key))
  long standardExpiryTime; // ms
  boolean renewOnOverwrite = true, renewOnGet;
  Object onChange;
  // new RestartableCountdown countdown; // TODO
  
  ExpiringMap2() {}
  ExpiringMap2(long standardExpiryTime) {
  this.standardExpiryTime = standardExpiryTime;}
  ExpiringMap2(long standardExpiryTime, Object onChange) {
  this.onChange = onChange;
  this.standardExpiryTime = standardExpiryTime;}
  
  synchronized boolean clean() {
    boolean changes = false;
    Pair<Long, A> p;
    while ((p = queue.peek()) != null && sysTime() >= p.a) {
      p = queue.poll();
      
      Pair<Long, B> v = byKey.get(p.b);
      if (v != null /*&& v.a == p.a*/) {
        
        byKey.remove(p.b);
        changes = true;
        change();
      }
    }
    return changes;
  }
  
  void change() { callF(onChange); }
  
  synchronized public B put(A a, B b) {
    clean();
    long timeout = sysTime()+standardExpiryTime;
    Pair<Long, B> p = byKey.get(a);
    if (p != null && renewOnOverwrite)
      queue.remove(new Pair(p.a, a));
    byKey.put(a, pair(timeout, b));
    change();
    if (p == null || renewOnOverwrite)
      queue.add(new Pair(timeout, a));
    return pairB(p);
  }
  
  synchronized public B remove(Object a) {
    clean();
    Pair<Long, B> p = byKey.get(a);
    if (p == null) return null;
    queue.remove(new Pair(p.a, a));
    byKey.remove(a);
    change();
    return p.b;
  }
  
  synchronized public B get(Object a) {
    clean();
    Pair<Long, B> p = byKey.get(a);
    if (renewOnGet && p != null) {
      queue.remove(new Pair(p.a, a));
      long timeout = sysTime()+standardExpiryTime;
      byKey.put((A) a, pair(timeout, p.b));
      queue.add(new Pair(timeout, a));
    }
    return pairB(p);
  }
  
  synchronized public Set<Map.Entry<A,B>> entrySet() {
    clean();
    // TODO: mutex
    return synchronizedSet(mapValues("pairB", byKey).entrySet());
  }
  
  synchronized public Set<A> keySet() {
    clean();
    return synchronizedSet(byKey.keySet());
  }
  
  synchronized public int size() {
    clean();
    return byKey.size();
  }
  
  void setStandardExpiryTime(long ms) { standardExpiryTime = ms; }
  
  synchronized ExpiringMap2<A, B> setMap(Map innerMap) {
    byKey = innerMap;
    return this;
  }
}
static class JavaXClassLoaderWithParent2 extends JavaXClassLoader {
  ClassLoader virtualParent;
  List<String> classesToSkip; // classes that should be taken from parent

  JavaXClassLoaderWithParent2(String progID, List<File> files, ClassLoader virtualParent, List<String> classesToSkip) {
    super(progID, files);
    this.virtualParent = virtualParent;
    this.classesToSkip = classesToSkip;
  }
  
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (shouldDelegate(name)) {
      Class<?> c = virtualParent.loadClass(name);
      if (c != null) return c;
    }
    return super.findClass(name);
  }
    
  boolean shouldDelegate(String name) {
    for (String s : classesToSkip)
      if (eq(name, s) || startsWith(name, s + "$"))
        return true;
    return false;
  }
}static interface IF2<A, B, C> {
  C get(A a, B b);
}static interface Producer<A> {
  public A next();
}static ThreadLocal<Boolean> DynamicObject_loading = new ThreadLocal();

static class DynamicObject {
  String className; // just the name, without the "main$"
  /*new XXX - not initializing anymore - may cause problems in the odd legacy program */ LinkedHashMap<String, Object> fieldValues;
  
  DynamicObject() {}
  // className = just the name, without the "main$"
  DynamicObject(String className) {
  this.className = className;}
  
  Map<String, Object> _map() { return fieldValues; }
}static interface IF1<A, B> {
  B get(A a);
}static class PersistableThrowable {
  String className;
  String msg;
  String stacktrace;
  
  PersistableThrowable() {}
  PersistableThrowable(Throwable e) {
    if (e == null)
      className = "Crazy Null Error";
    else {
      className = getClassName(e).replace('/', '.');
      msg = e.getMessage();
      stacktrace = getStackTrace_noRecord(e);
    }
  }
  
  public String toString() {
    return nempty(msg) ? className + ": " + msg : className;
  }
}static interface IVar<A> {
  void set(A a);
  A get();
  default boolean has() { return get() != null; }
  default void clear() { set(null); }
}static class HasIndex implements Comparable<HasIndex> {
  int idx;
  
  HasIndex() {}
  HasIndex(int idx) {
  this.idx = idx;}
  
  public int compareTo(HasIndex h) {
    return idx-h.idx;
  }
}

static interface IVF1<A> {
  void get(A a);
}// uses HashMap by default
static class MultiSet<A> {
  Map<A, Integer> map = new HashMap();
  
  MultiSet(boolean useTreeMap) {
    if (useTreeMap) map = new TreeMap();
  }
  MultiSet() {}
  MultiSet(Iterable<A> c) { addAll(c); }
  MultiSet(MultiSet<A> ms) { synchronized(ms) {
    for (A a : ms.keySet()) add(a, ms.get(a));
  }}
  
  synchronized void add(A key) { add(key, 1); }
  
  synchronized void addAll(Iterable<A> c) {
    if (c != null) for (A a : c) add(a);
  }

  synchronized void addAll(MultiSet<A> ms) {
    for (A a : ms.keySet()) add(a, ms.get(a));
  }
  
  synchronized void add(A key, int count) {
    if (count <= 0) return;
    if (map.containsKey(key))
      map.put(key, map.get(key)+count);
    else
      map.put(key, count);
  }

  synchronized int get(A key) {
    Integer i = map.get(key);
    return i != null ? i : 0;
    //ret key != null && map.containsKey(key) ? map.get(key) : 0;
  }
  
  synchronized boolean contains(A key) {
    return map.containsKey(key);
  }

  synchronized void remove(A key) {
    Integer i = map.get(key);
    if (i != null && i > 1)
      map.put(key, i - 1);
    else
      map.remove(key);
  }

  synchronized List<A> topTen() { return getTopTen(); }
  
  synchronized List<A> getTopTen() { return getTopTen(10); }
  synchronized List<A> getTopTen(int maxSize) {
    List<A> list = getSortedListDescending();
    return list.size() > maxSize ? list.subList(0, maxSize) : list;
  }
  
  synchronized List<A> highestFirst() {
    return getSortedListDescending();
  }

  synchronized List<A> lowestFirst() {
    return reversedList(getSortedListDescending());
  }

  synchronized List<A> getSortedListDescending() {
    List<A> list = new ArrayList<A>(map.keySet());
    Collections.sort(list, new Comparator<A>() {
      public int compare(A a, A b) {
        return map.get(b).compareTo(map.get(a));
      }
    });
    return list;
  }

  synchronized int getNumberOfUniqueElements() {
    return map.size();
  }
  
  synchronized int uniqueSize() {
    return map.size();
  }

  synchronized Set<A> asSet() {
    return map.keySet();
  }

  synchronized NavigableSet<A> navigableSet() {
    return navigableKeys((NavigableMap) map);
  }

  synchronized Set<A> keySet() {
    return map.keySet();
  }
  
  synchronized A getMostPopularEntry() {
    int max = 0;
    A a = null;
    for (Map.Entry<A,Integer> entry : map.entrySet()) {
      if (entry.getValue() > max) {
        max = entry.getValue();
        a = entry.getKey();
      }
    }
    return a;
  }

  synchronized void removeAll(A key) {
    map.remove(key);
  }

  synchronized int size() {
    int size = 0;
    for (int i : map.values())
      size += i;
    return size;
  }

  synchronized MultiSet<A> mergeWith(MultiSet<A> set) {
    MultiSet<A> result = new MultiSet<A>();
    for (A a : set.asSet()) {
      result.add(a, set.get(a));
    }
    return result;
  }
  
  synchronized boolean isEmpty() {
    return map.isEmpty();
  }
  
  synchronized public String toString() { // hmm. sync this?
    return str(map);
  }
  
  synchronized void clear() {
    map.clear();
  }
  
  synchronized Map<A, Integer> asMap() {
    return cloneMap(map);
  }
}static interface IContentsIndexedList<A> extends List<A> {
  int[] indicesOf(Object o);
}abstract static class RandomAccessAbstractList<A> extends AbstractList<A> implements RandomAccess {
}

static <A, B> B pairB(Pair<A, B> p) {
  return p == null ? null : p.b;
}
static Set<Class> allInterfacesImplementedBy(Class c) {
  if (c == null) return null;
  HashSet<Class> set = new HashSet();
  allInterfacesImplementedBy_find(c, set);
  return set;
}

static void allInterfacesImplementedBy_find(Class c, Set<Class> set) {
  if (c.isInterface() && !set.add(c)) return;
  do {
    for (Class intf : c.getInterfaces())
      allInterfacesImplementedBy_find(intf, set);
  } while ((c = c.getSuperclass()) != null);
}
static Method findMethod(Object o, String method, Object... args) {
  return findMethod_cached(o, method, args);
}

static boolean findMethod_checkArgs(Method m, Object[] args, boolean debug) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length) {
    if (debug)
      System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
    return false;
  }
  for (int i = 0; i < types.length; i++)
    if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
      if (debug)
        System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
      return false;
    }
  return true;
}
static Method findStaticMethod(Class c, String method, Object... args) {
  Class _c = c;
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (!m.getName().equals(method))
        continue;

      if ((m.getModifiers() & Modifier.STATIC) == 0 || !findStaticMethod_checkArgs(m, args))
        continue;

      return m;
    }
    c = c.getSuperclass();
  }
  return null;
}

static boolean findStaticMethod_checkArgs(Method m, Object[] args) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length)
    return false;
  for (int i = 0; i < types.length; i++)
    if (!(args[i] == null || isInstanceX(types[i], args[i])))
      return false;
  return true;
}

static String fsi(String id) {
  return formatSnippetID(id);
}
static List<String> quoteAll(Collection<String> l) {
  List<String> x = new ArrayList();
  for (String s : l)
    x.add(quote(s));
  return x;
}
static int _hashCode(Object a) {
  return a == null ? 0 : a.hashCode();
}
static boolean arraysEqual(Object[] a, Object[] b) {
  if (a.length != b.length) return false;
  for (int i = 0; i < a.length; i++)
    if (neq(a[i], b[i])) return false;
  return true;
}
static <A, B extends A> void addAllNonNulls(Collection<A> c, Iterable<B> b) {
  if (c != null && b != null) for (A a : b) if (a != null) c.add(a);
}

static <A, B extends A> void addAllNonNulls(Collection<A> c, B... b) {
  if (c != null && b != null) for (A a : b) if (a != null) c.add(a);
}
static <A> int lengthLevel2_maps(Collection<? extends Map> l) {
  int sum = 0;
  for (Map c : l) sum += l(c);
  return sum;
}
static int[] emptyIntArray_a = new int[0];
static int[] emptyIntArray() { return emptyIntArray_a; }


static boolean getServerTranspiled2_allowLocalFallback = true, getServerTranspiled2_localFallbackVerbose = true;

// to avoid checking server for transpilations too often when booting OS
static Map<String, String> getServerTranspiled2_tempCache;

static String getServerTranspiled2(String id) {
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.getTranspiled(id);
  
  
  id = fsIOpt(id);
  
  String transpiled = mapGet(getServerTranspiled2_tempCache, id);
  if (transpiled != null) return transpiled;
  if (getServerTranspiled2_tempCache != null) print("CACHE FAIL on " + id);
  
  transpiled = loadCachedTranspilation(id);
  String md5 = null;
  if (machineIsOffline() || isOfflineMode() || isLocalSnippet(id)) return transpiled;
  if (transpiled != null)
    md5 = md5(transpiled);
  String transpiledSrc;
  try {
    transpiledSrc = getServerTranspiled(formatSnippetID(id), md5);
  } catch (Throwable e) {
    if (!getServerTranspiled2_allowLocalFallback) rethrow(e);
    printExceptionShort(e);
    if (getServerTranspiled2_localFallbackVerbose) print("Fallback to local code");
    return transpiled;
  }
  if (eq(transpiledSrc, "SAME")) {
    if (!isTrue(loadPage_silent.get())) print("SAME");
    return mapPut_returnValue(getServerTranspiled2_tempCache, id, transpiled);
  }
  return mapPut_returnValue(getServerTranspiled2_tempCache, id, transpiledSrc);
}


static String fsIOpt(String s) {
  return formatSnippetIDOpt(s);
}
static boolean useDummyMainClasses() {
  return true;
  //ret eq("1", trim(loadTextFile(getProgramFile(#1008755, "use-dummy-main-classes"))));
}
static boolean tok_classHasModifier(List<String> classDecl, String modifier) {
  if (classDecl == null) return false;
  int i = classDecl.indexOf("class");
  return subList(classDecl, 0, i).contains(modifier);
}
// before you use this, add a RAM disk cleaner
static boolean javaCompileToJar_useRAMDisk = false;

static File javaCompileToJar_optionalRename(String src, File destJar, String progIDForRename) {
  return javaCompileToJar_optionalRename(src, "", destJar, progIDForRename);
}

static synchronized File javaCompileToJar_optionalRename(String src, String dehlibs, File destJar, String progIDForRename) {
  return javaCompileToJar_optionalRename(src, dehlibs, destJar, progIDForRename, null);
}

// returns path to jar
static synchronized File javaCompileToJar_optionalRename(String src, String dehlibs, File destJar, String progIDForRename, String progID) {
  String javaTarget = null; // use default target
  
  //print("Compiling " + l(src) + " chars");
  String dummyClass = "main";
  if (progIDForRename != null) {
    dummyClass = dummyMainClassName(progIDForRename);
    src += "\nclass " + dummyClass + "{}";
  }
  String md5 = md5(src);
  File jar = destJar;

  Class j = getJavaX();
  if (javaTarget != null)
    setOpt(j, "javaTarget", javaTarget);
  //setOpt(j, "verbose", true);
  File srcDir = tempDir();
  String className = "main";
  String fileName = dummyClass + ".java";
  List<String> tok = javaTok(src);
  String packageName = tok_packageName(tok);
  if (packageName != null)
    fileName = packageName.replace(".", "/") + "/" + tok_firstClassName(tok) + ".java";
  File mainJava = new File(srcDir, fileName);
  //print("main java: " + mainJava.getAbsolutePath());
  saveTextFile(mainJava, src);
  File classesDir = javaCompileToJar_useRAMDisk ? tempDirPossiblyInRAMDisk() : tempDir();
  //print("Compiling to " + f2s(classesDir));
  try {
    List<File> libraries = new ArrayList();
    
    Matcher m = Pattern.compile("\\d+").matcher(dehlibs);
    while (m.find()) {
      String libID = m.group();
      //print("libID=" + quote(libID));
      assertTrue(isSnippetID(libID));
      libraries.add(loadLibraryOrSrcLib(libID));
    }
    
    libraries.add(pathToJavaxJar());
      
    String compilerOutput;
    try {
      compilerOutput = (String) call(j, "compileJava", srcDir, libraries, classesDir);
    } catch (Throwable e) {
      compilerOutput = (String) get(getJavaX(), "javaCompilerOutput");
      //fail("Compile Error. " + cleanJavaCompilerOutput(compilerOutput) + " " + e);
      compilerOutput = indentx("> ", cleanJavaCompilerOutput(compilerOutput));
      if (!swic(e.getMessage(), "Java compiler returned errors."))
        compilerOutput = appendWithNewLine(compilerOutput, str(e));
      //printStackTrace(e);
      throw fail(compilerOutput, e);
    }
    
    compilerOutput = cleanJavaCompilerOutput("Annotation processing got disabled, since it requires a 1.6 compliant JVM");

    if (nempty(compilerOutput)) {
      print("Compiler said: " + compilerOutput);
      //fail("Compile Error. " + compilerOutput);
    }
  
    // sanity test
    if (!anyFileWithExtensionInDir(classesDir, ".class"))
      throw fail("No classes generated");
        
    // add sources to .jar
    saveTextFile(new File(classesDir, "main.java"), src);
    
    // add information about libraries to jar
    if (nempty(dehlibs))
      saveTextFile(new File(classesDir, "libraries"), dehlibs);
      
    // add prog id to jar
    saveTextFile(new File(classesDir, "progID"), progID);
  
    //print("Zipping: " + classesDir.getAbsolutePath() + " to " + jar.getAbsolutePath());
    dir2zip_recurse_verbose = false;
    int n = dir2zip_recurse(classesDir, jar); // cache on success only
    //print("Files zipped: " + n);
  
    return jar;
  } finally {
    if (isInRAMDisk(classesDir)) deleteDirectory(classesDir);
  }
}

// will create the file or update its last modified timestamp
static void touchFile(File file) { try {
  closeRandomAccessFile(newRandomAccessFile(mkdirsForFile(file), "rw"));
} catch (Exception __e) { throw rethrow(__e); } }
static File actualUserDir() {
  return new File(actualUserHome());
}

static File actualUserDir(String path) {
  return new File(actualUserHome(), path);
}
static int hashCodeFor(Object a) {
  return a == null ? 0 : a.hashCode();
}
static void fixACCInClassLoader(Object o) {
  try {
    AccessControlContext acc = vm_globalACC();
    if (acc != null)
      replaceACCInClassLoader(o, acc);
  } catch (Throwable __e) { _handleException(__e); }
}
static String systemHashCodeHex(Object o) {
  return intToHex(identityHashCode(o));
}
static boolean stdEq2(Object a, Object b) {
  if (a == null) return b == null;
  if (b == null) return false;
  if (a.getClass() != b.getClass()) return false;
  for (String field : allFields(a))
    if (neq(getOpt(a, field), getOpt(b, field)))
      return false;
  return true;
}
static int stdHash2(Object a) {
  if (a == null) return 0;
  return stdHash(a, toStringArray(allFields(a)));
}
static UnsupportedOperationException unsupportedOperation() {
  throw new UnsupportedOperationException();
}
static <A, B> LinkedHashMap<A, B> cloneLinkedHashMap(Map<A, B> map) {
  return map == null ? new LinkedHashMap() : new LinkedHashMap(map);
}
static long sysTime() {
  return sysNow();
}
static void change() {
  //mainConcepts.allChanged();
  // safe version for now cause function is sometimes included unnecessarily (e.g. by EGDiff)
  callOpt(getOptMC("mainConcepts"), "allChanged");
}
static <A> void remove(List<A> l, int i) {
  if (l != null && i >= 0 && i < l(l))
    l.remove(i);
}

static <A> void remove(Collection<A> l, A a) {
  if (l != null) l.remove(a);
}
static Map mapValues(Object func, Map map) {
  Map m = similarEmptyMap(map);
  for (Object key : keys(map))
    m.put(key, callF(func, map.get(key)));
  return m;
}

static <A, B, C> Map<A, C> mapValues(Map<A, B> map, IF1<B, C> f) {
  return mapValues(f, map);
}

static Map mapValues(Map map, Object func) {
  return mapValues(func, map);
}
static <A, B> Set<A> keySet(Map<A, B> map) {
  return map == null ? new HashSet() : map.keySet();
}

static Set keySet(Object map) {
  return keys((Map) map);
}


  static <A> Set<A> keySet(MultiSet<A> ms) {
    return ms.keySet();
  }





static <A, B> NavigableSet<A> navigableKeys(NavigableMap<A, B> map) {
  return map == null ? new TreeSet() : map.navigableKeySet();
}


  static <A> NavigableSet<A> navigableKeys(MultiSet<A> ms) {
    return ((NavigableMap) ms.map).navigableKeySet();
  }



static <A, B> Map<A, B> cloneMap(Map<A, B> map) {
  if (map == null) return new HashMap();
  // assume mutex is equal to collection
  synchronized(map) {
    return map instanceof TreeMap ? new TreeMap((TreeMap) map) // copies comparator
      : map instanceof LinkedHashMap ? new LinkedHashMap(map)
      : new HashMap(map);
  }
}


static Method findMethod_cached(Object o, String method, Object... args) { try {
  if (o == null) return null;
  if (o instanceof Class) {
    _MethodCache cache = callOpt_getCache(((Class) o));
    List<Method> methods = cache.cache.get(method);
    if (methods != null) for (Method m : methods)
      if (isStaticMethod(m) && findMethod_checkArgs(m, args, false))
        return m;
    return null;
  } else {
    _MethodCache cache = callOpt_getCache(o.getClass());
    List<Method> methods = cache.cache.get(method);
    if (methods != null) for (Method m : methods)
      if (findMethod_checkArgs(m, args, false))
        return m;
    return null;
  }
} catch (Exception __e) { throw rethrow(__e); } }

static String loadCachedTranspilation(String id) { try {
  return loadTextFilePossiblyGZipped(getCachedTranspilationFile(id));
} catch (Throwable __e) { return null; } }

static boolean machineIsOffline() {
  return isFalse(callF(vmGeneralMap_get("areWeOnline")));
}
static boolean isOfflineMode() {
  return eq("1", trim(loadProgramTextFile("#1005806", "offline-mode")));
}
static boolean isLocalSnippet(String snippetID) {
  return isLocalSnippetID(snippetID);
}



static boolean isLocalSnippet(long snippetID) {
  return isLocalSnippetID(snippetID);
}
static String getServerTranspiled(String snippetID) {
  return getServerTranspiled(snippetID, null);
}

static boolean getServerTranspiled_printStackTrace = false;

// returns "SAME" if md5 matches
static String getServerTranspiled(String snippetID, String expectedMD5) { try {
  if (getServerTranspiled_printStackTrace) printStackTrace();
  long id = parseSnippetID(snippetID);
  /*S t = getTranspilationFromBossBot(id);
  if (t != null) return t;*/
  
  String text = loadPage_utf8(tb_mainServer() + "/tb-int/get-transpiled.php?raw=1&withlibs=1&id=" + id + "&utf8=1"
    + (l(expectedMD5) > 1 ? "&md5=" + urlencode(expectedMD5) : "")
    + standardCredentials());
  if (nempty(text) && neq(text, "SAME"))
    saveTranspiledCode(snippetID, text);
  return text;
} catch (Exception __e) { throw rethrow(__e); } }
static void printExceptionShort(Throwable e) {
  print(exceptionToStringShort(e));
}
static <A, B> B mapPut_returnValue(Map<A, B> map, A key, B value) {
  mapPut(map, key, value);
  return value;
}
static String dummyMainClassName(String progID) {
  return "m" + psI(progID);
}
  static File tempDir() {
    return makeTempDir();
  }
static String tok_firstClassName(List<String> tok) {
  int i = jfind(tok, "class <id>");
  return i < 0 ? null : tok.get(i+2);
}
static File tempDirPossiblyInRAMDisk() {
  File f = linux_fileInRamDisk(aGlobalID());
  if (f != null) { f.mkdirs(); return f; }
  return makeTempDir();
}
static boolean loadLibraryOrSrcLib_srcLibsEnabled = true;

// to avoid checking src libs too often when booting OS
static ThreadLocal<Map<Long, File>> loadLibraryOrSrcLib_tempCache = new ThreadLocal();

static File loadLibraryOrSrcLib(String snippetID) { try {
  long id = parseSnippetID(snippetID);
  
  if (loadLibraryOrSrcLib_tempCache.get() != null)
    { File f = loadLibraryOrSrcLib_tempCache.get().get(id); if (f != null) return f; }
  
  boolean srcLib = loadLibraryOrSrcLib_srcLibsEnabled && isMarkedAsSrcLib(snippetID);
  if (srcLib) {
    File f = pairA(hotwire_compile(snippetID));
    mapPut(loadLibraryOrSrcLib_tempCache.get(), id, f);
  }
    
  File f = DiskSnippetCache_getLibrary(id);
  if (fileSize(f) != 0) return f;
  try {
    return loadDataSnippetToFile(snippetID);
  } catch (Throwable e) {
    if (loadLibraryOrSrcLib_srcLibsEnabled && nempty(loadSnippet(snippetID))) {
      markAsSrcLib(snippetID);
      return pairA(hotwire_compile(snippetID));
    }
    throw rethrow(e);
  }
} catch (Exception __e) { throw rethrow(__e); } }
static File pathToJavaxJar() { try {
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.pathToJavaXJar();
  
  
  int x = latestInstalledJavaX();
  File xfile = new File(userHome(), ".javax/x" + Math.max(x, 30) + ".jar");
  if (!xfile.isFile()) {
    print("Saving " + f2s(xfile));
    String url = x30JarServerURL();
    byte[] data = loadBinaryPage(url);
    if (data.length < 1000000)
      throw fail("Could not load " + url);
    saveBinaryFile(xfile.getPath(), data);
  }
  return xfile;
} catch (Exception __e) { throw rethrow(__e); } }
static String indentx(String s) {
  return indentx(indent_default, s);
}

static String indentx(int n, String s) {
  return dropSuffix(repeat(' ', n), indent(n, s));
}

static String indentx(String indent, String s) {
  return dropSuffix(indent, indent(indent, s));
}
static String cleanJavaCompilerOutput(String s) {
  return dropPrefixTrim("Annotation processing got disabled, since it requires a 1.6 compliant JVM", s);
}

static String appendWithNewLine(String a, String b) {
  if (empty(b)) return a;
  if (empty(a)) return b;
  return addSuffix(a, "\n") + b;
}
static boolean anyFileWithExtensionInDir(File dir, String ext) {
  return nempty(filesWithExtension(ext, findAllFiles_noDirs(dir)));
}
static boolean dir2zip_recurse_verbose = false;

static int dir2zip_recurse(File inDir, File zip) {
  return dir2zip_recurse(inDir, zip, "");
}

// TODO: the zero files case?
static int dir2zip_recurse(File inDir, File zip, String outPrefix) { try {
  mkdirsForFile(zip);
  FileOutputStream fout = newFileOutputStream(zip);
  ZipOutputStream outZip = new ZipOutputStream(fout);
  try {
    return dir2zip_recurse(inDir, outZip, outPrefix, 0);
  } finally {
    outZip.close();
  }
} catch (Exception __e) { throw rethrow(__e); } }

static int dir2zip_recurse(File inDir, ZipOutputStream outZip) {
  return dir2zip_recurse(inDir, outZip, "", 0);
}

static int dir2zip_recurse(File inDir, ZipOutputStream outZip, String outPrefix, int level) { try {
  if (++level >= 20) throw fail("woot? 20 levels in zip?");
  
  List<File> files = new ArrayList();
  for (File f : listFiles(inDir))
    files.add(f);

  int n = 0;
  sortFilesByName(files);
  for (File f : files) {
    if (f.isDirectory()) {
      print("dir2zip_recurse: Scanning " + f.getAbsolutePath());
      n += dir2zip_recurse(f, outZip, outPrefix + f.getName() + "/", level);
    } else {
      if (dir2zip_recurse_verbose) print("Copying " + f.getName());
      outZip.putNextEntry(new ZipEntry(outPrefix + f.getName()));
      InputStream fin = new FileInputStream(f);
      copyStream(fin, outZip);
      fin.close();
      ++n;
    }
  }
  return n;
} catch (Exception __e) { throw rethrow(__e); } }

static boolean isInRAMDisk(File f) {
  return startsWith(f2s(f), "/dev/shm");
}
static void deleteDirectory(File dir) {
  deleteDirectory(dir, false, false);
}

static void deleteDirectory(File dir, boolean verbose, boolean testRun) {
  deleteAllFilesInDirectory(dir, verbose, testRun);
  if (verbose) print((testRun ? "Would delete " : "Deleting ") + dir.getAbsolutePath());
  if (!testRun)
    dir.delete();
}

static void closeRandomAccessFile(RandomAccessFile f) {
  if (f != null) try {
    f.close();
    callJavaX("dropIO", f);
  } catch (Throwable e) {
    printStackTrace(e);
  }
}
static RandomAccessFile newRandomAccessFile(File path, String mode) throws IOException {
  boolean forWrite = mode.indexOf('w') >= 0;
  if (forWrite) mkdirsForFile(path);
  RandomAccessFile f = new RandomAccessFile(path, mode);
  callJavaX("registerIO", f, path, forWrite);
  return f;
}
static AccessControlContext vm_globalACC() {
  return (AccessControlContext) vm_generalMap_get("Global ACC");
}
static void replaceACCInClassLoader(Object o, AccessControlContext newACC) {
  ClassLoader cl = getClassLoaderOrSame(o);
  if (cl instanceof URLClassLoader) { try {
    setOpt(cl, "acc" , newACC);
    try {
      setOpt((Object) getOpt(cl, "ucp"), "acc" , newACC);
    } catch (Throwable e) {
      
      printShortException("replaceACCInClassLoader: ", e);
      if (java10OrHigher())
        if (addDefaultVMOption("--add-opens java.base/jdk.internal.loader=ALL-UNNAMED"))
          print("Please restart the OS");
    }
  } catch (Throwable __e) { _handleException(__e); }}
}
static String intToHex(int i) {
  return bytesToHex(intToBytes(i));
}
static Set<String> allFields(Object o) {
  TreeSet<String> fields = new TreeSet();
  Class _c = _getClass(o);
  do {
    for (Field f : _c.getDeclaredFields())
      fields.add(f.getName());
    _c = _c.getSuperclass();
  } while (_c != null);
  return fields;
}
static int stdHash(Object a, String... fields) {
  if (a == null) return 0;
  int hash = getClassName(a).hashCode();
  for (String field : fields)
    hash = boostHashCombine(hash, hashCode(getOpt(a, field)));
  return hash;
}
static Object getOptMC(String field) {
  return getOpt(mc(), field);
}
static Map similarEmptyMap(Map m) {
  if (m instanceof TreeMap) return new TreeMap(((TreeMap) m).comparator());
  if (m instanceof LinkedHashMap) return new LinkedHashMap();
  
  // default to a hash map
  return new HashMap();
}


static String loadTextFilePossiblyGZipped(String fileName) {
  return loadTextFilePossiblyGZipped(fileName, null);
}
  
static String loadTextFilePossiblyGZipped(String fileName, String defaultContents) {
  File gz = new File(fileName + ".gz");
  return gz.exists() ? loadGZTextFile(gz) : loadTextFile(fileName, defaultContents);
}

static String loadTextFilePossiblyGZipped(File fileName) {
  return loadTextFilePossiblyGZipped(fileName, null);
}

static String loadTextFilePossiblyGZipped(File fileName, String defaultContents) {
  return loadTextFilePossiblyGZipped(fileName.getPath(), defaultContents);
}

static File getCachedTranspilationFile(String id) {
  return newFile(getCodeProgramDir(id), "Transpilation");
}

static Object vmGeneralMap_get(Object key) {
  return vm_generalMap_get(key);
}
static String loadProgramTextFile(String name) {
  return loadTextFile(getProgramFile(name));
}

static String loadProgramTextFile(String progID, String name) {
  return loadTextFile(getProgramFile(progID, name));
}

static String loadProgramTextFile(String progID, String name, String defaultText) {
  return loadTextFile(getProgramFile(progID, name), defaultText);
}
static void saveTranspiledCode(String progID, String code) {
  File dir = getCodeProgramDir(progID);
  new File(dir, "Transpilation").delete();
  saveGZTextFile(new File(dir, "Transpilation.gz"), code);
}
static File linux_fileInRamDisk(String name) {
  if (!isLinux()) return null;
  File dir = newFile("/dev/shm");
  if (dir.isDirectory()) return newFile(dir, name);
  return null;
}
static String aGlobalID() {
  return randomID(globalIDLength());
}
static boolean isMarkedAsSrcLib(String snippetID) {
  if (snippetID == null) return false;
  
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return isJavaxCompilableSnippetType(rl.getSnippetType(snippetID));
  
  
  return fileExists(javaxCodeDir("srclibs/" + psI(snippetID)));
}
static <A, B> A pairA(Pair<A, B> p) {
  return p == null ? null : p.a;
}
static Object hotwire_onCompile; // voidfunc(Pair<jarFile, transpiledSource>)
static boolean hotwire_serially = false;
static Lock hotwire_overInternalBot_lock = lock();
static boolean hotwire_compileOnServer = false;

static Class<?> hotwire_overInternalBot(String progID) {
  return hotwire_overInternalBot(progID, "main");
}

static Class<?> hotwire_overInternalBot(String progID, String mainClass) { try {
  Pair<File, String> p;
  try {
    p = hotwire_compile(progID);
  } catch (Throwable e) {
    throw rethrow("Error hotwiring " + progID, e);
  }
  File jar = p.a;
  assertTrue(jar.getAbsolutePath(), jar.isFile());
  
  List<File> files = hotwire_collectJars(jar);

  // make class loader
  JavaXClassLoader classLoader = hotwire_makeClassLoader(files);
  classLoader.progID = progID;
  
  return hotwire_finish(classLoader, progID, p.b, mainClass);
} catch (Exception __e) { throw rethrow(__e); } }

// returns pair(jar, transpiled src)
static Pair<File, String> hotwire_compile(String progID) {
  Pair<File, String> p = hotwire_compileOnServer && !isLocalSnippetID(progID)
    ? compileSnippetThroughServer(progID)
    : CompilerBot.compileSnippet2(progID);
  Lock __2155 = hotwire_serially ? hotwire_overInternalBot_lock : null; lock(__2155); try {
  callF(hotwire_onCompile, p);
  return p;
} finally { unlock(__2155); } }
static void markAsSrcLib(String snippetID) {
  saveTextFile(javaxCodeDir("srclibs/" + psI(snippetID)), "");
}
static int latestInstalledJavaX() {
  File[] files = new File(userHome(), ".javax").listFiles();
  int v = 0;
  if (files != null) for (File f : files) {
    Matcher m = Pattern.compile("x(\\d\\d\\d?)\\.jar").matcher(f.getName());
    if (m.matches())
      v = Math.max(v, Integer.parseInt(m.group(1)));
  }
  return v;
}
static String x30JarServerURL() {
  return "http://botcompany.de:8081/x30.jar";
}
static int indent_default = 2;

static String indent(int indent) {
  return repeat(' ', indent);
}

static String indent(int indent, String s) {
  return indent(repeat(' ', indent), s);
}

static String indent(String indent, String s) {
  return indent + s.replace("\n", "\n" + indent);
}

static String indent(String s) {
  return indent(indent_default, s);
}

static List<String> indent(String indent, List<String> lines) {
  List<String> l = new ArrayList();
  if (lines != null) for (String s : lines)
    l.add(indent + s);
  return l;
}
static String addSuffix(String s, String suffix) {
  return s == null || s.endsWith(suffix) ? s : s + suffix;
}
static List<File> filesWithExtension(String ext, List<File> files) {
  return filesEndingWith(files, addPrefixIfNotEmpty2(".", ext));
}
static List<File> findAllFiles_noDirs(List dirs) {
  return findAllFiles_noDirs(asObjectArray(dirs));
}

// dirs are String's or File's
static List<File> findAllFiles_noDirs(Object... dirs) {
  List<File> l = new ArrayList();
  for (Object dir : dirs) {
    if (dir instanceof String && ((String) dir).endsWith("/.")) {
      // "/." means non-recurse
      for (File f : listFiles(dropSuffix("/.", (String) dir)))
        if (f.isFile()) l.add(f);
    } else
      findAllFiles_noDirs_impl(toFile(dir), l);
  }
  return l;
}

static void findAllFiles_noDirs_impl(File dir, List<File> l) {
  for (File f : listFiles(dir)) {
    if (f.isDirectory())
      findAllFiles_noDirs_impl(f, l);
    else
      l.add(f);
  }
}

static File[] listFiles(File dir) {
  File[] files = dir.listFiles();
  return files == null ? new File[0] : files;
}

static File[] listFiles(String dir) {
  return listFiles(new File(dir));
}
static List<File> sortFilesByName(List<File> l) {
  sort(l, new Comparator<File>() {
    public int compare(File a, File b) {
      return stdcompare(a.getName(), b.getName());
    }
  });
  return l;
}
static int deleteAllFilesInDirectory_minPathLength = 10;

static void deleteAllFilesInDirectory(File dir) {
  deleteAllFilesInDirectory(dir, false, false);
}

static void deleteAllFilesInDirectory(File dir, boolean verbose, boolean testRun) {
  dir = getCanonicalFile(dir);
  assertTrue(f2s(dir), l(f2s(dir)) >= deleteAllFilesInDirectory_minPathLength);
  File[] files = dir.listFiles();
  if (files == null) return;
  for (File f : files) {
    if (!isSymLink(f) && f.isDirectory()) // just delete the symlink, don't follow it
      deleteDirectory(f, verbose, testRun);
    else {
      if (verbose)
        print((testRun ? "Would delete " : "Deleting ") + f.getAbsolutePath());
      if (!testRun)
        f.delete();
    }
  }
}

static Object callJavaX(String method, Object... args) {
  return callOpt(getJavaX(), method, args);
}
static ClassLoader getClassLoaderOrSame(Object o) {
  return o instanceof ClassLoader ? (ClassLoader) o : getClassLoader(o);
}
static void printShortException(Throwable e) {
  print(exceptionToStringShort(e));
}

static void printShortException(String s, Throwable e) {
  print(s, exceptionToStringShort(e));
}
static boolean java10OrHigher() {
  return parseFirstInt(javaVersion()) >= 10;
}
static boolean addDefaultVMOption(String option) {
  String s = defaultVMArgs(), old = s;
  if (!s.contains(option)) // rough
    s = trim(s + " " + option);
  if (eq(old, s)) return false;
  { setDefaultVMArgs(s); return true; }
}
static byte[] intToBytes(int i) {
  return new byte[] {
          (byte) (i >>> 24),
          (byte) (i >>> 16),
          (byte) (i >>> 8),
          (byte) i};
}
static int boostHashCombine(int a, int b) {
  return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}


static String loadGZTextFile(File file) { try {
  if (!file.isFile()) return null;
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  InputStream fis = new FileInputStream(file);
  GZIPInputStream gis = newGZIPInputStream(fis);
  try {
    byte[] buffer = new byte[1024];
    int len;
    while((len = gis.read(buffer)) != -1){
        baos.write(buffer, 0, len);
    }
  } finally {
    fis.close();
  }
  baos.close();
  return fromUtf8(baos.toByteArray()); // TODO: use a Reader
} catch (Exception __e) { throw rethrow(__e); } }
static File getCodeProgramDir() {
  return getCodeProgramDir(getProgramID());
}

static File getCodeProgramDir(String snippetID) {
  return new File(javaxCodeDir(), formatSnippetID(snippetID));
}

static File getCodeProgramDir(long snippetID) {
  return getCodeProgramDir(formatSnippetID(snippetID));
}
static void saveGZTextFile(File file, String contents) { try {
  File parentFile = file.getParentFile();
  if (parentFile != null)
    parentFile.mkdirs();
  String tempFileName = file.getPath() + "_temp";
  File tempFile = new File(tempFileName);
  if (contents != null) {
    if (tempFile.exists()) try {
      String saveName = tempFileName + ".saved." + now();
      copyFile(tempFile, new File(saveName));
    } catch (Throwable e) { printStackTrace(e); }
    FileOutputStream fileOutputStream = newFileOutputStream(tempFile.getPath());
    GZIPOutputStream gos = new GZIPOutputStream(fileOutputStream);
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(gos, "UTF-8");
    PrintWriter printWriter = new PrintWriter(outputStreamWriter);
    printWriter.print(contents);
    printWriter.close();
    gos.close();
    fileOutputStream.close();
  }
  
  if (file.exists() && !file.delete())
    throw new IOException("Can't delete " + file.getPath());

  if (contents != null)
    if (!tempFile.renameTo(file))
      throw new IOException("Can't rename " + tempFile + " to " + file);
} catch (Exception __e) { throw rethrow(__e); } }
static Cache<Boolean> isLinux_cache = new Cache("isLinux_load");
static boolean isLinux() { return isLinux_cache.get(); }

static Boolean isLinux_load() {
  return !isWindows() && !isMac() && !isAndroid();
}

static int randomID_defaultLength = 12;

static String randomID(int length) {
  return makeRandomID(length);
}

static String randomID(Random r, int length) {
  return makeRandomID(r, length);
}

static String randomID() {
  return randomID(randomID_defaultLength);
}

static String randomID(Random r) {
  return randomID(r, randomID_defaultLength);
}
static int globalIDLength() {
  return 16;
}
static boolean isJavaxCompilableSnippetType(int type) {
  return isJavaxCompilableSnippetTypeExceptInclude(type)
    || type == javaxIncludeSnippetType();
}
static List<File> hotwire_collectJars(File jar) {
  List<String> libIDs = hotwire_libraryIDsFromJar_deleteJarOnFail(jar);
  List<File> files = ll(jar); // main program has to be first entry! (e.g. for hotwire_makeClassLoader_stickyAndSrcLibs)
  for (String libID : libIDs)
    files.add(loadLibraryOrSrcLib(libID));
  return files;
}
// returns (jar, transpiled src)
static Pair<File, String> compileSnippetThroughServer(String progID) {
  String transpiledSrc = getServerTranspiled2(progID);
  String md5 = md5(transpiledSrc + "\n" + progID);
  File jar = CompilerBot.getJarFile(md5);
  if (jar == null || jar.length() <= 22) {
    byte[] jarData = null;
    boolean dontLoad = false;
    
    
    IResourceLoader rl = vm_getResourceLoader();
    if (rl != null) {
      dontLoad = true;
      File jar2 = rl.getSnippetJar(progID, transpiledSrc);
      if (jar2 != null) return pair(jar2, transpiledSrc); 
    }
    
    if (!dontLoad) { try {
      jarData = loadBinaryPage("http://www.botcompany.de/jar/" + psI(progID) + "?md5=" + md5(transpiledSrc));
    } catch (Throwable __e) { _handleException(__e); }}
      
    if (!isJAR(jarData)) {
      if (jarData != null) {
        print(bytesToHex(takeFirstOfByteArray(8, jarData)));
        print("fallback to CompilerBot: " + fromUtf8(takeFirstOfByteArray(80, jarData)));
      }
      return CompilerBot.compileSnippet2(progID);
    }
    saveBinaryFile(jar, jarData);
  }
  return pair(jar, transpiledSrc);
}
static List<File> filesEndingWith(File dir, String suffix) {
  return listFilesWithSuffix(dir, suffix);
}

static List<File> filesEndingWith(List<File> l, String suffix) {
  List<File> out = new ArrayList();
  for (File f : unnull(l))
    if (!f.isDirectory() && (empty(suffix) || endsWithIgnoreCase(f.getName(), suffix)))
      out.add(f);
  return out;
}
static Object[] asObjectArray(Collection l) {
  return toObjectArray(l);
}
static File toFile(Object o) {
  if (o instanceof File) return (File) o;
  if (o instanceof String) return new File((String) o);
  throw fail("Not a file: " + o);
}
static <T> void sort(T[] a, Comparator<? super T> c) {
  Arrays.sort(a, c);
}

static <T> void sort(T[] a) {
  Arrays.sort(a);
}

static void sort(int[] a) { if (a != null) Arrays.sort(a); }

static <T> void sort(List<T> a, Comparator<? super T> c) {
  Collections.sort(a, c);
}

static void sort(List a) {
  Collections.sort(a);
}
static File getCanonicalFile(File f) { try {
  return f == null ? null : f.getCanonicalFile();
} catch (Exception __e) { throw rethrow(__e); } }

static boolean isSymLink(File f) {
  
  
  
  return f != null && Files.isSymbolicLink(toPath(f));
  
}
static ClassLoader getClassLoader(Object o) {
  return o == null ? null : _getClass(o).getClassLoader();
}
static int parseFirstInt(String s) {
  return parseInt(jextract("<int>", s));
}
static String javaVersion() {
  return System.getProperty("java.version");
}
static String defaultVMArgs() {
  return javaxDefaultVMArgs();
}
static void setDefaultVMArgs(String args) {
  String oldArgs = javaxDefaultVMArgs();
  args = trim(args);
  if (neq(unnull(oldArgs), unnull(args))) {
    print();
    print("Changing default VM arguments from");
    print("  " + oldArgs);
    print("to");
    print("  " + args);
    print();
    saveTextFile(javaxDataDir("default-vm-args"), nullIfEmpty(args));
  }
}


public static boolean isWindows() {
  return System.getProperty("os.name").contains("Windows");
}
  static boolean isMac() {
    return System.getProperty("os.name").toLowerCase().contains("mac");
	}
static boolean isJavaxCompilableSnippetTypeExceptInclude(int type) {
  return isJavaxApplicationSnippetType(type)
    || isJavaxModuleSnippetType(type)
    || type == snippetType_dynModule();
}
static int javaxIncludeSnippetType() {
  return 42;
}
static List<String> hotwire_libraryIDsFromJar_deleteJarOnFail(File jar) {
  try {
    return hotwire_libraryIDsFromJar(jar);
  } catch (Throwable _e) {
    jar.delete();
  
throw rethrow(_e); }
}
static byte[] isJAR_magic = bytesFromHex("504B0304");

static boolean isJAR(byte[] data) {
  return byteArrayStartsWith(data, isJAR_magic);
}

static boolean isJAR(File f) {
  return isJAR(loadBeginningOfBinaryFile(f, l(isJAR_magic)));
}
static byte[] takeFirstOfByteArray(byte[] b, int n) {
  return subByteArray(b, 0, n);
}

static byte[] takeFirstOfByteArray(int n, byte[] b) {
  return takeFirstOfByteArray(b, n);
}
static List<File> listFilesWithSuffix(File dir, String suffix) {
  List<File> l = new ArrayList();
  for (File f : listFiles(dir))
    if (!f.isDirectory() && (empty(suffix) || endsWithIgnoreCase(f.getName(), suffix)))
      l.add(f);
  return l;
}


static Path toPath(File f) {
  return f == null ? null : f.toPath();
}
// returns from C to C
static String jextract(String pat, String s) {
  return jextract(pat, javaTok(s));
}

static String jextract(String pat, List<String> tok) {
  List<String> tokpat = javaTok(pat);
  jfind_preprocess(tokpat);
  int i = jfind(tok, tokpat);
  if (i < 0) return null;
  int j = i + l(tokpat) - 2;
  return joinSubList(tok, i, j);
}

static String javaxDefaultVMArgs() {
  File fileNew, fileOld;
  String text = loadTextFile(fileNew = javaxDataDir("default-vm-args"));
  if (text == null) {
    text = loadTextFile(fileOld = programFile("#1005850", "default-vm-args"));
    if (text != null)
      moveFile(fileOld, fileNew);
  }
  return trim(unnull(text));
}
static String nullIfEmpty(String s) {
  return isEmpty(s) ? null : s;
}

static <A, B> Map<A, B> nullIfEmpty(Map<A, B> map) {
  return isEmpty(map) ? null : map;
}

static <A> List<A> nullIfEmpty(List<A> l) {
  return isEmpty(l) ? null : l;
}


static boolean isJavaxApplicationSnippetType(int type) {
  return type == snippetType_javaxSource() || type == snippetType_JavaXDesktop();
}
static boolean isJavaxModuleSnippetType(int type) {
  return type == snippetType_javaxModule() || type == snippetType_javaxDesktopModule();
}
static int snippetType_dynModule() {
  return 57;
}
static List<String> hotwire_libraryIDsFromJar(File jar) {
  String dehlibs = unnull(loadTextFileFromZip(jar, "libraries"));
  return regexpExtractAll("\\d+", dehlibs);
}
static byte[] subByteArray(byte[] b, int start) {
  return subByteArray(b, start, l(b));
}
  
static byte[] subByteArray(byte[] b, int start, int end) {
  start = max(start, 0); end = min(end, l(b));
  if (start == 0 && end == l(b)) return b;
  if (start >= end) return new byte[0];
  byte[] x = new byte[end-start];
  System.arraycopy(b, start, x, 0, end-start);
  return x;
}
static File programFile(String name) {
  return prepareProgramFile(name);
}

static File programFile(String progID, String name) {
  return prepareProgramFile(progID, name);
}
static void moveFile(File a, File b) {
  if (!renameFile(a, b))
    throw fail("File move failed: " + a + " to " + b);
}
static boolean isEmpty(Collection c) {
  return c == null || c.isEmpty();
}

static boolean isEmpty(CharSequence s) {
  return s == null || s.length() == 0;
}

static boolean isEmpty(Object[] a) { return a == null || a.length == 0; }
static boolean isEmpty(byte[] a) { return a == null || a.length == 0; }

static boolean isEmpty(Map map) {
  return map == null || map.isEmpty();
}


static int snippetType_javaxSource() {
  return 34;
}
static int snippetType_JavaXDesktop() {
  return 55;
}
static int snippetType_javaxModule() {
  return 54;
}
static int snippetType_javaxDesktopModule() {
  return 58;
}
static List<String> regexpExtractAll(String pat, String s) {
  if (s == null) return null;
  Matcher m = regexpMatcher(pat, s);
  List<String> out = new ArrayList();
  while (m.find())
    out.add(m.group());
  return out;
}
static File prepareProgramFile(String name) {
  return mkdirsForFile(getProgramFile(name));
}

static File prepareProgramFile(String progID, String name) {
  return mkdirsForFile(getProgramFile(progID, name));
}
static boolean renameFile(File a, File b) {
  mkdirsForFile(b);
  return a.renameTo(b);
}

static boolean renameFile(File a, String newName) {
  return renameFile(a, fileInSameDir(a, newName));
}


static class Cache<A> {
  Object maker; // func -> A
  A value;
  long loaded;
  static boolean debug = false;
  long changeCount;
  Lock lock = lock();
  
  Cache() {}
  Cache(Object maker) {
  this.maker = maker;}

  A get() {
    if (hasLock(lock)) return value; // Must be called from within maker
    Lock __2289 = lock; lock(__2289); try {
    if (loaded == 0) {
      value = make();
      changeCount++;
      loaded = sysNow();
    }
    return value;
  } finally { unlock(__2289); } }

  void clear() {
    Lock __2290 = lock; lock(__2290); try {
    if (debug && loaded != 0)
      print("Clearing cache");
    value = null;
    changeCount++;
    loaded = 0;
  } finally { unlock(__2290); } }

  // clear if older than x seconds
  // 0 does not do anything
  void clear(double seconds) {
    Lock __2291 = lock; lock(__2291); try {
    if (seconds != 0 && loaded != 0 && sysNow() >= loaded+seconds*1000)
      clear();
  } finally { unlock(__2291); } }
  
  // override
  void set(A a) {
    Lock __2292 = lock; lock(__2292); try {
    value = a;
    ++changeCount;
    loaded = sysNow();
  } finally { unlock(__2292); } }
  
  A make() {
    return (A) callF(maker);
  }
}

static boolean hasLock(Lock lock) {
  return ((ReentrantLock) lock).isHeldByCurrentThread();
}

}