Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

28369
LINES

< > BotCompany Repo | #1032684 // #759 transpilation backup

JavaX source code (desktop) - run with: x30.jar

Download Jar.

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.*;

// We don't use #1006722 anymore
//   (but it's still listed in #7!?)

// TODO: deprecate (remove) the "f <id>" syntax

// TODO: skip ifclass blocks in findFunctionInvocations.
//       after main loop completes (including standard functions+
//       classes), run tok_ifclass and if any change, run main loop
//       again.
//       This will allow clean handling of functions like "unnull"
//       which right now _always_ include Symbol (because unnull
//       references emptySymbol inside an ifclass Symbol block).

// Note: Before editing this, transpile #7
// Use 'Very fresh' (R-transpile doesn't seem to work) or Q/M if you haven't screwed yourself currently
// by making your transpiler unable to transpile itself... or
// something

// Note: Don't try hijackPrint, it doesn't seem to work in here
// (because of the way it is called by e.g. transpileForServer_returnPair?)

/* functions to possibly edit when creating/changing JavaX constructs:
  tok_pingify
*/

// new JavaParser includes
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 com.github.javaparser.ast.NodeList;
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 java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.text.NumberFormat;
import java.nio.charset.Charset;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import static x30_pkg.x30_util.DynamicObject;
import java.nio.file.Files;
import java.nio.file.Path;

class main {




 // JavaParser







static CompilationUnit javaParseCompilationUnit(String java) { return javaParseCompilationUnit(java, false); }
static CompilationUnit javaParseCompilationUnit(String java, boolean withComments) {
  JavaParser p = new JavaParser();
  p.getParserConfiguration().setAttributeComments(withComments);
  ParseResult<CompilationUnit> r = p.parse(java);
  if (!r.isSuccessful())
    throw fail(str(r));
  return r.getResult().get();
}







static String javaParser_makeAllPublic(String src, Object... __) {
  CompilationUnit cu = javaParseCompilationUnit(src);
  PrettyPrinterConfiguration ppconf = new PrettyPrinterConfiguration();
  ppconf.setIndentSize(2);
  ppconf.setIndentType(PrettyPrinterConfiguration.IndentType.SPACES);
  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(NodeList<com.github.javaparser.ast.Modifier> modifiers) {
    // XXX: does this work?
    modifiers.remove(com.github.javaparser.ast.Modifier.privateModifier());
    modifiers.remove(com.github.javaparser.ast.Modifier.protectedModifier());
    if (!modifiers.contains(com.github.javaparser.ast.Modifier.publicModifier()))
      modifiers.add(com.github.javaparser.ast.Modifier.publicModifier());
  }

  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) {
    NodeList<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.privateModifier()) && !m.contains(com.github.javaparser.ast.Modifier.staticModifier()) && !m.contains(com.github.javaparser.ast.Modifier.finalModifier()))
      m.add(com.github.javaparser.ast.Modifier.finalModifier());
    makePublic(m);
    super.visit(n, arg);
  }
}
static String javaParser_reparse_keepComments(String src) {
  CompilationUnit cu = javaParseCompilationUnit(src, true);
  PrettyPrinterConfiguration ppconf = new PrettyPrinterConfiguration();
  ppconf.setPrintComments(true);
  ppconf.setIndentSize(2);
  ppconf.setIndentType(PrettyPrinterConfiguration.IndentType.SPACES);
  return cu.toString(ppconf);
}

static String javaParser_makeAllPublic_keepComments(String src) {
  CompilationUnit cu = javaParseCompilationUnit(src);
  PrettyPrinterConfiguration ppconf = new PrettyPrinterConfiguration();
  ppconf.setPrintComments(true);
  ppconf.setIndentSize(2);
  ppconf.setIndentType(PrettyPrinterConfiguration.IndentType.SPACES);
  new javaParser_makeAllPublic_Visitor().visit(cu, null);
  return cu.toString(ppconf);
}

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
static boolean debug_jreplace = false;

// _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", "MultiSetMap", "AutoMap", "ValueOnDemandMap", "NavigableMap", "SortedMap");

// classes with 3 type parameters that can written with just one
// e.g. T3<S> => T3<S, S, S>
static Set<String> tripleClasses = lithashset("T3", "IF2", "F2", "IVF3", "VF3");

static String transpilingSnippetID;
//static new AtomicInteger varCount;
static ThreadLocal<AtomicInteger> varCountByThread = new ThreadLocal();
static Map<String, String> snippetCache = syncMap();
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 NavigableSet<String> definitions = ciSet();
static HashMap<String, String> rewrites = new HashMap();
static HashSet<String> shouldNotIncludeFunction = new HashSet(); // this verifies after the fact that the function was not included
static HashSet<String> shouldNotIncludeClass = new HashSet();
static HashSet<String> doNotIncludeFunction = new HashSet(); // this actually prevents the function from being included
static Map<String, String> functionToPackage = new HashMap();
static HashSet<String> doNotIncludeClass = new HashSet();
static HashSet<String> needLatest = new HashSet(); // this forces a function to be included
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> lambdaMapLikeFunctions = new HashSet();
static HashSet<String> curry1LikeFunctions = new HashSet();
static HashSet<String> mapMethodLikeFunctions = new HashSet();
static HashSet<String> nuLikeFunctions = new HashSet();
static HashSet<String> getLikeFunctions = new HashSet(); // name is slightly misleading. this turns a pre-argument into a fieldLambda
static HashSet<String> lambdaMethod0LikeFunctions = new HashSet(); // we're getting better again with the names (maybe)
static HashSet<String> lambda0LikeFunctions = 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, enable all standard class ifclass [todo])
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 HashSet<String> haveClasses = new HashSet();

static class CachedInclude {
  String javax;
  Future<String> java;
  String realJava;
  long snippetID;
  
  CachedInclude() {}
  CachedInclude(long snippetID) {
  this.snippetID = snippetID;}
  
  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);
  transpileRaw_dontCopyFromCreator = true;
  
  final Object oldPrint = or(print_byThread().get(), "print_raw");
   AutoCloseable __17 = 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(__17); }}

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));
  // clear things
  includeInMainLoaded_magicComment = null;
  included.clear();
  definitions.clear();
  rewrites.clear();
  // XXX definitions.add("SymbolAsString");
  shouldNotIncludeFunction.clear();
  shouldNotIncludeClass.clear();
  doNotIncludeFunction.clear();
  functionToPackage.clear();
  needLatest.clear();
  doNotIncludeClass.clear();
  addedFunctions.clear();
  addedClasses.clear();
  hardFunctionReferences.clear();
  mapLikeFunctions = cloneHashSet(tok_mapLikeFunctions());
  lambdaMapLikeFunctions = new HashSet();
  curry1LikeFunctions = new HashSet();
  mapMethodLikeFunctions = cloneHashSet(tok_mapMethodLikeFunctions());
  nuLikeFunctions.clear();
  getLikeFunctions.clear();
  lambdaMethod0LikeFunctions.clear();
  lambda0LikeFunctions.clear();
  extraStandardFunctions = new HashMap();
  libs.clear();
  mainBaseClass = mainPackage = mainClassName = null;
  varCountByThread.set(null);
  quickmainDone1 = quickmainDone2 = false;
  metaPostBlocks = new ArrayList();
  metaTransformers = new ArrayList();
  dontPrintSource = false;
  defaultMaxQuineLength_value = defaultMaxQuineLength_defaultValue;
  debug_jreplace = false;
  haveClasses.clear();
  
  //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")) {
      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
      ping();
      List<String> before = cloneList(tok);
      
      // do the non-local stuff (flags and rewrites, things that correlate across includes like tok_selfType)
      
      // to allow crazy stuff like "nonStatic !include #someStaticClass"
      tok_processEarlyIncludes(tok);
      
      jreplace(tok, "nonStatic static", "");
      
      tok_selfType(tok);
      tok_mainClassNameAndPackage(tok);
      tok_definitions(tok);
      tok_ifndef(tok);
      tok_ifdef(tok);
      defineMapLikes(tok);
      defineLambdaMapLikes(tok);
      defineCurry1Likes(tok);
      defineMapMethodLikes(tok);
      defineNuLikes(tok);
      defineXLikes(tok, "getLike", getLikeFunctions);
      defineXLikes(tok, "lambdaMethod0Like", lambdaMethod0LikeFunctions);
      defineXLikes(tok, "lambda0Like", lambda0LikeFunctions);
      if (tok_applyAllXLikeFunctions(tok)) {
        functionReferences(tok);
        lambdaReferences(tok);
      }
      
      tok_dropExtraCommas(tok); // from e.g. tok_applyMapMethodLikeFunctions
      tok_delegateTo(tok);
      tok_replaceWith(tok);
      tok_findAndClearRewrites(tok);
      tok_processRewrites(tok);
      
      tok_multiTypeArguments_v2(tok);
      
      // main bla(...) => mainClassName.bla(...)
      // or main ClassName => main.ClassName
      //jreplace(tok, "main <id>(", mainClassName() + ".$2(");
      grabImportedStaticFunctions(tok);
      jreplace_dyn_allowNull(tok, "main <id>", (_tok, cIdx, end) -> {
        String fname = _tok.get(cIdx+2);
        boolean isClass = haveClasses.contains(fname);
        printVars("expandMainRef", "fname", fname, "isClass", isClass);
        if (isClass || eqGet(_tok, cIdx+4, "(")) {
          String pkg = functionToPackage.get(fname);
          printVars("expandMainRef", "pkg", pkg);
          String call = "." + fname;
          return or(pkg, mainClassName()) + call;
        }
        return null;
      });
      
      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 = eq(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>() { public 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)
    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"));
      }
    }
    
    if (hasDef("SymbolAsString"))
      jreplace(tok, "Symbol", "String");
  
    // using non-lazy version now for cleanImports
    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
    
    if (hasDef("cleanImports"))
      tok_cleanImports(tok);
    
    if (includeInMainLoaded_magicComment != null) {
      int i = lastIndexOfStartingWith(tok, includeInMainLoaded_magicComment);
      if (i >= 0) tok.set(i, dropPrefix(includeInMainLoaded_magicComment, tok.get(i)));
    }

    print("definitions=" + sfu(definitions));
    if (containsOneOf(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, "allpublic"))
            src = javaParser_makeAllPublic(src);
          else if (containsIC(definitions, "PublicExceptTopClass"))
            src = javaParser_makeAllPublic(src, "notTopLevelClassDecl" , true);
          else if (containsIC(definitions, "keepComments"))
            src = javaParser_makeAllPublic_keepComments(src);
          else {
            print("javaParser_reparse_keepComments. size=" + l(src));
            src = javaParser_reparse_keepComments(src);
            print("size=" + l(src));
          }
        } catch (Throwable e) {
          String _src = src;
          String errorContext = extractAndPrintJavaParseError(_src, e);
          File f = transpilerErrorSourceFile();
          saveTextFileVerbose(f, src);
          dontPrintSource = true;
          throw fail("Java parsing error:" + errorContext + innerMessage(e)); // drop the long nested parser stacktraces
        }
        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>() { public 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) {
    if (!dontPrintSource)
      printSources(tok);
    String src = join(tok);
    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 {
    ping();
    List<String> before = cloneList(tok);
    
    //print("localStuff loop " + safety);
    
    earlyStuff(tok);
    
    // EARLY local stuff goes here
    
    tok_dropMetaComments(tok);
    
    tok_earlyGeneralStuff(tok);
    
    conceptDeclarations(tok);
    tok_recordDecls(tok);
    
    tok = multilineStrings(tok);
    tok_singleQuoteIdentifiersToStringConstants(tok);
    tok_inStringEvals(tok);
    tok_listComprehensions(tok);
    
    tok_eventFunctions(tok);
    
    tok_for_single(tok);
    
    tok_for_unpair(tok); // Do this...
    //tok_doubleFor_v2(tok);
    tok_doubleFor_v3(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, "WeakRef", "WeakReference");
    
    jreplace(tok, "dispose <id>;", "{ cleanUp($2); $2 = null; }");
  
    tok_doPing(tok);
      
    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 { public bool get(final L<S> tok, final int i) {",
        "}}");
      
    jreplace(tok, "synced <id>", "synchronized $2");
    jreplace(tok, "sync <id>", "synchronized $2");
    
    jreplace(tok, "synchronized {", "synchronized(this) {");
    
    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);
    }
      
    for (String pat : ll("visual {", "visualize {"))
      jreplace(tok, pat, "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() ctex { super.start();", tokCondition_beginningOfMethodDeclaration());
    
    var notVoidMethod = new TokCondition() { public boolean get(final List<String> tok, final int i) {
      return neqGet(tok, i-1, "void");
    }};
    
    // run { ... } => public void run() { ... }
    // same with close
    jreplace(tok, "run {", "public void run() {", notVoidMethod);
    jreplace(tok, "close {", "public void close() {", notVoidMethod);
    
    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");

    tok_shortFinals(tok);
    
    tok_moduleClassDecls(tok);

    jreplace(tok, "static sync", "static synchronized");
    jreplace(tok, "sclass", "static class");
    jreplace(tok, "fclass", "final class");
    jreplace(tok, "fsclass", "final static class");
    jreplace(tok, "srecord", "static record");
    jreplace(tok, "strecord", "static transformable 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, "MapSO", "Map<S, O>");
    
    jreplace(tok, "ByName<", "IF1<S, ");
    
    jreplace(tok, "ITransform<<id>>", "IF1<$3>");
    jreplace(tok, "ITransform", "IF1");
    
    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", new TokCondition() { public boolean get(final List<String> tok, final int i) {
      String t = _get(tok, i+1+4*2);
      return eq(t, "{") || isIdentifier(t);
    }});
    jreplace(tok, "class <id> > <id><<id>> {", "class $2 extends $4  $5 $6 $7 {");
    
    jreplace(tok, "ISegmenter", "IF1<BufferedImage, L<Rect>>");
    
    // IPred<A, B> => IF2<A, B, Bool>
    jreplace(tok, "IPred<<id>, <id>>", "IF2<$3, $5, Bool>");
    
    // IPred<A> => IF1<A, Bool>
    jreplace(tok, "IPred<<id>>", "IF1<$3, Bool>");
    jreplace(tok, "IPred<<id>[]>", "IF1<$3[], Bool>");
    
    // Proposition => WithReasoning<S> (should we really define this?)
    jreplace(tok, "Proposition", "WithReasoning<S>");
    
    replaceKeywordBlock(tok, "print exceptions",
      "{ try {",
      "} on fail __e { printStackTrace(__e); } }");
    
    // "on fail {" => "catch (Throwable _e) { ... rethrow(_e); }"
    replaceKeywordBlock(tok, "on fail",
      "catch (Throwable _e) {",
      "\nthrow rethrow(_e); }");
  
    // "on fail e {" => "catch (Throwable e) { ... rethrow(e); }"
    replaceKeywordBlock_dyn2_legacy(tok, "on fail <id>", new Object() {
      String[] get(List<String> tok, int iOpening, int iClosing) {
        String var = tok.get(iOpening-2);
        return new String[] {
          "catch (Throwable " + var + ") {",
          "\nthrow rethrow(" + var + "); }"
        };
      }
    });

    // "on fail Type e {" => "catch (Type e) { ... rethrow(e); }"
    replaceKeywordBlock_dyn2_legacy(tok, "on fail <id> <id>", new Object() {
      String[] get(List<String> tok, int iOpening, int iClosing) {
        String type = tok.get(iOpening-4);
        String var = tok.get(iOpening-2);
        return new String[] {
          "catch (" + type + " " + var + ") {",
          "\nthrow " + var + "; }"
        };
      }
    });

    // "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() { public boolean get(final List<String> tok, final int i) {
      String word = tok.get(i+3);
      return startsWithLowerCaseOrUnderscore(word);
    }});
    
    jreplace(tok, "+ +", "+", new TokCondition() { public 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;
    }});
  
    // 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); }");
    
    // [stdToString] -> toString { ret stdToString(this); }
    jreplace(tok, "[stdToString]",
      "toString { ret stdToString(this); }");
    
    // [concepts] "concept.field!" for dereferencing references
    
    // "<id>!", "?!" etc.
    jreplace(tok, "*!", "$1.get()", new TokCondition() { public boolean get(final List<String> tok, final int i) {
      String l = tok.get(i+1);
      if (!(isIdentifier(l) || eqOneOf(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> :", "for (var $3 :");
    
    jreplace(tok, "for (<id> <id>)", "for ($3 $4 : list($3))");
    jreplace(tok, "for (final <id> <id>)", "for (final $4 $5 : list($4))");

    jreplace(tok, "ret", "return", new TokCondition() { public boolean get(final List<String> tok, final int i) {
      if (eqGetOneOf(tok, i-1, "int", "return")) return false;
      String next = _get(tok, i+3);
      if (eq(next, ".")) return isInteger(_get(tok, i+5)); // e.g. "ret .1", but not "ret.bla"
      return !eqOneOf(next, "=", ")");
    }});
    
    // "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);
      }
    
    // S s = bla(), return if null; => S s = bla(); if (s == null) return;
    // same with continue, break
    while ((i = jfind(tok, ", <id> if null;", new TokCondition() { public boolean get(final List<String> tok, final int i) {
      return eqOneOf(tok.get(i+3), "return", "ret", "continue", "break");
    }})) >= 0) {
      String cmd = tok.get(i+2);
      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) " + cmd + ";");
    }
    
    // S s = bla(), return false if null; => S s = bla(); if (s == null) return false;
    // (with false being any identifier)
    while ((i = jfind(tok, ", return <id> if null;")) >= 0) {
      String returnValue = tok.get(i+4);
      int j = tok_findBeginningOfStatement(tok, i);
      String var = getVarDeclarationName(subList(tok, j-1, i));
      replaceTokens_reTok(tok, i, i+6*2-1, "; if (" + var + " == null) return " + returnValue + ";");
    }
    
    // "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 unless set <var>; => { if (var) return; set var; }
    jreplace(tok, "return unless set <id>;", "{ if ($4) return; set $4; }");

    // set x; => x = true;
    // yeah it doesn't save many characters, but I like it
    jreplace(tok, "set <id>;", "$2 = true;", new TokCondition() { public boolean get(final List<String> tok, final int i) {
      return !eqGet(tok, i-1, "unless");
    }});
    
    // set !x; => x = false; (did this one day and thought yes it makes sense in a way)
    jreplace(tok, "set !<id>;", "$3 = false;");
    
    // unset x; => x = false;
    jreplace(tok, "unset <id>;", "$2 = false;");
    
    // "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);
    }
    
    tok_ifNullAssign(tok);

    // "return <id> if"
    
    // "return with <statement>" / "continue with <statement>" / "break with <statement>"
    
    while ((i = jfind(tok, "<id> with", new TokCondition() { public boolean get(final List<String> tok, final int i) { return eqOneOf(tok.get(i+1), "return", "continue", "break"); }})) >= 0) {
      // XXX int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      int j = tok_findEndOfStatement(tok, i+4)-1;
      //print("Found statement: " + joinSubList(tok, i+4, j+1));
      tok.set(j, "; " + tok.get(i) + "; }");
      replaceTokens(tok, i, i+3, "{");
      reTok(tok, i, j+1);
    }
    
    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 "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() { public 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() { public 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() { public 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);
    
    tok_beaConceptDecls(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);
    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, "rEnterThread {", "rThreadEnter {");
    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-pcall", "r { pcall {", "}}");
  
    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() { public 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\"; }",
      new F2<List<String>, Integer, Boolean>() { public Boolean get(List<String> _tok, Integer nIdx) { try {  return !eqGetOneOf(_tok, nIdx-1, "void", "svoid");  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "!eqGetOneOf(_tok, nIdx-1, \"void\", \"svoid\")"; }});
  
    // "myFunction;" instead of "myFunction();" - quite rough
    // (isolated identifier as function call)
    cond = new TokCondition() {
      public 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
    
    tok_expandIfQuoted(tok);
    
    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 {",
      "}}");
    
    //jreplace(tok, "void <id> pcall {", "$1 $2() pcall {");
    
    replaceKeywordBlock(tok,
      "pcall",
      "try {",
      "} catch (Throwable __e) { _handleException(__e); }");
  
    replaceKeywordBlock(tok,
      "pcall-print",
      "try {",
      "} catch (Throwable __e) { printStackTrace(__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() { public 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(defaultMaxQuineLength(), 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);
    }
    
    // <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)
    
    jreplace_dyn(tok, "try object (<id>)", (_tok, cIdx) -> {
      String type = _tok.get(cIdx+6);
      return "try object " + type + " " + makeVar() + " = (" + type + ")";
    });
    
    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);
      S 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);
    }*/
    while ((i = jfind(tok, "try <id>")) >= 0) {
      int iType = i+2, iVar = tok_findEndOfType(tok, iType);
      String v = tok.get(iVar);
      assertEquals("try object", "=", get(tok, iVar+2));
      int j = findEndOfStatement(tok, iVar);
      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);
    tok_quicknew2(tok);
    
    // before temp blocks
    tok_varNamedLikeFunction(tok);
    
    tempBlocks(tok); // after quicknew2 for stuff like "temp new X x;"

    // 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_timedCachedFunctions(tok);
    
    tok_optPar(tok);
    
    throwFailEtc(tok);
    tok_typeAA(tok, pairClasses);
    tok_typeAAA(tok, tripleClasses);
    tok_typeAO(tok, litset("WithTrail"));

    // before star constructors so we can define star constructors in a macro
    tok_localMacro(tok);

    // 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
    tok_svoidEtc(tok);
    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);
    
    jreplace(tok, "if (<id>?!)", "if ($3 != null && $3!)");
    
    tok_embeddedFunctions(tok);
    
    // quicknew for arrays
    
    jreplace(tok, "<id>[] <id> = new[", "$1[] $4 = new $1[");
    jreplace(tok, "<id>[] <id> = new {", "$1[] $4 = {");
    jreplace(tok, "<id><<id>>[] <id> = new[", "$1 $2 $3 $4[] $7 = 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);
    });
    
    lambdaReferences(tok);
    
    tok_returnSelf(tok);
    
    // Lua-like print statement
    jreplace(tok, "print <quoted>", "print($2);");
    
    tok_tildeCalls(tok);
    
    tok_svoidEtc(tok);
    tok_swappableFunctions(tok);
    
    tok_optParLambda(tok);
    
    tok_runnableClasses(tok);
    
    tok_swapStatement(tok);
    
    tok_akaFunctionNames(tok);
    
    tok_defaultArguments(tok);
    
    tok_persistableClasses(tok);
    
    tok_transientClasses(tok);
    
    tok_shortMethodReferences(tok);
    
    jreplace(tok, "== null ?:", "== null ? null :");
    jreplace(tok, "?:", "== null ? null :");
    
    tok_orCase(tok);
    
    tok_beforeMethods(tok);
    tok_afterMethods(tok);
    
    tok_returnAsFunctionName(tok);
    
    tok_transpileGetSet(tok);
    
    tok_pcallPrefix(tok);
    
    tok_numberFunctionNames(tok);
    
    tok_castToStatements(tok);
    
    tok_optionalFields(tok);
    
    tok_getFromMap(tok);
    
    jreplace(tok, "for <id> : <id> {", "for (var $2 : $4) {");
    
    tok_shortLambdas(tok);
    
    // end of local stuff
    
    tok_processMetaBlocks(tok, metaCodeAllowed());
    if (metaCodeAllowed()) runMetaTransformers(tok);

    same = eq(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);
  
  List<String> dontImports = tok_processDontImports(tok, definitions);
  for (String s : dontImports)
    doNotIncludeFunction.remove(afterLastDot(s));
  
  //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");
  haveClasses = addStandardClasses_v2(tok);
  
  tok_quickInstanceOf(tok, haveClasses);

  // concept-related stuff
  
  // auto-import concepts
  /*bool _a = tok_hasClassRef2(tok, "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));
      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 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, (definitions.contains("mainClassPublic") ? "public " : "") + "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);
    }
    
  jreplace(tok, "O", "Object");
  jreplace(tok, "S", "String");
  jreplace(tok, "L", "List");
  jreplace(tok, "Cl", "Collection");
  
  // bool -> boolean if it's not a function name
  jreplace(tok, "bool", "boolean", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return neqGetOneOf(tok, i+3, "(", null);
  }});
  
  jreplace(tok, "AtomicBool", "AtomicBoolean");
  jreplace(tok, "AtomicInt", "AtomicInteger");

  jreplace(tok, "LL< <id> >", "L<L<$3>>");
  jreplace(tok, "LL< <id><<id>> >", "L<L<$3<$5>>>");
  jreplace(tok, "LL<?>", "L<L<?>>");
  jreplace(tok, "LL", "L<L>");
  jreplace(tok, "LPt", "L<Pt>");
  
  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 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;
}

// ctex and various other error-related keyword blocks
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; } }");
  replaceKeywordBlock(tok, "try-OrError",
    "{ try {",
    "} catch (Throwable __e) { return OrError.error(__e); } }");
}
  
static List<String> dialogHandler(List<String> tok) {
  return replaceKeywordBlock(tok,
    "dialogHandler",
    "new DialogHandler() {\n" +
      "public void run(final DialogIO io) {",
    "}}");
}

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 = joinSubList(tok, idx+1, j-1);
    List<String> c = findInnerClassOfMain(tok, className);
    print("Extending class " + className);
    clearTokens(subList(tok, i, j+1));
    if (c == null) {
      print("Warning: Can't extend class " + className + ", not found");
      continue;
    }
    int startOfClass = indexOfSubList(tok, c);
    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(joinSubList(tok, 0, i), '\n')+1));
    reTok(tok, i, i+5);
  }
}

// done before any other processing
static void earlyStuff(List<String> tok) {
  int i;
  
  tok_processEarlyIncludes(tok);
      
  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(joinSubList(tok, idx+1, j-1)) + ", ");
    reTok(tok, i, idx+1);
  }
  
  jreplace_check("after", tok, "void <id> after super {", "void $2 { super.$2();");
  
  replaceKeywordBlock(tok, "r-enter", "r { enter {", "}}");
    
  // do this before func & voidfunc because otherwise they swallow it
  jreplace(tok, "enter {", "{ temp enter();");
  
  tok_mutatorMethods(tok);
  
  // func keyword for lambdas - now automatically quines toString() if enabled
  
  replaceKeywordBlock(tok,
    "null",
    "{",
    "null; }");
    
  // 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(joinSubList(tok, argsTo+6, idx-1));
      
    String toString = autoQuine ? "  public S toString() { ret " + quote(shorten(defaultMaxQuineLength(), 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 + "() { public "
        + 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);
    
    if (jcontains(subList(tok, argsTo+1, idx), "->"))
      throw fail("voidfunc with return type: " + joinSubList(tok, i, 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(defaultMaxQuineLength(), 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);
  
  jreplace(tok, "func fullToString {", "func -> O fullToString {");
  
  for (String keyword : ll(/*"f",*/ "func")) {
    while ((i = jfind(tok, keyword + " ->", new TokCondition() { public 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(joinSubList(tok, i+6, idx-1));
      int quineLength = defaultMaxQuineLength();
      if (eq(lastJavaToken(returnType), "fullToString")) {
        quineLength = Integer.MAX_VALUE;
        returnType = dropLastJavaTokenAndSpacing(returnType);
      }
      List<String> contents = subList(tok, idx+1, j-1);
      replaceTokens(tok, i, j, "new F0<" + returnType + ">() { public " + returnType + " get() ctex { " + tok_addReturn(contents) + " }\n" +
        (autoQuine ? "  public S toString() { ret " + quote(shorten(quineLength, trimJoin(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", ".", "void", "main", "Throwable")) {
      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++) {
    print("Looking for required functions");
    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(subList(tok, j, j+12));
    }

    while ((j = jfind(tok, "do not include function *.")) >= 0) {
      String fname = tok.get(j+8);
      doNotIncludeFunction.add(fname);
      clearAllTokens(subList(tok, j, j+12));
    }
    
    while ((j = jfind(tok, "need latest <id>.")) >= 0) {
      String t = tok.get(j+4);
      needLatest.add(t);
      doNotIncludeClass.remove(t);
      clearAllTokens(subList(tok, j, j+8));
    }
    
    grabImportedStaticFunctions(tok);
    doNotIncludeFunction.removeAll(needLatest);
    
    // changes tok
    Set<String> invocations = findFunctionInvocations(tok, sfMap, hardFunctionReferences, defd, true, mainClassName());
    /*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) + "\n";
      //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(_id));
          //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));
        
      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!");
        }
    }
    
    print("Getting " + nParts(parts));
    String text = lines(getAllFutures(parts));
    print("Including " + nParts(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));
    
    print("Processing definitions");
    
    dontPrint("definitions"); tok_definitions(tok);
    dontPrint("ifndef"); tok_ifndef(tok);
    dontPrint("ifdef"); tok_ifdef(tok);
    
    if (i >= 1000) throw fail("Too many iterations");
  }
  
  return tok;
}

// TODO: skip functions defined in inner classes!
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) { return haveClasses_addImported(tok, have, true); }
static HashSet<String> haveClasses_addImported(List<String> tok, HashSet<String> have, boolean mainLevel) {
  have.addAll(tok_importedClassNames(tok, mainLevel ? (__18, __19) -> onImportFound(__18, __19) : null));
  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 HashSet<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);
    haveClasses_addImported(tok, have);
    have.addAll(keys(rewrites));

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

    for (int i = 0; i+1 < l(definitions); i += 2) {
      String className = definitions.get(i);
      String snippetID = fsI(definitions.get(i+1));
      if (idx.contains(className) && !have.contains(className) && snippets.add(snippetID))
        need.put(className, snippetID);
    }
    if (hasDef("SymbolAsString")) {
      print("Have SymbolAsString.");
      if (need.containsKey("Symbol")) {
        print("Have Symbol.");
        need.remove("Symbol");
      }
    } else
      print("No SymbolAsString.");
      
    removeAll(need, doNotIncludeClass);
    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));
    
    List<Pair<String, CachedInclude>> parts = new ArrayList(); // class name, Java code
    
    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) + "\n";
      
      assertTrue(cacheStdClasses);
      long _id = psI(snippetID);
      CachedInclude ci = cachedIncludes.get(_id);
      if (ci == null) cachedIncludes.put(_id, ci = new CachedInclude(_id));
      if (neq(ci.javax, text)) {
        print("Compiling class: " + className);
        ci.javax = text;
        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(text)));
          }
        });
        ci.realJava = null;
      }
      parts.add(pair(className, ci));
    }
    
    StringBuilder buf = new StringBuilder();
    for (Pair<String, CachedInclude> p : parts) {
      String className = p.a;
      List<String> ct = javaTok(p.b.java());
      Map<String, String> rewritten = new HashMap();
      tok_findAndClearRewrites(ct, rewritten);
      if (rewritten.containsKey(className))
        have.add(className);
      for (List<String> c : allClasses(ct)) {
        String name = getClassDeclarationName(c);
        have.add(name);
      }
      
      haveClasses_addImported(ct, have, false);
      
      if (!have.contains(className))
        throw fail("Wrongly defined class: " + className + " / " + p.b.snippetID + "\n\n" + p.b.java());
      if (!addedClasses.add(className)) {
        printSources(tok);
        throw fail("Trying to add class " + className + " again - main class syntax broken!");
      }
      buf.append(p.b.java());
    } // for part
    
    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 void expandClassReferences_lazy(List<String> tok, Set<String> classNames) { expandClassReferences_lazy(tok, classNames, null); }
static void expandClassReferences_lazy(List<String> tok, Set<String> classNames, List<IntRange> reToks) {
  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);
      // Now s is token before class name, t is token after class name
      // 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 (eq(t, ":")) continue; // e.g. String::concat
      if (isInteger(s)) continue;
      if (isIdentifier(s) && !eqOneOf(s, "ret", "return")) 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, "<") || eqGet(tok, i-6, ",")
        && isIdentifier(get(tok, i-8)) && eqGet(tok, i-10, "<"))) continue; // e.g. T3<S, S, S> or IF3<S, 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;
        }
      }
      
      // check following token
      if (eqOneOf(t, ",", ")", ";", ":", "|")) {
        tok.set(i, tok.get(i) + ".class");
        { if (reToks != null) reToks.add(intRange(i, i+1)); }
      }
    }
  }
}

static void expandClassReferences(List<String> tok, Set<String> classNames) {
  List<IntRange> reToks = new ArrayList();
  expandClassReferences_lazy(tok, classNames, reToks);
  reTok_multi(tok, reToks);
}

// "<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 two cases with type args for now
  jreplace(tok, "<id><<id>>(", "new $1<$3>(", cond);
  jreplace(tok, "<id><>(", "new $1<>(", cond);
}

static TokCondition newWithoutNew_condition(final Set<String> classNames) {
  return new TokCondition() { public 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 = //!(eqGet(tok, i-3, "ifclass") && isIdentifier(prev))
      nempty(prev) // discarded ifclass
      && (eq(prev, ">") ? eqGet(tok, i-3, "-")
      : neqOneOf(prev, "new", ";", "}", "{", "public", "protected", "private", "."));
    //print("newWithoutNew: checking " + struct(subList(tok, i-3, 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) || isQuoted(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>() { public 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() { public boolean get(final List<String> tok, final int i) {
    return !eqOneOf(tok.get(i+3), "instanceof", "default");
  }})) >= 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|rEnterThread fname => r|rThread|... { fname() }
  while ((i = jfindOneOf_cond(tok, new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return !eq(tok.get(i+3), "instanceof")
      && !eq(_get(tok, i-1), "cast");
  }}, "r <id>", "rThread <id>", "rEnter <id>", "rThreadEnter <id>",
    "rEnterThread <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<L<S>, S> fname => func(L<S> x) -> S { fname(x) }
  jreplace(tok, "f<<id><<id>>, <id>> <id>", "func($3 $4 $5 $6 x) -> $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)
  // ivf1 fname => a -> fname(a)
  for (String _keyword : ll("if1", "ivf1"))
    jreplace_dyn(tok, _keyword + " <id>", new F2<List<String>, Integer, String>() { public 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);
}

static boolean ifclass_reTokImmediately = false;
static boolean ifclass_noReTok = true;

// process ifclass x ... endif blocks
static void tok_ifclass(List<String> tok, Set<String> have) {
  tok_conditionals(tok, "ifclass", "endif", id -> contains(have, id), ifclass_reTokImmediately, ifclass_noReTok);
}

// returns number of changes
static int tok_conditionals(List<String> tok, String keyword, String keywordEnd, IF1<String, Boolean> pred, boolean reTokImmediately, boolean noReTok) {
  int changes = 0;
  if (tok instanceof IContentsIndexedList) {
    int[] l = ((IContentsIndexedList) tok).indicesOf(keyword);
    for (int j = l(l)-1; j >= 0; j--) {
      int i = l[j];
      if (isIdentifier(get(tok, i+2))) {
        processConditional(tok, i, keyword, keywordEnd, pred, reTokImmediately && !noReTok);
        ++changes;
      }
    }
  } else {
    if (!tok.contains(keyword)) return changes;
    int i = l(tok);
    while ((i = rjfind(tok, 1, i-1, keyword + " <id>")) >= 0) {
      ++changes;
      processConditional(tok, i, keyword, keywordEnd, pred, reTokImmediately && !noReTok);
    }
  }
  if (changes != 0 && !reTokImmediately && !noReTok) reTok(tok);
  //print(keyword + ": " + nChanges(changes));
  return changes;
}

static void processConditional(List<String> tok, int i, String keyword, String keywordEnd, IF1<String, Boolean> pred, boolean reTokImmediately) {
  int j = jfind(tok, i+4, keywordEnd);
  if (j < 0) j = l(tok)-1;
  String name = tok.get(i+2);
  boolean has = pred.get(name);
  //print(keyword + " " + name + " => " + has);
  if (has) {
    clearTokens_maybeReTok(tok, j, j+1, reTokImmediately);
    clearTokens_maybeReTok(tok, i, i+3, reTokImmediately);
  } else
    clearTokens_maybeReTok(tok, i, j+1, reTokImmediately); // safer than before
}

// 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);
    if (eqic(fname, "debug_jreplace")) debug_jreplace = true;
    clearAllTokens(subList(tok, 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(subList(tok, i, i+8));
  }
}

// rewrite <id> [=|with|to] <definition>
// - a global version of "replace <id> with"
// new version - may not work yet
/*svoid tok_findAndClearRewrites(LS tok, SS newlyDefined default null) {
  tok_findRewrites(tok, newlyDefined, f -> {
    print("Have rewrite: " + f.token + " => " + f.replacement());
    clearTokens(f.tok, f.startCIdx(), f.endNIdx());
  });
}*/


// rewrite <id> [=|with|to] <definition>
// - a global version of "replace <id> with"
// old version (works)
static void tok_findAndClearRewrites(List<String> tok) { tok_findAndClearRewrites(tok, null); }
static void tok_findAndClearRewrites(List<String> tok, Map<String, String> newlyDefined) {
  int i;
  while ((i = jfind(tok, "rewrite <id>", (_tok, nIdx) ->
    eqGetOneOf(_tok, nIdx+5, "with", "=", "to"))) >= 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);
    mapPut(newlyDefined, token, replacement);
    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) {
  tok_conditionals(tok, "ifndef", "endifndef", id -> !definitions.contains(id), true, false);
}
  
// process ifdef x ... endifdef blocks
static void tok_ifdef(List<String> tok) {
  tok_conditionals(tok, "ifdef", "endifdef", id -> definitions.contains(id), true, false);
}
  
static void conceptDeclarations(List<String> tok) {
  for (String kw : ll("concept", "sconcept")) {
    Object cond = new TokCondition() { public 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() { public 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() { public 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;
  jreplace_dyn(tok, "temp (<id>) <id>", (_tok, cIdx) -> {
    String var = makeVar(), type = tok.get(cIdx+4), firstTokenOfExpr = tok.get(cIdx+8);
    return ("temp " + type + " " + var + " = cast " + firstTokenOfExpr);
  });
  
  jreplace(tok, "temp <id> =", "temp var $2 =");
  
  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) {
  String src = join(tok);
  print("----");
  print(src);
  print("----");
  print("Bracket hygiene: " + testBracketHygiene2(src));
}

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() { public 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 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) {
    int j = i+2;
    while (subListEquals(tok, j+1, "", ".", "") && isIdentifier(get(tok, j+4)))
      j += 4;
    mainPackage = joinSubList(tok, i+2, j+1);
    clearTokens_reTok(tok, i, j+1);
  }
}

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

static void defineLambdaMapLikes(List<String> tok) {
  int i;
  while ((i = jfind(tok, "lambdaMapLike <id>")) >= 0) {
    lambdaMapLikeFunctions.add(printIf(printMapLikes(), "lambdaMapLike", tok.get(i+2)));
    clearTokens_reTok(tok, i, i+2);
  }
}

static void defineCurry1Likes(List<String> tok) {
  int i;
  while ((i = jfind(tok, "curry1Like <id>")) >= 0) {
    curry1LikeFunctions.add(printIf(printMapLikes(), "curry1Like", 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(printIf(printMapLikes(), "mapMethodLike", 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(printIf(printMapLikes(), "nuLike", tok.get(i+2)));
    clearTokens_reTok(tok, i, i+2);
  }
}

static void defineXLikes(List<String> tok, String keyword, Set<String> xLikeFunctions) {
  int i;
  while ((i = jfind(tok, keyword + " <id>")) >= 0) {
    xLikeFunctions.add(printIf(printMapLikes(), keyword, 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_applyAllXLikeFunctions(List<String> tok) {
  List<IntRange> reToks = new ArrayList();
  for (int i : jfindAll(tok, "<id> <id> (")) {
    String f = tok.get(i), arg = tok.get(i+2), replacement = null;
    if (contains(mapLikeFunctions, f))
      replacement = (f + "(f " + arg + ",");
    else if (contains(lambdaMapLikeFunctions, f))
      replacement = (f + "(lambda1 " + arg + ",");
    else if (contains(curry1LikeFunctions, f))
      replacement = (f + "(lambda2 " + arg + ",");
    else if (contains(mapMethodLikeFunctions, f))
      replacement = (f + "(" + (quote(arg)) + ",");
    else if (contains(nuLikeFunctions, f))
      replacement = (f + "(" + arg + ".class,");
    else if (contains(getLikeFunctions, f))
      replacement = (f + "(lambdaField " + arg + ",");
    else if (contains(lambdaMethod0LikeFunctions, f))
      replacement = (f + "(methodLambda0 " + arg + ",");
    else if (contains(lambda0LikeFunctions, f))
      replacement = (f + "(lambda0 " + arg + ",");
    
    if (replacement != null)
      replaceTokens_reTokLater(tok, reToks, i, i+5, replacement + " ");
  }
  reTok_multi(tok, reToks);
  return nempty(reToks);
}

/*sbool tok_applyMapLikeFunctions(LS tok, final Set<S> mapLikeFunctions) {
  // map funcname(...) => map(f funcname, ...)
  // filter funcname(...) => filter(f funcname, ...)
  // ...
  ret jreplace(tok, "<id> <id>(", "$1(f $2,", func(L<S> tok, int i) -> bool {
    contains(mapLikeFunctions, tok.get(i+1))
  });
}*/

/*sbool tok_applyLambdaMapLikeFunctions(LS tok, final Set<S> lambdaMapLikeFunctions) {
  // mapNonNulls funcname(...) => mapNonNulls(lambda1 funcname, ...)
  // mapKeysAndValues funcname(...) => mapKeysAndValues(lambda1 funcname, ...)
  // ...
  ret jreplace(tok, "<id> <id>(", "$1(lambda1 $2,", func(L<S> tok, int i) -> bool {
    contains(lambdaMapLikeFunctions, tok.get(i+1))
  });
}*/

/*sbool tok_applyCurry1LikeFunctions(LS tok, final Set<S> curry1LikeFunctions) {
  // curry1 funcname(...) => curry1(lambda2 funcname, ...)
  ret jreplace(tok, "<id> <id>(", "$1(lambda2 $2,", func(L<S> tok, int i) -> bool {
    contains(curry1LikeFunctions, tok.get(i+1))
  });
}*/

/*sbool tok_applyMapMethodLikeFunctions(LS tok, final Set<S> mapMethodLikeFunctions) {
  // mapMethod funcname(...) => mapMethod('funcname, ...)
  // collect funcname(...) => collect('funcname, ...)
  // ...
  ret jreplace_dyn(tok, "<id> <id>(",
    func(L<S> tok, int cIdx) -> S { tok.get(cIdx) + "(" + quote(tok.get(cIdx+2)) + "," },
    func(L<S> tok, int i) -> bool {
      contains(mapMethodLikeFunctions, tok.get(i+1))
    });
}*/

static void runMetaPostBlocks(List<String> tok) {
  for  (String code : unnull(metaPostBlocks)) { ping(); 
    String snippetID = standardFunctionSnippet(assertIdentifier(code));
    if (empty(snippetID))
      throw fail("meta-post function not found: " + code);
    call(hotwireCached(snippetID), 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); }
}

/*sbool tok_applyNuLikeFunctions(LS tok, final Set<S> nuLikeFunctions) {
  // nu ClassName(...) => nu(ClassName, ...)
  // ...
  ret jreplace_dyn(tok, "<id> <id>(",
    func(L<S> tok, int cIdx) -> S { tok.get(cIdx) + "(" + tok.get(cIdx+2) + ".class," },
    func(L<S> tok, int i) -> bool {
      contains(nuLikeFunctions, tok.get(i+1))
    });
}*/

/*sbool tok_applyGetLikeFunctions(LS tok, Set<S> getLikeFunctions) {
  // get fieldName(...) => get(fieldLambda fieldName, ...)
  // ...
  ret jreplace_dyn(tok, "<id> <id>(",
    func(L<S> tok, int cIdx) -> S { tok.get(cIdx) + "(lambdaField " + tok.get(cIdx+2) + "," },
    func(L<S> tok, int i) -> bool {
      contains(getLikeFunctions, 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) {
  // self-compile construct (TODO)
  /*jreplace(tok, "self-compile", (tok, iOpening, iClosing) -> S[] {
  
    selfCompiling
  });*/
  
  tok_standardBot1(tok);
  tok_processSimplified(tok);
  tok_compactModules(tok);
  
  tok_metaFor(tok);
  
  // is, short for "implements"
  jreplace(tok, "is <id>", "implements $2", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return !eqGetOneOf(tok, i+3, "a", "in"); // "is a", "is in" are defined as something else
  }});
}

static void lambdaReferences(List<String> tok) {
  // lambda0 myFunction => () -> myFunction()
  jreplace(tok, "lambda0 <id>", "() -> $2()");
  
  // lambda1 myFunction => var123 -> myFunction(var123)
  jreplace_dyn(tok, "lambda1 <id>", new F2<List<String>, Integer, Object>() { public Object get(List<String> tok, Integer cIdx) { try { 
    String var = makeVar();
    String s = var + " -> " + tok.get(cIdx+2) + "(" + var + ")";
    return eqGet(tok, cIdx-2, ")") ? roundBracket(s) : s;
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S var = makeVar();\r\n    S s = var + \" -> \" + tok.get(cIdx+2) + \"(\" + var + \")..."; }});
  
  // lambda2 myFunction => (a, b) -> myFunction(a, b)
  jreplace_dyn(tok, "lambda2 <id>", new F2<List<String>, Integer, Object>() { public Object get(List<String> tok, Integer cIdx) { try { 
    String a = makeVar();
    String b = makeVar();
    String s = ("(" + a + ", " + b + ") -> " + (tok.get(cIdx+2)) + "(" + a + ", " + b + ")");
    return eqGet(tok, cIdx-2, ")") ? roundBracket(s) : s;
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S a = makeVar();\r\n    S b = makeVar();\r\n    S s = \"(\\*a*/, \\*b*/) -> \\*tok.ge..."; }});
  
  // methodLambda0 methodName => var123 -> var123.methodName()
  jreplace_dyn(tok, "methodLambda0 <id>", new F2<List<String>, Integer, Object>() { public Object get(List<String> tok, Integer cIdx) { try { 
    String var = makeVar();
    return var + " -> " + var + "." + tok.get(cIdx+2) + "()";
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S var = makeVar();\r\n    ret var + \" -> \" + var + \".\" + tok.get(cIdx+2) + \"()\";"; }});
  
  // fieldLambda fieldName => var123 -> var123.fieldName
  jreplace_dyn(tok, "fieldLambda <id>", new F2<List<String>, Integer, Object>() { public Object get(List<String> tok, Integer cIdx) { try { 
    String var = makeVar();
    return var + " -> " + var + "." + tok.get(cIdx+2);
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S var = makeVar();\r\n    ret var + \" -> \" + var + \".\" + tok.get(cIdx+2);"; }});
}

static void clearSnippetCache() {
  snippetCache.clear();
  sf = null;
  lclasses = null;
}

static void mediumRefresh() {
  clearSnippetCache();
  print("Medium-refreshed transpiler " + mc());
}

static void jreplace_performing(List<String> tok, int i, int end, String expansion) {
  if (debug_jreplace)
    print("jreplace: " + quote(joinSubList(tok, i, end)) + " => " + quote(expansion));
}

static String mainClassName() {
  return or(mainClassName, "main");
}

static void grabImportedStaticFunctions(List<String> tok) {
  for (String name : tok_importedStaticFunctionNamesWithPackages(tok)) {
    int idx = lastIndexOf(name, '.');
    String pkg = takeFirst(idx, name);
    String functionName = dropFirst(idx+1, name);
    //print(functionName + " => " + pkg);
    doNotIncludeFunction.add(functionName);
    functionToPackage.put(functionName, pkg);
  }
}

static boolean printMapLikes() {
  return contains(definitions, "printMapLikesEtc");
}

// delete import if marked as "need latest"
static void onImportFound(List<String> tok, IntRange r) {
  String id = get(tok, r.end-3);
  if (needLatest.contains(id)) {
    print("Purging import: " + joinSubList(tok, r));
    clearTokens(tok, r);
  }
}
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(Object... objects) { throw new Fail(objects); }


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 str(Object o) {
  return o == null ? "null" : o.toString();
}

static String str(char[] c) {
  return new String(c);
}


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 boolean neq(Object a, Object b) {
  return !eq(a, b);
}


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(Symbol a, Symbol b) {
  return eq(a, b);
}

static boolean eqic(Symbol a, String b) {
  return eqic(asString(a), 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(combinePrintParameters(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 = o + "\n";
  print_noNewLine(s);
  return o;
}

static void print_noNewLine(String s) {
  
  try {
    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;
  } catch (Throwable e) {
    System.out.println(getStackTrace(e));
  }
  

  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);
  
  vmBus_send("printed", mc(), 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 List syncMap(Object f, Map map) {
  return syncMap(map, f);
}

// map: func(key, value) -> list element
static List syncMap(Map map, Object f) {
  return map(cloneLinkedHashMap(map), f); // TODO: use a temporary list instead
}

static <A, B> Map<A, B> syncMap() {
  return synchroHashMap();
}

static <A, B> Map<A, B> syncMap(Map map) {
  return synchronizedMap(map);
}


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((IVF1<Throwable>) (__1 -> printStackTrace2(__1))));
static boolean _handleException_showThreadCancellations = false;

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) {
    if (_handleException_showThreadCancellations)
      System.out.println(getStackTrace_noRecord(e2));
    return;
  }

  for (Object f : cloneList(_handleException_onException)) try {
    callF(f, e);
  } catch (Throwable e3) {
    try {
      printStackTrace2(e3); // not using pcall here - it could lead to endless loops
    } catch (Throwable e4) {
      System.out.println(getStackTrace(e3));
      System.out.println(getStackTrace(e4));
    }
  }
}


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 getOptDynOnly(((DynamicObject) o), 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 boolean get(BitSet bs, int idx) {
  return bs != null && bs.get(idx);
}


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(Runnable r) { { if (r != null) r.run(); } return null; }

static Object callF(Object f, Object... args) {
  if (f instanceof String)
    return callMCWithVarArgs((String) f, args); // possible SLOWDOWN over callMC
    
  return safeCallF(f, args);
}

static Object safeCallF(Object f, Object... 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));
}

// 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 = emptySet();
  
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 Character first(CharSequence s) { return empty(s) ? null : s.charAt(0); }


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



static <A, B, C> A first(T3<A, B, C> t) {
  return t == null ? null : t.a;
}


static Byte first(byte[] l) { 
  return empty(l) ? null : l[0];
}





static <A> A first(A[] l, IF1<A, Boolean> pred) {
  return firstThat(l, pred);
}

static <A> A first(Iterable<A> l, IF1<A, Boolean> pred) {
  return firstThat(l, pred);
}

static <A> A first(IF1<A, Boolean> pred, Iterable<A> l) {
  return firstThat(pred, l);
}


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;
  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 && !regionMatches(s, j, "*/"));
            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));
        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 < l && !regionMatches(s, j, "]]"));
          j = Math.min(j+2, l);
        } else if (c == '[' && d == '=' && i+2 < l && s.charAt(i+2) == '[') {
          do ++j; while (j+2 < l && !regionMatches(s, j, "]=]"));
          j = Math.min(j+3, l);
        } else
          ++j;
      
    tok.add(javaTok_substringC(s, i, j));
    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 int[] unnull(int[] l) { return l == null ? emptyIntArray() : l; }
static char[] unnull(char[] l) { return l == null ? emptyCharArray() : l; }
static double[] unnull(double[] l) { return l == null ? emptyDoubleArray() : 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[]) emptyObjectArray() : a;
}

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



//ifclass Symbol

static Symbol unnull(Symbol s) {
  return s == null ? emptySymbol() : s;
}
//endif



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


static int unnull(Integer i) { return i == null ? 0 : i; }
static long unnull(Long l) { return l == null ? 0L : l; }
static double unnull(Double l) { return l == null ? 0.0 : l; }


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
  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);
  
  List<IntRange> reToks = new ArrayList();
  
  // 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(tok, i, j+1, "");
      
      i -= 2;
    }
    
  if (nempty(buf)) {
    if (l(tok) == 1) addAll(tok, "", "");
    tok.set(1, str(buf)+"\n"+tok.get(1));
    reToks.add(intRange(1, 2));
  }
    
  return reTok_multi(tok, reToks);
}


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);
  }
}





//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() {
  //ifdef useNewPing
  newPing();
  //endifdef
  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 <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 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;
    
    jreplace_performing(tok, i, end, expansion);
    
    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 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) { ping(); 
    String token = tok.get(i+2);
    int repStart = i+6;
    int repEnd = repStart;
    
    // Find . with space or line break or EOF afterwards
    while  (repEnd < l(tok) && !(
      eqGet(tok, repEnd, ".") && // end when there is a dot
      (nempty(get(tok, repEnd+1)) || repEnd == l(tok)-2))) // ...and it's the end of the text OR there is a space or newline after the dot
      { ping(); repEnd += 2; }
    print("tok_replaceWith: Found " + joinSubList(tok, repStart, repEnd));
    //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)
      { ping(); if (eq(tok.get(j), token)) tok.set(j, replacement); }
    reTok(tok, i, end);
  }
}



// example:
// sS loadTextFile(File/S file) { ... }

static void tok_multiTypeArguments_v2(List<String> tok) {
  int i;
  
  // File/S
  
  while ((i = jfind(tok, "File/<id> <id>", (_tok, nIdx) ->
    eqGetOneOf(_tok, nIdx+5, "S", "String"))) >= 0) {
      
    String argName = tok.get(i+6);
    String mainType = tok.get(i), altType = tok.get(i+4);
    String converter = "newFile";
    String expr = converter + "(" + argName + ")";
    
    tok_expandMultiTypeArgument(tok,
      i, i+8,
      argName,
      mainType, altType, expr);
  }
  
  // Graphics2D etc, IIntegralImage etc, ...
  
  while ((i = jfind(tok, "<id> etc <id>")) >= 0) {
    String mainType = tok.get(i);
    String argName = tok.get(i+4);
    int iArgEnd = i+6;
 
    List<Pair<String, String>> alts = new ArrayList(); // altType, converter
    if (eq(mainType, "Graphics2D"))
      alts.add(pair("BufferedImage", "graphics"));
    else if (eq(mainType, "IIntegralImage")) {
      alts.add(pair("BufferedImage", "IntegralImage"));
      alts.add(pair("MakesBufferedImage", "IntegralImage"));
    } else if (eq(mainType, "BufferedImage"))
      alts.add(pair("MakesBufferedImage", "toBufferedImage"));
    else if (eq(mainType, "Pt")) {
      alts.add(pair(("int " + argName + "_x, int " + argName + "_y"),
        ("pt(" + argName + "_x, " + argName + "_y)")));
      tok_expandMultiTypeArgument_v3(tok,
        i, iArgEnd,
        argName,
        mainType, alts);
      continue;
    }
    
    if (empty(alts))
      throw fail("Unknown multi-type: " + joinSubList(tok, i, iArgEnd));
      
    alts = mapPairB(alts, converter -> converter + "(" + argName + ")");
    
    tok_expandMultiTypeArgument_v2(tok,
      i, iArgEnd,
      argName,
      mainType, alts);
  }  
  
  // Iterable<X> or X[], Iterable<> or X...
  
  IntRange r;
  for (String modifier : ll("[]", "..."))
    while ((r = jfind_range(tok, "Iterable<<id>> or <id>" + modifier + " <id>")) != null) {
      i = r.start+1;
      String mainType = joinSubList(tok, i, i+7);
      String altType = joinSubList(tok, i+10, r.end-3);
      String argName = tok.get(r.end-2);
      String converter = "asList";
      String expr = converter + "(" + argName + ")";
      
      tok_expandMultiTypeArgument(tok, i, r.end, argName, mainType, altType, expr);
    }
    
  // Iterable or O[], Iterable or X..., Cl/O[], etc
    
  for (String modifier : ll("[]", "..."))
    while ((r = jfind_range(tok, "<id> * *" + modifier + " <id>", new F2<List<String>, Integer, Object>() { public Object get(List<String> tok, Integer nIdx) { try { 
      return eqOneOf(tok.get(nIdx+1), "Iterable", "Collection", "Cl", "List", "L")
      && eqGetOneOf(tok, nIdx+3, "or", "/")
      && eqGetOneOf(tok, nIdx+5, "O", "Object");
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "eqOneOf(tok.get(nIdx+1), \"Iterable\", \"Collection\", \"Cl\", \"List\", \"L\")\r\n      ..."; }})) != null) {
      i = r.start+1;
      String mainType = tok.get(i);
      String altType = joinSubList(tok, i+4, r.end-3);
      String argName = tok.get(r.end-2);
      String converter = "asList";
      String expr = converter + "(" + argName + ")";
      
      tok_expandMultiTypeArgument(tok, i, r.end, argName, mainType, altType, expr);
    }
}


// goes over input only once (doesn't start again at 1 like jreplace_dyn)

static String jreplace_dyn_allowNull(String s, String in, TokReplacer replacer) { return jreplace_dyn_allowNull(s, in, replacer, null); }
static String jreplace_dyn_allowNull(String s, String in, TokReplacer replacer, ITokCondition condition) {
  List<String> tok = javaTok(s);
  jreplace_dyn_allowNull(tok, in, replacer, condition);
  return join(tok);
}

static boolean jreplace_dyn_allowNull(List<String> tok, String in, TokReplacer replacer) { return jreplace_dyn_allowNull(tok, in, replacer, null); }
static boolean jreplace_dyn_allowNull(List<String> tok, String in, TokReplacer replacer, ITokCondition condition) {
  return jreplace_dyn_allowNull(tok, in, replacer, condition, false, true);
}

static boolean jreplace_dyn_allowNull(List<String> tok, String in, TokReplacer replacer, ITokCondition condition, boolean ignoreCase, boolean reTok) {
  List<String> tokin = javaTok(in);
  jfind_preprocess(tokin);
  String[] toks = toStringArray(codeTokensOnly(tokin));  

  boolean anyChange = false;
  int i = 0;
  for (int safety = 0; safety < 10000; safety++) {
    ping();
    
    i = findCodeTokens(tok, i, ignoreCase, toks, condition);
    if (i < 0) return anyChange;
    int start = i, end = i+l(tokin)-2;
    i = end+2;
    String expansion = replacer.get(tok, start, end);
    if (expansion != null) {
      clearAllTokens(tok, start, end); // C to C
      
      tok.set(start, expansion);
      if (reTok) { // would this ever be false??
        int n = l(tok);
        reTok(tok, start, end);
        i += l(tok)-n; // adjust for replacement
      }
      anyChange = true;
    }
  }
  throw fail("woot? 10000! " + quote(in) + " => " + replacer);
}


// Use like this: printVars(+x, +y);
// Or: printVars("bla", +x);
// Or: printVars bla(, +x);
static void printVars(Object... params) {
  printVars_str(params);
}


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);
}


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 eq(Object a, Object b) {
  return a == b || a != null && b != null && a.equals(b);
}


// a little kludge for stuff like eq(symbol, "$X")
static boolean eq(Symbol a, String b) {
  return eq(str(a), b);
}



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, IF2<List<String>, Integer, String> out) {
  return jreplace_dyn(tok, in, (Object) out);
}

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

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) {
  if (id == null) return null;
  if (!isSnippetID(id)) return "?";
  
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.getSnippetTitle(id);
  
  
  return getSnippetTitle_noResourceLoader(id);
}
  
static String getSnippetTitle_noResourceLoader(String id) { try {
  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(__45 -> tok_lastIdentifier(__45), 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 List<String> tok_cleanImports(List<String> tok) {
  List<String> index = toContentsIndexedList(tok);
  List<IntRange> imports = tok_findImports_returnRanges(tok);
  BitSet exclude = new BitSet();
  for (IntRange r : imports)
    set(exclude, r.end-4);
    
  List<IntRange> reToks = new ArrayList();
  loop: for (IntRange r : imports) {
    String id = get(tok, r.end-4);
    if (!isIdentifier(id)) continue;
    for (int i : indicesOf(index, id))
      if (!get(exclude, i))
        continue loop; // id is in use
    // import is unused - delete
    clearTokens_addToReToks(tok, r.start+1, r.end, reToks);
  }
  
  reTok_multi(tok, reToks);
  return tok;
}


static int lastIndexOfStartingWith(List<String> l, String s) {
  for (int i = l(l)-1; i >= 0; i--)
    if (startsWith(l.get(i), s))
      return i;
  return -1;
}


static String dropPrefix(String prefix, String s) {
  return s == null ? null : s.startsWith(prefix) ? s.substring(l(prefix)) : s;
}


static String sfu(Object o) { return structureForUser(o); }


// unclear semantics when l is a special set (e.g. ciSet)

static <A> boolean containsOneOf(Collection<A> l, A... x) {
  if (l instanceof Set) {
    if (x != null)
      for (A a : x)
        if (l.contains(a))
          return true;
  } else {
    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 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 boolean containsIC(Producer<String> p, String a) {
  if (p != null && a != null) while (true) {
    String x = p.next();
    if (x == null) break;
    if (eqic(x, a)) return true;
  }
  return false;
}



static String extractAndPrintJavaParseError(String src, Throwable e) {
  StringBuilder buf = new StringBuilder();
  print_tee(buf);
  String msg = takeFirstLines(2, innerMessage(e));
  print_tee(buf, msg);
  int line = parseIntOpt(regexpFirstGroupIC("line (\\d+)", msg));
  print_tee(buf);
  if (line != 0) {
    List<String> lines = lines(src);
    for (int i = max(1, line-5); i <= min(l(lines), line+5); i++)
      print_tee(buf, (i == line ? "* " : "  ") + "LINE " + i + ": " + lines.get(i-1));
  }
  print_tee(buf);
  return str(buf);
}


static File transpilerErrorSourceFile() {
  return javaxCachesDir("error-source.java");
}


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 String innerMessage(Throwable e) {
  return getInnerMessage(e);
}


static void tokPrepend(List<String> tok, String s) { tokPrepend(tok, 0, s); }
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 boolean nempty(IntRange r) { return !empty(r); }











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 <A> String concatMap_strings(Iterable<A> l, IF1<A, String> f) {
  return concatMap_strings(f, l);
}

static <A> String concatMap_strings(IF1<A, String> f, Iterable<A> l) {
  return concatMap_strings((Object) 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", __);
  
  // New syntax: scope bla { ... }
  replaceKeywordBlock(tok, "scope <id>",
    "scope $2. ",
    " end scope ");
  
  int i;
  // Old syntax: scope bla ... end scope
  while ((i = rjfind(tok, "scope <id>")) >= 0) {
    String scopeName = tok.get(i+2);
    int start = i+4;
    if (eqGet(tok, start, ".")) start += 2;
    int j = jfind(tok, start, "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, start-1);
    List<String> subTok = subList(tok, start-1, 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 = start; 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 = start; 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 void tok_dropMetaComments(List<String> tok) {
  int i = -1;
  while ((i = jfind(tok, i+1, "meta-comment {")) >= 0) {
    int iOpening = indexOf(tok, i, "{");
    int iClosing = findEndOfBracketPart(tok, iOpening)-1;
    clearTokens_reTok(tok, i, iClosing+1);
  }
}


// TODO: sometimes equals/hashCode is not generated. See an old version of #1026301

static boolean tok_recordDecls_autoWithToList = true;

static void tok_recordDecls(List<String> tok) {
  int i;
  boolean re = false;
  
  jreplace(tok, "record <id> > <id> {", "record $2 extends $4 {");
  jreplace(tok, "record <id> > <id><<id>> {", "record $2 extends $4  $5 $6 $7 {");
  
  jreplace(tok, "record <id> {", "record $2() {");
  jreplace(tok, "record <id> implements", "record $2() implements");
  jreplace(tok, "record <id> extends", "record $2() extends");
  
  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);
    
    int iMod = tok_leftScanCustomModifiers(tok, i, addAllAndReturnCollection(litset("noeq", "flexeq", "noToString", "withToList", "transformable"), getJavaModifiers()));
    Set<String> mods = codeTokensAsSet(subList(tok, iMod-1, i));
    clearTokensExceptFromSet(tok, iMod, i-1, getJavaModifiers());

    boolean withToList = mods.contains("withToList");
    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);
    boolean hasDefaultConstructor = tok_hasDefaultConstructor(contents, name);
    
    for (Pair<String, String> p : args)
      buf.append("\n  " + jreplace(p.a, "...", "[]") + " " + p.b + ";");
      
    if (nempty(args) && !hasDefaultConstructor) buf.append("\n  *() {}");
    buf.append("\n  *(" + joinWithComma(map(args, new F1<Pair<String, String>, String>() { public 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 (!mods.contains("noToString") && !(jfind(contents, "toString {") >= 0 || jfind(contents, "toString()") >= 0))
      buf.append("\n  toString { ret " 
        + "shortClassName_dropNumberPrefix(this) + \"(\" + "
        + join(" + \", \" + ", secondOfPairs(args))
        + " + \")\"; }");
      
    if (!mods.contains("noeq")) {
      //buf.append("\n  [stdEq]");
      boolean flexEq = mods.contains("flexeq"); // fix for records inside of parameterized classes
      buf.append(tok_genEqualsAndHashCode(name, args, "flexEq", flexEq));
    }
    
    if (withToList)
      buf.append(tok_genRecordFieldsToList(args));
      
    boolean transformable = mods.contains("transformable");
    if (transformable)
      buf.append(tok_genRecordTransformable(name, args));
      
    String interfaces = joinNemptiesWithComma(
      withToList ? "IFieldsToList" : "",
      transformable ? "Transformable, Visitable" : "");

    tok.set(idx, (empty(interfaces) ? ""
      : (contains(subList(tok, argsTo, idx), "implements") ? "," : "implements") + " " + interfaces)
      + "{" + buf);
    
    tok.set(i, "class");
    clearTokens(tok, argsFrom-2, argsTo+1);
    reTok(tok, i, idx+1);
    
    // no static fields allowed in non-static classes (for whatever reason)
    if (contains(tok_modifiersLeftOf(tok, i), "static"))
      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_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, transpile_inStringEval(t));
      change = true;
    }
  }
  if (change) reTok(tok);
}


static void tok_listComprehensions(List<String> tok) {
  if (!tok.contains("[")) return;
  for (int i = 1; i < l(tok); i += 2) try {
    { 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
    if (eqGet(tok, iOp+2, ".")) continue; // "in." (in is a variable name)
    
    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(subList(tok, iOp+2, j));
    j += 2;
    int k = scanOverExpression(tok, bracketMap, j, "]");
    String where = join(subList(tok, j, k));
    ++k;
      
    String code = "filter(" + exp + ", func(" + type + " " + id + ") -> Bool { " + where + " })";
    replaceTokens(tok, i, k, code);
    reTok(tok, i, k);
  } catch (Throwable _e) {
    print("Was processing list comprehension: " + joinSubList(tok, i, i+30) + "...");
  
throw rethrow(_e); }
}




/* event change; =>
   transient L<Runnable> onChange;
   selfType onChange(Runnable r) { onChange = syncAddOrCreate(onChange, r); this; }
   void change() { pcallFAll(onChange); }
*/
static void tok_eventFunctions(List<String> tok) {
  int i;

  while ((i = jfind(tok, "event <id>")) >= 0) {
    int iSemicolon = i+4;
    List<String> tokArgs = null;
    if (eqGet(tok, i+4, "(")) {
      int argsFrom = i+6;
      int argsTo = findCodeTokens(tok, argsFrom, false, ")");
      tokArgs = subList(tok, argsFrom-1, argsTo);
      iSemicolon = argsTo+2;
    }
    // TODO for next version: parse modifiers in square brackets
    //if (eqGet(tok, iSemicolon, "["))
    
    if (neqGet(tok, iSemicolon, ";"))
      throw fail("Semicolon expected at end: " + joinSubList(tok, i, iSemicolon+1));
    
    String change = tok.get(i+2);
    String onChange = "on" + firstToUpper(change);
    List<Pair<String, String>> args = tok_typesAndNamesOfParams(tokArgs);
    List<String> types = pairsA(args);
    String args1 = join(dropFirstAndLast(tokArgs));
    String args2 = joinWithComma(pairsB(args));
    String typeParams = joinWithComma(map(__46 -> tok_toNonPrimitiveTypes(__46), types));     String listenerType = empty(args) ? "Runnable" : "IVF" + l(args) + "<" + typeParams + ">";
    String r = empty(args) ? "r" : "f";
   
    replaceTokens_reTok(tok, i, iSemicolon+1, 
      ("transient L<" + listenerType + "> " + onChange + ";\n") +
      ("selfType " + onChange + "(" + listenerType + " " + r + ") { " + onChange + " = syncAddOrCreate(" + onChange + ", " + r + "); this; }\n") +
      ("void " + change + "(" + args1 + ") { pcallFAll(" + (joinNemptiesWithComma(onChange, args2)) + "); }")
    );
  }
}


// for single () iterates once when the expression is not null
// and zero times when it is null.
// It was actually used once.
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 :");
  jreplace(tok, "for (<id> <id>, <id> <id> : unpair", "for (unpair $3 $4, $6 $7 :");
  
  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;
    int iCurly = iClosing+2;
    tok_statementToBlock(tok, iCurly);
    String pairVar = makeVar();
    replaceTokens(tok, i+4, iColon-1, tok_toNonPrimitiveTypes("Pair<" + type1 + ", " + type2 + ">") + " " + pairVar);
    tok.set(iCurly, "{ "
      + type1 + " " + var1 + " = pairA(" + pairVar + "); "
      + type2 + " " + var2 + " = pairB(" + pairVar + "); ");
    reTok(tok, i, iCurly+1);
  }
}


static boolean tok_doubleFor_v3_debug = false;

static void tok_doubleFor_v3(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_v3_debug) print("have colon");
    tok_typesAndNamesOfParams_keepModifiers.set(true);
    List<Pair<String, String>> args;
    try {
      args = tok_typesAndNamesOfParams(subList(tok, argsFrom-1, iColon-1));
    } catch (Throwable _e) {
      print("tok_doubleFor_v3: Skipping parsing complicated for statement (probably not a double for)");
      continue;
    }
    
    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_v3_debug) print("have 2 args: " + sfu(args));
    int iClosing = tok_findEndOfForExpression(tok, iColon+2);
    if (iClosing < 0) continue;
    if (tok_doubleFor_v3_debug) print("have closing");
    String exp = trimJoinSubList(tok, iColon+2, iClosing-1);
    if (tok_doubleFor_v3_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_v3_debug) print("have curly end");
    
    tokAppend(tok, iColon, " _entrySet(");
    tokPrepend(tok, iClosing, ")");
    
    String entryVar = makeVar();
    
    replaceTokens(tok, argsFrom, iColon-1,
      "Map.Entry<? extends " + tok_toNonPrimitiveTypes(first(first(args))) + ", ? extends "
        + tok_toNonPrimitiveTypes(first(second(args))) + "> " + entryVar);
      /*"Map.Entry<" + first(first(args)) + ", "
        + first(second(args)) + "> " + entryVar);*/
    
    tokAppend(tok, iCurly, " " + joinPairWithSpace(first(args)) + " = " + entryVar + ".getKey(); "
      + joinPairWithSpace(second(args)) + " = " + entryVar + ".getValue(); ");
    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, "unnullForIteration(");
    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 iEndOfType = indexOfAny(tok, i, ")", "&");
    int iClosing = tok_endOfExpression(tok, i+4)+1;
    String var = tok.get(i+4), type = joinSubList(tok, i+8, iEndOfType-1);
    String rawType = tok_dropTypeParameters(type);
    int start = iClosing+2, end = tok_findEndOfStatement(tok, start);
    replaceTokens(tok, i+6, iEndOfType-1, "instanceof " + rawType);
    
    // replace "var" with "((Type) var)" in enclosed block
    // unless it's another cast expression or an assignment
    
    printVars("iEndOfType", iEndOfType, "iClosing", iClosing, "iClosing" , get(tok, iClosing), "start", start, "start" , get(tok, start), "end", end, "lTok" , l(tok));
    end = min(end, l(tok));
    
    for (int j = iEndOfType; j < end; j += 2)
      if (eq(tok.get(j), var) && neqGet(tok, j-2, ".")
        && neqGetOneOf(tok, j+2, "cast", "(")
        && !tok_isAssignment(tok, j+2))
        tok_insertCast(tok, j, type);
    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() { public 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);
  }
}


static void tok_doPing(List<String> tok) {
  jreplace(tok, "do ping {", "do { ping();");
  
  int i;
  while ((i = jfind(tok, "do ping <id>")) >= 0) {
    int j = tok_findEndOfStatement(tok, i+4);
    tok.set(i+2, "{ ping();");
    tokAppend(tok, j-1, " }");
    reTok(tok, i, j);
  }
}


// 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));
    List<String> subList = subList(tok, i-1, idx); // N to somewhere
    tok.set(j-1, jreplaceExpandRefs(end, subList));
    replaceTokens(tok, i, idx+1, jreplaceExpandRefs(beg, subList));
    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, String in, ITokCondition condition) { return jfind(tok, 1, in, condition); }
static int jfind(List<String> tok, int startIndex, String in, ITokCondition condition) {
  return jfind(tok, startIndex, in, (Object) 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. Special handling of "for" and "if"
  int j = i;
  boolean special = false;
  while (j < l(tok) && neq(tok.get(j), ";")) {
    String t = get(tok, j);
    if (eqOneOf(t, "for", "if")) special = true;
    if (eqOneOf(t, "{", "(")) {
      j = findEndOfBracketPart(tok, j)+1;
      if (special && eq(t, "{")) return j-1;
    } else
      j += 2;
  }
  return j+1;
}


static TokCondition tokCondition_beginningOfMethodDeclaration() {
  return new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i-1), "}", ";", "{", null, ""); // "" is there to hopefully handle badly re-toked includes preceding the declaration
  }};
}


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 {");
  jreplace(tok, "module {", "module " + stefansOS_defaultModuleClassName() + " {");
  jreplace(tok, "module > <id>", "module " + stefansOS_defaultModuleClassName() + " > $3");
  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 <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;
}


// 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 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(Iterable c) { return c == null || !c.iterator().hasNext(); }
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(double[] a) { return a == null || a.length == 0; }
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 boolean empty(IntRange r) { return r == null || r.empty(); }











static boolean eqOneOf(Object o, Object... l) {
  for (Object x : l) if (eq(o, x)) return true; return false;
}


static <A> boolean eqGetOneOf(List<A> l, int i, A... options) {
  return eqOneOf(get(l, i), options);
}


// 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, IntRange r, String s) {
  replaceTokens(tok, r.start, r.end, s);
}


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);
}


// 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) {
  return get(tok, tok_findVarDeclarationName(tok));
}


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

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

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 ourSubList_noRangeCheck(l, startIndex, endIndex);
  
  
}


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



static void tok_ifNullAssign(List<String> tok) {
  for (int i : jfindAll_reversed(tok, "return <id> if null =")) {
    String var = tok.get(i+2);
    int iEnd = findEndOfStatement(tok, i);
    tok.set(iEnd-1, ("; return " + var + "; }"));
    replaceTokens(tok, i, i+5*2-1,
      ("{ if (" + var + " == null) " + var + " ="));
  }
  
  jreplace(tok, "<id> if null =", "if ($1 == null) $1 =");
}



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;
  int n = s.length();
  if (y < x) y = x;
  if (y > n) y = n;
  if (x >= y) return "";
  return s.substring(x, y);
}


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


// convenience method for quickly dropping a prefix
static String substring(String s, CharSequence l) {
  return substring(s, lCharSequence(l));
}


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

static int parseInt(char c) {
  return Integer.parseInt(str(c));
}


static void tok_beaConceptDecls(List<String> tok) {
  { if (!(contains(tok, "beaConcept"))) return; }
  jreplace(tok, "beaConcept <id> > <id>", "concept $2 > $4");
  jreplace(tok, "beaConcept <id> {", "concept $2 > BEAObject {");
}


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 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(defaultMaxQuineLength(), 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 tok_expandIfQuoted(String s) { return applyTranspilationFunction(__47 -> tok_expandIfQuoted(__47), s); }

static void tok_expandIfQuoted(List<String> tok) {
  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>() { public 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() { public boolean get(final List<String> tok, final int i) {
    return javaTokC(unquote(tok.get(i+3))).contains("|");
  }});
  
  tok_transpileIfQuoted_dollarVars(tok);
  
  // "...bla..."
  jreplace(tok, "if <quoted>", "if (find3plusRestsX($2, s, m))",
    new TokCondition() { public 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() { public 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() { public 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))");
}


// 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;
}


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 == '\t')
      out.append("\\t");
    else if (c == '\0')
      out.append("\\0");
    else
      out.append(c);
  }
  out.append('"');
}


static int shorten_default = 100;

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

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

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

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


static int defaultMaxQuineLength_defaultValue = 80;
static int defaultMaxQuineLength_value = defaultMaxQuineLength_defaultValue;

static int defaultMaxQuineLength() {
  return defaultMaxQuineLength_value;
}


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 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 int tok_findEndOfType(List<String> tok) {
  return tok_findEndOfType(tok, 1);
}

// i = beginning of type (identifier)
// index returned C index after type
// now does packages/inner classes too
static int tok_findEndOfType(List<String> tok, int i) {
  while (licensed()) {
    if (eqGet(tok, i+2, ".") && isIdentifier(get(tok, i+4)))
      { i += 4; continue; }
    if (eqGet(tok, i+2, "<"))
      { i = findEndOfTypeArgs(tok, i+2)-1; continue; }
    if (eqGet(tok, i+2, "["))
      { i = findEndOfBracketPart2(tok, i+2)-1; continue; }
    break;
  }
  return i+2;
}


static <A> A assertEquals(Object x, A y) {
  return assertEquals("", 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 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;
  }
}



// pattern, replacement, index of following token
static List<T3<String, String, Integer>> tok_quicknew2_patterns = ll(
  t3("new <id>", "new $2()", 5),
  t3("new <id>.<id>", "new $2.$4()", 9),
  t3("new <id> <>", "new $2<>()", 9),
  t3("new <id> < <id> >", "new $2<$4>()", 11));

static void tok_quicknew2(List<String> tok) {
  tok_quicknew(tok);
  
  boolean needFix = false;

  // [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);
  
  for (T3<String, String, Integer> t : tok_quicknew2_patterns)
    jreplace1(tok, t.a, t.b, new TokCondition() { public boolean get(final List<String> tok, final int i) {
      return eqOneOf(_get(tok, i+t.c), "{", ",", ")", ";", ":");
    }});

  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> <id> = new", "$1.$3 $4 = new $1.$3", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return tokCondition_shortNew(tok, i+13);
  }});
  jreplace(tok, "<id> <id> = new", "$1 $2 = new $1", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return tokCondition_shortNew(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
  
  needFix |= jreplace(tok, "<id><<id>> <id> = new", "$1 $2 $3 $4 $5 = new $1<>", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return tokCondition_shortNew(tok, i+9+3*2);
  }});
  needFix |= jreplace(tok, "<id><<id>,<id>> <id> = new", "$1 $2 $3 $4 $5 $6 $7 = new $1<>", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return tokCondition_shortNew(tok, i+9+5*2);
  }});
  needFix |= jreplace(tok, "<id><<id><<id>>> <id> = new", "$1 $2 $3 $4 $5 $6 $7 $8 = new $1<>", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return tokCondition_shortNew(tok, i+9+6*2);
  }});
  needFix |= jreplace(tok, "<id><<id><<id>>,<id>> <id> = new", "$1 $2 $3 $4 $5 $6 $7 $8 $9 $10 = new $1<>", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return tokCondition_shortNew(tok, i+9+8*2);
  }});
  
  // fix
  if (needFix) jreplace(tok, "<><>", "<>");
}


// e.g.: temp resourceHolder = ();
// to  : temp var resourceHolder = resourceHolder();
static void tok_varNamedLikeFunction(List<String> tok) {
  jreplace(tok, "temp <id> = ();", "temp var $2 = $2();");
}


// +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) {
    String id = tok.get(i);
    
    // special case for null := which we interpret as actually null,
    // not "null"
    
    String string = eq(id, "null") ? id : quote(id);
    tok.set(i, string);
    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+2, ",");
    tok.set(i+4, "");
    reTok(tok, i, i+5);
  }
  
  // <int> := value
  while ((i = jfind_check(":", tok, "<int> :=")) >= 0) {
    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);
  }
}


// now using lambda0 instead of f
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<>(lambda0 " + 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, "simplyCached <id>")) >= 0) {
    Collection<String> modifiers = tok_modifiersLeftOf(tok, i);
    int bracket = indexOf(tok, "(", i);
    String fName = assertIdentifier(tok.get(bracket-2));
    String type = joinSubList(tok, i+2, bracket-3);
    String boxedType = tok_toNonPrimitiveTypes(type);
    String mods2 = joinWithSpace(listMinus(modifiers, "transient"));
    
    // we keep the modifiers (e.g. static, transient) for the variable
    // the function gets the modifiers minus "transient"
    replaceTokens(tok, i, bracket-1, (boxedType + " " + fName + "_cache;\n")
      + (mods2 + " " + type + " " + fName + "() { if (" + fName + "_cache == null) " + fName + "_cache = " + fName + "_load(); ret " + fName + "_cache; }\n\n") + (mods2 + " " + boxedType + " " + fName + "_load"));
    reTok(tok, i, bracket-3);
  }
} catch (Throwable __e) { _handleException(__e); }}


static void tok_timedCachedFunctions(List<String> tok) { try {
  int i;
  while ((i = jfind(tok, "timedCached[<int>.<int>] <id>")) >= 0) {
    int bracket1 = indexOf(tok, "]", i);
    String time = joinSubList(tok, i+4, bracket1-1);
    int bracket = indexOf(tok, "(", bracket1);
    String fName = assertIdentifier(tok.get(bracket-2));
    String type = joinSubList(tok, bracket1+2, bracket-3);
    String boxedType = tok_toNonPrimitiveTypes(type);
    var modifiersList = tok_modifiersLeftOf(tok, i);
    String modifiers = joinWithSpace(modifiersList);
    String functionModifiers = joinWithSpace(listMinus(modifiersList, "transient"));
    
    replaceTokens(tok, i, bracket-1,
      ("TimedCache<" + boxedType + "> " + fName + "_cache = new <>(" + time + ", lambda0 " + fName + "_load);\n")
      + (functionModifiers + " " + type + " " + fName + "() { ret " + fName + "_cache!; }\n\n")
      + (functionModifiers + " " + 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) {
  jreplace(tok, "boolPar <id>", "optPar bool $2", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return eqGetOneOf(tok, i+5, "=", ";");
  }});
  
  int i;
  
  while ((i = jfindOneOf(tok, "optPar <id> <id>", "optPar <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 void tok_typeAAA(List<String> tok, Set<String> tripleClasses) {
  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(tripleClasses, t))) 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 + ", " + type);
      change = true;
    }
  }
}



static void tok_typeAO(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))) 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 + ", Object");
      change = true;
    }
  }
}



static <A> HashSet<A> litset(A... items) {
  return lithashset(items);
}


// macro declareS { S s = blubbi(); }
//
// void myThing {
//   declareS     // expands to: S s = blubbi();
//   print(s);
// }
static void tok_localMacro(List<String> tok) {
  int i;
  while  ((i = jfind_reversed(tok, "macro <id> {")) >= 0) { ping(); 
    String macroName = tok.get(i+2);
    int iMacroOpeningBracket = i+4;
    int iMacroClosingBracket = findEndOfBlock(tok, iMacroOpeningBracket)-1;
    
    String replacement = joinSubList(tok, iMacroOpeningBracket+1, iMacroClosingBracket);
    
    // do the replacement
    print("Replacing macro " + macroName + " with <" + trim(replacement) + ">");
    
    int end = findEndOfBlock(tok, iMacroClosingBracket)-1;
    for  (int j = iMacroClosingBracket+2; j < end; j += 2)
      { ping(); if (eq(tok.get(j), macroName)) tok.set(j, replacement); }
      
    // delete macro declaration
    clearTokens(tok, i, iMacroClosingBracket+1);
    
    reTok(tok, i, end);
  }
}



static List<String> tok_expandStarConstructors(List<String> tok) {
  int n = tok.size();
  mainLoop: for (int i = 3; i < n-6; i += 2) {
    String t = tok.get(i);
    if (!t.equals("*"))
      continue;
    String l = tok.get(i-2);
    if (!tok.get(i+2).equals("("))
      continue;
    if (!eqOneOf(l, "}", "public", "private", "protected", ";", "{", "endif", "endifdef", ">", "") && 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;
        
        // find * arguments (copy to fields of same name)
        int block = j+1;
        for (int k = i+2; k < block-1; k += 2)
          if (tok.get(k).equals("*")) {
            removeSubList(tok, k, k+2);
            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_findEndOfMethodHeader(tok, i)+1;
    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() { public 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_svoidEtc(List<String> tok) {
  jreplace(tok, "svoid", "static void");
}


static void tok_questionDot(List<String> tok) {
  jreplace(tok, "if (<id> ?. <id)", "if ($3 != null && $3.$6)");
  
  jreplace_dyn(tok, "<id> ?.",
    new F2<List<String>, Integer, String>() { public String get(List<String> tok, Integer i) { try { 
      String var = tok.get(i);
      boolean isExpression = eqGetOneOf(tok, i-2, "ret", "return", ",", "=", "(", "?"); // too few
      if (!isExpression) {
        int j = tok_findEndOfStatement(tok, i)-1;
        tokAppend_reTok(tok, j, " }");
        return ("{ if (" + var + " != null) " + var + ".");
      } else
        return (var + " == null ? null : " + var + ".");
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S var = tok.get(i);\r\n      bool isExpression = eqGetOneOf(tok, i-2, \"ret\", \"r..."; }});
    
  jreplace_dyn(tok, "<id>()?.",
    new F2<List<String>, Integer, String>() { public String get(List<String> tok, Integer i) { try { 
      int iQ = indexOf(tok, i, "?");
      String expr = joinSubList(tok, i, iQ-1);
      boolean isExpression = eqGetOneOf(tok, i-2, "ret", "return", ",", "=", "(", "?"); // too few
      String var = makeVar();
      if (!isExpression) {
        int j = tok_findEndOfStatement(tok, i)-1;
        tokAppend_reTok(tok, j, " }");
        return ("{ var " + var + " = " + expr + "; if (" + var + " != null) " + var + ".");
      } else {  // experimental
        int j = tok_findEndOfExpression(tok, i)-1;
        tokAppend_reTok(tok, j, ")");
        return ("rCallF(" + expr + ", " + var + " -> " + var + " == null ?: " + var + ".");
      }
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "int iQ = indexOf(tok, i, \"?\");\r\n      S expr = joinSubList(tok, i, iQ-1);\r\n  ..."; }});
}


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 + " {");
    tok.set(i, "final 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_returnSelf(List<String> tok) {
  int i;
  mainLoop: while ((i = jfind(tok, "returnSelf <id>")) >= 0) {
    IntRange block = tok_findFirstBlock(tok, i);
    tok.set(i, "selfType");
    tokPrepend(tok, block.end-1, " ret this; ");
  }
}


static void tok_tildeCalls(List<String> tok) {
  int iTilde;
  
  jreplace(tok, ".~", "~.");
  
  // calls without type 
  while ((iTilde = jfind(tok, "~.<id>(")) >= 0) {
    int iStart = tok_findBeginningOfJavaXTerm(tok, iTilde-2);
    String fname = tok.get(iTilde+4);
    tokPrepend(tok, iStart, "call(");
    replaceTokens(tok, iTilde, iTilde+8, ", " + quote(fname)
      + (eqGet(tok, iTilde+8, ")") ? "" : ", "));
    reTok(tok, iStart, iTilde+8);
  }
  
  // calls with type
  while ((iTilde = jfind(tok, "~.<id> <id>(")) >= 0) {
    int iStart = tok_findBeginningOfJavaXTerm(tok, iTilde-2);
    String type = get(tok, iTilde+4);
    int iFname = iTilde+6;
    String fname = tok.get(iFname);
    // TODO: brackets around the whole thing
    tokPrepend(tok, iStart, "(" + type + ") call(");
    replaceTokens(tok, iTilde, iFname+4, ", " + quote(fname)
      + (eqGet(tok, iFname+4, ")") ? "" : ", "));
    reTok(tok, iStart, iFname+4);
  }
  
  // field access
  while ((iTilde = jfind(tok, "~.<id>")) >= 0) {
    int iStart = tok_findBeginningOfJavaXTerm(tok, iTilde-2);
    String fieldName = tok.get(iTilde+4);
    tokPrepend(tok, iStart, "getOpt " + fieldName + "(");
    replaceTokens(tok, iTilde, iTilde+5, ")");
    reTok(tok, iStart, iTilde+5);
  }
}


static void tok_swappableFunctions(List<String> tok) {
  jreplace(tok, "swappable static", "static swappable");
  jreplace(tok, "pswappable <id>", "persistently swappable $2");
  jreplace(tok, "persistent swappable <id>", "persistently swappable $3");
  jreplace(tok, "transient swappable <id>", "swappable $3");

  int i;
  while ((i = jfind_any(tok,
    "swappable <id> <id>(", "swappable <id> <", "swappable <id>.", "swappable <id>[")) >= 0) {
    // left-scan for modifiers
    int iModEnd = i;
    boolean persistently = eqGet(tok, i-2, "persistently");
    if (persistently) iModEnd -= 2;
    int iMod = leftScanModifiers(tok, iModEnd);
    String modifiers = joinSubList(tok, iMod, iModEnd);
    
    // scan annotations
    int iAnnot = iMod;
    boolean atOverride = subListEquals(tok, iAnnot-4, "@", "", "Override");
    if (atOverride) iAnnot -= 4;
    
    // scan return type, find function name
    int iEndOfType = tok_findEndOfType(tok, i+2);
    String returnType = joinSubList(tok, i+2, iEndOfType-1);
    String name = assertIdentifier(tok.get(iEndOfType));
    int iArgs = iEndOfType+2;
    assertEquals("(", get(tok, iArgs));
    
    try {
      int iCurly = indexOf(tok, iEndOfType, "{");
      assertEquals(")", get(tok, iCurly-2));
      List<String> tokArgs = subList(tok, iArgs+1, iCurly-2);
      List<Pair<String, String>> args = tok_typesAndNamesOfParams(tokArgs);
      String args1 = join(tokArgs);
      String args2 = joinWithComma(pairsB(args));
      boolean isVoidMethod = eq(returnType, "void");
      List<String> types = pairsA(args);
      if (!isVoidMethod) types.add(returnType);
      String typeParams = joinWithComma(map(type -> tok_varArgTypeToArray(tok_toNonPrimitiveTypes(type)), types)); // TODO: modifiers
      String base = name + "_base", fallback = name + "_fallback";
      String mods = modifiers + (persistently || containsJavaToken(modifiers, "static") ? "" : "transient ");
      String mainFunctionModifiers = modifiers;
      String baseFunctionModifiers = modifiers;
      if (atOverride) mainFunctionModifiers = "@Override " + mainFunctionModifiers;
      String src;
      if (isVoidMethod)
        if (empty(args))
          // no args, returning void
          src = mods + ("Runnable " + name + ";\n") + 
            (mainFunctionModifiers + returnType + " " + name + "(" + args1 + ") { if (" + name + " != null) " + name + ".run(); else " + base + "(" + args2 + "); }\n") +
            ("final " + modifiers + returnType + " " + fallback + "(Runnable _f, " + args1 + ") { if (_f != null) _f.run(); else " + base + "(); }\n") +
            (baseFunctionModifiers + returnType + " " + base + "(" + args1 + ") {");
        else {
          // args, returning void
          String varType = ("IVF" + (l(args)) + "<" + typeParams + ">"); 
          src = (mods + " " + varType + " " + name + ";\n") + 
            (mainFunctionModifiers + returnType + " " + name + "(" + args1 + ") { if (" + name + " != null) " + name + ".get(" + args2 + "); else " + base + "(" + args2 + "); }\n") +
            ("final " + modifiers + returnType + " " + fallback + "(" + varType + " _f, " + args1 + ") { if (_f != null) _f.get(" + args2 + "); else " + base + "(" + args2 + "); }\n") +
            (baseFunctionModifiers + returnType + " " + base + "(" + args1 + ") {");
        }
      else {
        // zero or more args, not returning void
        String varType = ("IF" + (l(args)) + "<" + typeParams + ">");
        src = (mods + " " + varType + " " + name + ";\n") + 
          (mainFunctionModifiers + returnType + " " + name + "(" + args1 + ") { ret " + name + " != null ? " + name + ".get(" + args2 + ") : " + base + "(" + args2 + "); }\n") +
            ("final " + modifiers + returnType + " " + fallback + "(" + varType + " _f, " + args1 + ") { ret _f != null ? _f.get(" + args2 + ") : " + base + "(" + args2 + "); }\n") +
          (baseFunctionModifiers + returnType + " " + base + "(" + args1 + ") {");
      }
      replaceTokens_reTok(tok, iAnnot, iCurly+1, src);
    } catch (Throwable e) {
      throw fail("Was processing function " + name, e);
    }
  }
}


static void tok_optParLambda(List<String> tok) {
  jreplace(tok, "optParLambda1 <id>", "optPar $2(_, lambda1 $2)");
}


static void tok_runnableClasses(List<String> tok) {
  jreplace(tok, "runnable static record", "static runnable record");
  
  int i;
  while ((i = jfind(tok, "runnable <id> <id>",
    (_tok, nIdx) -> eqOneOf(tok.get(nIdx+3), "record", "class"))) >= 0) {
    int iOpening = indexOf(tok, i, "{");
    int iClosing = tok_findEndOfBlock(tok, iOpening)-1;
    tokSet_reTok(tok, iClosing, "}}");
    tokSet_reTok(tok, iOpening, "implements Runnable { run {");
    clearTokens_reTok(tok, i, i+2);
  }
  
  /*replaceKeywordBlock(tok, "runnable class <id>",
    "class $3 implements Runnable { run {",
    "}}");*/
}


static void tok_swapStatement(List<String> tok) {
  jreplace_dyn(tok, "swap <id> <id>, <id>;", (_tok, cIdx) -> {
    String var = makeVar(), type = tok.get(cIdx+2),
      v1 = tok.get(cIdx+4), v2 = tok.get(cIdx+8);
    return ("{ " + type + " " + var + " = " + v1 + "; " + v1 + " = " + v2 + "; " + v2 + " = " + var + "; }");
  });
}
    


// e.g.
//   void tailCall aka replace(Computable x) {...}
// to:
//   void tailCall(Computable x) {...}
//   final void replace(Computable x) { tailCall(x); }
// (or the other way around)
static void tok_akaFunctionNames(List<String> tok) {
  int i;
  boolean useFinal = true;
  while ((i = jfind(tok, "<id> aka <id>")) >= 0) {
    int iName1 = i, _iLastName = i+4;
    while (eqGet(tok, _iLastName+2, "aka")
      && isIdentifier(get(tok, _iLastName+4)))
      _iLastName += 4;
    int iLastName = _iLastName;

    // find arguments
    int iOpening = indexOf(tok, iLastName, "(");
    int iClosing = findEndOfBracketPart2(tok, iOpening)-1;

    // find all names
    List<String> names = new ArrayList();
    for (int j = iName1; j <= iLastName; j += 4)
      names.add(tok.get(j));

    // find return type & modifiers
    int iType = tok_leftScanType(tok, iName1);
    int iStart = leftScanModifiers(tok, tok_leftScanTypeArgsOpt(tok, iType));

    // parse argument list
    List<String> _args = subList(tok, iOpening+1, iClosing);
    List<String> args = tok_parseArgsDeclList2(_args);
    
    // analyze return type
    List<String> type = subList(tok, iType-1, iName1); // return type
    boolean isVoid = containsOneOf(codeTokens(type), javaxVoidAliases());
    
    // drop all but first name
    clearTokens(tok, iName1+1, iLastName+1);
    
    // modifiers include return type actually
    List<String> tokModifiers = subList(tok, iStart, iName1-1);
    String modifiers = join(tokModifiers);
    if (!containsOneOf(tokModifiers, "final", "default")) modifiers = "final " + modifiers;
    String _modifiers = modifiers;
    
    // add synonyms
    tokPrepend_reTok(tok, iStart, joinMap(dropFirst(names), name ->
      _modifiers + " "
      + name
      + joinSubList(tok, iLastName+1, iClosing+1)
      + " { " + stringIf(!isVoid, "return ")
      + first(names) + "(" + joinWithComma(lmap(__48 -> tok_lastIdentifier(__48), args)) + "); }\n"));
  }
}


static boolean tok_defaultArguments_debug = true;

// only one default argument per declaration for now
// example:
// static LS recursiveProbabilisticParse(S productions, S sentenceClass default "sentence", S input) { ... }
static void tok_defaultArguments(List<String> tok) {
  int i;
  while ((i = jfind(tok, "<id> default", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return !eqGetOneOf(tok, i-1, "ifclass", "ifdef", "ifndef")
      && !isJavaModifier(_get(tok, i+1));
  }})) >= 0) {
    int iOpening = lastIndexOf(tok, i, "(");
    int iClosing = findEndOfBracketPart2(tok, iOpening)-1;
    //S args = joinSubList(tok, iOpening+2, iClosing-1);
    
    // function name
    int iName = iOpening-2;
    String name = assertIdentifier(get(tok, iName));
    
    int iType = tok_leftScanType(tok, iName);
    int iStart = tok_leftScanTypeArgsOpt(tok, iType);
    iStart = leftScanModifiers(tok, iStart);
    //print("Found method head: " + joinSubList(tok, iStart, iClosing+1));
    int iEndOfExpr = tok_findEndOfExpression(tok, i+4)+1; // should point at comma or closing bracket
    String expr = joinSubList(tok, i+4, iEndOfExpr);
    int iParamTypeStart = tok_leftScanType(tok, i);
    List<String> _args1 = subList(tok, iOpening+1, iParamTypeStart);
    List<String> _args2 = subList(tok, iEndOfExpr+1, iClosing+2);
    List<String> args1 = tok_parseArgsDeclList2(_args1);
    List<String> args2 = tok_parseArgsDeclList2(_args2);
    List<String> type = subList(tok, iType-1, iName);
    boolean isVoid = containsOneOf(codeTokens(type), javaxVoidAliases());
    if (eq(expr, "new")) expr = "new " + joinSubList(tok, iParamTypeStart, i-1) + "()";
    String rewrittenHead = joinSubList(tok, iStart, iOpening+1)
      + joinWithComma(concatLists(args1, args2)) + ")";
      
    if (tok_defaultArguments_debug)
      printVars("tok_defaultArguments", "expr", expr, "_args1", _args1, "_args2", _args2, "args1", args1, "args2", args2, "type", type, "isVoid", isVoid, "rewrittenHead", rewrittenHead);
    
    clearTokens_reTok(tok, i+1, iEndOfExpr-1);
    tokPrepend_reTok(tok, iStart, rewrittenHead + " { "
      + (isVoid ? "" : "return ") + name + "(" + 
        joinWithComma(concatLists(
          map(__49 -> tok_lastIdentifier(__49), args1),
          ll(expr),
          map(__50 -> tok_lastIdentifier(__50), args2))) + "); }\n");
 }
}


// adds default constructor to classes with "persistable" modifier
// also allows "persistent"
static void tok_persistableClasses(List<String> tok) {
  int i;
  
  while((i = jfind_any(tok, new TokCondition() { public boolean get(final List<String> tok, final int i) {
    for (int j = i+1; isIdentifier(_get(tok, j)); j += 2)
      if (eqGet(tok, j, "class"))
        return true;
    return false;
  }}, "persistable", "persistent")) >= 0) {
    int iClass = indexOf(tok, "class", i);
    String className = get(tok, iClass+2);
    int idx = indexOf(tok, iClass, "{");
    int j = findEndOfBracketPart(tok, idx);
    List<String> contents = subList(tok, idx+1, j);
    boolean hasDefaultConstructor = tok_hasDefaultConstructor(contents, className);
    printVars_str("tok_persistableClasses: ", "hasDefaultConstructor", hasDefaultConstructor, "className", className);
    if (!hasDefaultConstructor)
      tokAppend_reTok(tok, idx, "\n  *() {}");
    clearTokens_reTok(tok, i, i+2); // drop persistable keyword
  }
}


static void tok_transientClasses(List<String> tok) {
  int i;
  
  while ((i = jfindOneOf(tok,
    "transient class",
    "transient <id> class",
    "transient <id> <id> class")) >= 0) {
    IntRange body = tok_findCurlyBody(tok, i);
    tokAppend_reTok(tok, body.start, "\nbool _isTransient() { true; }");
    clearTokens_reTok(tok, i, i+2);
  }
}


// e.g. (::bla) => (main::bla)
static void tok_shortMethodReferences(List<String> tok) {
  jreplace(tok, "::", "main::", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    String next = _get(tok, i+5);
    return i > 0 && !isIdentifier(_get(tok, i-1))
      && (eq(next, "<") || isIdentifier(next));
  }});
}


static void tok_orCase(List<String> tok) {
  jreplace_dyn(tok, "orCase",
    (_tok, cIdx) ->
      stringUnless(eqGet(tok, cIdx-2, "{"), "break; ") + "case",
    (_tok, nIdx) -> {
      String next = get(tok, nIdx+3);
      return isIdentifier(next) || isInteger(next) || isQuoted(next);
    });
}


static void tok_beforeMethods(List<String> tok) {
  replaceKeywordBlock(tok, "void <id> :: before",
    "void $2() {",
    "super.$2(); }");
}


static void tok_afterMethods(List<String> tok) {
  replaceKeywordBlock(tok, "void <id> :: after",
    "void $2() { super.$2();",
    "}");
}


static void tok_returnAsFunctionName(List<String> tok) {
  //ITokCondition cond = (_tok, nIdx) -> eqGet(tok, nIdx-1, ".");
  jreplace(tok, ".ret", "._return");
  jreplace(tok, ".return", "._return");
}


// get/set fieldName        (refers to this.fieldName)
// get/set fieldName(expr)  (refers to expr.fieldName)
static void tok_transpileGetSet(List<String> tok) {
  jreplace(tok, "set/get <id>", "get/set $4");
  
  for (int i : jfindAll_reverse(tok, "get/set <id>")) {
    String field = tok.get(i+6);
    int iOpening = i+8;
    int iClosing;
    if (eq(get(tok, iOpening), "("))
      iClosing = tok_findEndOfBracketPart(tok, iOpening)-1;
    else
      iClosing = 0;
    String expr = iClosing == 0 ? "this" : tok_join(tok, iOpening+1, iClosing);
    int iEnd = iClosing == 0 ? iOpening-2 : iClosing;
    tokReplace_reTok(tok, i, iEnd+1,
      replaceDollarVars("iSetAndGet($expr, " +
        "(e, v) -> { e.$field = v; }, " +
        "e -> e.$field)",
        "expr", expr, "field", field));
  }
}


// e.g. pcall myFunc(...)
static void tok_pcallPrefix(List<String> tok) {
  jreplace_dyn(tok, "pcall <id>(", (_tok, i) -> {
    String fname = tok.get(i+2);
    int iClosing = tok_findEndOfBracketPart(tok, i+4)-1;
    tokAppend_reTok(tok, iClosing, ")");
    return "pcallF(() -> " + fname + "(";
  });
}


static void tok_numberFunctionNames(List<String> tok) {
  int n = l(tok);
  List<IntRange> reToks = new ArrayList();
  for (int i = 1; i < n-2; i += 2) {
    String number, id;
    if (isInteger(number = tok.get(i)) && empty(tok.get(i+1)) && isIdentifier(id = tok.get(i+2))) { try {
    
      { if (eq(number, "0") && swic(id, "x")) continue; } // skip hex constants
      { if (eqic(id, "f")) continue; } // skip float constants
      
      // skip exponential floating-point constants (e.g. 1e6)
      if (swic(id, "e") && (l(id) == 1 && empty(get(tok, i+3)) && eqGet(tok, i+4, "-")  || startsWithInteger(substring(id, 1))))
        continue;

      replaceTokens_reTokLater(tok, reToks, i, i+3, camelCase(numberToEnglish(parseLong(number))) + firstToUpper(id));
    } catch (Throwable __e) { _handleException(__e); }}
  }
  reTok_multi(tok, reToks);
}


// e.g. cast req to Req; 
// (converts req to (Req) req for the rest of the block)
static void tok_castToStatements(List<String> tok) {
  int i;
  while ((i = jfind(tok, "cast <id> to")) >= 0) {
    String id = tok.get(i+2);
    int iTypeStart = i+6;
    int semicolon = findEndOfStatement(tok, iTypeStart)-1;
    int endOfOuterBlock = findEndOfBracketPart(tok, semicolon)-1;
    String type = joinSubList(tok, iTypeStart, semicolon-1);
    
    for (int j = semicolon+2; j < endOfOuterBlock; j += 2)
      if (eqGet(tok, j, id))
        tok.set(j, "((" + type + ") " + id + ")");
        
    clearTokens(tok, i, semicolon+1);
    reTok(tok, i, endOfOuterBlock);
  }
}
  


// optional S bla;
// => S bla() { ret getOpt(this, "bla"); }
//    void bla(S bla) { setOpt(this, "bla", bla); }
static void tok_optionalFields(List<String> tok) {
  for (int i : jfindAll_reverse(tok, "optional <id>")) {
    int iSemicolon = tok_endOfStatement(tok, i)-1;
    String var = tok.get(iSemicolon-2);
    String type = joinSubList(tok, i+2, iSemicolon-3);
    tokReplace_reTok(tok, i, iSemicolon+1,
      ("public " + type + " " + var + "() { ret (" + type + " getOpt(this, " + (quote(var)) + "); }\n") +
      ("public void var(" + type + " var) { setOpt(this, " + (quote(var)) + ", " + var + "); }"));
  }
}


// S name from req; => S name = req?.get("name");
static void tok_getFromMap(List<String> tok) {
  int i;
  jreplace_dyn_allowNull(tok, "<id> <id> from <id>;",
    (_tok, start, end) -> {
      String type = tok.get(start);
      String var = tok.get(start+2);
      String map = tok.get(start+6);
      return type + " " + var + " = " + map + "?.get(" + quote(var) + ");";
    });
}
  


// "->" short for "() ->"
static void tok_shortLambdas(List<String> tok) {
  jreplace(tok, "->", "() ->", new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return eqGetOneOf(tok, i-1, null, "=", "(", ",");
  }});
}


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(joinSubList(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) { structureToPrintWriter(o, out, new structure_Data()); }
static void structureToPrintWriter(Object o, PrintWriter out, structure_Data d) {
  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 {
  Class c;
  List<Field> fields;
  Method customSerializer;
  IVF1<Object> serializeObject; // can be set by caller of structure function
  boolean special = false; // various special classes
  boolean nullInstances = false; // serialize all instances as null (e.g. lambdas/anonymous classes)
}

static class structure_Data {
  PrintWriter out;
  int stringSizeLimit;
  int shareStringsLongerThan = 20;
  boolean noStringSharing = false;
  boolean storeBaseClasses = false;
  String mcDollar = actualMCDollar();

  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; }

  structure_ClassInfo infoForClass(Class c) {
    structure_ClassInfo info = infoByClass.get(c);
    if (info == null) info = newClass(c);
    return info;
  }
  
  // called when a new class is detected
  // can be overridden by clients
  structure_ClassInfo newClass(Class c) {
    structure_ClassInfo info = new structure_ClassInfo();
    info.c = c;
    infoByClass.put(c, info);
    
    if (isSyntheticOrAnonymous(c)) {
      info.special = info.nullInstances = true;
      return info;
    }
    
    if ((info.customSerializer = findMethodNamed(c, "_serialize"))
      != null) info.special = true;
      
    if (storeBaseClasses) {
      Class sup = c.getSuperclass();
      if (sup != Object.class) {
        append("bc ");
        append(shortDynClassNameForStructure(c));
        out.print(" ");
        append(shortDynClassNameForStructure(sup));
        out.print(" ");
        infoForClass(sup); // transitively write out superclass relations
      }
    }
    
    return info;
  }
  
  void setFields(structure_ClassInfo info, List<Field> fields) { 
    info.fields = fields;
  }
  
  void writeObject(Object o, String shortName, Map<String, Object> fv) {
    String singleField = fv.size() == 1 ? first(fv.keySet()) : null;
  
    append(shortName);
    n += countDots(shortName)*2; // correct token count
    
  
    int l = n;
    Iterator it = fv.entrySet().iterator();
    
    stack.add(new Runnable() {  public void run() { try { 
      if (!it.hasNext()) {
        if (n != l)
          append(")");
      } else {
        Map.Entry e = (Map.Entry) it.next();
        append(n == l ? "(" : ", ");
        append((String) e.getKey()).append("=");
        stack.add(this);
        structure_1(e.getValue(), structure_Data.this);
      }
    
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (!it.hasNext()) {\r\n        if (n != l)\r\n          append(\")\");\r\n      } el..."; }});
  }
}

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.infoForClass(c);
  
  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
      && !isJavaXClassName(name)
      /* && 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 = cloneList((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, d.mcDollar)) {
      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 = cloneMap((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 = ", "*/; // sep is not used yet
  
      if (o instanceof int[]) {
        //ret "intarray " + quote(intArrayToHex((int[]) o));
        atype = "intarray";
        //sep = " ";
      } else if (o instanceof double[]) {
        atype = "dblarray";
        //sep = " ";
      } else {
        Pair<Class, Integer> p = arrayTypeAndDimensions(c);
        if (p.a == int.class) atype = "intarray";
        else if (p.a == byte.class) atype = "bytearray";
        else if (p.a == boolean.class) atype = "boolarray";
        else if (p.a == double.class) atype = "dblarray";
        else if (p.a == String.class) { atype = "array S"; d.n++; }
        else atype = "array"; // fail("Unsupported array type: " + p.a);
        if (p.b > 1) {
          atype += "/" + p.b; // add number of dimensions
          d.n += 2; // 2 additional tokens will be written
        }
      }
      
      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(d.mcDollar, name);
        d.append(shortName);
        d.out.append(' ');
        structure_1(o2, d);
        return;
      } else if (info.nullInstances) { d.append("null"); return; }
      else if (info.serializeObject != null)
        { info.serializeObject.get(o); return; }
      else throw fail("unknown special type");
    }
    
    String dynName = shortDynClassNameForStructure(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$0/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().startsWith("this$")) {
        lFields.remove(i);
        lFields.add(0, f);
        break;
      }
    }
  
    
    d.setFields(info, 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
  }

  // get _persistenceInfo from field and/or dynamic field
  Field persistenceInfoField =  (Field) (d.persistenceInfo.get(c));
  Map<String, Object> persistenceInfo = persistenceInfoField == null ? null : (Map) persistenceInfoField.get(o);
  
  if (persistenceInfoField == null && o instanceof DynamicObject)
    persistenceInfo = (Map<String, Object>) getOptDynOnly(((DynamicObject) o), "_persistenceInfo");

  
  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("loadableUtils.utils$", dropPrefix(d.mcDollar, 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 = shortDynClassNameForStructure(o);
    fv.remove("className");
  }
  
  d.writeObject(o, shortName, fv);
} catch (Exception __e) { throw rethrow(__e); } }



// TODO: optimize
// also doesn't seem to work yet
static List<String> tok_processDontImports(List<String> tok, NavigableSet<String> defs) {
  print("tok_processDontImports");
  String prefix = "dontImport_";
  List<String> names = new ArrayList();
  for (String def : startingWithIC_navigableSubSet(prefix, defs)) {
    def = dropPrefixIC(prefix, def);
    String cl = replace(def, "$$", ".");
    names.add(cl);
    boolean change = jreplace(tok, "import " + cl + ";", "")
      | jreplace(tok, "import static " + cl + ";", "");
    printVars("tok_processDontImports", "def", def, "cl", cl, "change", change);
  }
  return names;
}


static String afterLastDot(String s) {
  return s == null ? null : substring(s, s.lastIndexOf('.')+1);
}


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 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 <A> boolean neqGetOneOf(List<A> l, int i, A... options) {
  return !eqGetOneOf(l, i, options);
}


// finds static imports too
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 List<String> standardImports_cache;
static List<String> standardImports() { if (standardImports_cache == null) standardImports_cache = standardImports_load(); return standardImports_cache; }

static List<String> standardImports_load() {
  return ll(
    "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 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);
}


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 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 void tok_mutatorMethods(List<String> tok) {
  replaceKeywordBlock(tok, "mutator",
    "{ try { ",
    "} finally { _change(); } }");
}


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 boolean jcontains(String s, String pat) {
  return jfind(s, pat) >= 0;
}

static boolean jcontains(List<String> tok, String pat) {
  return jfind(tok, pat) >= 0;
}

static boolean jcontains(List<String> tok, String pat, Object condition) {
  return jfind(tok, pat, condition) >= 0;
}


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 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 + "(module(), 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 + "(module(), r {");
    tok.set(j-1, "}); }");
    reTok(tok, i+4, j);
    tok_makeArgumentsFinal(tok, opening, i);
  }
}



static String lastJavaToken(String s) {
  return last(javaTokC(s));
}


static String dropLastJavaTokenAndSpacing(String s) {
  return join(dropLast(3, javaTok(s)));
}


// 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(Iterable<A>... lists) {
  List<A> l = new ArrayList();
  if (lists != null) for (Iterable<A> list : lists)
    addAll(l, list);
  return l;
}

static <A> List<A> concatLists(Collection<? extends Iterable<A>> lists) {
  List<A> l = new ArrayList();
  if (lists != null) for (Iterable<A> list : lists)
    addAll(l, 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 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", "]", "double", "float", "short", "char", "long"));

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);
}

// tok: the code
// sf: map of functions (keys) to look for - null to record every function call
// hardReferences (out parameter): records every "please include"  statement if not null
// haveFunctions: functions to skip (or null)
// includePossiblyMapLikes: include calls with pre-brackets argument (e.g. map l(...))
// mainClassName: name of program's main class (calls to this class will be skipped)
static Set<String> findFunctionInvocations(List<String> tok, Map<String, String> sf, Collection<String> hardReferences, Set<String> haveFunctions, boolean includePossiblyMapLikes) { return findFunctionInvocations(tok, sf, hardReferences, haveFunctions, includePossiblyMapLikes, "main"); }
static Set<String> findFunctionInvocations(List<String> tok, Map<String, String> sf, Collection<String> hardReferences, Set<String> haveFunctions, boolean includePossiblyMapLikes, String mainClassName) {
  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, n = tok.size();
  boolean result = false;
  for (i = 1; i+2 < n; i += 2) {
    String f = tok.get(i);
    if (!isIdentifier_quick(f)) continue;
    
    // main.<A>bla()
    if (eq(mainClassName, 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 (eqGet(tok, i-2, ":") && eqGet(tok, i-4, ":") && eqGet(tok, i-6, mainClassName)) {
      // ok, function reference (main::bla)
    } else if (eqGet(tok, i+2, "(")) {
      // ok, normal function invocation
    } else if (includePossiblyMapLikes && eqGet(tok, i+4, "(") && isIdentifier(get(tok, i+2))) {
      // ok, mapLike invocation (bla blubb(...))
    } else
      // not an invocation
      continue;

    if (i == 1
        || !findFunctionInvocations_pre.contains(tok.get(i-2))
        || eqGet(tok, i-2, ".") && eqGet(tok, i-4, mainClassName)) {
      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(Collection<Symbol> l, Symbol s) {
  return contains(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(Collection<A> a, Collection<A> b) {
  Set<A> set = similarEmptySet(a);
  if (nempty(a) && nempty(b)) {
    Set<A> bSet = asSet(b);
    for (A x : a) if (bSet.contains(x)) set.add(x);
  }
  return set;
}


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(String s) {
  return findFunctionDefs(javaTok(s));
}

static List<String> findFunctionDefs(List<String> tok) {
  List<String> functions = new ArrayList();
  
  if (tok instanceof IContentsIndexedList2) {
    for (String keyword : findFunctionDefs_keywords) {
      TreeSet<HasIndex> indices = ((IContentsIndexedList2) tok).indicesOf_treeSetOfHasIndex(keyword);
      if (indices != null)
        for (HasIndex i : indices)
          findFunctionDefs_step(tok, i.idx, functions);
    }
  } else {
    int n = l(tok);
    for (int i = 1; i < n; i += 2)
      if (findFunctionDefs_keywords.contains(tok.get(i)))
        findFunctionDefs_step(tok, i, functions);
  }
  return functions;
}

// we found one of the keywords listed above at token i
// now scan forward for a function definition
static void findFunctionDefs_step(List<String> tok, int i, List<String> functions) {
  String t = tok.get(i);
  int j = i+2, n = l(tok);
  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));
}

static boolean findFunctionsDefs_checkToken(String t) {
  if (t.length() != 1) return false;
  char c = t.charAt(0);
  return c == ';' || c == '=' || c == '(' || c == '{' /*|| c == '#' XXX*/;
}


static String nParts(long n) { return n2(n, "part"); }
static String nParts(Collection l) { return nParts(l(l)); }


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); }

// convenience map call
static <A> String lines(Iterable<A> l, IF1<A, String> f) {
  return mapToLines(l, f);
}


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;
}


static String includeInMainLoaded_magicComment;

static List<String> includeInMainLoaded(List<String> tok, String text) {
  if (includeInMainLoaded_magicComment == null)
    includeInMainLoaded_magicComment = "/*" + randomID() + "*/";
  int i = lastIndexOfStartingWith(tok, includeInMainLoaded_magicComment);
  if (i > 0) --i; else {
    print("Finding main class");
    List<String> main = findMainClass(tok);
    if (main == null) {
      print(join(tok));
      throw fail("no main class");
    }
    i = main.lastIndexOf("}");
    i += magicIndexOfSubList(tok, main);
    tok.set(i, "}" + includeInMainLoaded_magicComment);
  }
  tok.set(i, "\n" + text + "\n" + tok.get(i));
  return includeInMainLoaded_reTok(tok, i, i+1);
}


static void dontPrint() {}
static <A> A dontPrint(String s, A o) { return o; }
static <A> A dontPrint(A o) { return o; }


// 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 ThreadLocal<Boolean> loadSnippet_publicOnly = 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);
}

static  IF1<Long, String> loadSnippet;
static String loadSnippet(long snippetID) { return loadSnippet != null ? loadSnippet.get(snippetID) : loadSnippet_base(snippetID); }
final static String loadSnippet_fallback(IF1<Long, String> _f, long snippetID) { return _f != null ? _f.get(snippetID) : loadSnippet_base(snippetID); }
static String loadSnippet_base(long snippetID) { try {
  return loadSnippet(snippetID, preferCached);
} catch (Exception __e) { throw rethrow(__e); } }

static String loadSnippet(long snippetID, boolean preferCached) throws IOException {
  if (isLocalSnippetID(snippetID))
    return loadLocalSnippet(snippetID);
    
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadSnippet(fsI(snippetID));
  
  
  return loadSnippet_noResourceLoader(snippetID, preferCached);
}

static String loadSnippet_noResourceLoader(long snippetID, boolean preferCached) throws IOException {
  String text;
  
  // boss bot (old concept)
  /*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);
    if (!isTrue(loadSnippet_publicOnly.get()))
      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;
}





// names that don't fit the uppercase-is-class mantra
static Map<String, Boolean> tok_importedClassNames_specialNames = litmap(
  "_MethodCache" , true,
  "DynamicObject_loading" , false
);

static List<String> tok_importedClassNames(List<String> tok) { return tok_importedClassNames(tok, null); }
static List<String> tok_importedClassNames(List<String> tok, IVF2<List<String>, IntRange> importFound) {
  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);
      
      { if (importFound != null) importFound.get(tok, intRange(i, j)); }
      
      // This is to distinguish from imported functions;
      // yes it's rough.
      if (or(tok_importedClassNames_specialNames.get(s), 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 BigInteger\r\n"));

static Set<String> usualJavaClassNames() {
  return usualJavaClassNames_set;
}


static List<String> tlft_j(String text) {
  return toLinesFullTrim_java(text);
}


static <A, B> Set<A> keys(Map<A, B> map) {
  return map == null ? new HashSet() : map.keySet();
}

// convenience shortcut for keys_gen
static Set keys(Object map) {
  return keys((Map) map);
}


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









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 String fsI(String id) {
  return formatSnippetID(id);
}

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


static <A> void removeAll(Collection<A> a, Collection<A> b) {
  if (a != null && b != null) a.removeAll(b);
}

static <A, B> void removeAll(Map<A, B> a, Collection<A> b) {
  if (a != null && b != null)
    for (A x : b)
      a.remove(x);
}

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

static <A, B> void removeAll(Map<A, B> a, A... b) {
  if (a != null && b != null)
    for (A x : b)
      a.remove(x);
}


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

// convenience shortcut for values_gen
static Collection values(Object map) {
  return values((Map) map);
}






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

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



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 HashSet<String> allClasses_keywords = lithashset("class", "interface", "enum", "sclass", "sinterface", "record", "srecord", "strecord", "asclass", "concept");

// 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")
          && !containsNewLine(tok.get(i-1))))) {
      int j = i;
      while (j < n && !tok.get(j).equals("{"))
        j += 2;
      j = findEndOfBlock(tok, j)+1;
      i = leftScanModifiers(tok, i);
      l.add(subList(tok, 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 IntRange intRange(int start, int end) {
  return new IntRange(start, end);
}


static List<String> reTok_multi(List<String> tok, List<IntRange> places) {
  if (empty(places)) return tok;
  if (l(places) == 1) return reTok(tok, first(places));
  List<String> orig = cloneList(tok); // copy to orig
  
  // sort, extend & merge ranges
  sortIntRangesInPlace(places);
  List<IntRange> places2 = new ArrayList();
  for (IntRange p : places) {
    p = intRange(p.start & ~1, p.end | 1); // extend to N-to-N
    if (nempty(places2) && p.start <= last(places2).end)
      last(places2).end = p.end; // merge if overlapping
    else
      places2.add(p);
  }
  
  
 
  int iPlace = 0, n = l(orig);
  IntRange p = get(places2, iPlace);

  int next = p.start, i = next;
  tok.subList(next, tok.size()).clear();
  while (i < n)
    if (i < next)
      tok.add(orig.get(i++));
    else {
      int j = p.end;
      
      String s = joinSubList(orig, i, j);
      

      tok.addAll(javaTok(s));
      i = j;
      p = get(places2, ++iPlace);
      if (p == null) break;
      next = p.start;
    }
    
  while (i < n)
    tok.add(orig.get(i++));
    
  

  return tok;
}


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

// tokPat must be jfind_preprocess'd
static List<Integer> jfindAll(List<String> tok, List<String> tokPat) { return jfindAll(tok, tokPat, null); }
static List<Integer> jfindAll(List<String> tok, List<String> tokPat, ITokCondition condition) {
  TokCondition cond = toTokCondition(condition);
  String[] toks = toStringArray(codeTokensOnly(tokPat));
  
  int i = -1;
  List<Integer> l = new ArrayList();
  while ((i = findCodeTokens(tok, i+1, false, toks, cond)) >= 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> boolean contains(Producer<A> p, A a) {
  if (p != null && a != null) while (true) {
    A x = p.next();
    if (x == null) break;
    if (eq(x, a)) return true;
  }
  return false;
}



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 double last(double[] 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 <A> A last(Collection<A> l) {
  if (l == null) return null;
  if (l instanceof List) return (A) last((List) l);
  if (l instanceof SortedSet) return (A) last((SortedSet) l);
  Iterator<A> it = iterator(l);
  A a = null;
  while  (it.hasNext()) { ping(); a = it.next(); }
  return a;
}

static <A> A last(SortedSet<A> l) {
  return l == null ? null : l.last();
}










static <A> A last(CompactLinkedHashSet<A> set) {
  return set == null ? null : set.last();
}



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_maybeReTok(List<String> tok, int i, int j, boolean reTok) {
  clearTokens(tok, i, j);
  if (reTok) 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 <A, B> void mapPut(Map<A, B> map, A key, B value) {
  if (map != null && key != null && value != null) map.put(key, value);
}


static <A, B> void mapPut(Map<A, B> map, Pair<A, B> p) {
  if (map != null && p != null) map.put(p.a, p.b);
}



static void tok_addFieldOrder(List<String> tok, int i) {
  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 final 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 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);
      String msg;
      if (empty(a))
        msg = "superfluous closing bracket (" + quote(b) + ") at line " + lineNr2;
      else
        msg = "brackets don't match (" + quote(a) + " vs " + quote(b) + ") at lines " + lineNr1 + " and " + lineNr2;
      return pair(tokenToCharIndex(tok, /*tossCoin() ?*/ i /*: j*/),
        "Bad hygiene - " + msg);
    }
  }
  
  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 void clearTokensAndReTok(List<String> tok, int i, int j) {
  clearTokens_reTok(tok, i, j);
}


static <A> boolean subListEquals(List<A> l, List<A> pat, int i) {
  if (i < 0) return false;
  int j = i+l(pat);
  if (j > l(l)) return false;
  for (int k = i; k < j; k++)
    if (!eq(l.get(k), pat.get(k-i)))
      return false;
  return true;
}

static <A> boolean subListEquals(List<A> l, int i, A... pat) {
  return subListEquals(l, asVirtualList(pat), i);
}


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


static <A> A printIf(boolean b, A a) {
  if (b) print(a);
  return a;
}

static <A> A printIf(boolean b, String s, A a) {
  if (b) print(s, a);
  return a;
}


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 void replaceTokens_reTokLater(List<String> tok, List<IntRange> reToks, int i, int j, String text) {
  if (j <= i) return;
  replaceTokens(tok, i, j, text);
  reToks.add(intRange(i, j));
}


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 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 __0 = hotwireCached_lock; lock(__0); 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(__0); } }


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);
    }

    String init = "";
    j = jfind(contents, "init {");
    if (j >= 0) {
      int k = findEndOfBracketPart(contents, j+2);
      init = joinSubList(contents, j+4, k-2);
      clearTokens(contents, j, k);
    }
    
    replaceTokens_reTok(tok, i, iClosing+1,
      " cmodule2 " + 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          " + init + "\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) {
  String latestID = "#1031187";
  
  // cm means "latest version of compact module"
  jreplace(tok, "cm *", "cmodule2 $2", (_tok, nIdx) -> {
    String t = _tok.get(nIdx+3);
    return eqOneOf(t, ">", "{") || isIdentifier(t);
  });
  
  for (String kw : ll("cprint", "cprint2")) {
    jreplace(tok, kw + " {", kw + " " + stefansOS_defaultModuleClassName() + " {");
    jreplace(tok, kw + " <id> {", "cmodule2 $2 > DynPrintLog {");
  }
  
  if (jreplace_multi(tok, ll("cmodule", "compact module"), "module")) {
    print("compact modules");
    includeInMainLoaded(tok, "!include once " + latestID + "\n");
  }
  
  if (jreplace_multi(tok, ll("cmodule2 <id>", "cmodule2 >", "cmodule2 {"), "module $2"))
    includeInMainLoaded(tok, "!include once " + latestID + "\n");
}



static void tok_metaFor(List<String> tok) {
  int i;
  while ((i = jfind(tok, "meta-for <id> in *, * {")) >= 0) {
    int iOpening = indexOf(tok, i, "{");
    int iClosing = tok_findEndOfBracketPart(tok, iOpening)-1;
    String var = tok.get(i+6);
    List<String> values = ll(tok.get(iOpening-6), tok.get(iOpening-2));
    List<String> body = subList(tok, iOpening+1, iClosing);
    
    tokReplace_reTok(tok, i, iClosing+1,
      mapToLines(values, value ->
        join(replaceInClonedList(body, var, value))));
  }
}


static String roundBracket(String s) {
  return "(" + s + ")";
}

static String roundBracket(Object s) {
  return roundBracket(str(s));
}


static Class mc() {
  return main.class;
}


static List<String> tok_importedStaticFunctionNamesWithPackages(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(join(codeTokens(subList(tok, i+3, j-1))));
      i = j-1;
    }
  return names;
}


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);
}

// starts searching from i-1
static <A> int lastIndexOf(List<A> l, int i, A a) {
  if (l == null) return -1;
  for (i = min(l(l), i)-1; i >= 0; i--)
    if (eq(l.get(i), a))
      return i;
  return -1;
}

static <A> int lastIndexOf(List<A> l, A a) {
  if (l == null) return -1;
  for (int i = l(l)-1; i >= 0; i--)
    if (eq(l.get(i), a))
      return i;
  return -1;
}


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 CharSequence takeFirst(int n, CharSequence s) { return subCharSequence(s, 0, n); }

static <A> List<A> takeFirst(int n, Iterator<A> it) {
  if (it == null) return null;
  List l = new ArrayList();
  for (int _repeat_0 = 0; _repeat_0 < n; _repeat_0++)  { if (it.hasNext()) l.add(it.next()); else break; }
  return l;
}

static <A> List<A> takeFirst(int n, Iterable<A> i) {
  if (i == null) return null;
  return i == null ? null : takeFirst(n, i.iterator());
}

static <A> List<A> takeFirst(int n, IterableIterator<A> i) {
  return takeFirst(n, (Iterator<A>) i);
}

static int[] takeFirst(int n, int[] a) {
  return takeFirstOfIntArray(n, a);
}




static AutoCloseable tempInterceptPrintIfNotIntercepted(F1<String, Boolean> f) {
  return print_byThread().get() == null ? tempInterceptPrint(f) : null;
}


// 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 RuntimeException asRuntimeException(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}


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 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 String combinePrintParameters(String s, Object o) {
  return (endsWithLetterOrDigit(s) ? s + ": " : s) + o;
}


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 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 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 void vmBus_send(String msg, Object... args) {
  Object arg = vmBus_wrapArgs(args);
  pcallFAll_minimalExceptionHandling(vm_busListeners_live(), msg, arg);
  pcallFAll_minimalExceptionHandling(vm_busListenersByMessage_live().get(msg), msg, arg);
}

static void vmBus_send(String msg) {
  vmBus_send(msg, (Object) null);
}


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 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)
    { ping(); 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)
      { ping(); 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)
    { ping(); 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)
    { ping(); 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()) { ping(); 
    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);
}

// new magic alias for mapLL - does it conflict?

static <A, B> List<A> map(IF1<A, B> f, A data1, A... moreData) {
  List x = emptyList(l(moreData)+1);
  x.add(f.get(data1));
  if (moreData != null) for  (A o : moreData)
    { ping(); x.add(f.get(o)); }
  return x;
}


static <A, B> LinkedHashMap<A, B> cloneLinkedHashMap(Map<A, B> map) {
  return map == null ? new LinkedHashMap() : new LinkedHashMap(map);
}


static Map synchroHashMap() {
  return synchronizedMap(new HashMap());
}



static Map synchronizedMap() {
  return synchroMap();
}

static <A, B> Map<A, B> synchronizedMap(Map<A, B> map) {
  return synchroMap(map);
}


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 Object callOpt(Object o, String method, Object... args) {
  return callOpt_withVarargs(o, method, args);
}


static AutoInitVar < String > md5OfMyJavaSource_cache = new AutoInitVar<>(new F0<String>() { public 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); return; }
  
  if (o instanceof IMeta)
    setDyn(((IMeta) 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;
}


// TODO: cyclic structures involving certain lists & sets



static Object unstructure(String text) {
  return unstructure(text, false);
}

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

static Object unstructure(String text, IF1<String, Class> classFinder) {
  return unstructure(text, false, classFinder);
}

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();
    String mcDollar = actualMCDollar();

    // use Eclipse primitive collection if possible (smaller & hopefully faster?)
    
    
    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();
    Map<String, String> baseClassMap = new HashMap();
    HashMap<Class, Constructor> innerClassConstructors = new HashMap();
    String curT;
    char[] unquoteBuf = new char[unstructure_unquoteBufSize];
    
    X() {
      try {
        Class mc =  (Class) (callF(_classFinder, "<main>"));
        if (mc != null) mcDollar = mc.getName() + "$";
      } catch (Throwable __e) { _handleException(__e); }
    }

    Class findAClass(String fullClassName) { try {
      return classFinder != null ? (Class) callF(classFinder, fullClassName) : findClass_fullName(fullClassName);
    } catch (Throwable __e) { return null; } }
    
    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;
      if (structure_isMarker(t, 0, l(t))) {
        refID = parseInt(t.substring(1));
        consume();
      } else refID = -1;
      
      // 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, unstructure_Receiver out) {
      String t = t();
      
      // if (debug) print("parse_inner: " + quote(t));
      
      String cname = t;
      Class c = classesMap.get(cname);
      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)
            warn("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)
            warn("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();
          LinkedList l = new LinkedList();
          if (refID >= 0) refs.put(refID, l);
          { parseList(l, 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(synchroNavigableMap((NavigableMap) value)); return; }
                
                if (value instanceof SortedMap)
                  { out.set(synchroSortedMap((SortedMap) value)); return; }
                { out.set(synchroMap((Map) value)); return; }
              } else
                { out.set(synchroList((List) value)); return; }
            }
          }); return; }
        }
        
        if (t.equals("{")) {
          parseMap(out); return;
        }
        if (t.equals("[")) {
          ArrayList l = new ArrayList();
          if (refID >= 0) refs.put(refID, l);
          this.parseList(l, out); return;
        }
        if (t.equals("bitset")) {
          parseBitSet(out); return;
        }
        if (t.equals("array") || t.equals("intarray") || t.equals("dblarray")) {
          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 = mcDollar + t;
          Class _c = findAClass(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();
        out.set(parseJava()); return;
      }
      
      if (eq(t, "bc")) {
        consume();
        String c1 = tpp();
        String c2 = tpp();
        baseClassMap.put(c1, c2);
        { parse_inner(refID, i, out); return; }
      }
      
      // add more tokens here

      // Now we want to find our target class c
      // Have we failed to look up the class before?
      //bool seenBefore = classesMap.containsKey(cname);

      // If we have seen the class before, we skip all of this
      // and simply leave c as null
      // TODO - how do we fill className?
      //if (!seenBefore) {
        if (c == null && !isJavaIdentifier(t))
          throw new RuntimeException("Unknown token " + (i+1) + ": " + quote(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 = mcDollar + t;
        }
        
        if (c == null && !allDynamic) {
          // First, find class
          c = findAClass(fullClassName);
          classesMap.put(className, c);
        }
        
        // check for existing base class
        if (c == null && !allDynamic) {
          Set<String> seen = new HashSet();
          String parent = className;
          while (true) {
            String baseName = baseClassMap.get(parent);
            if (baseName == null)
              break;
            if (!seen.add(baseName))
              throw fail("Cyclic superclass info: " + baseName);
            c = findAClass(mcDollar + baseName);
            if (c == null)
              print("Base class " + baseName + " of " + parent +  " doesn't exist either");
            else if (isAbstract(c))
              print("Can't instantiate abstract base class: " + c);
            else {
              printVars_str("Reverting to base class", "className", className, "baseName", baseName, "c", c);
              classesMap.put(className, c);
              break;
            }
            parent = baseName;
          }
        }
      //}
          
      // Check if it has an outer reference
      boolean hasBracket = eq(t(), "(");
      if (hasBracket) consume();
      boolean hasOuter = hasBracket && startsWith(t(), "this$");
      
      DynamicObject dO = null;
      Object o = null;
      final String thingName = t;
      if (c != null) {
        if (hasOuter) try {
          Constructor ctor = innerClassConstructors.get(c);
          if (ctor == null)
            innerClassConstructors.put(c, ctor = nuStubInnerObject_findConstructor(c, classFinder));
          o = ctor.newInstance(new Object[] {null});
        } catch (Exception e) {
          print("Error deserializing " + c + ": " + e);
          o = nuEmptyObject(c);
        } else
          o = nuEmptyObject(c);
        if (o instanceof DynamicObject) dO = (DynamicObject) o;
      } else {
        if (concepts.contains(t) && (c = findAClass(mcDollar + "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!
      
      HashMap<String, Object> fields = new HashMap(); // no longer preserving order (why did we do this?)
      Object _o = o;
      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) {
      
      
      // translate between diferent compilers (this$0 vs this$1)
      Object outer = fields.get("this$0");
      if (outer != null) fields.put("this$1", outer);
      else {
        outer = fields.get("this$1");
        if (outer != null) fields.put("this$0", outer);
      }
      
      if (o != null) {
        if (dO != null) {
          
          setOptAllDyn_pcall(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) {
      tokrefs.put(i, list);
      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(unstructure_Receiver out) {
      String _type = tpp();
      int dims;

      if (eq(t(), "S")) { // string array
        _type = "S";
        consume();
      }
      
      if (eq(t(), "/")) { // multi-dimensional array
        consume();
        dims = parseInt(tpp());
      } else
        dims = 1;
      
      consume("{");
      List list = new ArrayList();
      String type = _type;
      
      stack.add(new Runnable() {  public void run() { try { 
        if (eq(t(), "}")) {
          consume("}");
          if (dims > 1) {
            Class atype;
            if (type.equals("intarray")) atype = int.class;
            else if (type.equals("S")) atype = String.class;
            else throw todo("multi-dimensional arrays of other types");
            
            out.set(list.toArray((Object[]) newMultiDimensionalOuterArray(atype, dims, l(list))));
          } else
            out.set(
              type.equals("intarray") ? toIntArray(list)
              : type.equals("dblarray") ? toDoubleArray(list)
              : type.equals("S") ? toStringArray(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          if (dims > 1) {\r\n    ..."; }});
    }
    
    Object parseClass() {
      consume("class");
      consume("(");
      String name = unquote(tpp());
      consume(")");
      Class c = allDynamic ? null : findAClass(name);
      if (c != null) return c;
      DynamicObject dO = new DynamicObject();
      dO.className = "java.lang.Class";
      name = dropPrefix(mcDollar, 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();
    }
  }
  
  ThreadLocal<Boolean> tlLoading = dynamicObjectIsLoading_threadLocal();
  Boolean b = tlLoading.get();
  tlLoading.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 {
    tlLoading.set(b);
  }
}

static boolean unstructure_debug = false;


static <A> List<A> synchroList() {
  return synchroList(new ArrayList<A>());
}

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



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));
}



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 Map<String, Field> getOpt_getFieldMap(Object o) {
  Class c = _getClass(o);
  HashMap<String, Field> map = getOpt_cache.get(c);
  if (map == null)
    map = getOpt_makeCache(c);
  return map;
}

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

  Map<String, Field> map = getOpt_getFieldMap(o);

  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 syncMapGet2(((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 Object getOptDynOnly(DynamicObject o, String field) {
  if (o == null || o.fieldValues == null) return null;
  return o.fieldValues.get(field);
}


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 Object callMCWithVarArgs(String method, Object... args) {
  return call_withVarargs(mc(), method, args);
}


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) { return loadTextFile(f, defaultContents, "UTF-8"); }
static String loadTextFile(File f, String defaultContents, String encoding) { try {
  
  checkFileNotTooBigToRead(f);
  
  if (f == null || !f.exists()) return defaultContents;

  FileInputStream fileInputStream = new FileInputStream(f);
  InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, encoding);
  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 Set emptySet() {
  return new HashSet();
}


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);
}


// TODO: old syntax, get rid of
static <A> A firstThat(Iterable<A> l, Object pred) {
  if (l != null) for (A a : l)
    if (checkCondition(pred, a))
      return a;
  return null;
}

static <A> A firstThat(A[] l, IF1<A, Boolean> pred) {
  if (l != null) for (A a : l)
    if (pred.get(a))
      return a;
  return null;
}

static <A> A firstThat(Iterable<A> l, IF1<A, Boolean> pred) {
  return firstThat(l, (Object) pred);
}

static <A> A firstThat(IF1<A, Boolean> pred, Iterable<A> l) {
  return firstThat(l, pred);
}


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 boolean regionMatches(String a, int offsetA, String b, int offsetB, int len) {
  return a != null && b != null && a.regionMatches(offsetA, b, offsetB, len);
}

static boolean regionMatches(String a, int offsetA, String b) {
  return regionMatches(a, offsetA, b, 0, l(b));
}


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 int[] emptyIntArray_a = new int[0];
static int[] emptyIntArray() { return emptyIntArray_a; }


static char[] emptyCharArray = new char[0];
static char[] emptyCharArray() { return emptyCharArray; }


static double[] emptyDoubleArray = new double[0];
static double[] emptyDoubleArray() { return emptyDoubleArray; }


static Map emptyMap() {
  return new HashMap();
}


static Object[] emptyObjectArray_a = new Object[0];
static Object[] emptyObjectArray() { return emptyObjectArray_a; }


static Symbol emptySymbol_value;

static Symbol emptySymbol() {
  if (emptySymbol_value == null) emptySymbol_value = symbol("");
  return emptySymbol_value;
}


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 <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 && b != 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 && b != 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 <A, B> Map<A, B> newWeakHashMap() {
  return _registerWeakMap(synchroMap(new WeakHashMap()));
}


static void newPing() {
  var tl = newPing_actionTL();
  Runnable action = tl == null ? null : tl.get();
  { if (action != null) action.run(); }
}


// 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 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 isTrue(Boolean b) {
  return b != null && b.booleanValue();
}


static void failIfUnlicensed() {
  assertTrue("license off", licensed());
}


static Thread currentThread() {
  return Thread.currentThread();
}


// unclear semantics as to whether return null on null

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<Long> asList(long[] a) {
  if (a == null) return null;
  ArrayList<Long> l = emptyList(a.length);
  for (long 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 ArrayList<Double> asList(double[] a) {
  if (a == null) return null;
  ArrayList<Double> l = emptyList(a.length);
  for (double i : a) l.add(i);
  return l;
}

static <A> ArrayList<A> asList(Iterator<A> it) {
  ArrayList l = new ArrayList();
  if (it != null)
    while (it.hasNext())
      l.add(it.next());
  return l;  
}

// disambiguation
static <A> ArrayList<A> asList(IterableIterator<A> s) {
  return asList((Iterator) s);
}

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 <A> List<A> asList(Pair<A, A> p) {
  return p == null ? null : ll(p.a, p.b);
}






  static HashMap<String, String[]> javaTokForJFind_array_cache = new HashMap();



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) {
  if (!contains(s, '$')) return s;
  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);
}



// only one multi-type argument per declaration for now
// also allows only one combo right now (File/S with newFile)
// example:
// sS loadTextFile(File/S file) { ... }
static void tok_expandMultiTypeArgument(List<String> tok,
  int iArgStart, int iArgEnd, // start and end of argument
  String argName, String mainType, String altType, String conversionExpr) {
  
  iArgEnd |= 1;

  int iOpening = lastIndexOf(tok, iArgStart, "("); // TODO: brackets
  int iClosing = findEndOfBracketPart2(tok, iOpening)-1;
  
  // function name
  int iName = iOpening-2;
  String name = assertIdentifier(get(tok, iName));
  
  int iType = tok_leftScanType(tok, iName);
  int iStart = tok_leftScanTypeArgsOpt(tok, iType);
  iStart = leftScanModifiers(tok, iStart);
  //print("Found method head: " + joinSubList(tok, iStart, iClosing+1));
  List<String> _args1 = subList(tok, iOpening+1, iArgStart);
  List<String> _args2 = subList(tok, iArgEnd&~1, iClosing);
  List<String> args1 = tok_parseArgsDeclList2(_args1);
  List<String> args2 = tok_parseArgsDeclList2(_args2);
  List<String> type = subList(tok, iType-1, iName);
  boolean isVoid = containsOneOf(codeTokens(type), javaxVoidAliases());
  //printVars(+expr, +_args1, +_args2, +args1, +args2, +type, +isVoid);
  
  String altHead = joinSubList(tok, iStart, iOpening+1)
    + joinWithComma(concatLists(
        args1,
        ll(altType + " " + argName),
        args2)) + ")";
        
  replaceTokens_reTok(tok, iArgStart|1, iArgEnd, mainType + " " + argName);
  tokPrepend_reTok(tok, iStart, altHead + " { "
    + (isVoid ? "" : "return ") + name + "(" + 
      joinWithComma(concatLists(
        map(__51 -> tok_lastIdentifier(__51), args1),
        ll(conversionExpr),
        map(__52 -> tok_lastIdentifier(__52), args2))) + "); }\n");
}


static void tok_expandMultiTypeArgument_v3(List<String> tok,
  int iArgStart, int iArgEnd, // start and end of argument
  String argName, String mainType, List<Pair<String, String>> alts) {
  
  iArgEnd |= 1;

  int iOpening = lastIndexOf(tok, iArgStart, "("); // TODO: brackets
  int iClosing = findEndOfBracketPart2(tok, iOpening)-1;
  
  // function name
  int iName = iOpening-2;
  String name = assertIdentifier(get(tok, iName));
  
  int iType = tok_leftScanType(tok, iName);
  int iStart = tok_leftScanTypeArgsOpt(tok, iType);
  iStart = leftScanModifiers(tok, iStart);
  //print("Found method head: " + joinSubList(tok, iStart, iClosing+1));
  List<String> _args1 = subList(tok, iOpening+1, iArgStart);
  List<String> _args2 = subList(tok, iArgEnd&~1, iClosing);
  List<String> args1 = tok_parseArgsDeclList2(_args1);
  List<String> args2 = tok_parseArgsDeclList2(_args2);
  List<String> type = subList(tok, iType-1, iName);
  boolean isVoid = containsOneOf(codeTokens(type), javaxVoidAliases());
  //printVars(+expr, +_args1, +_args2, +args1, +args2, +type, +isVoid);
  
  List<String> altFunctions = new ArrayList();
  
  for (Pair<String, String> __0 : alts) { String altDecl = pairA(__0); String conversionExpr = pairB(__0); 
    String altHead = joinSubList(tok, iStart, iOpening+1)
      + joinWithComma(concatLists(
          args1,
          ll(altDecl),
          args2)) + ")";
    altFunctions.add(altHead + " { "
      + (isVoid ? "" : "return ") + name + "(" + 
        joinWithComma(concatLists(
          map(__53 -> tok_lastIdentifier(__53), args1),
          ll(conversionExpr),
          map(__54 -> tok_lastIdentifier(__54), args2))) + "); }\n");
  }
        
  replaceTokens_reTok(tok, iArgStart|1, iArgEnd, mainType + " " + argName);
  tokPrepend_reTok(tok, iStart, lines(altFunctions));
}


static <A, B, C> List<Pair<A, C>> mapPairB(final Object f, Iterable<Pair<A, B>> l) {
  return map(l, new F1<Pair<A, B>, Pair<A, C>>() { public Pair<A, C> get(Pair<A, B> p) { try { 
    return p == null ? null : pair(p.a, (C) callF(f, p.b));
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "p == null ? null : pair(p.a, (C) callF(f, p.b))"; }});
}

static <A, B, C> List<Pair<A, C>> mapPairB(final F1<B, C> f, Iterable<Pair<A, B>> l) {
  return mapPairB((Object) f, l);
}

static <A, B, C> List<Pair<A, C>> mapPairB(final IF1<B, C> f, Iterable<Pair<A, B>> l) {
  return mapPairB((Object) f, l);
}

static <A, B, C> List<Pair<A, C>> mapPairB(Iterable<Pair<A, B>> l, IF1<B, C> f) {
  return mapPairB((Object) f, l);
}

static <A, B, C> Pair<A, C> mapPairB(IF1<B, C> f, Pair<A, B> p) {
  return pairMapB(f, p);
}

static <A, B, C> Pair<A, C> mapPairB(Pair<A, B> p, IF1<B, C> f) {
  return pairMapB(f, p);
}


static void tok_expandMultiTypeArgument_v2(List<String> tok,
  int iArgStart, int iArgEnd, // start and end of argument
  String argName, String mainType, List<Pair<String, String>> alts) {
  
  iArgEnd |= 1;

  int iOpening = lastIndexOf(tok, iArgStart, "("); // TODO: brackets
  int iClosing = findEndOfBracketPart2(tok, iOpening)-1;
  
  // function name
  int iName = iOpening-2;
  String name = assertIdentifier(get(tok, iName));
  
  int iType = tok_leftScanType(tok, iName);
  int iStart = tok_leftScanTypeArgsOpt(tok, iType);
  iStart = leftScanModifiers(tok, iStart);
  //print("Found method head: " + joinSubList(tok, iStart, iClosing+1));
  List<String> _args1 = subList(tok, iOpening+1, iArgStart);
  List<String> _args2 = subList(tok, iArgEnd&~1, iClosing);
  List<String> args1 = tok_parseArgsDeclList2(_args1);
  List<String> args2 = tok_parseArgsDeclList2(_args2);
  List<String> type = subList(tok, iType-1, iName);
  boolean isVoid = containsOneOf(codeTokens(type), javaxVoidAliases());
  //printVars(+expr, +_args1, +_args2, +args1, +args2, +type, +isVoid);
  
  List<String> altFunctions = new ArrayList();
  
  for (Pair<String, String> __0 : alts) { String altType = pairA(__0); String conversionExpr = pairB(__0); 
    String altHead = joinSubList(tok, iStart, iOpening+1)
      + joinWithComma(concatLists(
          args1,
          ll(altType + " " + argName),
          args2)) + ")";
    altFunctions.add(altHead + " { "
      + (isVoid ? "" : "return ") + name + "(" + 
        joinWithComma(concatLists(
          map(__55 -> tok_lastIdentifier(__55), args1),
          ll(conversionExpr),
          map(__56 -> tok_lastIdentifier(__56), args2))) + "); }\n");
  }
        
  replaceTokens_reTok(tok, iArgStart|1, iArgEnd, mainType + " " + argName);
  tokPrepend_reTok(tok, iStart, lines(altFunctions));
}


static TokenRange jfind_range(List<String> tok, String in) {
  return jfind_range(tok, 1, in);
}

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

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

static TokenRange jfind_range(List<String> tok, int startIdx, String in, Object condition) {
  return jfind_range(tok, startIdx, javaTokForJFind_array(in), condition);
}

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

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

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

static TokenRange jfind_range(List<String> tok, int startIdx, String[] tokinC, Object condition) {
  int i = findCodeTokens(tok, startIdx, false, tokinC, condition);
  return i < 0 ? null : new TokenRange(i-1, i+l(tokinC)*2);
}


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 = l(tok);
  List<String> l = emptyList(n/2);
  for (int i = 1; i < n; i += 2)
    l.add(tok.get(i));
  return l;
}


// Use like this: printVars_str(+x, +y);
// Or: printVars("bla", +x);
// Or: printVars bla(+x);
static void printVars_str(Object... params) {
  print(renderVars_str(params));
}


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);
}


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 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();
static ThreadLocal<Long> loadPage_sizeLimit = 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 (!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 (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);
  Long limit = optPar(loadPage_sizeLimit);
  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 (limit == null || n < limit) {
      ping();
      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 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 <A> IContentsIndexedList<A> toContentsIndexedList(Collection<A> l) {
  if (l == null) return new ContentsIndexedList();
  if (l instanceof IContentsIndexedList) return ((IContentsIndexedList) l);
  return new ContentsIndexedList(l);
}


// returns actual CNC
static List<IntRange> tok_findImports_returnRanges(List<String> tok) {
  List<IntRange> imports = new ArrayList();
  int n = l(tok);
  for (int i : indicesOf(tok, "import")) {
    int j = indexOf(tok, ";", i+2);
    if (j < 0) break;
    imports.add(intRange(i-1, j+2));
    i = j;
  }
  return imports;
}


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());
}

static void set(BitSet bs, int idx) {
  { if (bs != null) bs.set(idx); }
}


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));
  
  
  if (l instanceof IContentsIndexedList2)
    return map(h -> ((HasIndex) h).idx, ((IContentsIndexedList2) l).indicesOf_treeSetOfHasIndex(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> List<Integer> indicesOf(String s, String x) {
  int i = -1;
  List<Integer> out = new ArrayList();
  while ((i = indexOf(s, x, i+1)) >= 0)
    out.add(i);
  return out;
}


static void clearTokens_addToReToks(List<String> tok, int i, int j, List<IntRange> reToks) {
  if (j <= i) return;
  clearAllTokens(tok, i, j);
  reToks.add(intRange(i, j));
}


static boolean startsWith(String a, String b) {
  return a != null && a.startsWith(unnull(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;
    if (m != null) 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 String structureForUser(Object o) {
  return beautifyStructure(struct_noStringSharing(o));
}


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 void print_tee(Appendable buf, Object o) { print_tee(buf, "", o); }
static void print_tee(Appendable buf, String prefix, Object o) { try {
  String s = combinePrintParameters(prefix, o);
  { if (buf != null) buf.append(s + "\n"); } // we prefer this over 2 calls, it's possibly atomic
  print(s);
} catch (Exception __e) { throw rethrow(__e); } }

static void print_tee(StringBuilder buf) {
  print_tee(buf, "");
}



static String takeFirstLines(int n, String s) {
  return lineRange(s, 0, n);
}


static int parseIntOpt(String s) { return parseIntOpt(s, 0); }
static int parseIntOpt(String s, int defValue) {
  return isInteger(s) ? parseInt(s) : defValue;
}


static String regexpFirstGroupIC(String pat, String s) {
  Matcher m = regexpIC(pat, s);
  if (m.find()) return m.group(1); else return null;
}


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 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 String getInnerMessage(Throwable e) {
  if (e == null) return null;
  return getInnerException(e).getMessage();
}


static <A> void replaceCollection(Collection<A> dest, Collection<A> src) {
  if (dest == src) return;
  dest.clear();
  if (src != null) dest.addAll(src);
}


static void replaceListPart(List l, int i, int j, List l2) {
  replaceSublist(l, i, j, l2);
}


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 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 tok_packageName(String src) {
  Producer<String> p = javaTokC_producer(src);
  String t = p.next();
  if (!eq(t, "package")) return "";
  StringBuilder buf = new StringBuilder();
  while (!eqOneOf(t = p.next(), null, ";"))
    buf.append(t);
  return str(buf);
}


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 int jfind_any(String src, String... patterns) {
  return jfind_any(javaTok(src), patterns);
}
  
static int jfind_any(List<String> tok, String... patterns) { return jfind_any(tok, null, patterns); }
static int jfind_any(List<String> tok, TokCondition cond, String... patterns) {
  int i = -1;
  for (String pat : patterns)
    i = minUnlessMinus1(i, jfind(tok, pat, cond));
  return i;
}


static int tok_leftScanCustomModifiers(List<String> tok, int i, Collection<String> modifiers) {
  while (i > 1 && contains(modifiers, tok.get(i-2)))
    i -= 2;
  return i;
}



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

static <A, B extends Collection<A>> B addAllAndReturnCollection(B c, A... b) {
  addAll(c, b);
  return c;
}



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

static List<String> getJavaModifiers() {
  return getJavaModifiers_list;
}


static Set<String> codeTokensAsSet(List<String> tok) {
  return asSet(codeTokens_lazy(tok));
}


// i must be odd
static void clearTokensExceptFromSet(List<String> tok, int i, int j, Collection<String> set) {
  set = asSet(set);
  for (; i < j; i += 2)
    if (!contains(set, tok.get(i)))
      clearTokens(tok, i, i+2);
}


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 boolean tok_hasDefaultConstructor(List<String> contents, String className) {
  List<String> contentsWithoutCurly = replaceCurlyBracedWith(tok_combineCurlyBrackets_keep(cloneList(contents)), "");
  return jcontains_any(contentsWithoutCurly, "\\*()", className + "()");
}



static <A, B> List<B> secondOfPairs(Collection<Pair<A, B>> l) {
  return lambdaMap(__57 -> secondOfPair(__57), l);
}


// fields: type + name
static String tok_genEqualsAndHashCode(String className, List<Pair<String, String>> fields, Object... __) {
  String qualifier = optPar("qualifier", __,  "");
  boolean flexEq = boolPar("flexEq", __);
  String argName = "o";
  while (pairsB(fields).contains(argName)) argName = "_" + argName;
  StringBuilder buf = new StringBuilder(("\n\npublic bool equals(O " + argName + ") {\n"));
  String instanceCheck = flexEq
    ? ("_getClass(" + argName + ") == getClass()")
    : (argName + " instanceof ") + qualifier + className;
  if (empty(fields))
    buf.append(("ret " + instanceCheck + ";\n"));
  else {
    String x = makeVar();
    buf.append("if (!(" + instanceCheck + ")) false;\r\n    " + className + (" " + x + " = cast ") + argName + ";\r\n    ret " + join(" && ", map(fields, new F1<Pair<String, String>, String>() { public String get(Pair<String, String> p) { try { 
      return tok_isPrimitiveType(p.a)
        ? p.b + (" == " + x + ".") + p.b
        : "eq(" + p.b + (", " + x + ".") + p.b + ")";
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "tok_isPrimitiveType(p.a)\r\n        ? p.b + \" == \\*x*/.\" + p.b\r\n        : \"eq(\"..."; }})) + ";\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>() { public 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 String tok_genRecordTransformable(String name, List<Pair<String, String>> fields) {
  return "public O transformUsing(IF1 f) { " +
    (empty(fields) ? "this;" : "ret new " + name + "("
    + joinWithComma(mapPairs(fields, (type, field) ->
      (eqOneOf(type, "O", "Object") ? "" : "(" + tok_varArgTypeToArray(type) + ") ")
      + "f.get(" + field + ")"))
    + ");")
    + " }\n"
    + "public void visitUsing(IVF1 f) {" + joinPrependSpace(map(pairsB(fields), field -> "f.get(" + field + ");"))
    + " }\n";
}


static String joinNemptiesWithComma(Object... strings) {
  return joinNempties(", ", strings);
}

static String joinNemptiesWithComma(Iterable strings) {
  return joinNempties(", ", strings);
}


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;
}


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 String transpile_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) + "\"");
    String code = trim(substring(t, idx+2, j));
    l.add(isIdentifier(code) ? code : "(" + code + ")");
    t = substring(t, j+2);
  }
  if (nempty(t))
    l.add("\"" + t + "\"");
  return "(" + join(" + ", l) + ")";
}


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 String firstToUpper(String s) {
  if (empty(s)) return s;
  return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}


static <A, B> List<A> pairsA(Collection<Pair<A, B>> l) {
  return firstOfPairs(l);
}


static <A> List<A> dropFirstAndLast(int n, List<A> l) {
  return cloneSubList(l, n, l(l)-n);
}

static <A> List<A> dropFirstAndLast(int m, int n, List<A> l) {
  return cloneSubList(l, m, l(l)-n);
}

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

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


static <A, B> List<B> pairsB(Collection<Pair<A, B>> l) {
  return secondOfPairs(l);
}


// add {} to a statement if it isn't a block already
// 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 <A> List<A> reversed(Iterable<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, B, C> B second(T3<A, B, C> t) {
  return t == null ? null : t.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)));
}


static String joinPairWithSpace(Pair p) {
  return str(p.a) + " " + str(p.b);
}


// returns index of trailing N token
static int tok_endOfExpression(List<String> tok, int i) {
  return tok_findEndOfExpression(tok, i);
}


static String tok_dropTypeParameters(String type) {
  return join(dropAfter("<", javaTok(type)));
}


static boolean tok_isAssignment(List<String> tok, int i) {
  if (!eqGet(tok, i, "=")) return false;
  if (nempty(get(tok, i+1))) return true;
  if (eqGet(tok, i+2, "=")) return false;
  return true;
}


static void tok_insertCast(List<String> tok, int i, String type) {
  String expr = tok.get(i);
  String prev = get(tok, i-2), next = get(tok, i+2);
  boolean needBrackets = !(
    doubleEq(prev, "=", next, ";")
    || doubleEq(prev, "(", next, ")")
  );
  tok.set(i, roundBracketIf(needBrackets, "(" + type + ") " + expr));
}
 



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;
}


// syntax 1: replace all occurrences of x in l with y
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;
}

// syntax 2: splice l at fromIndex-toIndex and replace middle part with y
static <A> List<A> replaceSublist(List<A> l, int fromIndex, int toIndex, List<A> y) {
  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 stefansOS_defaultModuleClassName() {
  return "AModule";
}


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 int tok_findVarDeclarationName(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 j;
  }


static <A> List<A> ourSubList_noRangeCheck(List<A> l, int startIndex, int endIndex) {
  if (l instanceof RandomAccess)
    return new RandomAccessSubList(l, startIndex, endIndex);
  else
    return new SubList(l, startIndex, endIndex);
}


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

static List<Integer> jfindAll_reversed(List<String> tok, String pat, ITokCondition condition) {
  return reversed(jfindAll(tok, pat, condition));
}

static List<Integer> jfindAll_reversed(List<String> tok, List<String> tokPat) {
  return reversed(jfindAll(tok, tokPat));
}


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 int lCharSequence(CharSequence 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 String applyTranspilationFunction(IVF1<List<String>> f, String s) {
  if (f == null) return s;
  List<String> tok = javaTok(s);
  f.get(tok);
  return join(tok);
}


static List<String> splitAtJavaToken(String s, String splitToken) {
  return splitByJavaToken(s, splitToken);
}


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;
}


// "i call you $name"
static void tok_transpileIfQuoted_dollarVars(List<String> tok) {
  jreplace_dyn(tok, "if <quoted>", new F2<List<String>, Integer, String>() { public String get(List<String> tok, Integer cIdx) { try { 
    String pat = unquote(tok.get(cIdx+2));
    List<String> vars = new ArrayList();
    String pat2 = dollarVarsToStars(pat, vars);
    
    int iCurly = cIdx+4;
    tok_statementToBlock(tok, iCurly);
    tokAppend_reTok(tok, iCurly, joinWithSpace(mapWithIndexStartingAt1(vars,
      (i, var) -> ("S " + var + " = $" + i + ";"))));
    return ("if (match(" + (quote(pat2)) + ", s, m))");
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S pat = unquote(tok.get(cIdx+2));\r\n    new LS vars;\r\n    S pat2 = dollarVarsT..."; }}, new TokCondition() { public boolean get(final List<String> tok, final int i) {
    return containsDollarVars(unquote(tok.get(i+3)));
  }});
}


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;
}


static CharSequence subCharSequence(CharSequence s, int x) {
  return subCharSequence(s, x, s == null ? 0 : s.length());
}

static CharSequence subCharSequence(CharSequence 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.subSequence(x, y);
}


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;
}


// 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();
}



// i must point at the (possibly imaginary) opening bracket (any of the 3 types, not type parameters)
// index returned is index of closing bracket + 1
static int findEndOfBracketPart2(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 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;
}

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

    throw fail((nempty(msg) ? msg + ": " : "") + "expected: "+ x + ", got: " + y);
  } else
    print("OK" + (empty(msg) ? "" : " " + msg) + ": " + /*sfu*/(x));
  return y;
}




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 <A, B, C> T3<A, B, C> t3(A a, B b, C c) {
  return new T3(a, b, c);
}


// 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)) && eqGetOneOf(tok, j+2, ";", ",")) {
      List<String> vars = ll(get(tok, j));
      int k = j;
      while (eqGet(tok, k+2, ",") && isIdentifier(get(tok, k+4))) {
        k += 4;
        vars.add(tok.get(k));
      }
      String type = joinSubList(tok, i+2, j-1);
      if (!contains(type, ".")) type = tok.get(i+2);
      clearTokens(tok, i, i+2);
      for (int iVar = 0; iVar < l(vars); iVar++)
        tokAppend(tok, j+iVar*4, " = new " + type + "()");
      reTok(tok, i, k+3);
    }
  }
}


static boolean tokCondition_shortNew(List<String> tok, int i) {
  return eqOneOf(_get(tok, i), "(", ";", ",", ")", "{", "<");
}


static <A> List<A> listMinus(Collection<A> l, Object... stuff) {
  if (l == null) return null;
  List l2 = similarEmptyList(l);
  Set set = lithashset(stuff);
  for (Object o : l)
    if (!set.contains(o))
      l2.add(o);
  return l2;
}


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 jfind_reversed(List<String> tok, String in) {
  return rjfind(tok, in);
}



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



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



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



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



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



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



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



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



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


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 boolean longIsInt(long l) {
  return l == (int) l;
}


// 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 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 void tokAppend_reTok(List<String> tok, int i, String s) {
  tok.set(i, tok.get(i)+s);
  reTok(tok, i, i+1);
}


// returns index of trailing N token
static int tok_findEndOfExpression(List<String> tok, int i) {
  return scanToEndOfInitializer2(tok, i);
}


// 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;
}


// returns range(opening bracket, closing bracket+1)
static IntRange tok_findFirstBlock(List<String> tok, int i) {
  i = indexOf(tok, i, "{");
  if (i < 0) return null;
  return intRange(i, findEndOfBlock(tok, i));
}


// i is index of last token of expression
// return value is C token
static int tok_findBeginningOfJavaXTerm(List<String> tok, int i) {
  i |= 1;
  int level = 0;
  
  while (i > 1) {
    String t = get(tok, i);
         if (eqOneOf(t, "}", ")")) level++;
    else if (eqOneOf(t, "{", "(")) level--;
    if (level == 0) {
      String prev = get(tok, i-2);
      printVars_str("prev", prev, "t", t);
      if (eqOneOf(prev, "ret", "return")
        || isIdentifier(t)
          && !isIdentifier(prev)
          && !eq(prev, ".")) {
        
        return i;
      }
    }
    i -= 2;
  }
  
  return i;
}


static String tok_varArgTypeToArray(String s) {
  return replaceSuffix("...", "[]", s);
}


static boolean containsJavaToken(String s, String t) {
  return contains(javaTokC_iterator(s), t);
}

static boolean containsJavaToken(List<String> tok, String t) {
  return containsToken(tok, t);
}


// i must point at the (possibly imaginary) opening bracket ("{")
// index returned is index of closing bracket + 1 (or l(cnc))
static int tok_findEndOfBlock(List<String> cnc, int i) {
  return findEndOfBlock(cnc, i);
}


// i = C token to the right of type
static int tok_leftScanType(List<String> tok, int i) {
  // first skip ellipsis
  if (subListEq(tok, i-6, ".", "", ".", "", "."))
    i -= 6;
  
  // skip array brackets
  while (eqGet(tok, i-2, "]") && eqGet(tok, i-4, "["))
    i -= 4;
  i = tok_leftScanTypeArgsOpt(tok, i);
  do {
    if (!isIdentifier(get(tok, i-2))) return i; // weird
    i = tok_leftScanType(tok, i-2); // Is this really supposed to be recursive??
  } while (eqGet(tok, i-2, "."));
  return i;
}



// returns index of opening <
static int tok_leftScanTypeArgsOpt(List<String> tok, int i) {
  if (!eqGet(tok, i-2, ">")) return i;
  int level = 0;
  while (i > 1) {
    String t = tok.get(i-2);
    if (eq(t, ">")) level++;
    else if (eq(t, "<")) {
      level--;
      if (level == 0) return i-2;
    }
    i -= 2;
  }
  return i;
}



static List<String> tok_parseArgsDeclList2(List<String> tok) {
  return tok_parseArgsDeclList2(tok, getBracketMapIncludingAngleBrackets(tok));
}

static List<String> tok_parseArgsDeclList2(List<String> tok, Map<Integer, Integer> bracketMap) {
  int argStart = 1;
  if (eqGet(tok, argStart, ",")) argStart += 2;
  int i = argStart;
  List<String> args = new ArrayList();
  while (i < l(tok) && 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 List<String> codeTokens(List<String> tok) {
  return codeTokensOnly(tok);
}


static Set<String> javaxVoidAliases_cache;
static Set<String> javaxVoidAliases() { if (javaxVoidAliases_cache == null) javaxVoidAliases_cache = javaxVoidAliases_load(); return javaxVoidAliases_cache; }

static Set<String> javaxVoidAliases_load() {
  return litset("void", "svoid");
}


// f must return a string
static String joinMap(Object f, Iterable l) {
  return join(map(f, l));
}

static String joinMap(Iterable l, Object f) {
  return joinMap(f, l);
}

static <A> String joinMap(Iterable<A> l, IF1<A, String> f) {
  return joinMap(f, l);
}

static <A> String joinMap(IF1<A, String> f, Iterable<A> l) {
  return join(map(f, l));
}

static <A, B> String joinMap(String separator, Map<A, B> map, IF2<A, B, String> f) {
  return join(separator, map(map, f));
}


static String stringIf(boolean b, String s) {
  return stringIfTrue(b, s);
}

static String stringIf(String s, boolean b) {
  return stringIf(b, s);
}


static <A, B> List<B> lmap(IF1<A, B> f, Iterable<A> l) {
  return lambdaMap(f, l);
}



static <A, B> List<B> lmap(IF1<A, B> f, A[] l) {
  return lambdaMap(f, l);
}


static boolean isJavaModifier(String s) {
  return getJavaModifiers().contains(s);
}


// opening bracket to closing bracket + 1
static IntRange tok_findCurlyBody(List<String> tok) { return tok_findCurlyBody(tok, 1); }
static IntRange tok_findCurlyBody(List<String> tok, int i) {
  int idx = findCodeTokens(tok, i, false, "{");
  if (idx < 0) return null;
  int j = findEndOfBracketPart(tok, idx);
  return intRange(idx, j);
}


static String stringUnless(boolean b, String s) {
  return b ? "" : s;
}


static List<Integer> jfindAll_reverse(List<String> tok, String pat) {
  return jfindAll_reversed(tok, pat);
}



static List<Integer> jfindAll_reverse(List<String> tok, String pat, ITokCondition condition) {
  return jfindAll_reversed(tok, pat, condition);
}



static List<Integer> jfindAll_reverse(List<String> tok, List<String> tokPat) {
  return jfindAll_reversed(tok, tokPat);
}


// 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 tok_findEndOfBracketPart(List<String> cnc, int i) {
  return findEndOfBracketPart(cnc, i);
}


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



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


static String tok_join(List<String> l, IntRange r) {
  return joinSubList(l, r);
}


static void tokReplace_reTok(List<String> tok, int i, int j, String s) {
  replaceTokens_reTok(tok, i, j, s);
}


static String replaceDollarVars(String s, Object... params) {
  if (empty(params)) return s;
  Map<String, Object> vars = mapKeys(__58 -> dropDollarPrefix(__58), (Map<String, Object>) litcimap(params));
  return replaceDollarVars_dyn(s, var -> strOrNull(vars.get(var)));
}

static String replaceDollarVars(String s, IF1<String, String> f) {
  return replaceDollarVars_dyn(s, f);
}


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 boolean startsWithInteger(String s) {
  if (empty(s)) return false;
  int start = 0;
  if (s.charAt(0) == '-') ++start;
  return l(s) >= start && isDigit(s.charAt(start));
}


static String camelCase(List<String> words) {
  StringBuilder buf = new StringBuilder();
  for (int i = 0; i < l(words); i++) {
    String word = words.get(i);
    word = toLower(word);
    buf.append(i == 0 ? word : firstToUpper(word));
  }
  return str(buf);
}

static String camelCase(String words) {
  return camelCase(words(words));
}


// from https://www.rgagnon.com/javadetails/java-0426.html



static String numberToEnglish(long number) {
  return numberToEnglish_EnglishNumberToWords.convert(number);
}

static class numberToEnglish_EnglishNumberToWords {
  private static final String[] tensNames = {
    "",
    " ten",
    " twenty",
    " thirty",
    " forty",
    " fifty",
    " sixty",
    " seventy",
    " eighty",
    " ninety"
  };

  private static final String[] numNames = {
    "",
    " one",
    " two",
    " three",
    " four",
    " five",
    " six",
    " seven",
    " eight",
    " nine",
    " ten",
    " eleven",
    " twelve",
    " thirteen",
    " fourteen",
    " fifteen",
    " sixteen",
    " seventeen",
    " eighteen",
    " nineteen"
  };

  private static String convertLessThanOneThousand(int number) {
    String soFar;

    if (number % 100 < 20){
      soFar = numNames[number % 100];
      number /= 100;
    }
    else {
      soFar = numNames[number % 10];
      number /= 10;

      soFar = tensNames[number % 10] + soFar;
      number /= 10;
    }
    if (number == 0) return soFar;
    return numNames[number] + " hundred" + soFar;
  }


  public static String convert(long number) {
    // 0 to 999 999 999 999
    if (number == 0) { return "zero"; }

    String snumber = Long.toString(number);

    // pad with "0"
    String mask = "000000000000";
    DecimalFormat df = new DecimalFormat(mask);
    snumber = df.format(number);

    // XXXnnnnnnnnn
    int billions = Integer.parseInt(snumber.substring(0,3));
    // nnnXXXnnnnnn
    int millions  = Integer.parseInt(snumber.substring(3,6));
    // nnnnnnXXXnnn
    int hundredThousands = Integer.parseInt(snumber.substring(6,9));
    // nnnnnnnnnXXX
    int thousands = Integer.parseInt(snumber.substring(9,12));

    String tradBillions;
    switch (billions) {
    case 0:
      tradBillions = "";
      break;
    case 1 :
      tradBillions = convertLessThanOneThousand(billions)
      + " billion ";
      break;
    default :
      tradBillions = convertLessThanOneThousand(billions)
      + " billion ";
    }
    String result =  tradBillions;

    String tradMillions;
    switch (millions) {
    case 0:
      tradMillions = "";
      break;
    case 1 :
      tradMillions = convertLessThanOneThousand(millions)
         + " million ";
      break;
    default :
      tradMillions = convertLessThanOneThousand(millions)
         + " million ";
    }
    result =  result + tradMillions;

    String tradHundredThousands;
    switch (hundredThousands) {
    case 0:
      tradHundredThousands = "";
      break;
    case 1 :
      tradHundredThousands = "one thousand ";
      break;
    default :
      tradHundredThousands = convertLessThanOneThousand(hundredThousands)
         + " thousand ";
    }
    result =  result + tradHundredThousands;

    String tradThousand;
    tradThousand = convertLessThanOneThousand(thousands);
    result =  result + tradThousand;

    // remove extra spaces!
    return result.replaceAll("^\\s+", "").replaceAll("\\b\\s{2,}\\b", " ");
  }
}


// Return value is index of semicolon/curly brace+1
static int tok_endOfStatement(List<String> tok, int i) {
  return tok_findEndOfStatement(tok, i);
}


static Object javaEval(String code) {
  return evalJava(code);
}


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);
}




static String actualMCDollar() {
  return actualMC().getName() + "$";
}


static boolean isSyntheticOrAnonymous(Class c) {
  return c != null && (c.isSynthetic() || isAnonymousClassName(c.getName()));
}


// 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;
}


// keeps package names for dynamic code (package dyn.*)
static String shortDynClassNameForStructure(Object o) {
   if (o instanceof DynamicObject && ((DynamicObject) o).className != null)
    return ((DynamicObject) o).className;
  if (o == null) return null;
  Class c = o instanceof Class ? (Class) o : o.getClass();
  String name = c.getName();
  return name.startsWith("dyn.") ? classNameToVM(name) : shortenClassName(name);
}


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 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 boolean isJavaXClassName(String s) {
  return startsWithOneOf(s, "main$", "loadableUtils.");
}


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");
}


// works for both java.util-wrapped maps as well as our own
static <A, B> Map<A, B> unwrapSynchronizedMap(Map<A, B> map) {
  if (eqOneOf(shortClassName(map),
    "SynchronizedMap",
    "SynchronizedSortedMap",
    "SynchronizedNavigableMap"))
    return (Map) get_raw(map, "m");
  return map;
}


static <A, B> Map<A, B> cloneMap(Map<A, B> map) {
  if (map == null) return new HashMap();
  // assume mutex is equal to map
  synchronized(map) {
    return map instanceof TreeMap ? new TreeMap((TreeMap) map) // copies comparator
      : map instanceof LinkedHashMap ? new LinkedHashMap(map)
      : new HashMap(map);
  }
}

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


  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 Pair<Class, Integer> arrayTypeAndDimensions(Object o) {
  return arrayTypeAndDimensions(_getClass(o));
}

static Pair<Class, Integer> arrayTypeAndDimensions(Class c) {
  if (c == null || !c.isArray()) return null;
  Class elem = c.getComponentType();
  if (elem.isArray())
    return mapPairB(arrayTypeAndDimensions(elem), dim -> dim+1);
  return pair(elem, 1);
}


static Iterator emptyIterator() {
  return Collections.emptyIterator();
}


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 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 <A, B> Map<A, B> putAll(Map<A, B> a, Object... b) {
  if (a != null)
    litmap_impl(a, b);
  return a;
}



// set needs to be a ciSet
static Set<String> startingWithIC_navigableSubSet(String prefix, NavigableSet<String> set) {
  if (empty(prefix) || empty(set)) return set;
  return set.subSet(prefix, true, lexicographicallyNextString(prefix), false);
}


static String dropPrefixIC(String prefix, String s) {
  return s == null ? null : swic(s, prefix) ? s.substring(l(prefix)) : s;
}


static <A> List<A> replace(List<A> l, A a, A b) {
  for (int i = 0; i < l(l); i++)
    if (eq(l.get(i), a))
      l.set(i, b);
  return l;
}

static <A> List<A> replace(A a, A b, List<A> l) {
  return replace(l, a, b);
}

// replace all occurrences of a in s with b
static String replace(String s, String a, String b) {
  return s == null ? null : a == null || b == null ? s : s.replace(a, b);
}

static String replace(String s, char a, char b) {
  return s == null ? null : s.replace(a, b);
}


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;
  }




// custom mainClass only works with hotwire_here
static Class<?> hotwire(String src) { return hotwire(src, __1 -> mainClassNameForClassLoader(__1)); }
static Class<?> hotwire(String src, IF1<ClassLoader, String> calculateMainClass) {
  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 String dropSuffix(String suffix, String s) {
  return nempty(suffix) && endsWith(s, suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}


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);
  if (eqOneOf(lastToken, ";", null)) return false;
  if (eq(lastToken, "}")) {
    int i = findBeginningOfBlock(tok, l(tok)-2);
    return eqGet(tok, i-4, "-") && eqGet(tok, i-3, "") && eqGet(tok, i-2, ">"); // it's a lambda definition
  }
  return true;
}

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;
}


static <A> A[] dropLast(A[] a) { return dropLast(a, 1); }
static <A> A[] dropLast(A[] a, int n) {
  if (a == null) return null;
  n = Math.min(n, a.length);
  A[] b = arrayOfSameType(a, 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);
}



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 boolean isIdentifier_quick(String s) {
  return startsWithJavaIdentifierStart(s);
}


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;
}


// Note: does not clone the set (keeps multiset alive)
static <A> Set<A> asSet(MultiSet<A> ms) {
  return ms == null ? null : ms.asSet();
}



static Set similarEmptySet(Iterable m) {
  if (m instanceof TreeSet) return new TreeSet(((TreeSet) m).comparator());
  if (m instanceof LinkedHashSet) return new LinkedHashSet();
  
  return new HashSet();
}

static Set similarEmptySet(Map m) {
  if (m instanceof TreeMap) return new TreeSet(((TreeMap) m).comparator());
  if (m instanceof LinkedHashMap) return new LinkedHashSet();
  
  return new HashSet();
}


static String n2(long l) { return formatWithThousands(l); }
static String n2(Collection l) { return n2(l(l)); }
static String n2(Map map) { return n2(l(map)); }

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(long[] a, String singular) { return n2(l(a), 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);
  }



// 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) {
  int n = s.length();
  for (int i = start; i < n; i++) {
    char c = s.charAt(i);
    if (c == '\r' || c == '\n')
      return i;
  }
  return -1;
}


static List<String> mapToLines(Map map) {
  List<String> l = new ArrayList();
  for (Object key : keys(map))
    l.add(str(key) + " = " + str(map.get(key)));
  return l;
}

static String mapToLines(Map map, Object f) {
  return lines(map(map, f));
}

static String mapToLines(Object f, Map map) {
  return lines(map(map, f));
}

static String mapToLines(Object f, Iterable l) {
  return lines(map(f, l));
}

static <A> String mapToLines(Iterable<A> l, IF1<A, String> f) {
  return mapToLines((Object) f, l);
}

static <A> String mapToLines(IF1<A, String> f, Iterable<A> l) {
  return mapToLines((Object) f, l);
}

static <A, B> String mapToLines(Map<A, B> map, IF2<A, B, String> f) {
  return lines(map(map, f));
}

static <A> String mapToLines(IF1<A, String> f, A data1, A... moreData) {
  return lines(map(f, data1, moreData));
}


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);
}


// 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)

// If you use NoIllegalAccesses: Make sure not to use subList(...)
// instead of .subList(...) in case you ever call magicIndexOfSubList
// on the result.

static <A> int magicIndexOfSubList(List<A> list, List<A> sublist) {
  if (sublist == list) return 0;
  
  
    if (sublist instanceof ISubList) {
      List root = ((ISubList) sublist).rootList();
      int o2 = ((ISubList) sublist).subListOffset();
      if (list instanceof ISubList) {
        if (((ISubList) list).rootList() != root) return -1;
        int o1 = ((ISubList) list).subListOffset();
        return o2-o1;
      } else {
        if (list != root) return -1;
        return o2;
      }
    }
  
  
  
  
  return -1;
}


static <A> List<A> reversedList(Iterable<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 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 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(String url, Map urlParameters) {
  return doPost(urlParameters, url);
}

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(subList(tok, i+1, tok.size()));
}


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 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(__59 -> javaDropComments(__59), 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 void sortIntRangesInPlace(List<IntRange> l) {
  sortInPlaceByCalculatedField(l, r -> r.start);
}


static TokCondition toTokCondition(ITokCondition condition) {
  return condition == null ? null : new TokCondition() {
    public boolean get(List<String> tok, int i) {
      return condition.get(tok, i);
    }
  };
}


static <A> Iterator<A> iterator(Iterable<A> c) {
  return c == null ? emptyIterator() : c.iterator();
}


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(Collection l) {
  Iterator it = iterator(l);
  if (!it.hasNext()) return true;
  Object a = it.next();
  while (it.hasNext()) {
    Object b = it.next(); 
    if (cmp(a, b) > 0)
      return false;
    a = b;
  }
  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 <A> int smartIndexOfAny(List<A> l, int i, Collection<A> x) {
  int n = l(l);
  while (i < n)
    if (contains(x, l.get(i))) return i; else ++i;
  return n;
}



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 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 <A> List<A> asVirtualList(A[] a) {
  return wrapArrayAsList(a);
}


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 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");
  }
} catch (Exception __e) { throw rethrow(__e); } }


static void lock(Lock lock) { try {
  ping();
  if (lock == null) return;
  try {
    vmBus_send("locking", lock, "thread" , currentThread());
    lock.lockInterruptibly();
    vmBus_send("locked", lock, "thread" , currentThread());
  } catch (InterruptedException e) {
    Object reason = vm_threadInterruptionReasonsMap().get(currentThread());
    print("Locking interrupted! Reason: " + strOr(reason, "Unknown"));
    printStackTrace(e);
    rethrow(e);
  }
  // NO call to ping here! Make sure lock is always released.
} 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;
  lock.unlock();
  vmBus_send("unlocked", lock, "thread" , currentThread());
  print("Unlocked: " + msg); // print afterwards to make sure the lock is always unlocked
}

static void unlock(Lock lock) {
  if (lock == null) return;
  lock.unlock();
  vmBus_send("unlocked", lock, "thread" , currentThread());
}


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) {
  if (empty(replacements)) return s;
  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> List<A> replaceInClonedList(List<A> l, A a, A b) {
  return replace(cloneList(l), a, b);
}

static <A> List<A> replaceInClonedList(A a, A b, List<A> l) {
  return replaceInClonedList(l, a, b);
}


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 int[] takeFirstOfIntArray(int[] b, int n) {
  return subIntArray(b, 0, n);
}

static int[] takeFirstOfIntArray(int n, int[] b) {
  return takeFirstOfIntArray(b, n);
}




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 <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 Object optParam(String name, Object[] params) {
  return optParam(params, name);
}


static boolean endsWithLetterOrDigit(String s) {
  return s != null && s.length() > 0 && Character.isLetterOrDigit(s.charAt(s.length()-1));
}


// 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 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|cookie)=[^&\\s\"]*", "$1$2=<hidden>");
}

static String hideCredentials(Object o) {
  return hideCredentials(str(o));
}


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 Object vmBus_wrapArgs(Object... args) {
  return empty(args) ? null
    : l(args) == 1 ? args[0]
    : args;
}


static void pcallFAll_minimalExceptionHandling(Collection l, Object... args) {
  if (l != null) for  (Object f : cloneList(l)) { ping(); pcallF_minimalExceptionHandling(f, args); }
}

static void pcallFAll_minimalExceptionHandling(Iterator it, Object... args) {
  while  (it.hasNext()) { ping(); pcallF_minimalExceptionHandling(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 Map synchroMap() {
  return synchroHashMap();
}

static <A, B> Map<A, B> synchroMap(Map<A, B> map) {
  
  
    return Collections.synchronizedMap(map);
  
}


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)
      { f.set(o, ((Long) value).intValue()); return; }
      
    if (type == boolean.class && value instanceof String)
      { f.set(o, isTrueOrYes(((String) value))); return; }
    
    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) {
  setDynObjectValue(o, key, value);
  return o;
}

static void setDyn(IMeta o, String key, Object value) {
  metaMapPut(o, key, value);
}


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 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 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 empty(s) ? 0.0 : Double.parseDouble(s);
}


static float parseFloat(String s) {
  return Float.parseFloat(s);
}


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 <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 <A, B> NavigableMap<A, B> synchroNavigableMap(NavigableMap<A, B> map) {
  return (NavigableMap) call(Collections.class, "synchronizedNavigableMap", map);
}


static <A, B> SortedMap<A, B> synchroSortedMap(SortedMap<A, B> map) {
  
  
    return Collections.synchronizedSortedMap(map);
  
}


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 boolean isAbstract(Class c) {
  return (c.getModifiers() & Modifier.ABSTRACT) != 0;
}

static boolean isAbstract(Method m) {
  return (m.getModifiers() & Modifier.ABSTRACT) != 0;
}


static <A> Constructor nuStubInnerObject_findConstructor(Class<A> c) { return nuStubInnerObject_findConstructor(c, null); }
static <A> Constructor nuStubInnerObject_findConstructor(Class<A> c, Object classFinder) { try {
  Class outerType = getOuterClass(c, classFinder);
  Constructor m = c.getDeclaredConstructor(outerType);
  makeAccessible(m);
  return m;
} 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 void setOptAllDyn_pcall(DynamicObject o, Map<String, Object> fields) {
  if (fields == null || o == null) return;
  HashMap<String, Field> fieldMap = instanceFieldsMap(o);
  for (Map.Entry<String, Object> e : fields.entrySet()) { try {
    String field = e.getKey();
    Object val = e.getValue();
    Field f = fieldMap.get(field);
    if (f != null)
      smartSet(f, o, val);
    else {
      dynamicObject_setRawFieldValue(o, intern(field), val);
      
    }
  } catch (Throwable __e) { _handleException(__e); }}
}


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) {
  dynamicObject_setRawFieldValue(o, 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 RuntimeException todo() {
    throw new RuntimeException("TODO");
  }
  
  static RuntimeException todo(Object msg) {
    throw new RuntimeException("TODO: " + msg);
  }


static Object newMultiDimensionalOuterArray(Class elementType, int dimensions, int length) {
  int[] dims = new int[dimensions];
  dims[0] = length;
  return Array.newInstance(elementType, dims);
}


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 double[] toDoubleArray(Collection<Double> l) {
  double[] a = new double[l(l)];
  int i = 0;
  if (a.length != 0) for (double x : l)
    a[i++] = x;
  return a;
}




// DIFFERENCES to jfind: always ignores case, doesn't recognize <id> etc
// You probably want jmatch2

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 ThreadLocal<Boolean> DynamicObject_loading = or((ThreadLocal) get(getClass("x30_pkg.x30_util"), "DynamicObject_loading"), new ThreadLocal());

static ThreadLocal<Boolean> dynamicObjectIsLoading_threadLocal() { 
  return DynamicObject_loading;
}


static String getStackTrace2(Throwable e) {
  return hideCredentials(getStackTrace(unwrapTrivialExceptionWraps(e)) + replacePrefix("java.lang.RuntimeException: ", "FAIL: ",
    hideCredentials(str(innerException2(e)))) + "\n");
}


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 void clear(Map map) {
  if (map != null) map.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 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 classLoaderForObject(realm).loadClass(classNameToVM(name));
  } catch (ClassNotFoundException e) {
    return null; // could optimize this
  }
}


static <A, B> B syncMapGet2(Map<A, B> map, A a) {
  if (map == null) return null;
  synchronized(collectionMutex(map)) {
    return map.get(a);
  }
}

static <A, B> B syncMapGet2(A a, Map<A, B> map) {
  return syncMapGet2(map, 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 Throwable getExceptionCause(Throwable e) {
  Throwable c = e.getCause();
  return c != null ? c : e;
}


static List<String> classNames(Collection l) {
  return getClassNames(l);
}

static List<String> classNames(Object[] l) {
  return getClassNames(Arrays.asList(l));
}


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;
  ClassLoader cl = (o instanceof Class ? (Class) o : o.getClass()).getClassLoader();
  if (cl == null) return null;
  String name = mainClassNameForClassLoader(cl);
  return loadClassFromClassLoader_orNull(cl, name);
} 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 WeakHasherMap<Symbol, Boolean> symbol_map = new WeakHasherMap(new Hasher<Symbol>() {
  public int hashCode(Symbol symbol) { return symbol.text.hashCode(); }
  public boolean equals(Symbol a, Symbol b) {
    if (a == null) return b == null;
    return b != null && eq(a.text, b.text);
  }
});



static Symbol symbol(String s) {
  
  
  if (s == null) return null;
  synchronized(symbol_map) {
    // TODO: avoid object creation by passing the string to findKey
    Symbol symbol = new Symbol(s, true);
    Symbol existingSymbol = symbol_map.findKey(symbol);
    if (existingSymbol == null)
      symbol_map.put(existingSymbol = symbol, true);
    
      
    return existingSymbol;
  }
  
}

static Symbol symbol(CharSequence s) {
  if (s == null) return null;
  
  
  if (s instanceof Symbol) return (Symbol) s;
  if (s instanceof String) return symbol((String) s);
  return symbol(str(s));
  
}

static Symbol symbol(Object o) {
  return symbol((CharSequence) o);
}


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 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 x30_pkg.x30_util.BetterThreadLocal<Runnable> newPing_actionTL;

static x30_pkg.x30_util.BetterThreadLocal<Runnable> newPing_actionTL() {
  if (newPing_actionTL == null)
    newPing_actionTL = vm_generalMap_getOrCreate("newPing_actionTL",
      () -> new x30_pkg.x30_util.BetterThreadLocal());
  return newPing_actionTL;
}



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 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 <A, B> A pairA(Pair<A, B> p) {
  return p == null ? null : p.a;
}


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


static Pair pairMapB(Object f, Pair p) {
  return p == null ? null : pair(p.a, callF(f, p.b));
}

static <A, B, C> Pair<A, C> pairMapB(IF1<B, C> f, Pair<A, B> p) {
  return p == null ? null : pair(p.a, f.get(p.b));
}

static Pair pairMapB(Pair p, Object f) {
  return pairMap(f, p);
}



// Use like this: renderVars(+x, +y)
static String renderVars_str(Object... params) {
  List<String> l = new ArrayList();
  int i = 0;
  if (odd(l(params))) {
    l.add(strOrNull(first(params)));
    ++i;
  }
  for (; i+1 < l(params); i += 2)
    l.add(params[i] + "=" + params[i+1]);
  return trim(joinWithComma(l));
}


static void addToContainer(Container a, Component... b) {
  if (a == null) return;
  { swing(new Runnable() {  public void run() { try { 
    for (Component c : unnullForIteration(b))
      if (c != null) 
        a.add(c);
  
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "for (Component c : unnullForIteration(b))\r\n      if (c != null) \r\n        a.a..."; }}); }
}


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 String trimSubstring(int x, String s) {
  return trimSubstring(s, x);
}


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> int smartLastIndexOf(List<A> l, A sub) {
  int i = lastIndexOf(l, sub);
  return i < 0 ? l(l) : i;
}


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 proxy_InvocationHandler(target));
}

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 boolean networkAllowanceTest(String url) {
  
  
  return isAllowed("networkAllowanceTest", url);
  
}


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 <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_0 = 0; _repeat_0 < 2; _repeat_0++)  {
    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);
  if (o instanceof Boolean)
    return boolToInt((Boolean) 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; // TODO: optimize
  { saveTextFile(f, contents); return true; }
}


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) ? unnull(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 <A> ArrayList<Integer> intArrayToList(int[] a) {
  if (a == null) return null;
  return intArrayToList(a, 0, a.length);
}

// no range checking on from/to in the interest of S P E E E E E EEED
static <A> ArrayList<Integer> intArrayToList(int[] a, int from, int to) {
  if (a == null) return null;
  ArrayList < Integer > l = new ArrayList<>(to-from);
  for (int i = from; i < to; i++) l.add(a[i]);
  return l;
}


static boolean nemptyString(String s) {
  return s != null && s.length() > 0;
}


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


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);
}


// 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 int indexOfIgnoreCase(String a, String b, int i) {
  return indexOfIgnoreCase_manual(a, b, i);
}


static String lineRange(String s, int from, int to) {
  return lines(subList(lines(s), from, to)); // optimizable
}


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);
}


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 getProgramDir() {
  return programDir();
}

static File getProgramDir(String snippetID) {
  return programDir(snippetID);
}


static Producer<String> javaTokC_producer(String s) {
  return javaTokC_iterator(s);
}


// 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 int minUnlessMinus1(int a, int b) {
  return a == -1 ? b : b == -1 ? a : min(a, b);
}


static List<String> codeTokens_lazy(final List<String> tok) {
  return new RandomAccessAbstractList<String>() {
    final int size = l(tok)/2;
    
    public int size() { return size; }
    public String get(int i) { return tok.get(i*2+1); }
  };
}


static String replaceCurlyBracedWith(String s, String replacement) {
  return join(replaceCurlyBracedWith(javaTokWithAllBrackets_cached(s), replacement));
}

static List<String> replaceCurlyBracedWith(List<String> tok, String replacement) {
  return mapCodeTokens(t -> isCurlyBraced_simple(t) ? replacement : t, tok);
}


static List<String> tok_combineCurlyBrackets_keep(List<String> tok) {
  List<String> l = new ArrayList();
  for (int i = 0; i < l(tok); i++) {
    String t = tok.get(i);
    if (odd(i) && eq(t, "{")) {
      int j = findEndOfCurlyBracketPart(tok, i);
      l.add(joinSubList(tok, i, j));
      i = j-1;
    } else
      l.add(t);
  }
  return l;
}


static boolean jcontains_any(List<String> tok, String... patterns) {
  return jcontainsOneOf(tok, patterns);
}


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

static <A, B> List<B> lambdaMap(IF1<A, B> f, A[] l) {
  return map(l, f);
}


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


static boolean tok_isPrimitiveType(String t) {
  return eqOneOf(t, "int", "char", "byte", "short", "long", "float", "double", "bool");
}


static int hashCode(Object a) {
  return a == null ? 0 : a.hashCode();
}


static <A, B> IterableIterator<Pair<A, B>> mapPairs(Map<A, B> map) {
  final Iterator<Map.Entry<A, B>> it = map.entrySet().iterator();
  return iteratorFromFunction(new F0<Pair<A, B>>() { public Pair<A, B> get() { try { 
    if (it.hasNext()) {
      Map.Entry<A, B> entry = it.next();
      return pair(entry.getKey(), entry.getValue());
    }
    return null;
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "if (it.hasNext()) {\r\n      Map.Entry<A, B> entry = it.next();\r\n      ret pair..."; }});
}

static <A, B, C> List<C> mapPairs(Iterable<Pair<A, B>> l, IF2<A, B, C> f) {
  return mapPairsToList(l, f);
}


static String joinPrependSpace(Collection l) {
  return join(prependAll(" ", allToString(l)));
}


static String joinNempties(String sep, Object... strings) {
  return joinStrings(sep, strings);
}

static String joinNempties(String sep, Iterable strings) {
  return joinStrings(sep, strings);
}


static <A, B> List<A> firstOfPairs(Collection<Pair<A, B>> l) {
  List<A> out = new ArrayList();
  for (Pair<A, B> p : unnull(l)) out.add(firstOfPair(p));
  return out;
}


static String reversedString(String s) {
  return reverseString(s);
}


static char charAt(String s, int i) {
  return s != null && i >= 0 && i < s.length() ? s.charAt(i) : '\0';
}


static <A> List<A> dropAfter(A a, List<A> l) {
  int n = l(l);
  for (int i = 0; i < n; i++)
    if (eq(a, l.get(i)))
      return takeFirst(l, i);
  return l;
}


// do these type parameters work? or back to O?
static <A, B> boolean doubleEq(A a1, A a2, B b1, B b2) {
  return eq(a1, a2) && eq(b1, b2);
}


static String roundBracketIf(boolean b, String s) {
  return b ? roundBracket(s) : s;
}


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 <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 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 String dollarVarsToStars(String s) {
  return dollarVarsToStars(s, null);
}

static String dollarVarsToStars(String s, List<String> varNames_out) {
  List<String> tok = javaTok(s);
  for (int i = 1; i < l(tok); i += 2) {
    String t = tok.get(i);
    if (isDollarVar(t)) {
      listAdd(varNames_out, t);
      tok.set(i, "*");
    } else if (eq(t, "*"))
      listAdd(varNames_out, "*");
  }
  return join(tok);
}




static <A, B> List<B> mapWithIndexStartingAt1(Collection<A> l, IF2<Integer, A, B> f) {
  int n = l(l), i = 0;
  List<B> out = emptyList(n);
  for (A a : unnullForIteration(l))
    out.add(f.get(++i, a));
  return out;
}


static boolean containsDollarVars(String s) {
  for (String t : javaTokC(s))
    if (isDollarVar(t)) return true;
  return false;
}


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 List similarEmptyList(Collection m) {
  return new ArrayList();
}


// 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 <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 String replaceSuffix(String a, String b, String s) {
  return endsWith(s, a) ? dropLast(s, l(a)) + b : s;
}


static Producer<String> javaTokC_iterator(String s) {
  return new Producer<String>() {
    final int l = strL(s);
    int i = 0;
    
    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)) || "'".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;
        
      String t = quickSubstring(s, i, j);
      i = j;
      return t;
    }
  };
}



static boolean containsToken(List<String> tok, String token) {
  return tok.contains(token);
}


static <A> boolean subListEq(List<A> l, List<A> pat, int i) {
  return subListEquals(l, pat, i);
}



static <A> boolean subListEq(List<A> l, int i, A... pat) {
  return subListEquals(l, i, pat);
}


static String stringIfTrue(boolean b, String s) {
  return b ? s : "";
}


static Map mapKeys(Object func, Map map) {
  Map m = similarEmptyMap(map); // TODO: this might break when key type changes through func
  for (Object key : keys(map))
    m.put(callF(func, key), map.get(key));
  return m;
}

static Map mapKeys(Map map, Object func) {
  return mapKeys(func, map);
}

static <A, B, C> Map<B, C> mapKeys(Map<A, C> map, IF1<A, B> func) {
  return mapKeys(map, (Object) func);
}

static <A, B, C> Map<B, C> mapKeys(IF1<A, B> func, Map<A, C> map) {
  return mapKeys(map, func);
}


static String dropDollarPrefix(String s) {
  return dropPrefix("$", s);
}


static TreeMap litcimap(Object... x) {
  return litCIMap(x);
}


// f takes variable without $ sign. if it returns null, variable is kept
static String replaceDollarVars_dyn(String s, IF1<String, String> f) {
  if (f == null) return s;
  return regexpReplaceIC(s, "\\$(\\w+)", matcher -> {
    String var = matcher.group(1);
    String val = f.get(var);
    return val == null ? matcher.group() : str(val);
  });
}


static String strOrNull(Object o) {
  return o == null ? null : str(o);
}


static boolean startsWithIgnoreCase(String a, String b) {
  return regionMatchesIC(a, 0, b, 0, b.length());
}


static boolean isDigit(char c) {
  return Character.isDigit(c);
}


static String toLower(String s) {
  return s == null ? null : s.toLowerCase();
}

static char toLower(char c) {
  return Character.toLowerCase(c);
}


static List<String> words(String s) {
  return codeTokens(dropPunctuation(javaTok(s)));
}


static Object evalJava(String code) {
  return evalJava_main(evalJava_prep(code));
}


static Class actualMC() {
  return or((Class) realMC(), mc());
}


static boolean isAnonymousClassName(String s) {
  for (int i = 0; i < l(s); i++)
    if (s.charAt(i) == '$' && Character.isDigit(s.charAt(i+1)))
      return true;
  return false;
}


// Note: This is actually broken. Inner classes must stay with a $ separator
static String classNameToVM(String name) {
  return name.replace(".", "$");
}


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 startsWithOneOf(String s, String... l) {
  for (String x : l) if (startsWith(s, x)) return true; return false;
}

static boolean startsWithOneOf(String s, Matches m, String... l) {
  for (String x : l) if (startsWith(s, x, m)) return true; return false;
}


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 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 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(int a, int b) {
  return a < b ? -1 : a == b ? 0 : 1;
}

static int cmp(long a, long 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);
}


// credits to stackoverflow.com/users/1442960/marcus
static String lexicographicallyNextString(String input) {
  final int lastCharPosition = input.length()-1;
  String inputWithoutLastChar = input.substring(0, lastCharPosition);
  char lastChar = input.charAt(lastCharPosition);
  char incrementedLastChar = (char) (lastChar + 1);
  // Handle int/char overflow.  This wasn't done above.
  if (incrementedLastChar == ((char) 0)) return input+incrementedLastChar;
  return inputWithoutLastChar+incrementedLastChar;
}


static char lastChar(String s) {
  return empty(s) ? '\0' : s.charAt(l(s)-1);
}


static String mainClassNameForClassLoader(ClassLoader cl) {
  return or((String) callOpt(cl, "mainClassName"), "main");
}


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 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);
}


// i must point at the (possibly imaginary) closing bracket
// index returned is index of opening bracket
static int findBeginningOfBlock(List<String> cnc, int i) {
  int j = i-2, level = 1;
  while (j > 0) {
    if (eq(cnc.get(j), "}")) ++level;
    else if (eq(cnc.get(j), "{")) --level;
    if (level == 0)
      return j;
    j -= 2;
  }
  return -1;
}


static <A> A[] arrayOfSameType(A[] a, int n) {
  return newObjectArrayOfSameType(a, n);
}


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 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 CloseableIterableIterator<String> linesFromFile(File f) { return linesFromFile(f, null); }
static CloseableIterableIterator<String> linesFromFile(File f, IResourceHolder resourceHolder) { try {
  if (!f.exists()) return emptyCloseableIterableIterator();
  
  if (ewic(f.getName(), ".gz"))
    return linesFromReader(utf8bufferedReader(newGZIPInputStream(f)), resourceHolder);
  
  return linesFromReader(utf8bufferedReader(f), resourceHolder);
} catch (Exception __e) { throw rethrow(__e); } }

static CloseableIterableIterator<String> linesFromFile(String path) { return linesFromFile(path, null); }
static CloseableIterableIterator<String> linesFromFile(String path, IResourceHolder resourceHolder) {
  return linesFromFile(newFile(path), resourceHolder);
}


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 byte[] toUtf8(String s) { try {
  return s.getBytes(utf8charset());
} 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(java.util.regex.Pattern pat, String s) {
  return pat.matcher(unnull(s));
}

static java.util.regex.Pattern regexp(String pat) {
  return compileRegexp(pat);
}




static Object jsonDecode(String text) {
  return new jsonDecode_Y(text).parse();
}

// the miraculous class Y
static class jsonDecode_Y {
  String text;
  List<String> tok;
  boolean useOrderedMaps = false;
  int i = 1;

  jsonDecode_Y(String text) {
  this.text = text;
    tok = jsonTok(text);
  }
  
  transient  IF1<String, RuntimeException> fail;
RuntimeException fail(String msg) { return fail != null ? fail.get(msg) : fail_base(msg); }
final RuntimeException fail_fallback(IF1<String, RuntimeException> _f, String msg) { return _f != null ? _f.get(msg) : fail_base(msg); }
RuntimeException fail_base(String msg) { return main.fail(msg); }
  
  Object parse() {
    if (l(tok) == 1) return null;
    return parseExpr();
  }
  
  Object parseExpr() {
    String t = tok.get(i);
    if (t.startsWith("\"") || 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)) {
      int j = i;
      i += 2;
      if (eqOneOf(get(tok, i), ".", "e", "E")) {
        // rough parsing for doubles
        while (isInteger(get(tok, i))
          || eqOneOf(get(tok, i), ".", "e", "E", "-"))
          i += 2;
        double d = parseDouble(joinSubList(tok, j, i-1));
        if (minus) d = -d;
        return d;
      } else {
        long l = parseLong(t);
        if (minus) l = -l;
        return l != (int) l ? (Object) new Long(l) : new Integer((int) l);
      }
    }
    
    throw fail("Unknown token " + (i+1) + ": " + t + ": " + text);
  }
  
  Object parseList() {
    consume("[");
    List list = new ArrayList();
    while (!tok.get(i).equals("]")) {
      list.add(parseExpr());
      if (tok.get(i).equals(",")) i += 2;
    }
    consume("]");
    return list;
  }
  
  Object parseMap() {
    consume("{");
    Map map = useOrderedMaps ? new LinkedHashMap() : new TreeMap();
    while (!tok.get(i).equals("}")) {
      String key = unquote(tok.get(i));
      i += 2;
      consume(":");
      Object value = parseExpr();
      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;
  }
}


static String makePostData(Map map) {
  StringBuilder buf = new StringBuilder();
  for (Map.Entry<Object, Object> e : castMapToMapO(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 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 __1 = tempSetTL(loadPage_charset, "UTF-8"); try {
  return loadPage(url);
} finally { _close(__1); }}

static String loadPage_utf8(URLConnection con, URL url, boolean addHeaders) throws IOException {
   AutoCloseable __2 = tempSetTL(loadPage_charset, "UTF-8"); try {
  return loadPage(con, url, addHeaders);
} finally { _close(__2); }}


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);
}


// f: A -> Comparable
static <A> List<A> sortInPlaceByCalculatedField(List<A> l, final F1<A, ?> f) {
  sort(l, new Comparator<A>() {
    public int compare(A a, A b) {
      return stdcompare(f.get(a), f.get(b));
    }
  });
  return l;
}

static <A> List<A> sortInPlaceByCalculatedField(List<A> l, final IF1<A, ?> f) {
  sort(l, new Comparator<A>() {
    public int compare(A a, A b) {
      return stdcompare(f.get(a), f.get(b));
    }
  });
  return l;
}


// 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 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 = litciset("time", "machine", "line", "rule");

static String singular(String s) {
  if (s == null) return null;
  { String __1 = singular_specials.get(s); if (!empty(__1)) return __1; }
  //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 (ewicOneOf(s, "ss", "ch")) return s + "es";
  if (ewic(s, "s")) return s;
  return s + "s";
}


static Map<String, String> stdFunctions_cached_map; // name -> snippet ID
static Lock stdFunctions_cached_lock = lock();

static Map<String, String> stdFunctions_cached() {
  Lock __0 = stdFunctions_cached_lock; lock(__0); try {
  if (stdFunctions_cached_map == null)
    stdFunctions_cached_map = stdFunctions_uncached();
  return stdFunctions_cached_map;
} finally { unlock(__0); } }

static synchronized void stdFunctions_clearCache() {
  stdFunctions_cached_map = null;
}


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); } }

// no longer synchronizes! (see #1102990)
static _MethodCache callOpt_getCache(Class c) {
  _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 Map<Thread, Object> vm_threadInterruptionReasonsMap() {
  return vm_generalWeakSubMap("Thread interruption reasons");
}


static String strOr(Object o, String ifNull) {
  return o == null ? ifNull : str(o);
}


static void lockOrFail(Lock lock, long timeout) { try {
  ping();
  vmBus_send("locking", lock, "thread" , currentThread());
  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);
  }
  vmBus_send("locked", lock, "thread" , currentThread());
  ping();
} catch (Exception __e) { throw rethrow(__e); } }


static ReentrantLock fairLock() {
  return new ReentrantLock(true);
}


static List<Class> dependentClasses() {
  return cleanUpAndGetWeakReferencesList(hotwire_classes);
}


static int[] subIntArray(int[] b, int start) {
  return subIntArray(b, start, l(b));
}
  
static int[] subIntArray(int[] 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 int[0];
  int[] x = new int[end-start];
  System.arraycopy(b, start, x, 0, end-start);
  return x;
}


static int[] subIntArray(int[] a, IntRange r) {
  return r == null ? null : subIntArray(a, r.start, r.end);
}





static List<WeakReference<Class>> hotwire_classes = synchroList();

static Class<?> hotwireDependent(String src) {
  Class c = hotwire(src);
  makeDependent(c);
  return c;
}



static boolean even(int i) {
  return (i & 1) == 0;
}

static boolean even(long i) {
  return (i & 1) == 0;
}

static boolean even(BigInteger n) {
  return even(n.intValue());
}


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 Object pcallF_minimalExceptionHandling(Object f, Object... args) {
  try {
    return callFunction(f, args);
  } catch (Throwable e) {
    System.out.println(getStackTrace(e));
    _storeException(e);
  }
  return null;
}


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 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 boolean isTrueOrYes(Object o) {
  return isTrueOpt(o) || o instanceof String && (eqicOneOf(((String) o), "1", "t", "true") || isYes(((String) o)));
}


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 void metaMapPut(IMeta o, Object key, Object value) {
  if (o == null || key == null) return;
  Map map = convertObjectMetaToMap(o);
  syncMapPutOrRemove(map, key, value);
}

// untested...
/*svoid metaMapPut(IMeta o, O key, O value, IMapImplementationPolicy policy) {
  if (o == null || key == null) ret;
  if (policy == null) ret with metaMapPut(o, key, value);
  
  // no shortcuts in this function, always get mutex & lock
  temp var mutex = tempMetaMutex(o);
  synchronized(mutex!) {
    O meta = o._getMeta();
    
    // create map if necessary
    Map map = null;
    if (meta cast Map)
      map = meta;
    else if (meta != null)
      map = policy.put(map, previousMeta := meta);
      
    // putOrRemove through policy
    if (value != null)
      map = policy.put(map, key, value);
    else
      map = policy.remove(map, key);
      
    if (map != meta) o._setMeta(map);
  }
}*/


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>() {
    public Class get(String name) {
      Class c = get2(name);
      
      return c;
    }
      
    Class get2(String name) {
      // special invocation to find main class irrelevant of name
      if (eq(name, "<main>")) return mc();
      
      { Class c = findClass_fullName(name); if (c != null) return c; }
      
      if (startsWithAny(name, "loadableUtils.utils$", "main$", mcDollar()))
        for (String pkg : ll("loadableUtils.utils$", mcDollar())) {
          String newName = pkg + afterDollar(name);
          
          { Class c = findClass_fullName(newName); if (c != null) return c; }
        }
      return null;
    }
  };
}


static <A> TreeMap<String, A> caseInsensitiveMap() {
  return new TreeMap(caseInsensitiveComparator());
}


static Object safeUnstructure(String s) {
  return unstructure(s, true);
}

static Object safeUnstructure(File f) {
  return safeUnstructureGZFile(f);
}


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);
  return classForName(name, classFinder);
} catch (Exception __e) { throw rethrow(__e); } }


static HashMap<String, Field> instanceFieldsMap(Object o) {
  return (HashMap) getOpt_getFieldMap(o);
}



static void dynamicObject_setRawFieldValue(DynamicObject o, Object key, Object value) {
  if (o == null) return;
  
  // double sync, but should be OK here because of locking order o > o.fieldValues
  synchronized(o) {
    o.fieldValues = syncMapPut2_createLinkedHashMap((LinkedHashMap) o.fieldValues, key, value);
  }
}


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().startsWith("this$"))
        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. TODO: should remove
      return null;
  }
  return result.toArray(new String[result.size()]);
}



static Map<String, Class> classForName_cache = synchroHashMap();

static Class classForName(String name) { return classForName(name, null); }
static Class classForName(String name, Object classFinder) {
  // first clause is when we're in class init
  if (classForName_cache == null || classFinder != null)
    return classForName_uncached(name, classFinder);
  Class c = classForName_cache.get(name);
  if (c == null)
    classForName_cache.put(name, c = classForName_uncached(name, null));
  return c;
}

static Class classForName_uncached(String name, Object classFinder) { try {
  if (classFinder != null) return (Class) callF(classFinder, name);
  return Class.forName(name);
} 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 List<Class> getClasses(Object[] array) {
  List<Class> l = emptyList(l(array));
  for (Object o : array) l.add(_getClass(o));
  return l;
}


static Class<?> getClass(String name) {
  return _getClass(name);
}

static Class getClass(Object o) {
  return _getClass(o);
}

static Class getClass(Object realm, String name) {
  return _getClass(realm, name);
}


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 Throwable getException(Runnable r) {
  try {
    callF(r);
    return null;
  } catch (Throwable e) {
    return e;
  }
}


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 <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 ClassLoader classLoaderForObject(Object o) {
  if (o instanceof ClassLoader) return ((ClassLoader) o);
  if (o == null) return null;
  return _getClass(o).getClassLoader();
}


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 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 Class loadClassFromClassLoader_orNull(ClassLoader cl, String name) {
  try {
    return cl == null ? null : cl.loadClass(name);
  } catch (ClassNotFoundException e) {
    return null;
  }
}


static List collect(Iterable c, String field) {
  return collectField(c, field);
}

static List collect(String field, Iterable 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 <A extends Throwable> A printException(A e) {
  printStackTrace(e);
  return e;
}


static <A> A vm_generalMap_getOrCreate(Object key, F0<A> create) {
  return vm_generalMap_getOrCreate(key, f0ToIF0(create));
}

static <A> A vm_generalMap_getOrCreate(Object key, IF0<A> create) {
  Map generalMap = vm_generalMap();
  if (generalMap == null) return null; // must be x30 init
  
  synchronized(generalMap) { // should switch to locks here
    A a =  (A) (vm_generalMap_get(key));
    if (a == null)
      vm_generalMap_put(key, a = create == null ? null : create.get());
    return a;
  }
}



static Pair pairMap(Object f, Pair p) {
  return p == null ? null : pair(callF(f, p.a), callF(f, p.b));
}

static <A> Pair<A, A> pairMap(IF1<A, A> f, Pair<A, A> p) {
  return p == null ? null : pair(callF(f, p.a), callF(f, p.b));
}

static Pair pairMap(Pair p, Object f) {
  return pairMap(f, p);
}



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 String unnullForIteration(String s) {
  return s == null ? "" : s;
}

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

static <A> List<A> unnullForIteration(List<A> l) { return l == null ? immutableEmptyList() : l; }
static int[] unnullForIteration(int[] l) { return l == null ? emptyIntArray() : l; }
static char[] unnullForIteration(char[] l) { return l == null ? emptyCharArray() : l; }
static double[] unnullForIteration(double[] l) { return l == null ? emptyDoubleArray() : l; }
static short[] unnullForIteration(short[] l) { return l == null ? emptyShortArray() : l; }

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

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

static <A> A[] unnullForIteration(A[] a) {
  return a == null ? (A[]) emptyObjectArray() : a;
}

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



//ifclass Symbol

static Symbol unnullForIteration(Symbol s) {
  return s == null ? emptySymbol() : s;
}
//endif



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


static long unnullForIteration(Long l) { return l == null ? 0L : l; }


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 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 round(String s) {
  return roundBracket(s);
}






static String hmsWithColons() {
  return hmsWithColons(now());
}

static String hmsWithColons(long time) {
  return new SimpleDateFormat("HH:mm:ss").format(time);
}



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 int boolToInt(boolean b) {
  return b ? 1 : 0;
}


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 int indexOfIgnoreCase_manual(String a, String b) {
  return indexOfIgnoreCase_manual(a, b, 0);
}

static int indexOfIgnoreCase_manual(String a, String b, int i) {
  int la = strL(a), lb = strL(b);
  if (la < lb) return -1;
  int n = la-lb;
  
  loop: for (; 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 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 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 Map<String, List<String>> javaTokWithAllBrackets_cached_cache = synchronizedMRUCache(defaultTokenizerCacheSize());

static List<String> javaTokWithAllBrackets_cached(String s) {
  List<String> tok = javaTokWithAllBrackets_cached_cache.get(s);
  if (tok == null) javaTokWithAllBrackets_cached_cache.put(s, tok = javaTokWithAllBrackets(s));
  return tok;
}




static List mapCodeTokens(Object f, List l) {
  List out = emptyList(l);
  for (int i = 0; i < l(l); i++) {
    Object o = l.get(i);
    out.add(odd(i) ? callF(f, o) : o);
  }
  return out;
}

static List mapCodeTokens(List l, Object f) {
  return mapCodeTokens(f, l);
}

static List<String> mapCodeTokens(List<String> tok, IF1<String, String> f) {
  return mapCodeTokens(tok, (Object) f);
}

static List<String> mapCodeTokens(IF1<String, String> f, List<String> tok) {
  return mapCodeTokens(tok, f);
}


static boolean isCurlyBraced_simple(String s) {
  return startsWith(s, "{") && endsWith(s, "}");
}


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



static boolean jcontainsOneOf(List<String> tok, String... patterns) {
  return jfindOneOf(tok, patterns) >= 0;
}


// f: func -> A (stream ends when f returns null)
static <A> IterableIterator<A> iteratorFromFunction(final Object f) {
  class IFF extends IterableIterator<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 = (A) callF(f);
      done = a == null;
    }
  };
  return new IFF();
}

// optimized version for F0 argument

static <A> IterableIterator<A> iteratorFromFunction(F0<A> f) {
  return iteratorFromFunction_f0(f);
}


static <A> IterableIterator<A> iteratorFromFunction(IF0<A> f) {
  return iteratorFromFunction_if0(f);
}


static <A, B, C> List<C> mapPairsToList(Iterable<Pair<A, B>> l, F2<A, B, C> f) {
  List<C> x = emptyList(l);
  if (l != null) for (Pair<A, B> p : l)
    x.add(callF(f, p.a, p.b));
  return x;
}

static <A, B, C> List<C> mapPairsToList(Iterable<Pair<A, B>> l, IF2<A, B, C> f) {
  List<C> x = emptyList(l);
  if (l != null) for (Pair<A, B> p : l)
    x.add(f.get(p.a, p.b));
  return x;
}

static <A, B> List mapPairsToList(Object f, Iterable<Pair<A, B>> l) {
  List x = emptyList(l);
  if (l != null) for (Pair<A, B> p : l)
    x.add(callF(f, p.a, p.b));
  return x;
}


static List<String> prependAll(final String s, Collection<String> l) {
  return map(new F1<String, String>() { public String get(String x) { try {  return s + x;  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "s + x"; }}, l);
}


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 String joinStrings(String sep, Object... strings) {
  return joinStrings(sep, Arrays.asList(strings));
}

static String joinStrings(String sep, Iterable strings) {
  StringBuilder buf = new StringBuilder();
  for (Object o : unnull(strings)) { 
    String s = strOrNull(o);
    if (nempty(s)) {
      if (nempty(buf)) buf.append(sep);
      buf.append(s);
    }
  }
  return str(buf);
}


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


static String reverseString(String s) {
  return empty(s) ? s : new StringBuilder(s).reverse().toString();
}


static String upper(String s) {
  return s == null ? null : s.toUpperCase();
}

static char upper(char c) {
  return Character.toUpperCase(c);
}


static boolean isDollarVar(String s) {
  // Possible BREAKING CHANGE (although probably OK): now also accepting $1 etc.
  return startsWith(s, '$') && l(s) > 1;
  // OLD: ret startsWith(s, '$') && isJavaIdentifierAfter(s, 1);
}






static <A> void listAdd(Collection<A> c, A a) {
  if (c != null) c.add(a);
}


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 Map similarEmptyMap(Iterable m) {
  if (m instanceof TreeSet) return new TreeMap(((TreeSet) m).comparator());
  if (m instanceof LinkedHashSet) return new LinkedHashMap();
  
  return new HashMap();
}


static TreeMap litCIMap(Object... x) {
  TreeMap map = caseInsensitiveMap();
  litmap_impl(map, x);
  return map;
}


// f : Matcher -> S
static String regexpReplaceIC(String s, String pat, Object f) {
  return regexReplaceIC(s, pat, f);
}

static String regexpReplaceIC(String s, String pat, String replacement) {
  return regexReplaceIC(s, pat, replacement);
}

static String regexpReplaceIC(String s, String pat, IF1<Matcher, String> f) {
  return regexReplaceIC(s, pat, f);
}


static boolean regionMatchesIC(String a, int offsetA, String b, int offsetB, int len) {
  
  
    return a != null && a.regionMatches(true, offsetA, b, offsetB, len);
  
}


static List<String> dropPunctuation_keep = ll("*", "<", ">");

static List<String> dropPunctuation(List<String> tok) {
  tok = new ArrayList<String>(tok);
  for (int i = 1; i < tok.size(); i += 2) {
    String t = tok.get(i);
    if (t.length() == 1 && !Character.isLetter(t.charAt(0)) && !Character.isDigit(t.charAt(0)) && !dropPunctuation_keep.contains(t)) {
      tok.set(i-1, tok.get(i-1) + tok.get(i+1));
      tok.remove(i);
      tok.remove(i);
      i -= 2;
    }
  }
  return tok;
}

static String dropPunctuation(String s) {
  return join(dropPunctuation(nlTok(s)));
}


static 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));
  
  String global = "";
    
  int idx;
  while ((idx = jfind(tok, "global {")) >= 0) {
    int j = findEndOfBracketPart(tok, idx+2);
    global = joinSubList(tok, idx+3, j-1) + "\n";
    clearTokens_reTok(tok, idx, j+1);
  }
  
  // =1*2 as in Lua
  if (eqGet(tok, 1, "=")) tok = subList(tok, 2);
  
  // if code is something like "sclass Bla {}", just return as is
  if (tok_isStaticLevelCode(tok))
    return 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, ";");
    
  // just a single identifier: interpret as function call
  if (l(tok) == 3 && isIdentifier(firstToken(tok)))
    tokAppend_reTok(tok, 1, "()");
    
  code = tok_addReturn(tok);
  String returnType = containsReturnWithArgument(code) ? "O" : "void";
  String main =
    global
    + "static " + returnType + " " + mainName + "(" + unnull(evalJava_prep_args.get()) + ") throws Exception {\n" 
      + preCode + code + "\n" + "}";
  return main;
}


static Object realMC() {
  return getThreadLocal(realMC_tl());
}


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 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 <A> A[] newObjectArrayOfSameType(A[] a) { return newObjectArrayOfSameType(a, a.length); }
static <A> A[] newObjectArrayOfSameType(A[] a, int n) {
  return (A[]) Array.newInstance(a.getClass().getComponentType(), n);
}




static String formatWithThousandsSeparator(long l) {
  return NumberFormat.getInstance(new Locale("en_US")).format(l);
}


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) { return linesFromReader(r, null); }
static CloseableIterableIterator<String> linesFromReader(Reader r, IResourceHolder resourceHolder) {
  final BufferedReader br = bufferedReader(r);
  return holdResource(resourceHolder, iteratorFromFunction_f0_autoCloseable(new F0<String>() { public 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 in == null ? null : 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 Random defaultRandomGenerator() {
  return ThreadLocalRandom.current();
}




static Charset utf8charset_cache;
static Charset utf8charset() { if (utf8charset_cache == null) utf8charset_cache = utf8charset_load(); return utf8charset_cache; }

static Charset utf8charset_load() {
  return Charset.forName("UTF-8");
}


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) {
  return oneOfTheFiles(asList(files));
}

static File oneOfTheFiles(Iterable<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 Map<Object, Object> castMapToMapO(Map map) {
  return map;
}


static <A> AutoCloseable tempSetTL(ThreadLocal<A> tl, A a) {
  return tempSetThreadLocal(tl, a);
}

static <A> AutoCloseable tempSetTL(x30_pkg.x30_util.BetterThreadLocal<A> tl, A a) {
  return tempSetThreadLocalIfNecessary(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 <T> void sort(T[] a, Comparator<? super T> c) {
  if (a != null) Arrays.sort(a, c);
}

static <T> void sort(T[] a) {
  if (a != null) 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) {
  if (a != null) Collections.sort(a, c);
}

static void sort(List a) {
  if (a != null) Collections.sort(a);
}


static TreeSet<String> litciset(String... items) {
  TreeSet<String> set = caseInsensitiveSet();
  for (String a : items) set.add(a);
  return set;
}


static TreeSet<Symbol> litciset(Symbol... items) {
  TreeSet<Symbol> set = treeSet(); // HashSet would also do, but we might have the return type fixed somewhere, and they might want a NavigableMap.
  for (Symbol a : items) set.add(a);
  return set;
}



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 boolean ewicOneOf(String s, String... l) {
  if (s != null) for (String x : l) if (ewic(s, x)) return true; return false;
}


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 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, Math.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 Map vm_generalWeakSubMap(Object name) {
  synchronized(get(javax(), "generalMap")) {
    Map map =  (Map) (vm_generalMap_get(name));
    if (map == null)
      vm_generalMap_put(name, map = newWeakMap());
    return map;
  }
}



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 boolean domainIsUnder(String domain, String mainDomain) {
  return eqic(domain, mainDomain) || ewic(domain, "." + mainDomain);
}


static String theAGIBlueDomain() {
  return "agi.blue";
}


static Object callFunction(Object f, Object... args) {
  return callF(f, args);
}


static Throwable _storeException_value;

static void _storeException(Throwable e) {
  _storeException_value = e;
}


static <A> Set<A> syncIdentityHashSet() {
  return (Set) synchronizedSet(identityHashSet());
}


static Map syncHashMap() {
  return synchroHashMap();
}


static betterCIComparator_C betterCIComparator_instance;

static betterCIComparator_C betterCIComparator() {
  if (betterCIComparator_instance == null)
    betterCIComparator_instance = new betterCIComparator_C();
  return betterCIComparator_instance;
}

final 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 boolean isCISet(Iterable<String> l) {
  return l instanceof TreeSet && ((TreeSet) l).comparator() == caseInsensitiveComparator();
}


static boolean isTrueOpt(Object o) {
  if (o instanceof Boolean)
    return ((Boolean) o).booleanValue();
  return false;
}

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


static boolean eqicOneOf(String s, String... l) {
  for (String x : l) if (eqic(s, x)) return true; return false;
}


static List<String> isYes_yesses = litlist("y", "yes", "yeah", "y", "yup", "yo", "corect", "sure", "ok", "afirmative"); // << collapsed words, so "corect" means "correct"

static boolean isYes(String s) {
  return isYes_yesses.contains(collapseWord(toLowerCase(firstWord2(s))));
}


static Map convertObjectMetaToMap(IMeta o) { return convertObjectMetaToMap(o, () -> synchroLinkedHashMap()); }
static Map convertObjectMetaToMap(IMeta o, IF0<Map> createEmptyMap) {
  if (o == null) return null;
  
  // The following shortcut depends on the assumption that a meta field never reverts
  // to null when it was a map
  
    Object meta = o._getMeta();
    if (meta instanceof Map) return ((Map) meta);
  
  
  // non-shortcut path (create meta)
   var mutex = tempMetaMutex(o); try {
  var actualMutex = mutex.get();
  synchronized(actualMutex) {
    meta = o._getMeta();
    if (meta instanceof Map) return ((Map) meta);
    Map map = createEmptyMap.get();
    if (meta != null) map.put("previousMeta" , meta);
    o._setMeta(map);
    return map;
  }
} finally { _close(mutex); }}


static <A, B> void syncMapPutOrRemove(Map<A, B> map, A key, B value) {
  syncMapPut2(map, key, value);
}


static Object get2(Object o, String field1, String field2) {
  return get(get(o, field1), field2);
}


static boolean startsWithAny(String a, Collection<String> b) {
  for (String prefix : unnullForIteration(b))
    if (startsWith(a, prefix))
      return true;
  return false;
}

static boolean startsWithAny(String a, String... b) {
  if (b != null)
    for (String prefix : unnullForIteration(b))
      if (startsWith(a, prefix))
        return true;
  return false;
}


static boolean startsWithAny(String a, Collection<String> b, Matches m) {
  for (String prefix : unnullForIteration(b))
    if (startsWith(a, prefix, m))
      return true;
  return false;
}



static String mcDollar() {
  return mcName() + "$";
}


static String afterDollar(String s) {
  return substring(s, smartIndexOf(s, '$')+1);
}


static Object safeUnstructureGZFile(File f) { try {
  if (!fileExists(f)) return null;
  BufferedReader reader = utf8BufferedReader(gzInputStream(f));
  return unstructure_tok(javaTokC_noMLS_onReader(reader), true, null);
} catch (Exception __e) { throw rethrow(__e); } }


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 <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 Object[] toArray(Collection c) {
  return toObjectArray(c);
}

static <A> A[] toArray(Class<A> type, Iterable<A> c) {
  return toArray(c, type);
}

static <A> A[] toArray(Iterable<A> 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 <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 List collectField(Iterable 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, Iterable c) {
  return collectField(c, field);
}


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


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 <A> List<A> immutableEmptyList() {
  return Collections.emptyList();
}


static short[] emptyShortArray = new short[0];
static short[] emptyShortArray() { return emptyShortArray; }


static <A, B> Map<A, B> immutableEmptyMap() {
  return Collections.emptyMap();
}


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 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 String _computerID;
static Lock computerID_lock = lock();

public static String computerID() {
  if (_computerID == null) {
    Lock __0 = computerID_lock; lock(__0); 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(__0); } }
  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 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 volatile String caseID_caseID;

static String caseID() { return caseID_caseID; }

static void caseID(String id) {
  caseID_caseID = id;
}


static <A, B> Map<A, B> synchronizedMRUCache(int maxSize) {
  return synchroMap(new MRUCache(maxSize));
}


static int defaultTokenizerCacheSize() {
  return 1000;
}


static List<String> javaTokWithAllBrackets(String s) {
  return javaTokPlusBrackets2(s);
}


static <A> IterableIterator<A> iteratorFromFunction_f0(final F0<A> f) {
  class IFF2 extends IterableIterator<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;
    }
  };
  return new IFF2();
}


static <A> IterableIterator<A> iteratorFromFunction_if0(final IF0<A> f) {
  class IFF2 extends IterableIterator<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;
    }
  };
  return new IFF2();
}


// f : Matcher -> S
static String regexReplaceIC(String s, String pat, Object f) {
  return regexReplace(regexpMatcherIC(pat, s), f);
}

static String regexReplaceIC(String s, String pat, String replacement) {
  return regexpReplaceIC_direct(s, pat, replacement);
}


static List<String> nlTok(String s) {
  return javaTokPlusPeriod(s);
}


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 tok_isStaticLevelCode(List<String> tok) {
  return eqOneOf(firstCodeToken(tok), "static", "sclass", "sinterface", "sS", "sO");
}

static boolean tok_isStaticLevelCode(String src) {
  return tok_isStaticLevelCode(javaTok(src));
}


static boolean isKnownVoidFunction(String name) {
  //ret mL_localCopy("Void Standard Functions").contains(name);
  return standardFunctionAlwaysReturnsVoid(name);
}


static String firstToken(String s) {
  return firstJavaToken(s);
}

static String firstToken(List<String> tok) {
  return get(tok, 1);
}


// We'd be really fancy if we filtered out return statements in all
// inner blocks.
static boolean containsReturnWithArgument(List<String> tok) {
  for (int i = 1; i+2 < l(tok); i += 2) {
    String t = tok.get(i);
    if (eqOneOf(t, "ret", "return") && neqOneOf(tok.get(i+2), ";", "if", "unless", "with"))
      return true;
      
    // skip embedded functions & classes
    if (eqOneOf(t, "embedded", "class") && isIdentifier(get(tok, i+2)))
      i = tok_findEndOfMethodDecl(tok, i)-1; // this might just work for classes too
      
    // skip runnables / lambdas
    if (eqOneOf(t, "r", ">") && eqGet(tok, i+2, "{"))
      i = findEndOfBlock(tok, i+2)-1;
  }
  return false;
}

static boolean containsReturnWithArgument(String code) {
  return containsReturnWithArgument(javaTok(code));
}


static ThreadLocal realMC_tl_tl = new ThreadLocal();

static ThreadLocal realMC_tl() {
  return realMC_tl_tl;
}


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 <A> A pcallF(IF0<A> f) { try {
  return f == null ? null : f.get();
} catch (Throwable __e) { return null; } }

static <A, B> B pcallF(IF1<A, B> f, A a) { try {
  return f == null ? null : f.get(a);
} catch (Throwable __e) { return null; } }



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;
  if (m != null)
    m.m = new String[] { substring(a, 0, l(a)-l(b)) };
  return true;
}



static BufferedReader bufferedReader(Reader r) { return bufferedReader(r, 8192); }
static BufferedReader bufferedReader(Reader r, int bufSize) {
  if (r == null) return null;
  return r instanceof BufferedReader ? (BufferedReader) r : _registerIOWrap(new BufferedReader(r, bufSize), r);
}


static <A extends AutoCloseable> A holdResource(IResourceHolder holder, A a) {
  { if (holder != null) holder.add(a); }
  return a;
}


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;
}


static boolean fileExists(String path) {
  return path != null && new File(path).exists();
}

static boolean fileExists(File f) {
  return f != null && f.exists();
}


static <A> AutoCloseable tempSetThreadLocalIfNecessary(ThreadLocal<A> tl, A a) {
  if (tl == null) return null;
  A prev = tl.get();
  if (eq(prev, a)) return null;
  tl.set(a);
  return new AutoCloseable() { public String toString() { return "tl.set(prev);"; } public void close() throws Exception { tl.set(prev); }};
}

static <A> AutoCloseable tempSetThreadLocalIfNecessary(x30_pkg.x30_util.BetterThreadLocal<A> tl, A a) {
  if (tl == null) return null;
  A prev = tl.get();
  if (eq(prev, a)) return null;
  tl.set(a);
  return new AutoCloseable() { public String toString() { return "tl.set(prev);"; } public void close() throws Exception { tl.set(prev); }};
}


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> TreeSet<A> treeSet() {
  return new TreeSet();
}


// 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 <A> A[] makeArray(Class<A> type, int n) {
  return (A[]) Array.newInstance(type, n);
}


static <A, B> Map<A, B> newWeakMap() {
  return newWeakHashMap();
}




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 String collapseWord(String s) {
  if (s == null) return "";
  StringBuilder buf = new StringBuilder();
  for (int i = 0; i < l(s); i++)
    if (i == 0 || !charactersEqualIC(s.charAt(i), s.charAt(i-1)))
      buf.append(s.charAt(i));
  return buf.toString();
}


static List<String> toLowerCase(List<String> strings) {
  List<String> x = new ArrayList();
  for (String s : strings)
    x.add(s.toLowerCase());
  return x;
}

static String[] toLowerCase(String[] strings) {
  String[] x = new String[l(strings)];
  for (int i = 0; i < l(strings); i++)
    x[i] = strings[i].toLowerCase();
  return x;
}

static String toLowerCase(String s) {
  return s == null ? "" : s.toLowerCase();
}


static String firstWord2(String s) {
  s = xltrim(s);
  if (empty(s)) return "";
  if (isLetterOrDigit(first(s)))
    return takeCharsWhile(__60 -> isLetterOrDigit(__60), s);
  else return "" + first(s);
}


static Map synchroLinkedHashMap() {
  return Collections.synchronizedMap(new LinkedHashMap());
}



static IAutoCloseableF0 tempMetaMutex(IMeta o) {
  return o == null ? null : o._tempMetaMutex();
}


static <A, B> void syncMapPut2(Map<A, B> map, A key, B value) {
  if (map != null && key != null) synchronized(collectionMutex(map)) {
    if (value != null) map.put(key, value);
    else map.remove(key);
  }
}


static String mcName() {
  return mc().getName();
}


static BufferedReader utf8BufferedReader(InputStream in) {
  return utf8bufferedReader(in);
}

static BufferedReader utf8BufferedReader(File f) {
  return utf8bufferedReader(f);
}


static char stringToChar(String s) {
  if (l(s) != 1) throw fail("bad stringToChar: " + s);
  return firstChar(s);
}


// binary legacy signature
static Object[] toObjectArray(Collection c) {
  return toObjectArray((Iterable) c);
}

static Object[] toObjectArray(Iterable 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 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 File dirOfFile(File f) {
  return f == null ? null : f.getParentFile();
}


static File computerIDFile() {
  return javaxDataDir("Basic Info/computer-id.txt");
}


static String utf8streamToString(InputStream in) {
  return readerToString(utf8bufferedReader(in));
}


static String multiLineQuoteWithSpaces(String s) {
  return multiLineQuote(" " + s + " ");
}


static List<String> javaTokPlusBrackets2(String s) {
  return tok_combineRoundCurlySquareBrackets_keep(javaTok(s));
}


// f : Matcher -> S
static String regexReplace(String s, String pat, Object f) {
  Matcher m = Pattern.compile(pat).matcher(s);
  return regexReplace(m, f);
}

static String regexReplace(String s, String pat, String replacement) {
  return regexpReplace_direct(s, pat, replacement);
}

static String regexReplace(Matcher m, Object f) {
  StringBuffer buf = new StringBuffer();
  while (m.find())
    m.appendReplacement(buf, m.quoteReplacement(str(callF(f, m))));
  m.appendTail(buf);
  return str(buf);
}

static String regexReplace(String s, String pat, IF1<Matcher, String> f) {
  return regexReplace(s, pat, (Object) f);
}


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


static String regexpReplaceIC_direct(String s, String pat, String replacement) {
  Matcher m = regexpIC(pat, s);
  StringBuffer buf = new StringBuffer();
  while (m.find())
    m.appendReplacement(buf, replacement);
  m.appendTail(buf);
  return str(buf);
}


// This is made for NL parsing.
// It's javaTok extended with "..." token, "$n" and "#n" and
// special quotes (which are converted to normal ones).

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

    // scan for non-whitespace
    if (c == (char) 0x201C || c == (char) 0x201D) c = '"'; // normalize quotes
    if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        char _c = s.charAt(j);
        if (_c == (char) 0x201C || _c == (char) 0x201D) _c = '"'; // normalize quotes
        if (_c == opener) {
          ++j;
          break;
        } else if (s.charAt(j) == '\\' && j+1 < l)
          j += 2;
        else
          ++j;
      }
      if (j-1 >= i+1) {
        tok.add(opener + s.substring(i+1, j-1) + opener);
        i = j;
        continue;
      }
    } else if (Character.isJavaIdentifierStart(c))
      do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || s.charAt(j) == '\'')); // for things like "this one's"
    else if (Character.isDigit(c))
      do ++j; while (j < l && Character.isDigit(s.charAt(j)));
    else if (cc.equals("[[")) {
      do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]"));
      j = Math.min(j+2, l);
    } else if (cc.equals("[=") && i+2 < l && s.charAt(i+2) == '[') {
      do ++j; while (j+2 < l && !s.substring(j, j+3).equals("]=]"));
      j = Math.min(j+3, l);
    } else if (s.substring(j, Math.min(j+3, l)).equals("..."))
      j += 3;
    else if (c == '$' || c == '#')
      do ++j; while (j < l && Character.isDigit(s.charAt(j)));
    else
      ++j;

    tok.add(s.substring(i, j));
    i = j;
  }
  
  if ((tok.size() % 2) == 0) tok.add("");
  return tok;
}



// 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 String firstCodeToken(String s) {
  return firstJavaToken(s);
}

static String firstCodeToken(List<String> tok) {
  return get(tok, 1);
}


static boolean standardFunctionAlwaysReturnsVoid(String name) {
  return tok_staticFunctionAlwaysReturnsVoid(javaTok(textOfStandardFunction_cached(name)), name);
}


static String firstJavaToken(String s) {
  if (s == null) return null;
  int l = s.length();

  int j = 0;
  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 (j >= l) return null;
  int i = j;
  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;
    
  return quickSubstring(s, i, j);
}


static Object pcallFunction(Object f, Object... args) {
  try { return callFunction(f, args); } catch (Throwable __e) { _handleException(__e); }
  return null;
}



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);
  }
}




// TODO: OurSyncCollections
static <A> Set<A> synchroHashSet() {
  return Collections.synchronizedSet(new HashSet<A>());
}



static boolean charactersEqualIC(char c1, char c2) {
  if (c1 == c2) return true;
  char u1 = Character.toUpperCase(c1);
  char u2 = Character.toUpperCase(c2);
  if (u1 == u2) return true;
  return Character.toLowerCase(u1) == Character.toLowerCase(u2);
}



static String xltrim(String s) {
  int i = 0, n = l(s);
  while (i < n && contains(" \t\r\n", s.charAt(i)))
    ++i;
  return substr(s, i);
}


static boolean isLetterOrDigit(char c) {
  return Character.isLetterOrDigit(c);
}


// pred: char -> bool
static String takeCharsWhile(String s, Object pred) {
  int i = 0;
  while (i < l(s) && isTrue(callF(pred, s.charAt(i)))) ++i;
  return substring(s, 0, i);
}

static String takeCharsWhile(IF1<Character, Boolean> f, String s) {
  return takeCharsWhile(s, f);
}


static char firstChar(String s) {
  return s.charAt(0);
}


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 String readerToString(Reader r) { try {
  if (r == null) return null;
  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 List<String> tok_combineRoundCurlySquareBrackets_keep(List<String> tok) {
  List<String> l = new ArrayList();
  for (int i = 0; i < l(tok); i++) {
    String t = tok.get(i);
    if (odd(i) && eqOneOf(t, "{", "(", "[")) {
      int j = findEndOfBracketPart2(tok, i);
      l.add(joinSubList(tok, i, j));
      i = j-1;
    } else
      l.add(t);
  }
  return l;
}


static String regexpReplace_direct(String s, String pat, String replacement) {
  Matcher m = regexp(pat, s);
  return regexpReplace_direct(m, replacement);
}
  
static String regexpReplace_direct(Matcher m, String replacement) {
  StringBuffer buf = new StringBuffer();
  while (m.find())
    m.appendReplacement(buf, replacement);
  m.appendTail(buf);
  return str(buf);
}




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
static ThreadLocal<IVF1<String>> veryQuickJava_onJavaSource = new ThreadLocal();

// 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 (empty(src)) {
    printWithIndent("JAVAX> ", mainJava);
    throw fail("Transpiler returned empty result");
  }
  if (veryQuickJava_transpiled.get() != null)
    veryQuickJava_transpiled.set(src);
  callF(veryQuickJava_onJavaSource.get(), 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), javaxVoidAliases())) return false;
  return true;
}


static String textOfStandardFunction_cached(String sfName) {
  return textOfStandardFunction(sfName);
}




static String substr(String s, int x) {
  return substring(s, x);
}

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


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_mine = true;
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 __0 = transpileRaw_lock; lock(__0); try {
  
  File cacheFile = null;
  if (useDiskCache) {
    cacheFile = new File(javaxCodeDir(), "Transpilations/" + uniqueFileNameUsingMD5_80_v2(mainJava) + ".java");
    { String __3 = loadTextFile(cacheFile); if (!empty(__3)) return __3; }
  }
  
   AutoCloseable __4 = 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"));
  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(__4); }} finally { unlock(__0); } }

static Class transpileRaw_translator() {
  if (transpileRaw_trans == null) {
    //print("Loading translator.");
    transpileRaw_trans = hotwireSharingLibraries_silently(defaultJavaXTranslatorID());
    transpileRaw_mine = true;
    makeDependent(transpileRaw_trans);
    //print("Loaded translator: " + identityHashCode(transpileRaw_trans));
  }
  setOpt(transpileRaw_trans, "print_silent", transpileRaw_silent);
  return transpileRaw_trans;
}

static void transpileRaw_copyFromCreator() {
  Lock __1 = transpileRaw_lock; lock(__1); 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;
    transpileRaw_mine = false;
  }
} finally { unlock(__1); } }

static void cleanMeUp_transpileRaw() {
  if (transpileRaw_mine)
    cleanUp(transpileRaw_trans);
  transpileRaw_trans = null; // release proactively (it's big)
}



static <A> A printWithIndent(A o) {
  return printIndent(o);
}



static <A> A printWithIndent(String indent, A o) {
  return printIndent(indent, o);
}



static void printWithIndent(int indent, Object o) {
  printIndent(indent, o);
}


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 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 Class<?> hotwireSharingLibraries_silently(String progID) {
   AutoCloseable __1 = temp_loadPage_silent(); try {
  return hotwireSharingLibraries(progID);
} finally { _close(__1); }}


static WeakReference<Object> creator_class;

static Object creator() {
  return creator_class == null ? null : creator_class.get();
}


static boolean cleanUp_interruptThreads = false; // experimental

static void cleanUp(Object c) {
  if (c == null) return;
  
  if (c instanceof AutoCloseable) { close_pcall((AutoCloseable) c); return; }
  
  if (c instanceof java.util.Timer) { ((java.util.Timer) c).cancel(); return; }
  
  if (c instanceof Collection) { cleanUp((Collection) c); return; }
  if (c instanceof Map) {
    for (Object o : keys((Map) c)) cleanUp(o);
    for (Object o : values((Map) c)) cleanUp(o);
    ((Map) c).clear();
    return;
  }
  //if (!(c instanceof Class)) ret;
  
  try {
    // revoke license
    
    preCleanUp(c);
    
    // unpause
    
    setOpt(c, "ping_pauseAll", false);
    
    // call custom cleanMeUp() and cleanMeUp_*() functions
    
    innerCleanUp(c);
        
    // Java spec says finalize should only be called by GC,
    // but we care to differ.
    // Edit: Not anymore (illegal access warnings)
    /*if (isTrue(vmMap_get('callFinalize)))
      pcallOpt(c, "finalize");*/

    // remove all virtual bots (hope this works)
    
    List androids = (List) getOpt(c, "record_list");
    for (Object android : unnull(androids))
      pcallOpt(android, "dispose"); // heck we'll dispose anything

    // sub-cleanup
    
    List<WeakReference> classes =  (List<WeakReference>) (getOpt(c, "hotwire_classes"));
    if (classes != null)
      for (WeakReference cc : classes) { try {
        cleanUp(cc.get());
      } catch (Throwable __e) { _handleException(__e); }}
      
    // interrupt all threads (experimental, they might be doing cleanup?)
    
    if (cleanUp_interruptThreads) {
      List<Thread> threads = registeredThreads(c);
      if (nempty(threads)) {
        print("cleanUp: Interrupting " + n2(threads, "thread") + ": " + joinWithComma(allToString(threads)));
        interruptThreads(threads);
      }
    }
  } catch (Throwable __e) { _handleException(__e); }
  
  setOpt(c, "cleaningUp_flag" , false);
  if (c instanceof Class && ((Class) c).getName().equals("main"))
    retireClassLoader(((Class) c).getClassLoader());
 }

static void cleanUp(Collection l) {
  if (l == null) return;
  for (Object c : l)
    cleanUp(c);
  l.clear();
}


static <A> A printIndent(A o) {
  print(indentx(str(o)));
  return o;
}

static <A> A printIndent(String indent, A o) {
  print(indentx(indent, str(o)));
  return o;
}

static void printIndent(int indent, Object o) {
  print(indentx(indent, str(o)));
}


// 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))
        && !contains(subList(tok, i, j), "new")) {
        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) {
  s = dropLeadingDots(s); // don't produce file names starting with a dot!
  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 void close_pcall(AutoCloseable c) {
  if (c != null) { try { c.close(); } catch (Throwable __e) { _handleException(__e); }}
}


static void preCleanUp(Object c) {
  if (c instanceof Collection) { for (Object o : ((Collection) c)) preCleanUp(o); return; }
  callOpt(c, "licensed_off");
  setOpt(c, "ping_anyActions" , true); // so ping notices
  setOpt(c, "cleaningUp_flag" , true);
}


static void innerCleanUp(Object c) {
  // call custom cleanMeUp() and cleanMeUp_*() functions
  
  if (!isFalse(pcallOpt(c, "cleanMeUp")))
    for (String name : sorted(methodsStartingWith(c, "cleanMeUp_"))) try {
      callOpt(c, name);
    } catch (Throwable e) {
      print("Error cleaning up: " + programID(c));
      _handleException(e);
    }
}

static void innerCleanUp() {
  innerCleanUp(mc());
}


static Object pcallOpt(Object o, String method, Object... args) {
  try { return callOpt(o, method, args); } catch (Throwable __e) { _handleException(__e); }
  return null;
}


static List<Thread> registeredThreads(Object o) {
  Map<Thread, Boolean> map =  (Map<Thread, Boolean>) (getOpt(o, "_registerThread_threads"));
  if (map == null) return ll();
  map.size(); // force clean-up
  synchronized(map) { return asList(keys(map)); }
}

static List<Thread> registeredThreads() {
  _registerThread_threads.size(); // force clean-up
  return asList(keys(_registerThread_threads));
}


static void interruptThreads(Collection<Thread> threads) {
  for (Thread t : unnull(threads))
    interruptThread(t);
}

static void interruptThreads(Class mainClass) {
  interruptThreads(registeredThreads(mainClass));
}


static void retireClassLoader(ClassLoader cl) {
  if (isJavaXClassLoader(cl))
    setOptAll(cl, "retired" , true, "retiredMarker" , new DefunctClassLoader());
}



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 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 __0 = loadSnippet_simpleCache_lock; lock(__0); 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(__0); } }




static Map<Thread, Boolean> _registerThread_threads;
static Object _onRegisterThread; // voidfunc(Thread)

static Thread _registerThread(Thread t) {
  if (_registerThread_threads == null)
    _registerThread_threads = newWeakHashMap();
  _registerThread_threads.put(t, true);
  vm_generalWeakSubMap("thread2mc").put(t, weakRef(mc()));
  callF(_onRegisterThread, t);
  return t;
}

static void _registerThread() {
  _registerThread(Thread.currentThread());
}


static File loadBinarySnippet(String snippetID) {
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadLibrary(snippetID);
  
  
  return loadBinarySnippet_noResourceLoader(snippetID);
}
  
static File loadBinarySnippet_noResourceLoader(String snippetID) { try {
  long id = parseSnippetID(snippetID);
  if (isImageServerSnippet(id)) return loadImageAsFile(snippetID);
  File f = DiskSnippetCache_getLibrary(id);
  if (fileSize(f) == 0)
    f = loadDataSnippetToFile_noResourceLoader(snippetID);
  return f;
} catch (Exception __e) { throw rethrow(__e); } }


static String dropLeadingDots(String s) {
  int i = 0;
  while (charAt(s, i) == '.') ++i;
  return dropFirst(s, i);
}



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 asList((Collection<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 <A> List<A> sorted(Collection<A> c, Object comparator) {
  List<A> l = cloneList(c);
  sort(l, makeComparator(comparator));
  return l;
}

static <A> List<A> sorted(Collection<A> c) {
  List<A> l = cloneList(c);
  sort(l);
  return l;
}

static <A> List<A> sorted(Comparator<A> comparator, Collection<A> c) {
  List<A> l = cloneList(c);
  sort(l, comparator);
  return l;
}


static List<String> methodsStartingWith(Object o, final String prefix) {
  return filter(allMethodNames(o), new F1<String, Object>() { public Object get(String s) { try {  return startsWith(s, prefix);  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "startsWith(s, prefix)"; }});
}


static boolean interruptThread_verbose = false;

static void interruptThread(Thread t) {
  if (t == null) return;
  if (interruptThread_verbose) print("Interrupting thread " + t);
  
  // note reason in global map
  
  vm_threadInterruptionReasonsMap().put(t, getStackTrace());
  
  t.interrupt();
  URLConnection c =  (URLConnection) (vm_generalSubMap("URLConnection per thread").get(t));
  if (c != null) { try {
    print("Closing URLConnection of interrupted thread.");
    call(c, "disconnect");
  } catch (Throwable __e) { _handleException(__e); }}
}


static boolean isJavaXClassLoader(ClassLoader cl) {
  return startsWithOneOf(className(cl), "main$JavaXClassLoader", "x30$JavaXClassLoader");
}


static void setOptAll(Object o, Map<String, Object> fields) {
  if (fields == null) return;
  for (String field : keys(fields))
    setOpt/*_flex*/(o, field, fields.get(field));
}

static void setOptAll(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];
    setOpt(o, field, value);
  }
}


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 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>>() { public List<JFrame> get() { try {  return keysList(myFrames_list);  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "ret keysList(myFrames_list);"; }});
}



static <A> WeakReference<A> weakRef(A a) {
  return newWeakReference(a);
}



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 {
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadLibrary(snippetID);
  
  
  return loadDataSnippetToFile_noResourceLoader(snippetID);
} catch (Exception __e) { throw rethrow(__e); } }
  
static File loadDataSnippetToFile_noResourceLoader(String snippetID) { try {
  snippetID = fsI(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(e);
      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;
  return swing(() -> {
    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>() { public 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 Comparator makeComparator(final Object f) {
  if (f instanceof Comparator) return (Comparator) f;
  return new Comparator() {
    public int compare(Object a, Object b) {
      return (Integer) callF(f, a, b);
    }
  };
}



static <A> List<A> filter(Iterable<A> c, Object pred) {
  if (pred instanceof F1) return filter(c, (F1<A, Boolean>) pred);

  
  List x = new ArrayList();
  if (c != null) for (Object o : c)
    if (isTrue(callF(pred, o)))
      x.add(o);
  return x;
}

static List filter(Object pred, Iterable c) {
  return filter(c, pred);
}

static <A, B extends A> List<B> filter(Iterable<B> c, F1<A, Boolean> pred) {
  List x = new ArrayList();
  if (c != null) for (B o : c)
    if (pred.get(o))
      x.add(o);
  return x;
}

static <A, B extends A> List<B> filter(F1<A, Boolean> pred, Iterable<B> c) {
  return filter(c, pred);
}

//ifclass IF1
static <A, B extends A> List<B> filter(Iterable<B> c, IF1<A, Boolean> pred) {
  List x = new ArrayList();
  if (c != null) for (B o : c)
    if (pred.get(o))
      x.add(o);
  return x;
}

static <A, B extends A> List<B> filter(B[] c, IF1<A, Boolean> pred) {
  List x = new ArrayList();
  if (c != null) for (B o : c)
    if (pred.get(o))
      x.add(o);
  return x;
}

static <A, B extends A> List<B> filter(IF1<A, Boolean> pred, Iterable<B> c) {
  return filter(c, pred);
}
//endif


static List<String> allMethodNames(Object o) {
  Class c = _getClass(o);
  TreeSet<String> names = new TreeSet();
  while (c != null) {
    for (Method m : c.getDeclaredMethods())
      names.add(m.getName());
    c = c.getSuperclass();
  }
  return asList(names);
}


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) {
  return loadBufferedImageFile(file);
}


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 <A> WeakReference<A> newWeakReference(A a) {
  return a == null ? null : new WeakReference(a);
}


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 >= 1200000 && id < 1300000) { // Woody files, actually
    String pw = muricaPassword();
    if (empty(pw)) throw fail("Please set 'murica password by running #1008829");
    return "https://botcompany.de/files/" + id + "?_pass=" + pw; // XXX, although it typically gets hidden when printing
  }
  return fileServerURL() + "/" + id /*+ "?_pass=" + muricaPassword()*/;
}


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 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 __0 = imageIcon_lock; lock(__0); 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(__0); } } 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 {
  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 BufferedImage loadBufferedImageFile(File file) { try {
  return isFile(file) ? ImageIO.read(file) : null;
} catch (Exception __e) { throw rethrow(__e); } }


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 (snippetID == null || isURL(snippetID)) return snippetID;
  long id = parseSnippetID(snippetID);
  String url;
  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://botcompany.de/img/" + id;
  return url;
}


static <A> A println(A a) {
  return print(a);
}


static String fileServerURL() {
  return "https://botcompany.de/files";
}


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(100000, total))));
    total += n;
  }
} catch (Exception __e) { throw rethrow(__e); } }


static File renameFile_assertTrue(File a, File b) { try {
  if (a.equals(b)) return b; // no rename necessary
  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));
  
  return 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 instanceof String)
    return parseDouble((String) o);
  if (o == null) return 0.0;
  throw fail(o);
}


static String fromUtf8(byte[] bytes) { try {
  return bytes == null ? null : new String(bytes, utf8charset());
} catch (Exception __e) { throw rethrow(__e); } }




static boolean isFile(File f) {
  return f != null && f.isFile();
}

static boolean isFile(String path) {
  return isFile(newFile(path));
}


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;
}


// TODO: optimize to x-(x%n) in case that's the same thing
// (or x-mod(x,n)?)
static int roundDownTo(int n, int x) {
  return x/n*n;
}

static long roundDownTo(long n, long x) {
  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 <A> IVF1<A> setVar(IVar<A> v) {
  return a -> { if (v != null) v.set(a); };
}


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 __1 = holdInstance(instanceToHold); try {
    callF(runnable);
  } finally { _close(__1); }} 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) {
  if (r == null) return null;
  Runnable r2 = toRunnable(r);
  Object mod = dm_current_generic();
  if (mod == null) return r2;
  return new Runnable() {  public void run() { try { 
    AutoCloseable c =  (AutoCloseable) (rcall("enter", mod));
     AutoCloseable __1 = c; try {
    r2.run();
  
} finally { _close(__1); }} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "AutoCloseable c =  (AutoCloseable) (rcall enter(mod));\r\n    temp c;\r\n    r2.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 Object dm_current_generic() {
  return getWeakRef(dm_current_generic_tl().get());
}


static Object rcall(String method, Object o, Object... args) {
  return call_withVarargs(o, method, args);
}




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 <A> A getWeakRef(Reference<A> ref) {
  return ref == null ? null : ref.get();
}


static x30_pkg.x30_util.BetterThreadLocal<WeakReference> dm_current_generic_tl;

static x30_pkg.x30_util.BetterThreadLocal<WeakReference> dm_current_generic_tl() {
  if (dm_current_generic_tl == null)
    dm_current_generic_tl = vm_generalMap_getOrCreate("currentModule", () -> new x30_pkg.x30_util.BetterThreadLocal());
  return dm_current_generic_tl;
}




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;
}




static abstract class VF1<A> implements IVF1<A> {
  public abstract void get(A a);
}
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>, ISetter<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(this.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 DefunctClassLoader {}
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 __0 = lock; lock(__0); try {
    if (has) return super.get();
    set((A) pcallF(make));
    make = null;
    has = true;
    return super.get();
  } finally { unlock(__0); } }
}
static interface ITokCondition {
  boolean get(List<String> tok, int i); // i = N Index
}
static abstract class TokCondition implements ITokCondition {
  public abstract boolean get(List<String> tok, int i); // i = N Index
}
// differences to AbstractList.subList / ArrayList.subList:
// -probably doesn't handle modCount the same way
//
// made from AbstractList.subList
static class SubList<E> extends AbstractList<E> implements ISubList<E> {
  List<E> root;
  SubList<E> parent;
  int offset;
  int size;

  /**
   * Constructs a sublist of an arbitrary AbstractList, which is
   * not a SubList itself.
   */
  public SubList(List<E> root, int fromIndex, int toIndex) {
    if (root instanceof SubList) {
      this.parent = (SubList) root;
      this.root = ((SubList) root).root;
      this.offset = ((SubList) root).offset + fromIndex;
    } else {
      this.parent = null;
      this.root = root;
      this.offset = fromIndex;
    }
    this.size = toIndex - fromIndex;
  }

  public E set(int index, E element) {
      Objects.checkIndex(index, size);
      checkForComodification();
      return root.set(offset + index, element);
  }

  public E get(int index) {
      Objects.checkIndex(index, size);
      checkForComodification();
      return root.get(offset + index);
  }

  public int size() {
      checkForComodification();
      return size;
  }

  public void add(int index, E element) {
      rangeCheckForAdd(index);
      checkForComodification();
      root.add(offset + index, element);
      updateSizeAndModCount(1);
  }

  public E remove(int index) {
      Objects.checkIndex(index, size);
      checkForComodification();
      E result = root.remove(offset + index);
      updateSizeAndModCount(-1);
      return result;
  }

  protected void removeRange(int fromIndex, int toIndex) {
      checkForComodification();
      root.subList(offset + fromIndex, offset + toIndex).clear();
      updateSizeAndModCount(fromIndex - toIndex);
  }

  public boolean addAll(Collection<? extends E> c) {
      return addAll(size, c);
  }

  public boolean addAll(int index, Collection<? extends E> c) {
      rangeCheckForAdd(index);
      int cSize = c.size();
      if (cSize==0)
          return false;
      checkForComodification();
      root.addAll(offset + index, c);
      updateSizeAndModCount(cSize);
      return true;
  }

  public Iterator<E> iterator() {
      return listIterator();
  }

  public ListIterator<E> listIterator(int index) {
      checkForComodification();
      rangeCheckForAdd(index);

      return new ListIterator<E>() {
          private final ListIterator<E> i =
                  root.listIterator(offset + index);

          public boolean hasNext() {
              return nextIndex() < size;
          }

          public E next() {
              if (hasNext())
                  return i.next();
              else
                  throw new NoSuchElementException();
          }

          public boolean hasPrevious() {
              return previousIndex() >= 0;
          }

          public E previous() {
              if (hasPrevious())
                  return i.previous();
              else
                  throw new NoSuchElementException();
          }

          public int nextIndex() {
              return i.nextIndex() - offset;
          }

          public int previousIndex() {
              return i.previousIndex() - offset;
          }

          public void remove() {
              i.remove();
              updateSizeAndModCount(-1);
          }

          public void set(E e) {
              i.set(e);
          }

          public void add(E e) {
              i.add(e);
              updateSizeAndModCount(1);
          }
      };
  }

  public List<E> subList(int fromIndex, int toIndex) {
      _subListRangeCheck(fromIndex, toIndex, size);
      return new SubList<>(this, fromIndex, toIndex);
  }

  private void rangeCheckForAdd(int index) {
      if (index < 0 || index > size)
          throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  }

  private String outOfBoundsMsg(int index) {
      return "Index: "+index+", Size: "+size;
  }

  private void checkForComodification() {}

  private void updateSizeAndModCount(int sizeChange) {
    SubList<E> slist = this;
    do {
      slist.size += sizeChange;
      slist = slist.parent;
    } while (slist != null);
  }
  
  public List<E> rootList() { return root; }
  public List<E> parentList() { return parent; }
  public int subListOffset() { return offset; }
}

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;
  }
}

static class Fail extends RuntimeException implements IFieldsToList{
  Object[] objects;
  Fail() {}
  Fail(Object... objects) {
  this.objects = objects;}
  public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + objects + ")"; }public Object[] _fieldsToList() { return new Object[] {objects}; }
}
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;
  boolean isPublic = false;
  
  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);
}
abstract static class RandomAccessAbstractList<A> extends AbstractList<A> implements RandomAccess {
}
// optimized for search - O(1) for searching for a certain element
// set is O(log m) with m = number of occurrences of element
final static class ContentsIndexedList<A> extends RandomAccessAbstractList<A> implements IContentsIndexedList<A>, IContentsIndexedList2<A> {
  Map<A, TreeSet<Elem<A>>> index = new HashMap(); // tokens by contents, sorted by index
  final ArrayList<Elem<A>> list = new ArrayList();

  final static class Elem<A> extends HasIndex {
    A s; // actual token
    
    public String toString() { return "Elem " + quote(s) + "@" + idx; }
  }
  
  ContentsIndexedList() {}
  ContentsIndexedList(Map<A, TreeSet<Elem<A>>> index) {
  this.index = index;} // use different type of index (e.g. ciMap)
  ContentsIndexedList(Collection<A> l) { addAll(l); }
  
  public A get(int i) { return list.get(i).s; }
  public int size() { return list.size(); }
  
  public A set(int i, A s) {
    Elem<A> t = list.get(i);
    A old = t.s;
    if (eq(old, s)) return old;
    removeFromIdx(t);
    t.s = s;
    addToIdx(t);
    return old;
  }
  
  public boolean add(A s) {
    ++modCount;
    Elem<A> t = new Elem();
    t.s = s;
    t.idx = size();
    list.add(t);
    addToIdx(t);
    return true;
  }
  
  public void add(int i, A s) {
    ++modCount;
    Elem<A> t = new Elem();
    t.s = s;
    t.idx = i;
    list.add(i, t);
    reorder(i+1);
    addToIdx(t);
  }
  
  public boolean addAll(int i, Collection<? extends A> l) {
    int n = l.size();
    if (n == 0) return false;
    ++modCount;
    List<Elem<A>> l2 = emptyList(n);
    int j = i;
    for (A s : l) {
      Elem<A> t = new Elem();
      t.s = s;
      t.idx = j++;
      l2.add(t);
    }
    list.addAll(i, l2);
    reorder(i+n);
    for (Elem<A> t : l2)
      addToIdx(t);
    return true;
  }
  
  public A remove(int i) {
    ++modCount;
    Elem<A> 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(Elem<A> t) {
    TreeSet<Elem<A>> idx = index.get(t.s);
    idx.remove(t);
    if (idx.isEmpty()) index.remove(t.s);
  }
  
  void addToIdx(Elem<A> t) {
    TreeSet<Elem<A>> idx = index.get(t.s);
    if (idx == null)
      index.put(t.s, idx = new TreeSet());
    idx.add(t);
  }
  
  @Override
  public int indexOf(Object s) {
    TreeSet<Elem<A>> l = index.get(s);
    return l == null ? -1 : first(l).idx;
  }
  
  @Override
  public int lastIndexOf(Object s) {
    TreeSet<Elem<A>> l = index.get(s);
    return l == null ? -1 : last(l).idx;
  }
  
  @Override
  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;
    
    for (int i = fromIndex; i < toIndex; i++)
      removeFromIdx(list.get(i));
      
    list.subList(fromIndex, toIndex).clear();
    reorder(fromIndex);
  }
  
  public int[] indicesOf(Object o) {
    TreeSet<Elem<A>> idx = index.get(o);
    if (idx == null) return emptyIntArray();
    int[] a = new int[idx.size()];
    int i = 0;
    for (Elem<A> t : idx) a[i++] = t.idx;
    return a;
  }
  
  public TreeSet<HasIndex> indicesOf_treeSetOfHasIndex(Object o) {
    return (TreeSet) index.get(o);
  }
}
static interface IF0<A> {
  A get();
}
static interface Hasher<A> {
  int hashCode(A a);
  boolean equals(A a, A b);
}
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 interface IF2<A, B, C> {
  C get(A a, B b);
}
static interface Producer<A> {
  public A next(); // null when end
}
static interface IF1<A, B> {
  B get(A a);
}
static interface IVF1<A> {
  void get(A a);
}
static interface IVF2<A, B> {
  void get(A a, B b);
}
static interface IVar<A> extends IF0<A> {
  void set(A a);
  A get();
  
  default boolean has() { return get() != null; }
  default void clear() { set(null); }
  
}
static interface TokReplacer {
  // return string to replace section with
  // only C to C (start to end, exclusively) is replaced
  abstract String get(List<String> tok, int start, int end); // start = C Index, end = N index
}
// immutable, has strong refs
// Do not run in a synchronized block - it goes wrong in the presence
// of elaborate classloaders (like in Gazelle BEA)
// see #1102990 and #1102991
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 (!isAbstract(m) && !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 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 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); }
}

// for the version with MasterSymbol (used WAY back in "Smart Bot"!) see #1010608

static class Symbol implements CharSequence {
  String text;
  
  Symbol() {}
  Symbol(String text, boolean dummy) {
  this.text = text;} // weird signature to prevent accidental calling
  
  public int hashCode() { return _hashCode(text); }
  public String toString() { return text; }
  public boolean equals(Object o) {
    return this == o;
  }

  // implementation of CharSequence methods
  
  public int length() { return text.length(); }
  public char charAt(int index) { return text.charAt(index); }
  public CharSequence subSequence(int start, int end) {
    return text.substring(start, end);
  }
}
static interface IMeta {
  // see class "Meta" for the bla bla
  
  public void _setMeta(Object meta);
  public Object _getMeta();
  default public IAutoCloseableF0 _tempMetaMutex() {
    return new IAutoCloseableF0() {
      public Object get() { return IMeta.this; }
      public void close() {}
    };
  }
}
static interface IContentsIndexedList<A> extends List<A> {
  int[] indicesOf(Object o);
}
static class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {
  RandomAccessSubList(List<E> root, int fromIndex, int toIndex) {
    super(root, fromIndex, toIndex);
  }
}

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);
  
  default File pathToJavaXJar() { return pathToJavaxJar_noResourceLoader(); }
  
  // may return null, then caller compiles themselves
  default File getSnippetJar(String snippetID, String transpiledSrc) { return null; }
}
/*
 * @(#)WeakHashMap.java 1.5 98/09/30
 *
 * Copyright 1998 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */
 
// From https://github.com/mernst/plume-lib/blob/df0bfafc3c16848d88f4ea0ef3c8bf3367ae085e/java/src/plume/WeakHasherMap.java

static final class WeakHasherMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {

    private Hasher hasher = null;
    /*@Pure*/
    private boolean keyEquals(Object k1, Object k2) {
  return (hasher==null ? k1.equals(k2)
           : hasher.equals(k1, k2));
    }
    /*@Pure*/
    private int keyHashCode(Object k1) {
  return (hasher==null ? k1.hashCode()
           : hasher.hashCode(k1));
    }

    // The WeakKey class can't be static because it depends on the hasher.
    // That in turn means that its methods can't be static.
    // However, I need to be able to call the methods such as create() that
    // were static in the original version of this code.
    // This finesses that.

    private /*@Nullable*/ WeakKey WeakKeyCreate(K k) {
  if (k == null) return null;
  else return new WeakKey(k);
    }
    private /*@Nullable*/ WeakKey WeakKeyCreate(K k, ReferenceQueue<? super K> q) {
  if (k == null) return null;
  else return new WeakKey(k, q);
    }

    // Cannot be a static class: uses keyHashCode() and keyEquals()
    private final class WeakKey extends WeakReference<K> {
  private int hash; /* Hashcode of key, stored here since the key
           may be tossed by the GC */

  private WeakKey(K k) {
      super(k);
      hash = keyHashCode(k);
  }

  private /*@Nullable*/ WeakKey create(K k) {
      if (k == null) return null;
      else return new WeakKey(k);
  }

  private WeakKey(K k, ReferenceQueue<? super K> q) {
      super(k, q);
      hash = keyHashCode(k);
  }

  private /*@Nullable*/ WeakKey create(K k, ReferenceQueue<? super K> q) {
      if (k == null) return null;
      else return new WeakKey(k, q);
  }

        /* A WeakKey is equal to another WeakKey iff they both refer to objects
     that are, in turn, equal according to their own equals methods */
  /*@Pure*/
  @Override
  public boolean equals(/*@Nullable*/ Object o) {
            if (o == null) return false; // never happens
      if (this == o) return true;
            // This test is illegal because WeakKey is a generic type,
            // so use the getClass hack below instead.
      // if (!(o instanceof WeakKey)) return false;
            if (!(o.getClass().equals(WeakKey.class))) return false;
      Object t = this.get();
            @SuppressWarnings("unchecked")
      Object u = ((WeakKey)o).get();
      if ((t == null) || (u == null)) return false;
      if (t == u) return true;
      return keyEquals(t, u);
  }

  /*@Pure*/
  @Override
  public int hashCode() {
      return hash;
  }

    }


    /* Hash table mapping WeakKeys to values */
    private HashMap<WeakKey,V> hash;

    /* Reference queue for cleared WeakKeys */
    private ReferenceQueue<? super K> queue = new ReferenceQueue<K>();


    /* Remove all invalidated entries from the map, that is, remove all entries
       whose keys have been discarded.  This method should be invoked once by
       each public mutator in this class.  We don't invoke this method in
       public accessors because that can lead to surprising
       ConcurrentModificationExceptions. */
    @SuppressWarnings("unchecked")
    private void processQueue() {
  WeakKey wk;
  while ((wk = (WeakKey)queue.poll()) != null) { // unchecked cast
      hash.remove(wk);
  }
    }


    /* -- Constructors -- */

    /**
     * Constructs a new, empty <code>WeakHashMap</code> with the given
     * initial capacity and the given load factor.
     *
     * @param  initialCapacity  the initial capacity of the
     *                          <code>WeakHashMap</code>
     *
     * @param  loadFactor       the load factor of the <code>WeakHashMap</code>
     *
     * @throws IllegalArgumentException  If the initial capacity is less than
     *                                   zero, or if the load factor is
     *                                   nonpositive
     */
    public WeakHasherMap(int initialCapacity, float loadFactor) {
  hash = new HashMap<WeakKey,V>(initialCapacity, loadFactor);
    }

    /**
     * Constructs a new, empty <code>WeakHashMap</code> with the given
     * initial capacity and the default load factor, which is
     * <code>0.75</code>.
     *
     * @param  initialCapacity  the initial capacity of the
     *                          <code>WeakHashMap</code>
     *
     * @throws IllegalArgumentException  If the initial capacity is less than
     *                                   zero
     */
    public WeakHasherMap(int initialCapacity) {
  hash = new HashMap<WeakKey,V>(initialCapacity);
    }

    /**
     * Constructs a new, empty <code>WeakHashMap</code> with the default
     * capacity and the default load factor, which is <code>0.75</code>.
     */
    public WeakHasherMap() {
  hash = new HashMap<WeakKey,V>();
    }

    /**
     * Constructs a new, empty <code>WeakHashMap</code> with the default
     * capacity and the default load factor, which is <code>0.75</code>.
     * The <code>WeakHashMap</code> uses the specified hasher for hashing
     * keys and comparing them for equality.
     * @param h the Hasher to use when hashing values for this map
     */
    public WeakHasherMap(Hasher h) {
  hash = new HashMap<WeakKey,V>();
  hasher = h;
    }


    /* -- Simple queries -- */

    /**
     * Returns the number of key-value mappings in this map.
     * <strong>Note:</strong> <em>In contrast to most implementations of the
     * <code>Map</code> interface, the time required by this operation is
     * linear in the size of the map.</em>
     */
    /*@Pure*/
    @Override
    public int size() {
  return entrySet().size();
    }

    /**
     * Returns <code>true</code> if this map contains no key-value mappings.
     */
    /*@Pure*/
    @Override
    public boolean isEmpty() {
  return entrySet().isEmpty();
    }

    /**
     * Returns <code>true</code> if this map contains a mapping for the
     * specified key.
     *
     * @param   key   the key whose presence in this map is to be tested
     */
    /*@Pure*/
    @Override
    public boolean containsKey(Object key) {
        @SuppressWarnings("unchecked")
        K kkey = (K) key;
  return hash.containsKey(WeakKeyCreate(kkey));
    }


    /* -- Lookup and modification operations -- */

    /**
     * Returns the value to which this map maps the specified <code>key</code>.
     * If this map does not contain a value for this key, then return
     * <code>null</code>.
     *
     * @param  key  the key whose associated value, if any, is to be returned
     */
    /*@Pure*/
    @Override
    public /*@Nullable*/ V get(Object key) {  // type of argument is Object, not K
        @SuppressWarnings("unchecked")
        K kkey = (K) key;
  return hash.get(WeakKeyCreate(kkey));
    }

    /**
     * Updates this map so that the given <code>key</code> maps to the given
     * <code>value</code>.  If the map previously contained a mapping for
     * <code>key</code> then that mapping is replaced and the previous value is
     * returned.
     *
     * @param  key    the key that is to be mapped to the given
     *                <code>value</code>
     * @param  value  the value to which the given <code>key</code> is to be
     *                mapped
     *
     * @return  the previous value to which this key was mapped, or
     *          <code>null</code> if if there was no mapping for the key
     */
    @Override
    public V put(K key, V value) {
  processQueue();
  return hash.put(WeakKeyCreate(key, queue), value);
    }

    /**
     * Removes the mapping for the given <code>key</code> from this map, if
     * present.
     *
     * @param  key  the key whose mapping is to be removed
     *
     * @return  the value to which this key was mapped, or <code>null</code> if
     *          there was no mapping for the key
     */
    @Override
    public V remove(Object key) { // type of argument is Object, not K
  processQueue();
        @SuppressWarnings("unchecked")
        K kkey = (K) key;
  return hash.remove(WeakKeyCreate(kkey));
    }

    /**
     * Removes all mappings from this map.
     */
    @Override
    public void clear() {
  processQueue();
  hash.clear();
    }


    /* -- Views -- */


    /* Internal class for entries */
    // This can't be static, again because of dependence on hasher.
    @SuppressWarnings("TypeParameterShadowing")
    private final class Entry<K,V> implements Map.Entry<K,V> {
  private Map.Entry<WeakKey,V> ent;
  private K key;  /* Strong reference to key, so that the GC
           will leave it alone as long as this Entry
           exists */

  Entry(Map.Entry<WeakKey,V> ent, K key) {
      this.ent = ent;
      this.key = key;
  }

  /*@Pure*/
  @Override
  public K getKey() {
      return key;
  }

  /*@Pure*/
  @Override
  public V getValue() {
      return ent.getValue();
  }

  @Override
  public V setValue(V value) {
      return ent.setValue(value);
  }

        /*@Pure*/
        private boolean keyvalEquals(K o1, K o2) {
      return (o1 == null) ? (o2 == null) : keyEquals(o1, o2);
  }

        /*@Pure*/
        private boolean valEquals(V o1, V o2) {
      return (o1 == null) ? (o2 == null) : o1.equals(o2);
  }

        /*@Pure*/
        @SuppressWarnings("NonOverridingEquals")
        public boolean equals(Map.Entry<K,V> e /* Object o*/) {
            // if (! (o instanceof Map.Entry)) return false;
            // Map.Entry<K,V> e = (Map.Entry<K,V>)o;
      return (keyvalEquals(key, e.getKey())
        && valEquals(getValue(), e.getValue()));
  }

  /*@Pure*/
  @Override
  public int hashCode() {
      V v;
      return (((key == null) ? 0 : keyHashCode(key))
        ^ (((v = getValue()) == null) ? 0 : v.hashCode()));
  }

    }


    /* Internal class for entry sets */
    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
  Set<Map.Entry<WeakKey,V>> hashEntrySet = hash.entrySet();

  @Override
  public Iterator<Map.Entry<K,V>> iterator() {

      return new Iterator<Map.Entry<K,V>>() {
    Iterator<Map.Entry<WeakKey,V>> hashIterator = hashEntrySet.iterator();
    Map.Entry<K,V> next = null;

    @Override
    public boolean hasNext() {
        while (hashIterator.hasNext()) {
      Map.Entry<WeakKey,V> ent = hashIterator.next();
      WeakKey wk = ent.getKey();
      K k = null;
      if ((wk != null) && ((k = wk.get()) == null)) {
          /* Weak key has been cleared by GC */
          continue;
      }
      next = new Entry<K,V>(ent, k);
      return true;
        }
        return false;
    }

    @Override
    public Map.Entry<K,V> next() {
        if ((next == null) && !hasNext())
      throw new NoSuchElementException();
        Map.Entry<K,V> e = next;
        next = null;
        return e;
    }

    @Override
    public void remove() {
        hashIterator.remove();
    }

      };
  }

  /*@Pure*/
  @Override
  public boolean isEmpty() {
      return !(iterator().hasNext());
  }

  /*@Pure*/
  @Override
  public int size() {
      int j = 0;
      for (Iterator<Map.Entry<K,V>> i = iterator(); i.hasNext(); i.next()) j++;
      return j;
  }

  @Override
  public boolean remove(Object o) {
      processQueue();
      if (!(o instanceof Map.Entry<?,?>)) return false;
            @SuppressWarnings("unchecked")
      Map.Entry<K,V> e = (Map.Entry<K,V>)o; // unchecked cast
      Object ev = e.getValue();
      WeakKey wk = WeakKeyCreate(e.getKey());
      Object hv = hash.get(wk);
      if ((hv == null)
    ? ((ev == null) && hash.containsKey(wk)) : hv.equals(ev)) {
    hash.remove(wk);
    return true;
      }
      return false;
  }

  /*@Pure*/
  @Override
  public int hashCode() {
      int h = 0;
      for (Iterator<Map.Entry<WeakKey,V>> i = hashEntrySet.iterator(); i.hasNext(); ) {
    Map.Entry<WeakKey,V> ent = i.next();
    WeakKey wk = ent.getKey();
    Object v;
    if (wk == null) continue;
    h += (wk.hashCode()
          ^ (((v = ent.getValue()) == null) ? 0 : v.hashCode()));
      }
      return h;
  }

    }


    private /*@Nullable*/ Set<Map.Entry<K,V>> entrySet = null;

    /**
     * Returns a <code>Set</code> view of the mappings in this map.
     */
    /*@SideEffectFree*/
    @Override
    public Set<Map.Entry<K,V>> entrySet() {
  if (entrySet == null) entrySet = new EntrySet();
  return entrySet;
    }

    // find matching key
    K findKey(Object key) {
      processQueue();
      K kkey = (K) key;
      // TODO: use replacement for HashMap to avoid reflection
      WeakKey wkey = WeakKeyCreate(kkey);
      WeakKey found = hashMap_findKey(hash, wkey);
      return found == null ? null : found.get();
    }
}
static class proxy_InvocationHandler implements InvocationHandler {
  Object target;
  
  proxy_InvocationHandler() {}
  proxy_InvocationHandler(Object target) {
  this.target = target;}
  
  public Object invoke(Object proxy, Method method, Object[] args) {
    return call(target, method.getName(), unnull(args));
  }
}
// optimized for search - O(1) for searching for a token
// set is O(log m) with m = number of occurrences of token
final static class TokenIndexedList3 extends RandomAccessAbstractList<String> implements IContentsIndexedList<String>, IContentsIndexedList2<String> {
  final HashMap<String, TreeSet<Token>> index = new HashMap(); // tokens by contents, sorted by index
  final ArrayList<Token> list = new ArrayList();

  final 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);
  }
  
  @Override
  public int indexOf(Object s) {
    TreeSet<Token> l = index.get(s);
    return l == null ? -1 : first(l).idx;
  }
  
  @Override
  public int lastIndexOf(Object s) {
    TreeSet<Token> l = index.get(s);
    return l == null ? -1 : last(l).idx;
  }
  
  @Override
  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;
    
    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);
    if (transpiledSrc == null) throw fail("Snippet not found or not public: " + 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> tok = javaTok(src);
      List<String> mainClass = findMainClass(tok);
      boolean canRename = mainClass != null && useDummyMainClasses() && isSnippetID(progID) && !tok_classHasModifier(mainClass, "public");
      if (verbose)
        print("useRenaming: " + useDummyMainClasses() + ", canRename: " + canRename + ", progID: " + progID);
        
      String mainClassName = joinNemptiesWithDot(tok_packageName(tok), or(getClassDeclarationName(mainClass), "main"));
      
       AutoCloseable __1 = tempSetTL(javaCompileToJar_addMoreFiles, 
        eq(mainClassName, "main") ? null
        : dir -> saveTextFile(newFile(dir, "main-class"), mainClassName)); try {

      javaCompileToJar_optionalRename(src, dehlibs, jar, canRename ? progID : null, progID);
    } finally { _close(__1); }} 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 T3<A, B, C> {
  A a;
  B b;
  C c;
  
  T3() {}
  T3(A a, B b, C c) {
  this.c = c;
  this.b = b;
  this.a = a;}
  T3(T3<A, B, C> t) { a = t.a; b = t.b; c = t.c; }
  
  public int hashCode() {
    return _hashCode(a) + 2*_hashCode(b) - 4*_hashCode(c);
  }
  
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof T3)) return false;
    T3 t = (T3) o;
    return eq(a, t.a) && eq(b, t.b) && eq(c, t.c);
  }
  
  public String toString() {
    return "(" + quoteBorderless(a) + ", " + quoteBorderless(b) + ", " + quoteBorderless(c) + ")";
  }
}
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 interface IResourceHolder {
  <A extends AutoCloseable> A add(A a);
}
static class JavaXClassLoader extends URLClassLoader {
  String progID;
  Set<File> files = syncLinkedHashSet();
  Set<String> triedToLoad = synchroSet();
  Set<Class> loadedClasses = synchroSet();
  boolean retired = false;
  Object retiredMarker;
  IF1<String, Class> findClass_extension;
  String mainClassName;
  boolean verbose = false;
  
  JavaXClassLoader(String progID, List<File> files) {
    this(progID, files, getSystemClassLoader());
  }
  
  JavaXClassLoader(String progID, List<File> files, ClassLoader parent) {
    // sadly can't see this constructor
    //super(progID, new URL[0], parent, vm_globalACC());
    super(new URL[0], parent);
    this.progID = progID;

    for (File f : unnullForIteration(files))
      addFile(f);
      
    // TODO: how to get around this
    fixACCInClassLoader(this);
  }
  
  Class<?> super_findClass(String name) throws ClassNotFoundException {
    return super.findClass(name);
  }
  
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (verbose) System.out.println(this + " findClass: " + name);
    if (findClass_extension != null) {
      Class<?> c = findClass_extension.get(name);
      if (verbose) System.out.println("extension returned: " + c);
      if (c != null) return c;
    }
    boolean triedBefore = !triedToLoad.add(name);
    try {
      Class<?> c = super.findClass(name);
      if (verbose) System.out.println("super.findClass returned: " + c);
      loadedClasses.add(c);
      if (eq(name, mainClassName()))
        callOpt(javax(), "registerAMainClass", c);
      return c;
    } catch (ClassNotFoundException e) {
      if (verbose) System.out.println(getStackTrace(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 shortClassName(this) + "[" + systemHashCodeHex(this) + "] - " + progID;
  }
  
  String mainClassName() {
    if (mainClassName == null)
      mainClassName = or2(trim(loadTextFileResource(this, "main-class")), "main");
    return mainClassName;
  }
  
  boolean addFile(File f) { try {
    if (!files.add(f)) return false;
    addURL(f.toURI().toURL());
    mainClassName(); // calculate early. no good loading text files later
    return true;
  } catch (Exception __e) { throw rethrow(__e); } }
}

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);
  }
}
    
// it's unclear whether the end is inclusive or exclusive
// (usually exclusive I guess)
static class IntRange {
  int start, end;
  
  IntRange() {}
  IntRange(int start, int end) {
  this.end = end;
  this.start = start;}
  IntRange(IntRange r) { start = r.start; end = r.end; }
  
  public boolean equals(Object o) { return stdEq2(this, o); }
public int hashCode() { return stdHash2(this); }
  
  final int length() { return end-start; }
  final boolean empty() { return start >= end; }
  final boolean isEmpty() { return start >= end; }
  
  static String _fieldOrder = "start end";
  
  public String toString() { return "[" + start + ";" + end + "]"; }
}
static interface IAutoCloseableF0<A> extends IF0<A>, AutoCloseable {}
// 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 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 class PersistableThrowable extends DynamicObject {
  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 class TokenRange extends IntRange {
  TokenRange() {}
  TokenRange(int start, int end) {
  this.end = end;
  this.start = start;}
}
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 IFieldsToList {
  Object[] _fieldsToList();
}
static interface ISetter<A> {
 void set(A a);
}

// uses HashMap by default
static class MultiSet<A> implements IMultiSet<A> {
  Map<A, Integer> map = new HashMap();
  int size; // now maintaining a size counter
  
  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));
  }}
  
  // returns new count
  public synchronized int add(A key) { return 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 int add(A key, int count) {
    if (count <= 0) return 0; // don't calculate return value in this case
    size += count;
    Integer i = map.get(key);
    map.put(key, i != null ? (count += i) : count);
    return count;
  }

  synchronized void put(A key, int count) {
    int oldCount = get(key);
    if (count == oldCount) return;
    size += count-oldCount;
    if (count != 0)
      map.put(key, count);
    else
      map.remove(key);
  }

  public synchronized int get(A key) {
    Integer i = map.get(key);
    return i != null ? i : 0;
  }
  
  synchronized boolean contains(A key) {
    return map.containsKey(key);
  }

  synchronized void remove(A key) {
    Integer i = map.get(key);
    if (i != null) {
      --size;
      if (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) {
    size -= get(key);
    map.remove(key);
  }

  synchronized int size() {
    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();
    size = 0;
  }
  
  synchronized Map<A, Integer> asMap() {
    return cloneMap(map);
  }
}
static interface ISubList<E> {
  public List<E> rootList();
  public List<E> parentList();
  public int subListOffset();
}


static interface IMultiSet<A> {
  // returns new count
  int add(A key);
  
  /*void addAll(Iterable<A> c) {
    if (c != null) for (A a : c) add(a);
  }

  void addAll(MultiSet<A> ms) {
    for (A a : ms.keySet()) add(a, ms.get(a));
  }*/
  
  //int add(A key, int count);

  //void put(A key, int count);

  int get(A key);
  //bool contains(A key);

  //void remove(A key);

  /*List<A> topTen();
  
  synchronized List<A> getTopTen() { ret getTopTen(10); }
  synchronized List<A> getTopTen(int maxSize) {
    List<A> list = getSortedListDescending();
    return list.size() > maxSize ? list.subList(0, maxSize) : list;
  }
  
  synchronized L<A> highestFirst() {
    ret getSortedListDescending();
  }

  synchronized L<A> lowestFirst() {
    ret reversedList(getSortedListDescending());
  }

  synchronized L<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));
      }
    });
    ret list;
  }

  synchronized int getNumberOfUniqueElements() {
    return map.size();
  }
  
  synchronized int uniqueSize() {
    ret 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) {
    size -= get(key);
    map.remove(key);
  }

  synchronized int size() {
    ret 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();
    size = 0;
  }
  
  synchronized Map<A, Int> asMap() {
    ret cloneMap(map);
  }*/
}


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 <A, B> B remove(Map<A, B> map, Object a) {
  return map == null ? null : map.remove(a);
}


static <A> ListIterator<A> listIterator(List<A> l) {
  return l == null ? emptyListIterator() : l.listIterator();
}


static void _subListRangeCheck(int fromIndex, int toIndex, int size) {
  subListRangeCheck(fromIndex, toIndex, size);
}


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 <A, B> Set<Map.Entry<A,B>> entrySet(Map<A, B> map) {
  return _entrySet(map);
}


static String shortClassName_dropNumberPrefix(Object o) {
  return dropNumberPrefix(shortClassName(o));
}


static UnsupportedOperationException unsupportedOperation() {
  throw new UnsupportedOperationException();
}


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 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 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 <A, B, C> Map<A, C> mapValues(IF1<B, C> f, Map<A, B> map) {
  Map m = similarEmptyMap(map);
  for (Map.Entry<? extends A, ? extends B> __0 : _entrySet( map))
    { A key = __0.getKey(); B val = __0.getValue();  m.put(key, f.get(val)); }
  return m;
}

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 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 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;
}


// TODO: use actualUserHome()?
// (there was a problem with onLocallyInferiorJavaX() always triggering inside #1013896)

static File pathToJavaxJar() {
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.pathToJavaXJar();
  
  
  return pathToJavaxJar_noResourceLoader();
}
  
static File pathToJavaxJar_noResourceLoader() { try {
  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 Method hashMap_findKey_method;

static <A, B> A hashMap_findKey(HashMap<A, B> map, Object key) { try {
  if (hashMap_findKey_method == null)
    hashMap_findKey_method = findMethodNamed(HashMap.class, "getNode");
  Map.Entry<A, B> entry = (Map.Entry) hashMap_findKey_method.invoke(map, hashMap_internalHash(key), key);
  // java.util.Map.Entry<A, B> entry = (java.util.Map.Entry) call(hash, 'getNode, hashMap_internalHash(key), wkey);
  return entry == null ? null : entry.getKey();
} catch (Exception __e) { throw rethrow(__e); } }




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);
  
  
  return getServerTranspiled2_noResourceLoader(id);
}
  
static String getServerTranspiled2_noResourceLoader(String 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);
}


static String joinNemptiesWithDot(Object... strings) {
  return joinNempties(".", strings);
}

static String joinNemptiesWithDot(Iterable strings) {
  return joinNempties(".", strings);
}


// before you use this, add a RAM disk cleaner
static boolean javaCompileToJar_useRAMDisk = false;

static ThreadLocal<List<File>> javaCompileToJar_localLibraries = new ThreadLocal();
static ThreadLocal<IVF1<File>> javaCompileToJar_addMoreFiles = new ThreadLocal();

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 fileName = dummyClass + ".java";
  
  // deriver name of main Java file from source
  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 = cloneList(getAndClearTL(javaCompileToJar_localLibraries));
    
    Matcher m = Pattern.compile("\\d+").matcher(dehlibs);
    while (m.find()) {
      String libID = m.group();
      //print("libID=" + quote(libID));
      assertTrue(isSnippetID(libID));
      print("Adding library " + 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")) {
      printWithIndent("SRC> ", src);
      throw fail("No classes generated (was compiling " + nChars(src) + ")");
    }
        
    // 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);

    // save pointer to main Java source    
    //saveTextFile(new File(classesDir, "main-src"), fileName);
    
    callF(javaCompileToJar_addMoreFiles.get(), classesDir);
  
    //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 File touchFile(File file) { try {
  closeRandomAccessFile(newRandomAccessFile(mkdirsForFile(file), "rw"));
  return file;
} catch (Exception __e) { throw rethrow(__e); } }


static File actualUserDir() {
  return new File(actualUserHome());
}

static File actualUserDir(String path) {
  return new File(actualUserHome(), path);
}


static String quoteBorderless(Object o) {
  if (o == null) return "null";
  return quoteBorderless(str(o));
}

static String quoteBorderless(String s) {
  if (s == null) return "null";
  StringBuilder out = new StringBuilder((int) (l(s)*1.5));
  quoteBorderless_impl(s, out);
  return out.toString();
}
  
static void quoteBorderless_impl(String s, StringBuilder out) {
  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
      out.append(c);
  }
}


static int hashCodeFor(Object a) {
  return a == null ? 0 : a.hashCode();
}


static <A> Set<A> syncLinkedHashSet() {
  return synchroLinkedHashSet();
}


static void fixACCInClassLoader(Object o) {
  try {
    AccessControlContext acc = vm_globalACC();
    if (acc != null)
      replaceACCInClassLoader(o, acc);
  } catch (Throwable __e) { _handleException(__e); }
}


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 String systemHashCodeHex(Object o) {
  return intToHex(identityHashCode(o));
}


static String loadTextFileResource(ClassLoader cl, String name) {
  return inputStreamToString(cl.getResourceAsStream(name));
}


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 ListIterator emptyListIterator() {
  return Collections.emptyListIterator();
}


static void subListRangeCheck(int fromIndex, int toIndex, int size) {
  if (fromIndex < 0)
      throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
  if (toIndex > size)
      throw new IndexOutOfBoundsException("toIndex = " + toIndex);
  if (fromIndex > toIndex)
      throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                         ") > toIndex(" + toIndex + ")");
}


static <A, B> Set<Map.Entry<A,B>> _entrySet(Map<A, B> map) {
  return map == null ? Collections.EMPTY_SET : map.entrySet();
}


static String dropNumberPrefix(String s) {
  return dropFirst(s, indexOfNonDigit(s));
}


static Map<Class, Set<String>> allFields_cache = weakHashMap();

static Set<String> allFields(Object o) {
  if (o == null) return emptySet();
  Class _c = _getClass(o);
  Set<String> fields = allFields_cache.get(_c);
  if (fields == null)
    allFields_cache.put(_c, fields = asTreeSet(keys(getOpt_getFieldMap(o))));
  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 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 int latestInstalledJavaX() {
  File[] files = new File(userHome(), ".javax").listFiles();
  int v = 0;
  if (files != null) for (File f : files) {
    Matcher m = regexpMatcher("x(\\d\\d\\d?)\\.jar", 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 hashMap_internalHash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}


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) { printExceptionShort("", e); }
static void printExceptionShort(String prefix, Throwable e) {
  print(prefix, 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 <A> A getAndClearTL(ThreadLocal<A> tl) {
  return getAndClearThreadLocal(tl);
}


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 {
  vmBus_send("loadLibraryOrSrcLib", snippetID);
  long id = parseSnippetID(snippetID);
  
  if (loadLibraryOrSrcLib_tempCache.get() != null) {
    File f = loadLibraryOrSrcLib_tempCache.get().get(id);
    if (f != null)
      { print(snippetID + " from tempCache: " + f); return f; }
  }
  
  boolean srcLib = loadLibraryOrSrcLib_srcLibsEnabled && isMarkedAsSrcLib(snippetID);
  if (srcLib) {
    print(snippetID + " marked as src lib, compiling");
    File f = pairA(hotwire_compile(snippetID));
    mapPut(loadLibraryOrSrcLib_tempCache.get(), id, f);
    return f;
  }
    
  File f = DiskSnippetCache_getLibrary(id);
  if (fileSize(f) != 0) { print(snippetID + " from disk cache: " + f); return f; }
  try {
    print("Trying " + snippetID + " as binary library");
    return loadDataSnippetToFile(snippetID);
  } catch (Throwable e) {
    if (loadLibraryOrSrcLib_srcLibsEnabled) {
      print("Trying " + snippetID + " as src lib");
      if (nempty(loadSnippet(snippetID))) {
        print("Is src lib.");
        markAsSrcLib(snippetID);
        return pairA(hotwire_compile(snippetID));
      }
    }
    throw rethrow(e);
  }
} catch (Exception __e) { throw rethrow(__e); } }


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 String nChars(long n) { return n2(n, "char"); }
static String nChars(String s) { return nChars(l(s)); }


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 startsWithOneOf(f2s(f), "/dev/shm/", "/run/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) { try {
  boolean forWrite = mode.indexOf('w') >= 0;
  if (forWrite) mkdirsForFile(path);
  RandomAccessFile f = new RandomAccessFile(path, mode);
  
  callJavaX("registerIO", f, path, forWrite);
  
  return f;
} catch (Exception __e) { throw rethrow(__e); } }


// BREAKING CHANGE!
// Also NOTE: Iterators of these sync-wrapped collections
// after generally NOT thread-safe!
// TODO: change that?
static <A> Set<A> synchroLinkedHashSet() {
  
  
  return Collections.synchronizedSet(new CompactLinkedHashSet());
  
}


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 String inputStreamToString(InputStream in) {
  return utf8streamToString(in);
}




static int indexOfNonDigit(String s) {
  int n = l(s);
  for (int i = 0; i < n; i++)
    if (!isDigit(s.charAt(i)))
      return i;
  return -1;
}


static <A> TreeSet<A> asTreeSet(Collection<A> set) {
  return set == null ? null : set instanceof TreeSet ? (TreeSet) set : new TreeSet(set);
}


static int boostHashCombine(int a, int b) {
  return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}


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 String aGlobalID(Random random) {
  return randomID(random, 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 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) {
  return hotwire_overInternalBot(progID, __ -> mainClass);
}

static Class<?> hotwire_overInternalBot(String progID, IF1<ClassLoader, String> calculateMainClass) { 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;
  
  String mainClass = calculateMainClass == null ? "main" : calculateMainClass.get(classLoader);
  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 __0 = hotwire_serially ? hotwire_overInternalBot_lock : null; lock(__0); try {
  callF(hotwire_onCompile, p);
  return p;
} finally { unlock(__0); } }


static void markAsSrcLib(String snippetID) {
  saveTextFile(javaxCodeDir("srclibs/" + psI(snippetID)), "");
}


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) { ping(); 
    if (dir instanceof String && ((String) dir).endsWith("/.")) {
      // "/." means non-recurse
      for  (File f : listFiles(dropSuffix("/.", ((String) dir))))
        { ping(); 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)) { ping(); 
    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, (a, b) -> 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 String loadGZTextFile(File file) { try {
  if (!file.isFile()) return null;
  ping();
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
   InputStream fis = new FileInputStream(file); try {
  GZIPInputStream gis = newGZIPInputStream(fis);
  byte[] buffer = new byte[1024];
  int len;
  while ((len = gis.read(buffer)) != -1) baos.write(buffer, 0, len);
  baos.close();
  return fromUtf8(baos.toByteArray()); // TODO: use a Reader
} finally { _close(fis); }} 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) { saveGZTextFile(file, contents, "UTF-8"); }
static void saveGZTextFile(File file, String contents, String encoding) { 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, encoding);
    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 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 List<File> filesEndingWith(String suffix, File dir) {
  return filesEndingWith(dir, suffix);
}


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 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 int parseFirstInt(Iterable<String> l) {
  return parseInt(firstIntegerString(l));
}


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 firstIntegerString(Iterable<String> c) {
  Iterator<String> it = c.iterator();
  while (it.hasNext()) {
    String s = it.next();
    if (isInteger(s)) return s;
  }
  return null;
}


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 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 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));
}




// -has fast nextElement() and prevElement()
// -design allows for more functions like reordering the list
// -Saves up to 34% in space over LinkedHashSet
//    (e.g. 22% for a set of 1,000 Ints)
static class CompactLinkedHashSet<A> extends AbstractSet<A> {
  UnsynchronizedCompactHashSet<Entry<A>> entries = new UnsynchronizedCompactHashSet();
  Entry<A> head, tail;
  
  static class Entry<A> {
    A value;
    Entry<A> prev, next;
    
    public int hashCode() {
      return _hashCode(value);
    }
    
    // "magic" equals function for CompactHashSet lookup without temp object
    public boolean equals(Object o) {
      return o == this || eq(value, o);
    }
  }
  
  public boolean add(A a) {
    if (entries.contains(a)) return false;
    Entry<A> n = new Entry();
    n.value = a;
    n.prev = tail;
    if (tail != null) tail.next = n;
    tail = n;
    if (head == null) head = n;
    entries.add(n);
    return true;
  }
  
  public boolean remove(Object a) {
    return remove(entries.find(a));
  }
  
  public boolean remove(Entry<A> node) {
    if (node == null) return false;
    if (node.next != null) node.next.prev = node.prev; else tail = node.prev;
    if (node.prev != null) node.prev.next = node.next; else head = node.next;
    entries.remove(node);
    return true;
  }
  
  public int size() { return entries.size(); }
  
  public IterableIterator<A> iterator() {
    return new IterableIterator<A>() {
      Entry<A> entry = head, prev = null;
      public boolean hasNext() { return entry != null; }
      public A next() {
        A a = entry.value;
        prev = entry;
        entry = entry.next;
        return a;
      }
      
      // untested
      public void remove() {
        if (prev == null) throw new IllegalStateException();
        CompactLinkedHashSet.this.remove(prev);
        prev = null;
      }
    };
  }
  
  public void clear() {
    entries.clear();
    head = tail = null;
  }
  
  public boolean contains(Object a) {
    return entries.contains(a);
  }
  
  public A find(Object o) {
    Entry<A> e = entries.find(o);
    return e == null ? null : e.value;
  }
  
  public A prevElement(A a) {
    Entry<A> e = entries.find(a);
    if (e == null || e.prev == null) return null;
    return e.prev.value;
  }
  
  public A nextElement(A a) {
    Entry<A> e = entries.find(a);
    if (e == null || e.next == null) return null;
    return e.next.value;
  }
  
  public A first() { return head == null ? null : head.value; }
  public A last() { return tail == null ? null : tail.value; }
  
  boolean removeIfSame(Object o) {
    A value = find(o);
    if (value == o) {
      remove(value);
      return true;
    }
    return false;
  }
}
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;}
  Cache(IF0<A> maker) {
  this.maker = maker;}

  A get() {
    if (hasLock(lock)) return value; // Must be called from within maker
    Lock __0 = lock; lock(__0); try {
    if (loaded == 0) {
      value = make();
      changeCount++;
      loaded = sysNow();
    }
    return value;
  } finally { unlock(__0); } }

  void clear() {
    Lock __1 = lock; lock(__1); try {
    if (debug && loaded != 0)
      print("Clearing cache");
    value = null;
    changeCount++;
    loaded = 0;
  } finally { unlock(__1); } }

  // clear if older than x seconds
  // 0 does not do anything
  void clear(double seconds) {
    Lock __2 = lock; lock(__2); try {
    if (seconds != 0 && loaded != 0 && sysNow() >= loaded+seconds*1000)
      clear();
  } finally { unlock(__2); } }
  
  // override
  void set(A a) {
    Lock __3 = lock; lock(__3); try {
    value = a;
    ++changeCount;
    loaded = sysNow();
  } finally { unlock(__3); } }
  
  A make() {
    return (A) callF(maker);
  }
}


/*
 * #!
 * Ontopia Engine
 * #-
 * Copyright (C) 2001 - 2013 The Ontopia Project
 * #-
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * !#
 */

// modified by Stefan Reich

// Implements the Set interface more compactly than
// java.util.HashSet by using a closed hashtable.

// Note: equals is always called on the _stored_ object, not the one
// passed as an argument to find(), contains() etc.
// (In case you want to put special magic in your equals() function)

static class UnsynchronizedCompactHashSet<A> extends java.util.AbstractSet<A> {
  protected final static int INITIAL_SIZE = 3;
  public final static double LOAD_FACTOR = 0.75;

  protected final static Object nullObject = new Object();
  protected final static Object deletedObject = new Object();
  protected int elements;
  protected int freecells;
  protected A[] objects;
  
  protected int modCount;
  
  
  UnsynchronizedCompactHashSet() {
    this(INITIAL_SIZE);
  }

  UnsynchronizedCompactHashSet(int size) {
    // NOTE: If array size is 0, we get a
    // "java.lang.ArithmeticException: / by zero" in add(Object).
    objects = (A[]) new Object[(size==0 ? 1 : size)];
    elements = 0;
    freecells = objects.length;
    
      modCount = 0;
    
  }

  UnsynchronizedCompactHashSet(Collection<A> c) {
    this(c.size());
    addAll(c);
  }

  @Override
  public Iterator<A> iterator() {
    return new CompactHashIterator<A>();
  }

  @Override
  public int size() {
    return elements;
  }

  @Override
  public boolean isEmpty() {
    return elements == 0;
  }

  @Override
  public boolean contains(Object o) {
    return find(o) != null;
  }
  
  A find(Object o) {
    if (o == null) o = nullObject;
    
    int hash = o.hashCode();
    int index = (hash & 0x7FFFFFFF) % objects.length;
    int offset = 1;

    // search for the object (continue while !null and !this object)
    while(objects[index] != null &&
          !(objects[index].hashCode() == hash &&
            objects[index].equals(o))) {
      index = ((index + offset) & 0x7FFFFFFF) % objects.length;
      offset = offset*2 + 1;

      if (offset == -1)
        offset = 2;
    }

    return objects[index];
  }
  
  boolean removeIfSame(Object o) {
    A value = find(o);
    if (value == o) {
      remove(value);
      return true;
    }
    return false;
  }

  @Override
  public boolean add(Object o) {
    if (o == null) o = nullObject;

    int hash = o.hashCode();
    int index = (hash & 0x7FFFFFFF) % objects.length;
    int offset = 1;
    int deletedix = -1;
    
    // search for the object (continue while !null and !this object)
    while(objects[index] != null &&
          !(objects[index].hashCode() == hash &&
            objects[index].equals(o))) {

      // if there's a deleted object here we can put this object here,
      // provided it's not in here somewhere else already
      if (objects[index] == deletedObject)
        deletedix = index;
      
      index = ((index + offset) & 0x7FFFFFFF) % objects.length;
      offset = offset*2 + 1;

      if (offset == -1)
        offset = 2;
    }
    
    if (objects[index] == null) { // wasn't present already
      if (deletedix != -1) // reusing a deleted cell
        index = deletedix;
      else
        freecells--;

      
        modCount++;
      
      elements++;

      // here we face a problem regarding generics:
      // add(A o) is not possible because of the null Object. We cant do 'new A()' or '(A) new Object()'
      // so adding an empty object is a problem here
      // If (! o instanceof A) : This will cause a class cast exception
      // If (o instanceof A) : This will work fine

      objects[index] = (A) o;
      
      // do we need to rehash?
      if (1 - (freecells / (double) objects.length) > LOAD_FACTOR)
        rehash();
      return true;
    } else // was there already 
      return false;
  }
  
  @Override
  public boolean remove(Object o) {
    if (o == null) o = nullObject;
    
    int hash = o.hashCode();
    int index = (hash & 0x7FFFFFFF) % objects.length;
    int offset = 1;
    
    // search for the object (continue while !null and !this object)
    while(objects[index] != null &&
          !(objects[index].hashCode() == hash &&
            objects[index].equals(o))) {
      index = ((index + offset) & 0x7FFFFFFF) % objects.length;
      offset = offset*2 + 1;

      if (offset == -1)
        offset = 2;
    }

    // we found the right position, now do the removal
    if (objects[index] != null) {
      // we found the object

      // same problem here as with add
      objects[index] = (A) deletedObject;
      
        modCount++;
      
      elements--;
      return true;
    } else
      // we did not find the object
      return false;
  }
  
  @Override
  public void clear() {
    elements = 0;
    for (int ix = 0; ix < objects.length; ix++)
      objects[ix] = null;
    freecells = objects.length;
    
      modCount++;
    
  }

  @Override
  public Object[] toArray() {
    Object[] result = new Object[elements];
    Object[] objects = this.objects;
    int pos = 0;
    for (int i = 0; i < objects.length; i++)
      if (objects[i] != null && objects[i] != deletedObject) {
        if (objects[i] == nullObject)
          result[pos++] = null;
        else
          result[pos++] = objects[i];
      }
    // unchecked because it should only contain A
    return result;
  }

  // not sure if this needs to have generics
  @Override
  public <T> T[] toArray(T[] a) {
    int size = elements;
    if (a.length < size)
      a = (T[])java.lang.reflect.Array.newInstance(
                                 a.getClass().getComponentType(), size);
    A[] objects = this.objects;
    int pos = 0;
    for (int i = 0; i < objects.length; i++)
      if (objects[i] != null && objects[i] != deletedObject) {
        if (objects[i] == nullObject)
          a[pos++] = null;
        else
          a[pos++] = (T) objects[i];
      }
    return a;
  }
  
  protected void rehash() {
    int garbagecells = objects.length - (elements + freecells);
    if (garbagecells / (double) objects.length > 0.05)
      // rehash with same size
      rehash(objects.length);
    else
      // rehash with increased capacity
      rehash(objects.length*2 + 1);
  }
  
  protected void rehash(int newCapacity) {
    int oldCapacity = objects.length;
    @SuppressWarnings("unchecked")
    A[] newObjects = (A[]) new Object[newCapacity];

    for (int ix = 0; ix < oldCapacity; ix++) {
      Object o = objects[ix];
      if (o == null || o == deletedObject)
        continue;
      
      int hash = o.hashCode();
      int index = (hash & 0x7FFFFFFF) % newCapacity;
      int offset = 1;

      // search for the object
      while(newObjects[index] != null) { // no need to test for duplicates
        index = ((index + offset) & 0x7FFFFFFF) % newCapacity;
        offset = offset*2 + 1;

        if (offset == -1)
          offset = 2;
      }

      newObjects[index] = (A) o;
    }

    objects = newObjects;
    freecells = objects.length - elements;
  }
  
  private class CompactHashIterator<T> implements Iterator<T> {
    private int index;
    private int lastReturned = -1;

    
      private int expectedModCount;
    

    @SuppressWarnings("empty-statement")
    public CompactHashIterator() {
        for (index = 0; index < objects.length &&
                        (objects[index] == null ||
                        objects[index] == deletedObject); index++)
          ;
        
          expectedModCount = modCount;
        
    }

    @Override
    public boolean hasNext() {
        return index < objects.length;
    }

    @SuppressWarnings("empty-statement")
    @Override
    public T next() {
        /*if (modCount != expectedModCount)
          throw new ConcurrentModificationException();*/
        int length = objects.length;
        if (index >= length) {
          lastReturned = -2;
          throw new NoSuchElementException();
        }
  
        lastReturned = index;
        for (index += 1; index < length &&
                         (objects[index] == null ||
                          objects[index] == deletedObject); index++)
          ;
        if (objects[lastReturned] == nullObject)
          return null;
        else
          return (T) objects[lastReturned];
    }

    @Override
    public void remove() {
        
          if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        
        if (lastReturned == -1 || lastReturned == -2)
          throw new IllegalStateException();
        // delete object
        if (objects[lastReturned] != null && objects[lastReturned] != deletedObject) {
          objects[lastReturned] = (A) deletedObject;
          elements--;
          
            modCount++;
            expectedModCount = modCount; // this is expected; we made the change
          
        }
    }
  }
  
  int capacity() { return objects.length; }
  
  // returns true if there was a shrink
  boolean shrinkToFactor(double factor) {
    if (factor > LOAD_FACTOR)
      throw fail("Shrink factor must be equal to or smaller than load factor: " + factor + " / " + LOAD_FACTOR);
    int newCapacity = max(INITIAL_SIZE, iround(size()/factor));
    if (newCapacity >= capacity()) return false;
    rehash(newCapacity);
    return true;
  }
}


static String find(String pattern, String text) {
  Matcher matcher = Pattern.compile(pattern).matcher(text);
  if (matcher.find())
    return matcher.group(1);
  return null;
}

static <A> A find(Collection<A> c, Object... data) {
  for (A x : c)
    if (checkFields(x, data))
      return x;
  return null;
}


static boolean hasLock(Lock lock) {
  return ((ReentrantLock) lock).isHeldByCurrentThread();
}




static boolean checkFields(Object x, Object... data) {
  for (int i = 0; i < l(data); i += 2)
    if (neq(getOpt(x, (String) data[i]), data[i+1]))
      return false;
  return true;
}



}

Author comment

Began life as a copy of #759

download  show line numbers  debug dex  old transpilations   

Travelled to 2 computer(s): bhatertpkbcr, mqqgnosmbjvj

No comments. add comment

Snippet ID: #1032684
Snippet name: #759 transpilation backup
Eternal ID of this version: #1032684/1
Text MD5: 8888dc23a082c69604ee2061e7f85605
Author: stefan
Category: javax
Type: JavaX source code (desktop)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2021-10-01 23:22:41
Source code size: 826505 bytes / 28369 lines
Pitched / IR pitched: No / No
Views / Downloads: 173 / 239
Referenced in: [show references]