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

13769
LINES
[SHOW ALL]

< > TinyBrain | #1006722 - Translator for #759 (transpilation of older #759)

JavaX translator [tags: use-pretranspiled]

Uses 1020K of libraries. Click here for Pure Java version (13769L/103K/299K).

!1011230 // JavaParser

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




// ifdef in cached includes? => i think cached includes still contain the ifdefs, so it's fine. tok_ifdef is not called within localStuff1

import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.stmt.LocalClassDeclarationStmt;
import com.github.javaparser.printer.*;
import com.github.javaparser.*;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.visitor.*;
import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.text.NumberFormat;
class main {
static boolean autoQuine = true;
static int maxQuineLength = 80;
static boolean assumeTriple = true;

// _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"); 

static class Matches {
  String[] m;
  
  Matches() {}
  Matches(String... m) {
  this.m = m;}
  
  String get(int i) { return i < m.length ? m[i] : null; }
  String unq(int i) { return unquote(get(i)); }
  String fsi(int i) { return formatSnippetID(unq(i)); }
  String fsi() { return fsi(0); }
  String tlc(int i) { return unq(i).toLowerCase(); }
  boolean bool(int i) { return "true".equals(unq(i)); }
  String rest() { return m[m.length-1]; } // for matchStart
  int psi(int i) { return Integer.parseInt(unq(i)); }
}
 // Matches
/**
 * A class to compare vectors of objects.  The result of comparison
 * is a list of <code>change</code> objects which form an
 * edit script.  The objects compared are traditionally lines
 * of text from two files.  Comparison options such as "ignore
 * whitespace" are implemented by modifying the <code>equals</code>
 * and <code>hashcode</code> methods for the objects compared.
 * <p/>
 * The basic algorithm is described in: </br>
 * "An O(ND) Difference Algorithm and its Variations", Eugene Myers,
 * Algorithmica Vol. 1 No. 2, 1986, p 251.
 * <p/>
 * This class outputs different results from GNU diff 1.15 on some
 * inputs.  Our results are actually better (smaller change list, smaller
 * total size of changes), but it would be nice to know why.  Perhaps
 * there is a memory overwrite bug in GNU diff 1.15.
 *
 * @author Stuart D. Gathman, translated from GNU diff 1.15
 *         Copyright (C) 2000  Business Management Systems, Inc.
 *         <p/>
 *         This program is free software; you can redistribute it and/or modify
 *         it under the terms of the GNU General Public License as published by
 *         the Free Software Foundation; either version 1, or (at your option)
 *         any later version.
 *         <p/>
 *         This program is distributed in the hope that it will be useful,
 *         but WITHOUT ANY WARRANTY; without even the implied warranty of
 *         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *         GNU General Public License for more details.
 *         <p/>
 *         You should have received a copy of the <a href=COPYING.txt>
 *         GNU General Public License</a>
 *         along with this program; if not, write to the Free Software
 *         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

static class EGDiff {

  /**
   * Prepare to find differences between two arrays.  Each element of
   * the arrays is translated to an "equivalence number" based on
   * the result of <code>equals</code>.  The original Object arrays
   * are no longer needed for computing the differences.  They will
   * be needed again later to print the results of the comparison as
   * an edit script, if desired.
   */
  public EGDiff(Object[] a, Object[] b) {
    Hashtable h = new Hashtable(a.length + b.length);
    filevec[0] = new file_data(a, h);
    filevec[1] = new file_data(b, h);
  }

  /**
   * 1 more than the maximum equivalence value used for this or its
   * sibling file.
   */
  private int equiv_max = 1;

  /**
   * When set to true, the comparison uses a heuristic to speed it up.
   * With this heuristic, for files with a constant small density
   * of changes, the algorithm is linear in the file size.
   */
  public boolean heuristic = false;

  /**
   * When set to true, the algorithm returns a guarranteed minimal
   * set of changes.  This makes things slower, sometimes much slower.
   */
  public boolean no_discards = false;

  private int[] xvec, yvec;	/* Vectors being compared. */
  private int[] fdiag;		/* Vector, indexed by diagonal, containing
				   the X coordinate of the point furthest
				   along the given diagonal in the forward
				   search of the edit matrix. */
  private int[] bdiag;		/* Vector, indexed by diagonal, containing
				   the X coordinate of the point furthest
				   along the given diagonal in the backward
				   search of the edit matrix. */
  private int fdiagoff, bdiagoff;
  private final file_data[] filevec = new file_data[2];
  private int cost;

  /**
   * Find the midpoint of the shortest edit script for a specified
   * portion of the two files.
   * <p/>
   * We scan from the beginnings of the files, and simultaneously from the ends,
   * doing a breadth-first search through the space of edit-sequence.
   * When the two searches meet, we have found the midpoint of the shortest
   * edit sequence.
   * <p/>
   * The value returned is the number of the diagonal on which the midpoint lies.
   * The diagonal number equals the number of inserted lines minus the number
   * of deleted lines (counting only lines before the midpoint).
   * The edit cost is stored into COST; this is the total number of
   * lines inserted or deleted (counting only lines before the midpoint).
   * <p/>
   * This function assumes that the first lines of the specified portions
   * of the two files do not match, and likewise that the last lines do not
   * match.  The caller must trim matching lines from the beginning and end
   * of the portions it is going to specify.
   * <p/>
   * Note that if we return the "wrong" diagonal value, or if
   * the value of bdiag at that diagonal is "wrong",
   * the worst this can do is cause suboptimal diff output.
   * It cannot cause incorrect diff output.
   */

  private int diag(int xoff, int xlim, int yoff, int ylim) {
    final int[] fd = fdiag;	// Give the compiler a chance.
    final int[] bd = bdiag;	// Additional help for the compiler.
    final int[] xv = xvec;		// Still more help for the compiler.
    final int[] yv = yvec;		// And more and more . . .
    final int dmin = xoff - ylim;	// Minimum valid diagonal.
    final int dmax = xlim - yoff;	// Maximum valid diagonal.
    final int fmid = xoff - yoff;	// Center diagonal of top-down search.
    final int bmid = xlim - ylim;	// Center diagonal of bottom-up search.
    int fmin = fmid, fmax = fmid;	// Limits of top-down search.
    int bmin = bmid, bmax = bmid;	// Limits of bottom-up search.
    /* True if southeast corner is on an odd
				     diagonal with respect to the northwest. */
    final boolean odd = (fmid - bmid & 1) != 0;

    fd[fdiagoff + fmid] = xoff;
    bd[bdiagoff + bmid] = xlim;

    for (int c = 1; ; ++c) {
      int d;			/* Active diagonal. */
      boolean big_snake = false;

      /* Extend the top-down search by an edit step in each diagonal. */
      if (fmin > dmin)
        fd[fdiagoff + --fmin - 1] = -1;
      else
        ++fmin;
      if (fmax < dmax)
        fd[fdiagoff + ++fmax + 1] = -1;
      else
        --fmax;
      for (d = fmax; d >= fmin; d -= 2) {
        int x, y, oldx, tlo = fd[fdiagoff + d - 1], thi = fd[fdiagoff + d + 1];

        if (tlo >= thi)
          x = tlo + 1;
        else
          x = thi;
        oldx = x;
        y = x - d;
        while (x < xlim && y < ylim && xv[x] == yv[y]) {
          ++x;
          ++y;
        }
        if (x - oldx > 20)
          big_snake = true;
        fd[fdiagoff + d] = x;
        if (odd && bmin <= d && d <= bmax && bd[bdiagoff + d] <= fd[fdiagoff + d]) {
          cost = 2 * c - 1;
          return d;
        }
      }

      /* Similar extend the bottom-up search. */
      if (bmin > dmin)
        bd[bdiagoff + --bmin - 1] = Integer.MAX_VALUE;
      else
        ++bmin;
      if (bmax < dmax)
        bd[bdiagoff + ++bmax + 1] = Integer.MAX_VALUE;
      else
        --bmax;
      for (d = bmax; d >= bmin; d -= 2) {
        int x, y, oldx, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1];

        if (tlo < thi)
          x = tlo;
        else
          x = thi - 1;
        oldx = x;
        y = x - d;
        while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) {
          --x;
          --y;
        }
        if (oldx - x > 20)
          big_snake = true;
        bd[bdiagoff + d] = x;
        if (!odd && fmin <= d && d <= fmax && bd[bdiagoff + d] <= fd[fdiagoff + d]) {
          cost = 2 * c;
          return d;
        }
      }

      /* Heuristic: check occasionally for a diagonal that has made
         lots of progress compared with the edit distance.
         If we have any such, find the one that has made the most
         progress and return it as if it had succeeded.

         With this heuristic, for files with a constant small density
         of changes, the algorithm is linear in the file size.  */

      if (c > 200 && big_snake && heuristic) {
        int best = 0;
        int bestpos = -1;

        for (d = fmax; d >= fmin; d -= 2) {
          int dd = d - fmid;
          if ((fd[fdiagoff + d] - xoff) * 2 - dd > 12 * (c + (dd > 0 ? dd : -dd))) {
            if (fd[fdiagoff + d] * 2 - dd > best
              && fd[fdiagoff + d] - xoff > 20
              && fd[fdiagoff + d] - d - yoff > 20) {
              int k;
              int x = fd[fdiagoff + d];

              /* We have a good enough best diagonal;
                 now insist that it end with a significant snake.  */
              for (k = 1; k <= 20; k++)
                if (xvec[x - k] != yvec[x - d - k])
                  break;

              if (k == 21) {
                best = fd[fdiagoff + d] * 2 - dd;
                bestpos = d;
              }
            }
          }
        }
        if (best > 0) {
          cost = 2 * c - 1;
          return bestpos;
        }

        best = 0;
        for (d = bmax; d >= bmin; d -= 2) {
          int dd = d - bmid;
          if ((xlim - bd[bdiagoff + d]) * 2 + dd > 12 * (c + (dd > 0 ? dd : -dd))) {
            if ((xlim - bd[bdiagoff + d]) * 2 + dd > best
              && xlim - bd[bdiagoff + d] > 20
              && ylim - (bd[bdiagoff + d] - d) > 20) {
              /* We have a good enough best diagonal;
                 now insist that it end with a significant snake.  */
              int k;
              int x = bd[bdiagoff + d];

              for (k = 0; k < 20; k++)
                if (xvec[x + k] != yvec[x - d + k])
                  break;
              if (k == 20) {
                best = (xlim - bd[bdiagoff + d]) * 2 + dd;
                bestpos = d;
              }
            }
          }
        }
        if (best > 0) {
          cost = 2 * c - 1;
          return bestpos;
        }
      }
    }
  }

  /**
   * Compare in detail contiguous subsequences of the two files
   * which are known, as a whole, to match each other.
   * <p/>
   * The results are recorded in the vectors filevec[N].changed_flag, by
   * storing a 1 in the element for each line that is an insertion or deletion.
   * <p/>
   * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
   * <p/>
   * Note that XLIM, YLIM are exclusive bounds.
   * All line numbers are origin-0 and discarded lines are not counted.
   */

  private void compareseq(int xoff, int xlim, int yoff, int ylim) {
    /* Slide down the bottom initial diagonal. */
    while (xoff < xlim && yoff < ylim && xvec[xoff] == yvec[yoff]) {
      ++xoff;
      ++yoff;
    }
    /* Slide up the top initial diagonal. */
    while (xlim > xoff && ylim > yoff && xvec[xlim - 1] == yvec[ylim - 1]) {
      --xlim;
      --ylim;
    }
    
    /* Handle simple cases. */
    if (xoff == xlim)
      while (yoff < ylim)
        filevec[1].changed_flag[1 + filevec[1].realindexes[yoff++]] = true;
    else if (yoff == ylim)
      while (xoff < xlim)
        filevec[0].changed_flag[1 + filevec[0].realindexes[xoff++]] = true;
    else {
      /* Find a point of correspondence in the middle of the files.  */

      int d = diag(xoff, xlim, yoff, ylim);
      int c = cost;
      int b = bdiag[bdiagoff + d];

      if (c == 1) {
        /* This should be impossible, because it implies that
           one of the two subsequences is empty,
           and that case was handled above without calling `diag'.
           Let's verify that this is true.  */
        throw new IllegalArgumentException("Empty subsequence");
      } else {
        /* Use that point to split this problem into two subproblems.  */
        compareseq(xoff, b, yoff, b - d);
        /* This used to use f instead of b,
           but that is incorrect!
           It is not necessarily the case that diagonal d
           has a snake from b to f.  */
        compareseq(b, xlim, b - d, ylim);
      }
    }
  }

  /**
   * Discard lines from one file that have no matches in the other file.
   */

  private void discard_confusing_lines() {
    filevec[0].discard_confusing_lines(filevec[1]);
    filevec[1].discard_confusing_lines(filevec[0]);
  }

  private boolean inhibit = false;

  /**
   * Adjust inserts/deletes of blank lines to join changes
   * as much as possible.
   */

  private void shift_boundaries() {
    if (inhibit)
      return;
    filevec[0].shift_boundaries(filevec[1]);
    filevec[1].shift_boundaries(filevec[0]);
  }

  public interface ScriptBuilder {
    /**
     * Scan the tables of which lines are inserted and deleted,
     * producing an edit script.
     *
     * @param changed0 true for lines in first file which do not match 2nd
     * @param len0     number of lines in first file
     * @param changed1 true for lines in 2nd file which do not match 1st
     * @param len1     number of lines in 2nd file
     * @return a linked list of changes - or null
     */
    public change build_script(boolean[] changed0, int len0,
                               boolean[] changed1, int len1);
  }

  /**
   * Scan the tables of which lines are inserted and deleted,
   * producing an edit script in reverse order.
   */

  static class ReverseScript implements ScriptBuilder {
    public change build_script(final boolean[] changed0, int len0,
                               final boolean[] changed1, int len1) {
      change script = null;
      int i0 = 0, i1 = 0;
      while (i0 < len0 || i1 < len1) {
        if (changed0[1 + i0] || changed1[1 + i1]) {
          int line0 = i0, line1 = i1;

          /* Find # lines changed here in each file.  */
          while (changed0[1 + i0]) ++i0;
          while (changed1[1 + i1]) ++i1;

          /* Record this change.  */
          script = new change(line0, line1, i0 - line0, i1 - line1, script);
        }

        /* We have reached lines in the two files that match each other.  */
        i0++;
        i1++;
      }

      return script;
    }
  }

  static class ForwardScript implements ScriptBuilder {
    /**
     * Scan the tables of which lines are inserted and deleted,
     * producing an edit script in forward order.
     */
    public change build_script(final boolean[] changed0, int len0,
                               final boolean[] changed1, int len1) {
      change script = null;
      int i0 = len0, i1 = len1;

      while (i0 >= 0 || i1 >= 0) {
        if (changed0[i0] || changed1[i1]) {
          int line0 = i0, line1 = i1;

          /* Find # lines changed here in each file.  */
          while (changed0[i0]) --i0;
          while (changed1[i1]) --i1;

          /* Record this change.  */
          script = new change(i0, i1, line0 - i0, line1 - i1, script);
        }

        /* We have reached lines in the two files that match each other.  */
        i0--;
        i1--;
      }

      return script;
    }
  }

  /**
   * Standard ScriptBuilders.
   */
  public final static ScriptBuilder
    forwardScript = new ForwardScript(),
  reverseScript = new ReverseScript();

  /* Report the differences of two files.  DEPTH is the current directory
     depth. */
  public final change diff_2(final boolean reverse) {
    return diff(reverse ? reverseScript : forwardScript);
  }

  /**
   * Get the results of comparison as an edit script.  The script
   * is described by a list of changes.  The standard ScriptBuilder
   * implementations provide for forward and reverse edit scripts.
   * Alternate implementations could, for instance, list common elements
   * instead of differences.
   *
   * @param bld an object to build the script from change flags
   * @return the head of a list of changes
   */
  public change diff(final ScriptBuilder bld) {

    /* Some lines are obviously insertions or deletions
       because they don't match anything.  Detect them now,
       and avoid even thinking about them in the main comparison algorithm.  */

    discard_confusing_lines();

    /* Now do the main comparison algorithm, considering just the
       undiscarded lines.  */

    xvec = filevec[0].undiscarded;
    yvec = filevec[1].undiscarded;

    int diags =
      filevec[0].nondiscarded_lines + filevec[1].nondiscarded_lines + 3;
    fdiag = new int[diags];
    fdiagoff = filevec[1].nondiscarded_lines + 1;
    bdiag = new int[diags];
    bdiagoff = filevec[1].nondiscarded_lines + 1;

    compareseq(0, filevec[0].nondiscarded_lines,
      0, filevec[1].nondiscarded_lines);
    fdiag = null;
    bdiag = null;

    /* Modify the results slightly to make them prettier
       in cases where that can validly be done.  */

    shift_boundaries();

    /* Get the results of comparison in the form of a chain
       of `struct change's -- an edit script.  */
    return bld.build_script(filevec[0].changed_flag,
      filevec[0].buffered_lines,
      filevec[1].changed_flag,
      filevec[1].buffered_lines);

  }

  /**
   * The result of comparison is an "edit script": a chain of change objects.
   * Each change represents one place where some lines are deleted
   * and some are inserted.
   * <p/>
   * LINE0 and LINE1 are the first affected lines in the two files (origin 0).
   * DELETED is the number of lines deleted here from file 0.
   * INSERTED is the number of lines inserted here in file 1.
   * <p/>
   * If DELETED is 0 then LINE0 is the number of the line before
   * which the insertion was done; vice versa for INSERTED and LINE1.
   */

  public static class change {
    /**
     * Previous or next edit command.
     */
    public change link;
    /**
     * # lines of file 1 changed here.
     */
    public final int inserted;
    /**
     * # lines of file 0 changed here.
     */
    public final int deleted;
    /**
     * Line number of 1st deleted line.
     */
    public final int line0;
    /**
     * Line number of 1st inserted line.
     */
    public final int line1;

    /**
     * Cons an additional entry onto the front of an edit script OLD.
     * LINE0 and LINE1 are the first affected lines in the two files (origin 0).
     * DELETED is the number of lines deleted here from file 0.
     * INSERTED is the number of lines inserted here in file 1.
     * <p/>
     * If DELETED is 0 then LINE0 is the number of the line before
     * which the insertion was done; vice versa for INSERTED and LINE1.
     */
    public change(int line0, int line1, int deleted, int inserted, change old) {
      this.line0 = line0;
      this.line1 = line1;
      this.inserted = inserted;
      this.deleted = deleted;
      this.link = old;
      //System.err.println(line0+","+line1+","+inserted+","+deleted);
    }
  }

  /**
   * Data on one input file being compared.
   */

  class file_data {

    /**
     * Allocate changed array for the results of comparison.
     */
    void clear() {
      /* Allocate a flag for each line of each file, saying whether that line
	 is an insertion or deletion.
	 Allocate an extra element, always zero, at each end of each vector.
       */
      changed_flag = new boolean[buffered_lines + 2];
    }

    /**
     * Return equiv_count[I] as the number of lines in this file
     * that fall in equivalence class I.
     *
     * @return the array of equivalence class counts.
     */
    int[] equivCount() {
      int[] equiv_count = new int[equiv_max];
      for (int i = 0; i < buffered_lines; ++i)
        ++equiv_count[equivs[i]];
      return equiv_count;
    }

    /**
     * Discard lines that have no matches in another file.
     * <p/>
     * A line which is discarded will not be considered by the actual
     * comparison algorithm; it will be as if that line were not in the file.
     * The file's `realindexes' table maps virtual line numbers
     * (which don't count the discarded lines) into real line numbers;
     * this is how the actual comparison algorithm produces results
     * that are comprehensible when the discarded lines are counted.
     * <p/>
     * When we discard a line, we also mark it as a deletion or insertion
     * so that it will be printed in the output.
     *
     * @param f the other file
     */
    void discard_confusing_lines(file_data f) {
      clear();
      /* Set up table of which lines are going to be discarded. */
      final byte[] discarded = discardable(f.equivCount());

      /* Don't really discard the provisional lines except when they occur
         in a run of discardables, with nonprovisionals at the beginning
         and end.  */
      filterDiscards(discarded);

      /* Actually discard the lines. */
      discard(discarded);
    }

    /**
     * Mark to be discarded each line that matches no line of another file.
     * If a line matches many lines, mark it as provisionally discardable.
     *
     * @param counts The count of each equivalence number for the other file.
     * @return 0=nondiscardable, 1=discardable or 2=provisionally discardable
     *         for each line
     */

    private byte[] discardable(final int[] counts) {
      final int end = buffered_lines;
      final byte[] discards = new byte[end];
      final int[] equivs = this.equivs;
      int many = 5;
      int tem = end / 64;

      /* Multiply MANY by approximate square root of number of lines.
	 That is the threshold for provisionally discardable lines.  */
      while ((tem = tem >> 2) > 0)
        many *= 2;

      for (int i = 0; i < end; i++) {
        int nmatch;
        if (equivs[i] == 0)
          continue;
        nmatch = counts[equivs[i]];
        if (nmatch == 0)
          discards[i] = 1;
        else if (nmatch > many)
          discards[i] = 2;
      }
      return discards;
    }

    /**
     * Don't really discard the provisional lines except when they occur
     * in a run of discardables, with nonprovisionals at the beginning
     * and end.
     */

    private void filterDiscards(final byte[] discards) {
      final int end = buffered_lines;

      for (int i = 0; i < end; i++) {
        /* Cancel provisional discards not in middle of run of discards.  */
        if (discards[i] == 2)
          discards[i] = 0;
        else if (discards[i] != 0) {
          /* We have found a nonprovisional discard.  */
          int j;
          int length;
          int provisional = 0;

          /* Find end of this run of discardable lines.
             Count how many are provisionally discardable.  */
          for (j = i; j < end; j++) {
            if (discards[j] == 0)
              break;
            if (discards[j] == 2)
              ++provisional;
          }

          /* Cancel provisional discards at end, and shrink the run.  */
          while (j > i && discards[j - 1] == 2) {
            discards[--j] = 0;
            --provisional;
          }

          /* Now we have the length of a run of discardable lines
             whose first and last are not provisional.  */
          length = j - i;

          /* If 1/4 of the lines in the run are provisional,
             cancel discarding of all provisional lines in the run.  */
          if (provisional * 4 > length) {
            while (j > i)
              if (discards[--j] == 2)
                discards[j] = 0;
          } else {
            int consec;
            int minimum = 1;
            int tem = length / 4;

            /* MINIMUM is approximate square root of LENGTH/4.
               A subrun of two or more provisionals can stand
               when LENGTH is at least 16.
               A subrun of 4 or more can stand when LENGTH >= 64.  */
            while ((tem = tem >> 2) > 0)
              minimum *= 2;
            minimum++;

            /* Cancel any subrun of MINIMUM or more provisionals
               within the larger run.  */
            for (j = 0, consec = 0; j < length; j++)
              if (discards[i + j] != 2)
                consec = 0;
              else if (minimum == ++consec)
              /* Back up to start of subrun, to cancel it all.  */
                j -= consec;
              else if (minimum < consec)
                discards[i + j] = 0;

            /* Scan from beginning of run
               until we find 3 or more nonprovisionals in a row
               or until the first nonprovisional at least 8 lines in.
               Until that point, cancel any provisionals.  */
            for (j = 0, consec = 0; j < length; j++) {
              if (j >= 8 && discards[i + j] == 1)
                break;
              if (discards[i + j] == 2) {
                consec = 0;
                discards[i + j] = 0;
              } else if (discards[i + j] == 0)
                consec = 0;
              else
                consec++;
              if (consec == 3)
                break;
            }

            /* I advances to the last line of the run.  */
            i += length - 1;

            /* Same thing, from end.  */
            for (j = 0, consec = 0; j < length; j++) {
              if (j >= 8 && discards[i - j] == 1)
                break;
              if (discards[i - j] == 2) {
                consec = 0;
                discards[i - j] = 0;
              } else if (discards[i - j] == 0)
                consec = 0;
              else
                consec++;
              if (consec == 3)
                break;
            }
          }
        }
      }
    }

    /**
     * Actually discard the lines.
     *
     * @param discards flags lines to be discarded
     */
    private void discard(final byte[] discards) {
      final int end = buffered_lines;
      int j = 0;
      for (int i = 0; i < end; ++i)
        if (no_discards || discards[i] == 0) {
          undiscarded[j] = equivs[i];
          realindexes[j++] = i;
        } else
          changed_flag[1 + i] = true;
      nondiscarded_lines = j;
    }

    file_data(Object[] data, Hashtable h) {
      buffered_lines = data.length;

      equivs = new int[buffered_lines];
      undiscarded = new int[buffered_lines];
      realindexes = new int[buffered_lines];

      for (int i = 0; i < data.length; ++i) {
        Integer ir = (Integer) h.get(data[i]);
        if (ir == null)
          h.put(data[i], new Integer(equivs[i] = equiv_max++));
        else
          equivs[i] = ir.intValue();
      }
    }

    /**
     * Adjust inserts/deletes of blank lines to join changes
     * as much as possible.
     * <p/>
     * We do something when a run of changed lines include a blank
     * line at one end and have an excluded blank line at the other.
     * We are free to choose which blank line is included.
     * `compareseq' always chooses the one at the beginning,
     * but usually it is cleaner to consider the following blank line
     * to be the "change".  The only exception is if the preceding blank line
     * would join this change to other changes.
     *
     * @param f the file being compared against
     */

    void shift_boundaries(file_data f) {
      final boolean[] changed = changed_flag;
      final boolean[] other_changed = f.changed_flag;
      int i = 0;
      int j = 0;
      int i_end = buffered_lines;
      int preceding = -1;
      int other_preceding = -1;

      for (; ;) {
        int start, end, other_start;

        /* Scan forwards to find beginning of another run of changes.
           Also keep track of the corresponding point in the other file.  */

        while (i < i_end && !changed[1 + i]) {
          while (other_changed[1 + j++])
            /* Non-corresponding lines in the other file
               will count as the preceding batch of changes.  */
            other_preceding = j;
          i++;
        }

        if (i == i_end)
          break;

        start = i;
        other_start = j;

        for (; ;) {
          /* Now find the end of this run of changes.  */

          while (i < i_end && changed[1 + i]) i++;
          end = i;

          /* If the first changed line matches the following unchanged one,
       and this run does not follow right after a previous run,
       and there are no lines deleted from the other file here,
       then classify the first changed line as unchanged
       and the following line as changed in its place.  */

          /* You might ask, how could this run follow right after another?
       Only because the previous run was shifted here.  */

          if (end != i_end
            && equivs[start] == equivs[end]
            && !other_changed[1 + j]
            && end != i_end
            && !((preceding >= 0 && start == preceding)
            || (other_preceding >= 0
            && other_start == other_preceding))) {
            changed[1 + end] = true;
            changed[1 + start++] = false;
            ++i;
            /* Since one line-that-matches is now before this run
               instead of after, we must advance in the other file
               to keep in synch.  */
            ++j;
          } else
            break;
        }

        preceding = i;
        other_preceding = j;
      }
    }

    /**
     * Number of elements (lines) in this file.
     */
    final int buffered_lines;

    /**
     * Vector, indexed by line number, containing an equivalence code for
     * each line.  It is this vector that is actually compared with that
     * of another file to generate differences.
     */
    private final int[] equivs;

    /**
     * Vector, like the previous one except that
     * the elements for discarded lines have been squeezed out.
     */
    final int[] undiscarded;

    /**
     * Vector mapping virtual line numbers (not counting discarded lines)
     * to real ones (counting those lines).  Both are origin-0.
     */
    final int[] realindexes;

    /**
     * Total number of nondiscarded lines.
     */
    int nondiscarded_lines;

    /**
     * Array, indexed by real origin-1 line number,
     * containing true for a line that is an insertion or a deletion.
     * The results of comparison are stored here.
     */
    boolean[] changed_flag;

  }
} // EGDiff

static class BlockDiff {
  public CopyBlock asCopyBlock() { return null; }
  public NewBlock  asNewBlock () { return null; }
}

static class CopyBlock extends BlockDiff {
  int firstLine, lines;

  CopyBlock(int firstLine, int lines) {
    this.firstLine = firstLine;
    this.lines = lines;
  }

  public CopyBlock asCopyBlock() { return this; }
  public int getFirstLine() { return firstLine; }
  public int getLines() { return lines; }
}

static class NewBlock extends BlockDiff {
  int originalStart;
  List<String> contents;

  NewBlock(int originalStart, List<String> contents) {
    this.originalStart = originalStart;
    this.contents = contents;
  }

  public NewBlock  asNewBlock () { return this; }

  public int getOriginalStart() {
    return originalStart;
  }

  public List<String> getContents() {
    return contents;
  }
}

static class ExplodedLine {
  int type;
  String left, right;
  int leftIndex, rightIndex;

  ExplodedLine(int type, String left, String right, int leftIndex, int rightIndex) {
    this.type = type;
    this.left = left;
    this.right = right;
    this.leftIndex = leftIndex;
    this.rightIndex = rightIndex;
  }
  
  public int getType() {
    return type;
  }

  public String getLeft() {
    return left;
  }

  public String getRight() {
    return right;
  }

  public int getLeftIndex() {
    return leftIndex;
  }

  public int getRightIndex() {
    return rightIndex;
  }
}

static class BlockDiffer {
  public static final int IDENTICAL = 0;
  public static final int DIFFERENT = 1;
  public static final int LEFT_ONLY = 2;
  public static final int RIGHT_ONLY = 3;

  private static void printChange(EGDiff.change change) {
    if (change != null) {
      System.out.println("line0="+change.line0+", line1="+change.line1
        +", inserted="+change.inserted+", deleted="+change.deleted);
      printChange(change.link);
    }
  }


  /** Generates the text content of a Unified-format context diff between 2 files
   *  (NB the 'files-changed' header must be added separately).
   */
  public static List<String> generateUniDiff(List<String> fileA, List<String> fileB, int contextSize) {
    EGDiff diff = new EGDiff(fileA.toArray(), fileB.toArray());
    EGDiff.change change = diff.diff_2(false);

    if (change != null) {
      int inserted, deleted;
      List<String> hunkLines = new ArrayList<String>();
      int cumulExtraLinesBwrtA = 0;

      // Each hunk is generated with a header
      do {
        int line0 = change.line0, line1 = change.line1;
        int changeStart = ((line1 < line0) ? line1 : line0);
        int contextStart = ((changeStart > contextSize) ? changeStart - contextSize : 0);
        int headerPosn = hunkLines.size();

        // Provide the first lines of context
        for (int i = contextStart; i < changeStart; i++)
          //System.out.println(" " + fileA.get(i));
          hunkLines.add(" " + fileA.get(i));

        boolean hunkFinish = false;
        // Step through each change giving the change lines and following context
        do {
          inserted = change.inserted;
          deleted = change.deleted;
          line0 = change.line0;
          line1 = change.line1;

          if (line1 < line0) // An insert comes earlier
            while (inserted-- > 0)
              hunkLines.add("+" + fileB.get(line1++));
          while (deleted-- > 0)
            hunkLines.add("-" + fileA.get(line0++));
          while (inserted-- > 0)
            hunkLines.add("+" + fileB.get(line1++));

          // Lines following are trailing context, identical in fileA and fileB
          // The next change may overlap the context, so check and if so, form one hunk
          EGDiff.change nextChange = change.link;
          int nextChangeStart = fileA.size();
          if (nextChange != null)
            nextChangeStart = ((nextChange.line1 < nextChange.line0) ? nextChange.line1 : nextChange.line0);

          if (nextChangeStart - line0 > contextSize * 2) { // A separate hunk
            nextChangeStart = line0 + contextSize;
            hunkFinish = true;
          }
          if (nextChangeStart > fileA.size())
            nextChangeStart = fileA.size(); // Limit to file size
          while (line0 < nextChangeStart) {
            hunkLines.add(" " + fileA.get(line0++));
            line1++; // Keep in sync with trailing context
          }

          change = change.link;
        } while (!hunkFinish && change != null);

        int hunkStartB = contextStart + cumulExtraLinesBwrtA;
        int hunkTotA = line0 - contextStart;
        int hunkTotB = line1 - hunkStartB;

        hunkLines.add(headerPosn, "@@ -" + (contextStart + 1) + ',' + hunkTotA +
                        " +" + (hunkStartB + 1) + ',' + hunkTotB + " @@");
        cumulExtraLinesBwrtA += hunkTotB - hunkTotA;

      } while (change != null);

      return hunkLines;
    }
    return null;
  }

/* For testing:
  private static void printUniDiff(List<String> fileA, List<String> fileB, int contextSize) {
    List<String> uniDiff = generateUniDiff(fileA, fileB, contextSize);

    if (uniDiff != null)
      for (int j = 0; j < uniDiff.size(); j++)
        System.out.println(uniDiff.get(j));
  }
 */


  public static List<BlockDiff> diffLines(List<String> lines, List<String> reference) {
    List<BlockDiff> diffs = new ArrayList<BlockDiff>();

    EGDiff diff = new EGDiff(reference.toArray(), lines.toArray());
    EGDiff.change change = diff.diff_2(false);
    //printChange(change);
    //printUniDiff(reference, lines, 3);

    int l0 = 0, l1 = 0;
    while (change != null) {
      if (change.line0 > l0 && change.line1 > l1)
        diffs.add(new CopyBlock(l0, change.line0-l0));

      if (change.inserted != 0)
        diffs.add(new NewBlock(change.line1, lines.subList(change.line1, change.line1+change.inserted)));

      l0 = change.line0 + change.deleted;
      l1 = change.line1 + change.inserted;
      change = change.link;
    }

    if (l0 < reference.size())
      diffs.add(new CopyBlock(l0, reference.size()-l0));

    return diffs;
  }

  /** fills files with empty lines to align matching blocks
   *
   * @param file1 first file
   * @param file2 second file
   * @return an array with two lists
   */
  public static List<ExplodedLine> explode(List<String> file1, List<String> file2) {
    List<ExplodedLine> lines = new ArrayList<ExplodedLine>();
    List<BlockDiff> diffs = BlockDiffer.diffLines(file2, file1);
    int lastLineCopied = 0, rightOnlyStart = -1, rightPosition = 0;
    for (int i = 0; i < diffs.size(); i++) {
      BlockDiff diff = diffs.get(i);
      if (diff instanceof CopyBlock) {
        CopyBlock copyBlock = (CopyBlock) diff;
        if (lastLineCopied < copyBlock.getFirstLine()) {
          if (rightOnlyStart >= 0) {
            int overlap = Math.min(lines.size()-rightOnlyStart, copyBlock.getFirstLine()-lastLineCopied);
            //lines.subList(rightOnlyStart, rightOnlyStart+overlap).clear();
            convertRightOnlyToDifferent(lines, rightOnlyStart, overlap, file1, lastLineCopied);
            lastLineCopied += overlap;
          }
          addBlock(lines, LEFT_ONLY, file1, lastLineCopied, copyBlock.getFirstLine(), lastLineCopied, -1);
        }
        addBlock(lines, IDENTICAL, file1, copyBlock.getFirstLine(), copyBlock.getFirstLine()+copyBlock.getLines(),
          copyBlock.getFirstLine(), rightPosition);
        rightPosition += copyBlock.getLines();
        lastLineCopied = copyBlock.getFirstLine()+copyBlock.getLines();
        rightOnlyStart = -1;
      } else if (diff instanceof NewBlock) {
        NewBlock newBlock = (NewBlock) diff;
        /*if (nextDiff instanceof BlockDiffer.CopyBlock) {
          BlockDiffer.CopyBlock copyBlock = (BlockDiffer.CopyBlock) nextDiff;
          copyBlock.getFirstLine()-lastLineCopied*/
        rightOnlyStart = lines.size();
        addBlock(lines, RIGHT_ONLY, newBlock.getContents(), 0, newBlock.getContents().size(), -1, rightPosition);
        rightPosition += newBlock.getContents().size();
      }
    }

    if (rightOnlyStart >= 0) {
      int overlap = Math.min(lines.size()-rightOnlyStart, file1.size()-lastLineCopied);
      //lines.subList(rightOnlyStart, rightOnlyStart+overlap).clear();
      convertRightOnlyToDifferent(lines, rightOnlyStart, overlap, file1, lastLineCopied);
      lastLineCopied += overlap;
    }
    addBlock(lines, LEFT_ONLY, file1, lastLineCopied, file1.size(), lastLineCopied, -1);

    return lines;
  }

  private static void convertRightOnlyToDifferent(List<ExplodedLine> lines, int start, int numLines,
                                                  List<String> leftLines, int leftStart) {
    for (int i = 0; i < numLines; i++) {
      ExplodedLine line = lines.get(start+i);
      lines.set(start+i,
        new ExplodedLine(DIFFERENT, leftLines.get(i+leftStart), line.getRight(), i+leftStart, line.getRightIndex()));
    }
  }

  private static void addBlock(List<ExplodedLine> lines, int type, List<String> srcLines, int start, int end,
                               int leftStart, int rightStart) {
    for (int i = start; i < end; i++)
      lines.add(new ExplodedLine(type, type == RIGHT_ONLY ? "" : srcLines.get(i),
                                       type == LEFT_ONLY ? "" : srcLines.get(i),
                                       type == RIGHT_ONLY ? -1 : i - start + leftStart,
                                       type == LEFT_ONLY ? -1 : i - start + rightStart));
  }

  public static List<ExplodedLine> condense(List<ExplodedLine> lines) {
    List<ExplodedLine> result = new ArrayList<ExplodedLine>();
    for (Iterator<ExplodedLine> i = lines.iterator(); i.hasNext();) {
      ExplodedLine line = i.next();
      if (line.getType() == IDENTICAL) {
        if (result.isEmpty() || result.get(result.size()-1).getType() != IDENTICAL)
          result.add(new ExplodedLine(IDENTICAL, "[...]", "[...]", -1, -1));
      } else
        result.add(line);
    }
    return result;
  }
} // BlockDiffer
static abstract class DialogIO {
  String line;
  boolean eos, loud, noClose;
  Lock lock = lock();
  
  abstract String readLineImpl();
  abstract boolean isStillConnected();
  abstract void sendLine(String line);
  abstract boolean isLocalConnection();
  abstract Socket getSocket();
  abstract void close();
  
  int getPort() { Socket s = getSocket(); return s == null ? 0 : s.getPort(); }
  
  boolean helloRead;
  int shortenOutputTo = 500;
  
  String readLineNoBlock() {
    String l = line;
    line = null;
    return l;
  }
  
  boolean waitForLine() { try {
    ping();
    if (line != null) return true;
    //print("Readline");
    line = readLineImpl();
    //print("Readline done: " + line);
    if (line == null) eos = true;
    return line != null;
  } catch (Exception __e) { throw rethrow(__e); } }
  
  String readLine() {
    waitForLine();
    helloRead = true;
    return readLineNoBlock();
  }
  
  String ask(String s, Object... args) {
    if (loud) return askLoudly(s, args);
    if (!helloRead) readLine();
    if (args.length != 0) s = format3(s, args);
    sendLine(s);
    return readLine();
  }
  
  String askLoudly(String s, Object... args) {
    if (!helloRead) readLine();
    if (args.length != 0) s = format3(s, args);
    print("> " + shorten(s, shortenOutputTo));
    sendLine(s);
    String answer = readLine();
    print("< " + shorten(answer, shortenOutputTo));
    return answer;
  }
  
  void pushback(String l) {
    if (line != null)
      throw fail();
    line = l;
    helloRead = false;
  }
}

static abstract class DialogHandler {
  abstract void run(DialogIO io);
} // DialoGIO
static final class IndexedList2<A> extends AbstractList<A> {
  ArrayList<A> l = new ArrayList();
  MultiSet<A> index = new MultiSet(false); // HashMap
  static boolean debug;
  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);
  }
} // IndexedList2
//include #1001296 // MultiMap
// uses HashMap by default
static class MultiSet<A> {
  Map<A, Integer> map = new HashMap();
  
  MultiSet(boolean useTreeMap) {
    if (useTreeMap) map = new TreeMap();
  }
  MultiSet() {}
  MultiSet(Iterable<A> c) { addAll(c); }
  MultiSet(MultiSet<A> ms) { synchronized(ms) {
    for (A a : ms.keySet()) add(a, ms.get(a));
  }}
  
  synchronized void add(A key) { add(key, 1); }
  
  synchronized void addAll(Iterable<A> c) {
    if (c != null) for (A a : c) add(a);
  }

  synchronized void addAll(MultiSet<A> ms) {
    for (A a : ms.keySet()) add(a, ms.get(a));
  }
  
  synchronized void add(A key, int count) {
    if (map.containsKey(key))
      map.put(key, map.get(key)+count);
    else
      map.put(key, count);
  }

  synchronized int get(A key) {
    Integer i = map.get(key);
    return i != null ? i : 0;
    //ret key != null && map.containsKey(key) ? map.get(key) : 0;
  }
  
  synchronized boolean contains(A key) {
    return map.containsKey(key);
  }

  synchronized void remove(A key) {
    Integer i = map.get(key);
    if (i != null && i > 1)
      map.put(key, i - 1);
    else
      map.remove(key);
  }

  synchronized List<A> topTen() { return getTopTen(); }
  
  synchronized List<A> getTopTen() { return getTopTen(10); }
  synchronized List<A> getTopTen(int maxSize) {
    List<A> list = getSortedListDescending();
    return list.size() > maxSize ? list.subList(0, maxSize) : list;
  }
  
  synchronized List<A> highestFirst() {
    return getSortedListDescending();
  }

  synchronized List<A> lowestFirst() {
    return reversedList(getSortedListDescending());
  }

  synchronized List<A> getSortedListDescending() {
    List<A> list = new ArrayList<A>(map.keySet());
    Collections.sort(list, new Comparator<A>() {
      public int compare(A a, A b) {
        return map.get(b).compareTo(map.get(a));
      }
    });
    return list;
  }

  synchronized int getNumberOfUniqueElements() {
    return map.size();
  }
  
  synchronized int uniqueSize() {
    return map.size();
  }

  synchronized Set<A> asSet() {
    return map.keySet();
  }

  synchronized NavigableSet<A> navigableSet() {
    return navigableKeys((NavigableMap) map);
  }

  synchronized Set<A> keySet() {
    return map.keySet();
  }
  
  synchronized A getMostPopularEntry() {
    int max = 0;
    A a = null;
    for (Map.Entry<A,Integer> entry : map.entrySet()) {
      if (entry.getValue() > max) {
        max = entry.getValue();
        a = entry.getKey();
      }
    }
    return a;
  }

  synchronized void removeAll(A key) {
    map.remove(key);
  }

  synchronized int size() {
    int size = 0;
    for (int i : map.values())
      size += i;
    return size;
  }

  synchronized MultiSet<A> mergeWith(MultiSet<A> set) {
    MultiSet<A> result = new MultiSet<A>();
    for (A a : set.asSet()) {
      result.add(a, set.get(a));
    }
    return result;
  }
  
  synchronized boolean isEmpty() {
    return map.isEmpty();
  }
  
  synchronized public String toString() { // hmm. sync this?
    return str(map);
  }
  
  synchronized void clear() {
    map.clear();
  }
  
  synchronized Map<A, Integer> asMap() {
    return cloneMap(map);
  }
} // MultiSet
static ThreadLocal<Boolean> DynamicObject_loading = new ThreadLocal();

static class DynamicObject {
  String className; // just the name, without the "main$"
  LinkedHashMap<String,Object> fieldValues = new LinkedHashMap();
  
  DynamicObject() {}
  // className = just the name, without the "main$"
  DynamicObject(String className) {
  this.className = className;}
} // DynamicObject
static class CompilerBot {
  static boolean verbose;

  static File compileSnippet(String snippetID) {
    return compileSnippet(snippetID, "");
  }
  
  static Pair<File, String> compileSnippet2(String snippetID) {
    return compileSnippet2(snippetID, "");
  }
  
  // returns jar path
  static File compileSnippet(String snippetID, String javaTarget) {
    return compileSnippet2(snippetID, javaTarget).a;
  }
  
  // returns jar path, Java source
  static Pair<File, String> compileSnippet2(String snippetID, String javaTarget) {
    String transpiledSrc = getServerTranspiled2(snippetID);
    int i = transpiledSrc.indexOf('\n');
    String libs = transpiledSrc.substring(0, Math.max(0, i));
    if (verbose)
      print("Compiling snippet: " + snippetID + ". Libs: " + libs);
    transpiledSrc = transpiledSrc.substring(i+1);
    return pair(compile(transpiledSrc, libs, javaTarget, snippetID), transpiledSrc);
  }

  static File compile(String src) {
    return compile(src, "");
  }
  
  static File compile(String src, String libs) {
    return compile(src, libs, null);
  }

  static File compile(String src, String dehlibs, String javaTarget) {
    return compile(src, dehlibs, javaTarget, null);
  }
  
  static File compile(String src, String dehlibs, String javaTarget, String progID) {
    if (verbose)
      print("Compiling " + l(src) + " chars");
      
    // Note: This is different from the calculation in x30
    // (might lead to programs being compiled twice)
    String md5 = md5(dehlibs + "\n" + src + "\n" + progID);
    File jar = getJarFile(md5);
    if (jar == null || jar.length() <= 22) {
      // have to compile
      
      boolean canRename = useDummyMainClasses() && isSnippetID(progID) && !tok_classHasModifier(findMainClass(javaTok(src)), "public");
      if (verbose)
        print("useRenaming: " + useDummyMainClasses() + ", canRename: " + canRename + ", progID: " + progID);

      javaCompileToJar_optionalRename(src, dehlibs, jar, canRename ? progID : null);
    } 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;
  }

  static File getJarFile(String md5) {
    assertTrue(isMD5(md5));
    return new File(getCacheProgramDir("#1002203"), md5 + ".jar");
  }
} // CompilerBot

static boolean isTrue(Object o) {
  if (o instanceof Boolean)
    return ((Boolean) o).booleanValue();
  if (o == null) return false;
  if (o instanceof ThreadLocal)
    return isTrue(((ThreadLocal) o).get());
  throw fail(getClassName(o));
} // isTrue
static String unquote(String s) {
  if (s == null) return null;
  if (s.startsWith("[")) {
    int i = 1;
    while (i < s.length() && s.charAt(i) == '=') ++i;
    if (i < s.length() && s.charAt(i) == '[') {
      String m = s.substring(1, i);
      if (s.endsWith("]" + m + "]"))
        return s.substring(i+1, s.length()-i-1);
    }
  }
  
  if ((s.startsWith("\"") || s.startsWith("\'")) && s.length() > 1) {
    int l = s.endsWith(substring(s, 0, 1)) ? 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 'b':
            ch = '\b';
            break;
        case 'f':
            ch = '\f';
            break;
        case 'n':
            ch = '\n';
            break;
        case 'r':
            ch = '\r';
            break;
        case 't':
            ch = '\t';
            break;
        case '\"':
            ch = '\"';
            break;
        case '\'':
            ch = '\'';
            break;
        // Hex Unicode: u????
        case 'u':
            if (i >= 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
} // unquote

static String transpilingSnippetID;
static int varCount;
static Map<String,String> snippetCache = new HashMap();
static boolean useIndexedList = true, opt_javaTok = true;
static boolean cacheStdFunctions = true, cacheStdClasses = true;
static HashMap<Long,CachedInclude> cachedIncludes = new HashMap();
static ExecutorService executor;
static List lclasses;
static long startTime, lastPrint;

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

static HashSet<Long> included = new HashSet();
static Set<String> definitions = ciSet();
static HashMap<String, String> rewrites = new HashMap();
static HashSet<String> shouldNotIncludeFunction = new HashSet(), shouldNotIncludeClass = new HashSet();
static HashSet<String> doNotIncludeFunction = new HashSet();
static HashSet<String> addedFunctions = new HashSet();
static HashSet<String> addedClasses = new HashSet();
static HashSet<String> hardFunctionReferences = new HashSet();
static boolean quickmainDone1, quickmainDone2;
static TreeSet<String> libs = new TreeSet();
static String mainBaseClass;
static boolean localStuffOnly; // for transpiling a fragment

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

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

static void _main() { try {
  //reTok_modify_check = true;
  if (useIndexedList) findCodeTokens_debug = true;
  javaTok_opt = opt_javaTok;
  findCodeTokens_indexed = findCodeTokens_unindexed = 0;
  findCodeTokens_bails = findCodeTokens_nonbails = 0;
  javaTok_n = javaTok_elements = 0;
  String in = loadMainJava();
  
  print("759 STARTING " + identityHashCode(main.class));
  included.clear();
  definitions.clear();
  rewrites.clear();
  definitions.add("SymbolAsString");
  shouldNotIncludeFunction.clear();
  shouldNotIncludeClass.clear();
  doNotIncludeFunction.clear();
  addedFunctions.clear();
  addedClasses.clear();
  hardFunctionReferences.clear();
  libs.clear();
  mainBaseClass = null;
  varCount = 0;
  quickmainDone1 = quickmainDone2 = false;
  
  //L ts = findTranslators(toLines(join(tok)));
  //print("Translators in source at start: " + structure(ts));
  
  // "duplicate" statement - unused
  
  /*L<S> lines = toLines(in);
  call(getJavaX(), "findTranslators", lines);
  in = fromLines(lines);
  new Matches m;
  if (match("duplicate *", in, m)) {
    // actual copying - unused
    // tok = jtok(loadSnippet(m.get(0)));
    
    // reference by include()
    in = "m { p { callMain(include(" + quote(m.get(0)) + ")); } }";
  }*/
  
  List<String> tok = jtok(in);

  tok_definitions(tok);
  
  // add m { }
  
  if (!localStuffOnly && !hasCodeTokens(tok, "m", "{") && !hasCodeTokens(tok, "main", "{") && !hasCodeTokens(tok, "class", "main")) {
    //tok = jtok(moveImportsUp("m {\n" + in + "\n}"));
    replaceTokens_reTok(tok, 1, 2, "m {\n\n" + tok.get(1));
    replaceTokens_reTok(tok, l(tok)-2, l(tok)-1, tok.get(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 = tok_processIncludes(tok); // before standard functions
  if (processConceptsDot(tok))
    tok = tok_processIncludes(tok);
  tok = localStuff1(tok);
  
 if (!localStuffOnly) {
  int safety = 0;
  boolean same;
  do {
    String before = join(tok);
    
    tok = localStuff1(tok);
    
    // shortened method declarations BEFORE standardFunctions
    jreplace(tok, "svoid", "static void");
    jreplace(tok, "void <id> {", "$1 $2() {");
    jreplace(tok, "String <id> {", "$1 $2() {");
    jreplace(tok, "Object <id> {", "$1 $2() {");
    jreplace(tok, "List <id> {", "$1 $2() {");
    
    // do the non-local stuff (flags and rewrites)
    tok_definitions(tok);
    tok_ifndef(tok);
    tok_ifdef(tok);
    tok_replaceWith(tok);
    tok_findRewrites(tok);
    tok_processRewrites(tok);
    try {
      if (safety == 0) tok = quickmain(tok);
    } catch (Throwable e) {
      printSources(tok);
      rethrow(e);
    }
    
    // Hack to allow DynModule to reimplement _registerThread
    /*if (tok.contains("DynModule") && !addedClasses.contains("DynModule"))
      addStandardClasses_v2(tok);*/
    
    tok = standardFunctions(tok);
    tok = stdstuff(tok); // all the keywords, standard
    String diff;
    long startTime = now();
    //diff = unidiff(before, join(tok));
    //print("unidiff: " + (now()-startTime) + " ms");
    //same = eq(diff, "");
    same = tok_sameTest(tok, before);
    if (!same) {
      print("Not same " + safety + ".");
      //print(indent(2, diff));
    }
    if (safety++ >= 10) {
      //print(unidiff(before, join(tok)));
      printSources(tok);
      throw fail("safety 10 error!");
    }
  } while (!same);
  
  print("Post.");
  
  if (mainBaseClass != null) {
    jreplace1(tok, "class main", "class main extends " + mainBaseClass);
    mainBaseClass = null;
  }
  
  print("moveImportsUp"); tok_moveImportsUp(tok);
  
  print("Indexing"); tok = indexedList2(tok);
  
  // POST-PROCESSING after stdstuff loop
  
  if (transpilingSnippetID != null)
    jreplace_dyn(tok, "class whatever", 
      new Object() { String get(List<String> tok, int cIndex) { try { 
        try { return "class " + stringToLegalIdentifier(getSnippetTitle(transpilingSnippetID)); } catch (Throwable __e) { printStackTrace2(__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);
  
  // escaping for lambdas
  jreplace(tok, "-=>", "->");

  // Stuff that depends on the list of inner classes (haveClasses)
  HashSet<String> haveClasses = haveClasses_actual(tok);
  print("innerClassesVar"); innerClassesVar(tok, haveClasses);
  fillVar_transpilationDate(tok);
  haveClasses_addImported(tok, haveClasses);
  print("ifclass"); tok_ifclass(tok, haveClasses);
  print("slashCasts"); slashCasts(tok, haveClasses);
  print("newWithoutNew"); newWithoutNew(tok, haveClasses);
  
  if (!assumeTriple) {
    print("Triple");
    if (tok.contains("Triple") && !haveClasses.contains("Triple")) {
      jreplace(tok, "Triple", "T3");
      haveClasses.remove("Triple");
      haveClasses.add("T3");
      slashCasts(tok, lithashset("T3"));
      tok_quickInstanceOf(tok, lithashset("T3"));
    }
    expandTriple(tok);
  }
  
  if (hasDef("SymbolAsString"))
    jreplace(tok, "Symbol", "String");

  print("classReferences"); expandClassReferences_lazy(tok, haveClasses);
  
  // Error-checking
  print("Error-checking"); 
  Set<String> functions = new HashSet(findFunctions(tok));
  for (String f : hardFunctionReferences)
    if (!functions.contains(f))
      throw fail("Function " + f + " requested, but not supplied");

  print("autoImports"); tok = autoImports(tok); // faster to do it at the end
  
  print("definitions=" + sfu(definitions));
  if (containsOneOfIC(definitions, "allpublic", "reparse")) {
    boolean dontPrintSource = false;
    // Fire up the Java parser & pretty printer!
    print(containsIC(definitions, "allpublic")? "Making all public." : "Reparsing.");
    try {
      String src = join(tok);
      try {
        if (containsIC(definitions, "keepComments"))
          src = javaParser_makeAllPublic_keepComments(src);
        else if (containsIC(definitions, "allpublic"))
          src = javaParser_makeAllPublic(src);
        else
          src = javaParser_reparse_keepComments(src);
      } catch (Throwable e) {
        extractAndPrintJavaParseError(src, e);
        dontPrintSource = true;
        throw e;
      }
      tok = jtok(src);
    } catch (Throwable e) {
      String src = join(tok);
      if (!dontPrintSource)
        print(src);
      print(f2s(saveProgramTextFile("error.java", src)));
      throw rethrow(e);
    }
  }
  
  if (nempty(libs))
    for (String lib : libs)
      tok.add(concatMap_strings(new F1<String, String>() { String get(String s) { try { return  "\n!" + s ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "\"\\n!\" + s"; }}, libs));
 } // if (!localStuffOnly)
  
  /*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.");
  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;
  tok = indexedList2(tok);
  do {
    String before = join(tok);
    
    earlyStuff(tok);
    
    conceptDeclarations(tok);
    tok_recordDecls(tok);
  
    tok = multilineStrings(tok);
    tok_singleQuoteIdentifiersToStringConstants(tok);
    inStringEvals(tok);
    tok_listComprehensions(tok);
    tok_doubleFor_v2(tok);
    forPing(tok);
    directSnippetRefs(tok);
    quicknu(tok);
    //tok_juxtaposeCalls(tok);
    expandVarCopies(tok);
    
    jreplace(tok, "LS", "L<S>");
  
    jreplace(tok, "do ping {", "do { ping();");

    replaceKeywordBlock(tok,
      "swing",
      "{ swing(r {",
      "}); }");
      
    replaceKeywordBlock(tok,
      "tokcondition",
      "new TokCondition { bool get(final L<S> tok, final int i) {",
      "}}");
      
    jreplace(tok, "synced <id>", "synchronized $2");
    
    replaceKeywordBlock(tok, "answer",
      "static S answer(S s) {\nfinal new Matches m;\n",
      "\nret null;\n}");
      
    replaceKeywordBlock(tok, "static-pcall",
      "static { pcall {",
      "}}");
      
    replaceKeywordBlock(tok, "loading",
      "{ JWindow _loading_window = showLoadingAnimation(); try {",
      "} finally { disposeWindow(_loading_window); }}");
  
    replaceKeywordPlusQuotedBlock(tok, "loading",
      new Object() { String[] get(List<String> tok, int i) {
        String text = tok.get(i+2);
        return new String[] {
          "{ JWindow _loading_window = showLoadingAnimation(" + text + "); try {",
          "} finally { disposeWindow(_loading_window); }}" };
      }});
      
    jreplace(tok, "visualize {", "JComponent visualize() {", tokCondition_beginningOfMethodDeclaration());
  
    jreplace(tok, "start {", "void start() { super.start();", tokCondition_beginningOfMethodDeclaration());
    
    replaceKeywordBlock(tok, "html",
      "static O html(S uri, fMap<S, S> params) ctex " + "{\n", "}");
    
    if (assumeTriple) {
      jreplace(tok, "Triple", "T3");
      expandTriple(tok);
    }
    
    tok_shortFinals(tok);
    
    jreplace(tok, "static sync", "static synchronized");
    jreplace(tok, "sclass", "static class");
    jreplace(tok, "srecord", "static record");
    jreplace(tok, "record noeq", "noeq record");
    jreplace(tok, "asclass", "abstract static class");
    jreplace(tok, "sinterface", "static interface");
    jreplace(tok, "ssynchronized", "static synchronized");
  
    jreplace(tok, "ssvoid", "static synchronized void");
    jreplace(tok, "sbool", "static bool");
    jreplace(tok, "sint", "static int");
    jreplace(tok, "snew", "static new");
    jreplace(tok, "sv <id>", "static void $2");
    jreplace(tok, "pvoid", "public void");
  
    // "sS" => static S
    jreplace(tok, "sS", "static S");
  
    // "sO" => static O
    jreplace(tok, "sO", "static O");
  
    // "sL" => static L
    jreplace(tok, "sL", "static L");
  
    // "toString {" => "public S toString() {"
    jreplace(tok, "toString {", "public S toString() {");
    
    jreplace(tok, "Int", "Integer");
    jreplace(tok, "Bool", "Boolean");
    jreplace(tok, "BigInt", "BigInteger");
    jreplace(tok, "Char", "Character");
    
    jreplace(tok, "Sym", "Symbol");
    jreplace(tok, "SymSym", "SymbolSymbol");
    
    jreplace(tok, "SS", "Map<S>");
    jreplace(tok, "SymbolSymbol", "Map<Symbol>");
    
    jreplace(tok, "PairS", "Pair<S>");

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

    // "continue unless"
    
    while ((i = jfind(tok, "continue unless")) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      replaceTokens(tok, i, i+4, "{ if (!(");
      tok.set(j, ")) continue; }");
      reTok(tok, i, j+1);
    }
    
    // "continue if"
    
    while ((i = jfind(tok, "continue if")) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      replaceTokens(tok, i, i+4, "{ if (");
      tok.set(j, ") continue; }");
      reTok(tok, i, j+1);
    }
    
    // "return if"
    
    while ((i = jfind(tok, "return if")) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      replaceTokens(tok, i, i+4, "{ if (");
      tok.set(j, ") return; }");
      reTok(tok, i, j+1);
    }
    
    // "return unless"
    
    while ((i = jfind(tok, "return unless")) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      replaceTokens(tok, i, i+4, "{ if (!(");
      tok.set(j, ")) return; }");
      reTok(tok, i, j+1);
    }
    
    // "return with <statement>" / "continue with <statement>"
    
    while ((i = jfind(tok, "<id> with", new TokCondition() { boolean get(final List<String> tok, final int i) { return eqOneOf(tok.get(i+1), "return", "continue"); }})) >= 0) {
      int j = scanOverExpression(tok, getBracketMap(tok), i+4, ";");
      tok.set(j, "; " + tok.get(i) + "; }");
      replaceTokens(tok, i, i+3, "{");
      reTok(tok, i, j+1);
    }
    
    // return "bla" with <statement>
    
    while ((i = jfindOneOf(tok,
      "return <quoted> with", "return <id> with")) >= 0) {
      String result = tok.get(i+2);
      int j = scanOverExpression(tok, getBracketMap(tok), i+6, ";");
      replaceTokens(tok, i, i+5, "{");
      tok.set(j, "; return " + result + "; }");
      reTok(tok, i, j+1);
    }
    
    // while not null ()
    
    while ((i = jfind(tok, "while not null (")) >= 0) {
      int closingBracket = findEndOfBracketPart(tok, i+6)-1;
      replaceTokens(tok, i+2, i+6, "(");
      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 funcname(...) => map(f funcname, ...)
    // filter funcname(...) => filter(f funcname, ...)
    // ...
    jreplace(tok, "<id> <id>(", "$1(f $2,", new Object() { boolean get(List<String> tok, int i) { try { return 
      contains(tok_mapLikeFunctions(), tok.get(i+1))
    ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "contains(tok_mapLikeFunctions(), tok.get(i+1))"; }});

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

    replaceKeywordBlock(tok, "awt-messagebox", "awt { pcall-messagebox {", "}}");
    replaceKeywordBlock(tok, "awt", "swingLater(r {", "});");
  
    unswing(tok);
    lockBlocks(tok);
    tempBlocks(tok);
    
    // trim x;
    
    jreplace(tok, "trim <id>;", "$2 = trim($2);");
  
    // crazy stuff
  
    jreplace (tok, "for <id> over <id>:", "for (int $2 = 0; $2 < l($4); $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> 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);
      
    if (containsToken(tok, "cast")) {
      // TODO: use tokens
      String s = join(tok);
      s = s.replaceAll("(\\w+<[\\w\\s,\\[\\]]+>|\\w+|\\w+\\[\\]|\\w+\\[\\]\\[\\])\\s+(\\w+)\\s*=\\s*cast(\\W[^;]*);", "$1 $2 = ($1) ($3);");
      tok = jtok(s);
    }
    
    replaceKeywordBlock(tok, "r-thread-messagebox", "r-thread { pcall-messagebox {", "}}");
    
    replaceKeywordBlock(tok, "thread-messagebox", "thread { pcall-messagebox {", "}}");
    
    jreplace(tok, "rThread {", "r-thread {");
  
    replaceKeywordBlock(tok, "r-thread", "runnableThread(r {", "})");
    rNamedThread(tok);
    
    replaceKeywordBlock(tok, "r-messagebox", "r { pcall-messagebox {", "}}");
    
    // 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);
        //print("r contents: " + structure(contents));
        replaceTokens(tok, i, j+1, "new Runnable() { public void run() { try { " + tok_addSemicolon(contents) + 
          "\n} catch (Exception __e) { throw rethrow(__e); } }" +
      (autoQuine ? "  public S toString() { return " + quote(shorten(maxQuineLength, trim(join(contents)))) + "; }" : "") +
          "}");
        reTok(tok, i, j+1);
      }
    
    replaceKeywordBlock(tok,
      "expectException",
      "{ bool __ok = false; try {",
      "} catch { __ok = true; } assertTrue(\"expected exception\", __ok); }");
      
    while ((i = tok.indexOf("tex")) >= 0) {
      tok.set(i, "throws Exception");
      tok = jtok(tok);
    }
    
    // shorter & smarter whiles
    
    jreplace(tok, "while true", "while (true)");
    jreplace(tok, "while licensed", "while (licensed())");
    jreplace(tok, "repeat {", "while (licensed()) {");
    tok_repeatWithSleep(tok);
    
    // null; => return null; etc.
  
    Object cond = new TokCondition() { boolean get(final List<String> tok, final int i) {
      return tok_tokenBeforeLonelyReturnValue(_get(tok, i-1));
    }};
    jreplace(tok, "null;", "return null;", cond);
    jreplace(tok, "false;", "return false;", cond);
    jreplace(tok, "true;", "return true;", cond);
    jreplace(tok, "this;", "return this;", cond);
    
    // ok <cmd> => ret "OK" with <cmd>
    jreplace(tok, "ok <id>", "return \"OK\" with $2");
    replaceKeywordBlock(tok, "ok", "{", " return \"OK\"; }");
  
    // "myFunction;" instead of "myFunction();" - quite rough
    cond = new TokCondition() {
      boolean get(List<String> tok, int i) {
        String word = tok.get(i+3);
        //print("single word: " + word);
        return !litlist("break", "continue", "return", "else").contains(word);
      }
    };
    for (String pre : litlist("}", ";"))
      jreplace(tok, pre + " <id>;", "$1 $2();", cond);
  
    // shorter match syntax for answer methods
    
    jreplace(tok, "if <quoted> || <quoted>",
      "if (matchOneOf(s, m, $2, $5))");
      
    // "...bla..."
    jreplace(tok, "if <quoted>", "if (find3plusRestsX($2, s, m))",
      new TokCondition() { boolean get(final List<String> tok, final int i) {
        return startsAndEndsWith(unquote(tok.get(i+3)), "...");
      }});
      
    // "bla..."
    jreplace(tok, "if <quoted>", "if (matchStartX($2, s, m))",
      new TokCondition() { boolean get(final List<String> tok, final int i) {
        return unquote(tok.get(i+3)).endsWith("...");
      }});
      
    // "bla * bla | blubb * blubb"
    jreplace_dyn(tok, "if <quoted>", new Object() { Object get(List<String> tok, int 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 (javaTok(pat).contains("*"))
          l.add("match(" + quote(trim(pat)) + ", s, m)");
        else
          l.add("match(" + quote(trim(pat)) + ", s)");
      }
      return "if (" + join(" || ", l) + ")";
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S s = unquote(tok.get(cIdx+2));\r\n      //print(\"multimatch: \" + quote(s));\r\n ..."; }}, new TokCondition() { boolean get(final List<String> tok, final int i) {
      return javaTokC(unquote(tok.get(i+3))).contains("|");
    }});
    
    // "bla"
    jreplace(tok, "if <quoted>", "if (match($2, s))",
      new TokCondition() { boolean get(final List<String> tok, final int i) {
        return !javaTokC(unquote(tok.get(i+3))).contains("*");
      }});
    // "bla * bla"
    jreplace(tok, "if <quoted>", "if (match($2, s, m))");
    jreplace(tok, "if match <quoted>", "if (match($3, s, m))");
  
    // extra commas ("litlist(1, 2,)")
    
    jreplace(tok, ",)", ")");
    
    // additional translations (if necessary)
    
    jreplace(tok, "pcall ping {", "pcall { ping();");
    
    replaceKeywordBlock(tok,
      "pcall",
      "try {",
      "} catch (Throwable __e) { _handleException(__e); }");
  
    replaceKeywordBlock(tok,
      "pcall-short",
      "try {",
      "} catch (Throwable __e) { print(exceptionToStringShort(__e)); }");
  
    replaceKeywordBlock(tok,
      "pcall-silent",
      "try {",
      "} catch (Throwable __e) { silentException(__e); }");
  
    replaceKeywordBlock(tok,
      "pcall-messagebox",
      "try {",
      "} catch __e { messageBox(__e); }");
  
    replaceKeywordBlock(tok,
      "pcall-infobox",
      "try {",
      "} catch __e { infoBox(__e); }");
  
    tok = dialogHandler(tok);
    
    replaceKeywordBlock(tok, "exceptionToUser",
      "try {",
      "} catch (Throwable __e) { ret exceptionToUser(__e); }"); 
  
    if (hasCodeTokens(tok, "twice", "{"))
      replaceKeywordBlock(tok, "twice",
        "for (int __twice = 0; __twice < 2; __twice++) {",
        "}"); 
  
    while ((i = findCodeTokens(tok, "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);
    }
  
    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" {
    replaceKeywordPlusQuotedBlock(tok,
      "time",
      new Object() { String[] get(List<String> tok, int i) {
        String var = makeVar("startTime");
        return new String[] {
          "long " + var + " = sysNow(); ",
          " done2_always(" + tok.get(i+2) + ", " + var + "); "};
      }});
    
    if (hasCodeTokens(tok, "assertFail", "{")) {
      String var = makeVar("oops");
      
      replaceKeywordBlock(tok,
        "assertFail",
        "boolean " + var + " = false; try {",
        "\n" + var + " = true; } catch (Exception e) { /* ok */ } assertFalse(" + var + ");"); 
    }
    
    replaceKeywordBlock(tok,
      "yo",
      "try {",
      "} catch (Exception " + makeVar("e") + ") { ret false; }",
      new TokCondition() { boolean get(final List<String> tok, final int i) {
        return neqOneOf(_get(tok, i-1), "svoid", "void");
      }});
  
    replaceKeywordBlock(tok,
      "awtIfNecessary",
      "swingNowOrLater(r " + "{",
      "});");
      
    ctex(tok);
      
    replaceKeywordBlock(tok,
      "actionListener",
      "new java.awt.event.ActionListener() { " +
        "public void actionPerformed(java.awt.event.ActionEvent _evt) { pcall-messagebox {",
      "}}}");
      
    for (String keyword : ll("autocloseable", "autoCloseable"))
      replaceKeywordBlock(tok,
        keyword,
        "new AutoCloseable() { public void close() throws Exception {",
        "}}");
      
    namedThreads(tok);
    threads(tok);
  
    // try answer (string, test with nempty)
    while ((i = findCodeTokens(tok, "try", "answer")) >= 0) {
      int j = findEndOfStatement(tok, i);
      String v = makeVar("a");
      tok.set(i, "{ S " + v);
      tok.set(i+2, "=");
      tok.set(j-1, "; if (!empty(" + v + ")) ret " + v + "; }");
      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("a");
      tok.set(i, "{ Bool " + v);
      tok.set(i+2, "=");
      tok.set(j-1, "; if (" + v + " != null) ret " + v + "; }");
      reTok(tok, i, j);
    }
  
    // return optional (return if not null)
    while ((i = jfind(tok, "return optional <id> =")) >= 0) {
      int j = findEndOfStatement(tok, i);
      String v = tok.get(i+4);
      clearTokens(tok, i+2, i+4);
      tok.set(i, "{");
      tok.set(j-1, "; if (" + v + " != null) ret " + v + "; }");
      reTok(tok, i, j);
    }
    
    // try object (return if not null)
    while ((i = jfind(tok, "try object")) >= 0) {
      int j = findEndOfStatement(tok, i);
      String v = makeVar();
      clearTokens(tok, i, i+3);
      tok.set(i, "{ O " + v + "=");
      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);
    quicknew2(tok);
    
    tok_unpair(tok);
    tok_cachedFunctions(tok);
    
    throwFailEtc(tok);
    tok_typeAA(tok);

    // do this after expanding sclass etc.
    tok = expandStarConstructors(tok);
    
    tok_kiloConstants(tok);
    
    // end of localStuff1

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

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

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

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

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

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

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

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

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

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

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

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

static String makeVar(String name) {
  return "_" + name + "_" + varCount++;
}

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

/*static L<S> standardFunctions(L<S> tok) {
  ret rtq(tok, "#1002474");
}*/

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

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

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

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

static List<String> expandStarConstructors(List<String> tok) {
  mainLoop: for (int i = 3; i+6 < tok.size(); i += 2) {
    String t = tok.get(i), l = tok.get(i-2);
    if (!t.equals("*"))
      continue;
    if (!tok.get(i+2).equals("("))
      continue;
    if (!eqOneOf(l, "}", "public", "private", "protected", ";", "{", "endif") && neq(get(tok, i-4), "ifclass")) // is this correct...??
      continue;
      
    // ok, it seems like a constructor declaration.
    // Now find class name by going backwards.
    
    int j = i, level = 1;
    while (j > 0 && level > 0) {
      t = tok.get(j);
      if (t.equals("}")) ++level;
      if (t.equals("{")) --level;
      j -= 2;
    }
    
    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;
        int block = j+1;
        for (int k = i+2; k < block-1; k += 2)
          if (tok.get(k).equals("*")) {
            tok.remove(k);
            tok.remove(k);
            block -= 2;
            String name = tok.get(k);
            tok.addAll(block, Arrays.asList(new String[] {
              "\n  ", "this", "", ".", "", name, " ", "=", " ", name, "", ";" }));
          }
        
        continue mainLoop;
      }
      j -= 2;
    }
  }
  return tok;
}

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

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

static void quicknew2(List<String> tok) {
  jreplace(tok, "new <id> <id>;", "$2 $3 = new $2;");
  jreplace(tok, "new <id><<id>> <id>;", "$2<$4> $6 = new $2;");
  jreplace(tok, "new <id><<id>> <id>, <id>;", "$2<$4> $6 = new $2, $8 = new $2;");
  jreplace(tok, "new <id><<id>,<id>> <id>;", "$2<$4,$6> $8 = new $2;");
  jreplace(tok, "new <id><<id><<id>>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 = new $2;");
  jreplace(tok, "new <id><<id>[]> <id>;", "$2 $3 $4 $5 $6 $7 $8 = new $2;");
  jreplace(tok, "new <id>< <id><<id>>, <id>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 = new $2;");
  jreplace(tok, "new <id>< <id><<id>,<id>> > <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 = new $2;");
  jreplace(tok, "new <id>< <id>, <id><<id>> > <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 = new $2;");
  jreplace(tok, "new <id>< <id><<id>,<id>,<id>> > <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 = new $2;");
  jreplace(tok, "new <id>< <id><<id>,<id>>, <id>> <id>;", "$2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 = new $2;");
  
  // [abandoned, confusing, looks like a function definition] with arguments - new A a(...); => A a = new A(...);
  //jreplace(tok, "new <id> <id>(", "$2 $3 = new $2(");

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

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

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

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

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

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

      replaceTokens(tok, i, j, "new " + type + "() { public void get(" + trimJoin(args) + ") ctex { " + tok_addSemicolon(contents) + " }\n" +
      (autoQuine ? "  public S toString() { ret " + quote(shorten(maxQuineLength, trim(join(contents)))) + "; }" : "") + "}");
      reTok(tok, i, j);
    }
    
    jreplace(tok, "func {", "func -> O {");
    jreplace(tok, "f {", "f -> O {");
    
    for (String keyword : ll("f", "func")) {
      while ((i = jfind(tok, keyword + " ->")) >= 0) {
        // I think there is a bug here for something like func -> x { new x { } }
        int idx = findCodeTokens(tok, i, false, "{");
        int j = findEndOfBracketPart(tok, idx);
        String returnType = tok_toNonPrimitiveTypes(join(subList(tok, i+6, idx-1)));
        List<String> contents = subList(tok, idx+1, j-1);
        replaceTokens(tok, i, j, "new F0<" + returnType + ">() { " + returnType + " get() ctex { " + tok_addReturn(contents) + " }\n" +
        (autoQuine ? "  public S toString() { ret " + quote(shorten(maxQuineLength, trim(join(contents)))) + "; }" : "") + "}");
        reTok(tok, i, j);
      }
    }
}

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

static void namedThreads(List<String> tok) {
  for (int i = 0; i < 100; i++) {
    int idx = findCodeTokens(tok, "thread", "<quoted>", "{");
    if (idx < 0) idx = findCodeTokens(tok, "thread", "<id>", "{");
    if (idx < 0)
      break;
    int j = findEndOfBracketPart(tok, idx+4);
    String tName = tok.get(idx+2);
    
    String var = "_t_" + i;
    String pre = "{ Thread " + var + " = new Thread(" + tName + ") {\n" +
      "public void run() { pcall {\n";
    String post = "} }\n};\n" +
      "startThread(" + var + "); }";

    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) {
  for (boolean daemon : litlist(false, true))
    for (int i = 0; i < 100; i++) {
      int idx = findCodeTokens(tok, daemon ? "daemon" : "thread", "{");
      if (idx < 0)
        break;
      int j = findEndOfBracketPart(tok, idx+2);

      String var = "_t_" + i;
      String pre = "{ Thread " + var + " = new Thread() {\n" +
        "public void run() { pcall\n";
      String post = "} }\n};\n" +
        (daemon ? var + ".setDaemon(true);\n" : "") +
        "startThread(" + var + "); }";

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

static Map<String, String> sf;
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]);
    }
  }
  
  for (int i = 0; ; i++) {
    Set<String> defd = new HashSet(findFunctions(tok));
    
    int j;
    while ((j = jfind(tok, "should not include function *.")) >= 0) {
      String fname = tok.get(j+8);
      shouldNotIncludeFunction.add(fname);
      clearAllTokens(tok.subList(j, j+12));
    }

    while ((j = jfind(tok, "do not include function *.")) >= 0) {
      String fname = tok.get(j+8);
      doNotIncludeFunction.add(fname);
      clearAllTokens(tok.subList(j, j+12));
    }
    
    doNotIncludeFunction.addAll(tok_importedStaticFunctionNames(tok));
    
    // changes tok
    Set<String> invocations = findFunctionInvocations(tok, sf, hardFunctionReferences, defd);
    /*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 (sf.containsKey(x))
        preload.add(sf.get(x));
    cachePreload(preload);
    
    for (String x : cloneList(needed)) {
      if (defd.contains(x)) continue;
      
      String id = sf.get(x);
      if (id == null) {
        print("Standard function " + x + " not found.");
        needed.remove(x);
        continue;
      }
      //print("Adding function: " + x + " (" + id + ")");
       
      String function = cacheGet(id);
      //if (("\n" + function).contains("\n!")) print("Warning: " + id + " contains translators.");
      
      if (cacheStdFunctions) {
        long _id = psI(id);
        CachedInclude ci = cachedIncludes.get(_id);
        if (ci == null) {
          cachedIncludes.put(_id, ci = new CachedInclude());
          //println("Caching include " + _id + ", l=" + l(cachedIncludes));
        }
        function += "\n";
        final String _function = function;
        if (neq(ci.javax, function)) {
          print("Compiling function: " + x);
          ci.javax = function;
          ci.java = executor.submit(new Callable<String>() {
            public String call() {
              return join(localStuff1(jtok(_function)));
            }
          });
          ci.realJava = null;
        }
        parts.add(ci.javaFuture());
      } else
        parts.add(nowFuture(function + "\n"));
        
      added.add(x);
      Collection<String> newFunctions = new HashSet(findFunctionDefs(javaTok(function)));
      defd.addAll(newFunctions);
      for (String f : newFunctions)
        if (!addedFunctions.add(f)) {
          printSources(tok);
          throw fail("Trying to add function " + f + " again - main class syntax broken!");
        }
    }
    
    String text = join(getAllFutures(parts));
    if (cacheStdFunctions)
      tok = includeInMainLoaded_stdReTok(tok, text);
    else
      tok = includeInMainLoaded(tok, text);
      
    // defd = new HashSet(findFunctions(tok));
    //print("Functions added: " + joinWithSpace(added));
    
    for (String x : needed)
      if (!defd.contains(x)) {
        print(join(tok));
        throw fail("Function not defined properly: " + x);
      }
    //print("Iteration " + (i+2));
    
    tok_definitions(tok);
    tok_ifndef(tok);
    tok_ifdef(tok);
    
    if (i >= 1000) throw fail("Too many iterations");
  }
  
  return tok;
}

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

static String cacheGet(String snippetID) {
  snippetID = formatSnippetID(snippetID);
  String text = snippetCache.get(snippetID);
  if (text == null) {
    snippetCache.put(snippetID, text = loadSnippet(snippetID));
    if (hasUnclosedStringLiterals(text))
      throw fail("Unclosed string literals in " + snippetID);
  }
  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) {
  List<String> l = javaTok(s);
  return useIndexedList ? new IndexedList2(l) : l;
}

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

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

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

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

    int j;
    while ((j = jfind(tok, "should not include class *.")) >= 0) {
      String cname = tok.get(j+8);
      shouldNotIncludeClass.add(cname);
      clearAllTokens(tok.subList(j, j+12));
    }
    
    Map<String, String> need = new HashMap();
    Set<String> idx = tokenIndexWithoutIfclass_forStdClasses(tok);
    for (int i = 0; i+1 < l(definitions); i += 2) {
      String className = definitions.get(i);
      if (idx.contains(className) && !have.contains(className))
        need.put(className, definitions.get(i+1));
    }
    if (hasDef("SymbolAsString")) {
      print("Have SymbolAsString.");
      if (need.containsKey("Symbol")) {
        print("Have Symbol.");
        need.remove("Symbol");
      }
    } else
      print("No SymbolAsString.");
      
    if (empty(need)) return have;
  
    for (String className : keys(need))
      if (shouldNotIncludeClass.contains(className)) {
        String msg = "INCLUDING BAD CLASS: " + className;
        print(msg);
        print(join(tok));
        throw fail(msg);
      }
      
    cachePreload(values(need));
    
    StringBuilder buf = new StringBuilder();
    print("Adding classes: " + joinWithSpace(keys(need)));
    for (String className : keys(need)) {
      if (have.contains(className)) continue; // intermittent add
      String snippetID = need.get(className);
      //print("Adding class " + className + " / " + snippetID);
      snippetID = fsI(snippetID);
      String text = cacheGet(snippetID);
      
      assertTrue(cacheStdClasses);
      long _id = psI(snippetID);
      CachedInclude ci = cachedIncludes.get(_id);
      if (ci == null) cachedIncludes.put(_id, ci = new CachedInclude());
      if (neq(ci.javax, text)) {
        print("Compiling class: " + className);
        ci.javax = text;
        ci.java = null;
        ci.realJava = join(localStuff1(jtok(text)));
      }
      buf.append(ci.java());

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

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

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

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

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

// experimental - "<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) {
  jreplace(tok, "<id>(", "new $1(", new TokCondition() { boolean get(final List<String> tok, final int i) {
    if (!classNames.contains(tok.get(i+1))) return false;
    boolean ok = neqOneOf(_get(tok, i-1), "new", ";", "}", "{", "public", "protected", "private", ".");
    //print("newWithoutNew: checking " + struct(subList(tok, i-1, i+2)) + " => " + ok);
    return ok;
  }});
}

// +var => "var", +var
static void expandVarCopies(List<String> tok) {
  boolean change = false;
  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) + ", ");
    change = true;
  }
  if (change) reTok(tok);
}

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 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 (!vars.contains("_fieldOrder")
    && !isSortedList(vars)) {
    print("Adding field order");
    tok.set(idx+2, "static String _fieldOrder = " + quote(join(" ", vars)) + ";\n  " + tok.get(idx+2));
    // reTok has to be done by caller
  }
}

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

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

// f bla => "bla" - and "please include function bla."
static void functionReferences(List<String> tok) {
  int i;
  
  jreplace_dyn(tok, "f-thread <id>", new Object() { Object get(List<String> tok, int cIdx) { try { return 
    "dynamicCallableMC_thread(" + quote(tok.get(cIdx+6)) + ")"
  ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "\"dynamicCallableMC_thread(\" + quote(tok.get(cIdx+6)) + \")\""; }});
  
  String keyword = "f";
  while ((i = jfind(tok, keyword + " <id>", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return !eq(tok.get(i+3), "instanceof");
  }})) >= 0) {
    String f = tok.get(i+2);
    clearTokens(tok, i, i+2);
    tok.set(i+2, quote(f));
    reTok(tok, i, i+2);
    tok.set(l(tok)-1, last(tok) + "\nplease include function " + f + ".");
    reTok(tok, l(tok)-1, l(tok));
  }
  
  // r fname => r { fname() }
  // rThread fname => rThread { fname() }
  while ((i = jfindOneOf_cond(tok, new TokCondition() { boolean get(final List<String> tok, final int i) {
    return !eq(tok.get(i+3), "instanceof");
  }}, "r <id>", "rThread <id>")) >= 0) {
    String f = tok.get(i+2);
    replaceTokens(tok, i, i+3, tok.get(i) + " { " + f + "(); }");
    reTok(tok, i, i+3);
  }
}

// # 123 => "#123"
static void directSnippetRefs(List<String> tok) {
  int i;
  while ((i = jfind(tok, "#<int>", new TokCondition() { boolean get(final List<String> tok, final int i) {
    return !eqOneOf(_get(tok, i-1), "include", "once");
  }})) >= 0) {
    String id = tok.get(i+2);
    clearTokens(tok, i+1, i+3);
    tok.set(i, quote("#" + id));
    reTok(tok, i, i+3);
  }
}

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

// fill variable innerClasses_list
static void innerClassesVar(List<String> tok, Set<String> have) {
  if (!tok.contains("myInnerClasses_list")) return;
  
  int i = jfind(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) {
  if (!tok.contains("myTranspilationDate_value")) return;
  
  int i = jfind(tok, "long myTranspilationDate_value;");
  if (i < 0) return;
  tok.set(i+4, " = " + now() + "L;");
  reTok(tok, i+4, i+5);
}

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

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

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

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

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

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

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

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

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

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

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

// -Syntax: temp Bla bla = bla();
// -expands to try(Bla bla = bla()) { ... } with rest of block inside
static void tempBlocks(List<String> tok) {
  int i;
  while ((i = jfind(tok, "temp <id>")) >= 0) {
    int semicolon = findEndOfStatement(tok, i)-1;
    int endOfOuterBlock = findEndOfBracketPart(tok, semicolon)-1;
    List<String> sub = subList(tok, i-1, semicolon);
    int eq = subList(sub, 0, smartIndexOf(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 cleanMeUp() {
  for (CachedInclude ci : values(cachedIncludes))
    ci.clean();
  vmKeepWithProgramMD5_save("cachedIncludes");
}

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

static void tok_typeAA(List<String> tok) {
  int n = l(tok)-6;
  boolean change = false;
  for (int i = 1; i < l(tok); i += 2) {
    if (!(eq(get(tok, i+2), "<") && contains(pairClasses, tok.get(i)))) 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;
    }
  }
  //if (change) reTok(tok);
  
  /*  
  O cond = tokcondition { pairClasses.contains(tok.get(i+1)) };
  jreplace(tok, "<id> < <id> >", "$1<$3, $3>", cond);
  jreplace(tok, "<id> < <id><<id>> >", "$1<$3<$5>, $3<$5>>", cond);
  jreplace(tok, "<id> < <id><<id>,<id>> >", "$1<$3<$5,$7>, $3<$5,$7>>", cond);
  */
}

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

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

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

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

static List<List<String>> innerClassesOfMain(String src) {
  return innerClassesOfMain(javaTok(src));
}
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 void extractAndPrintJavaParseError(String src, Throwable e) {
  print();
  String msg = firstLine(innerMessage(e));
  print(msg);
  int line = parseIntOpt(regexpFirstGroupIC("line (\\d+)", msg));
  print();
  if (line != 0) {
    List<String> lines = lines(src);
    for (int i = max(0, line-5); i <= min(l(lines), line+5); i++)
      print((i == line ? "* " : "  ") + "LINE " + i + ": " + lines.get(i-1));
  }
  print();
}
static String quote(Object o) {
  if (o == null) return "null";
  return quote(str(o));
}

static String quote(String s) {
  if (s == null) return "null";
  StringBuilder out = new StringBuilder((int) (l(s)*1.5+2));
  quote_impl(s, out);
  return out.toString();
}
  
static void quote_impl(String s, StringBuilder out) {
  out.append('"');
  int l = s.length();
  for (int i = 0; i < l; i++) {
    char c = s.charAt(i);
    if (c == '\\' || c == '"')
      out.append('\\').append(c);
    else if (c == '\r')
      out.append("\\r");
    else if (c == '\n')
      out.append("\\n");
    else if (c == '\0')
      out.append("\\0");
    else
      out.append(c);
  }
  out.append('"');
}
static String trim(String s) { return s == null ? null : s.trim(); }
static String trim(StringBuilder buf) { return buf.toString().trim(); }
static String trim(StringBuffer buf) { return buf.toString().trim(); }
static 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);
}
// 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 String getClassDeclarationName(List<String> c) {
  if (c != null)
    for (int i = 1; i+2 < c.size(); i += 2)
      if (allClasses_keywords.contains(c.get(i)))
        return c.get(i+2);
  return null;
}

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 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 boolean tok_isSingleTypeArg(List<String> tok, int iOpeningBracket) {
  int iClosingBracket = findEndOfTypeArgs(tok, iOpeningBracket)-1;
  if (iClosingBracket == iOpeningBracket+4) return true;
  if (eq(get(tok, iOpeningBracket+4), "<")) {
    int j = findEndOfTypeArgs(tok, iOpeningBracket+4);
    return j == iClosingBracket-1;
  }
  return false; // hmm
}
static String str(Object o) {
  return o == null ? "null" : o.toString();
}

static String str(char[] c) {
  return new String(c);
}
// It's rough but should work if you don't make anonymous classes inside the statement or something...
// 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.
  while (j < l(tok) && neq(tok.get(j), ";"))
    if (eq(get(tok, j), "{"))
      j = findEndOfBlock(tok, j)+1;
    else
      j += 2;
  return j+1;
}
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);
}
// 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 Set<String> tok_mapLikeFunctions_set = asHashSet(
  splitAtSpace("map mapLL filter concatMap pairMap antiFilter"));
  
static Set<String> tok_mapLikeFunctions() {
  return tok_mapLikeFunctions_set;
}
// before you use this, add a RAM disk cleaner
static boolean javaCompileToJar_useRAMDisk;

static File javaCompileToJar_optionalRename(String src, File destJar, String progID) {
  return javaCompileToJar_optionalRename(src, "", destJar, progID);
}

// returns path to jar
static synchronized File javaCompileToJar_optionalRename(String src, String dehlibs, File destJar, String progID) {
  String javaTarget = null; // use default target
  
  //print("Compiling " + l(src) + " chars");
  String dummyClass = "main";
  if (progID != null) {
    dummyClass = dummyMainClassName(progID);
    src += "\nclass " + dummyClass + "{}";
  }
  String md5 = md5(src);
  File jar = destJar;

  Class j = getJavaX();
  if (javaTarget != null)
    setOpt(j, "javaTarget", javaTarget);
  //setOpt(j, "verbose", true);
  File srcDir = tempDir();
  String className = "main";
  String fileName = dummyClass + ".java";
  File mainJava = new File(srcDir, fileName);
  //print("main java: " + mainJava.getAbsolutePath());
  saveTextFile(mainJava, src);
  File classesDir = javaCompileToJar_useRAMDisk ? tempDirPossiblyInRAMDisk() : tempDir();
  //print("Compiling to " + f2s(classesDir));
  try {
    List<File> libraries = new ArrayList();
    
    Matcher m = Pattern.compile("\\d+").matcher(dehlibs);
    while (m.find()) {
      String libID = m.group();
      //print("libID=" + quote(libID));
      assertTrue(isSnippetID(libID));
      libraries.add(loadLibrary(libID));
    }
    
    libraries.add(pathToJavaxJar());
      
    String compilerOutput;
    try {
      compilerOutput = (String) call(j, "compileJava", srcDir, libraries, classesDir);
    } catch (Throwable e) {
      compilerOutput = (String) get(getJavaX(), "javaCompilerOutput");
      throw fail("Compile Error. " + compilerOutput + " " + e);
    }
    
    if (nempty(compilerOutput)) {
      print("Compiler said: " + compilerOutput);
      //fail("Compile Error. " + compilerOutput);
    }
  
    // sanity test
    if (!new File(classesDir, className + ".class").exists())
      throw fail("No class generated (" + className + ")");
        
    // 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);
  
    //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);
  }
}

// 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;
}
// TODO: extended multi-line strings

static int javaTok_n, javaTok_elements;
static boolean javaTok_opt;

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

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

static List<String> javaTok(List<String> tok) {
  return javaTokWithExisting(join(tok), tok);
}
static void tok_kiloConstants(List<String> tok) {
  if (!tok.contains("K")) return;
  jreplace(tok, "<int> K", "($1*1024)");
}
static String joinWithComma(Collection<String> c) {
  return join(", ", c);
}

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

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 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));
}
// 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;
}
static int jfindOneOf(List<String> tok, String... patterns) {
  for (String in : patterns) {
    int i = jfind(tok, in);
    if (i >= 0) return i;
  }
  return -1;
}
static <A> List<A> reversedList(Collection<A> l) {
  List<A> x = cloneList(l);
  Collections.reverse(x);
  return x;
}
static <A> List<A> subList(List<A> l, int startIndex) {
  return subList(l, startIndex, l(l));
}

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


static <A, B> Map<A, B> cloneMap(Map<A, B> map) {
  if (map == null) return litmap();
  // assume mutex is equal to collection
  synchronized(map) {
    return map instanceof TreeMap ? new TreeMap((TreeMap) map) // copies comparator
      : map instanceof LinkedHashMap ? new LinkedHashMap(map)
      : new HashMap(map);
  }
}
static 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 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 = i & ~1;
  j = j | 1;
  
  List<String> t = javaTok(join(subList(tok, i, j)));
  replaceListPart(tok, i, j, t);
  
  // fallback to safety
  // reTok(tok);
  
  return tok;
}

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(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)
    : (Integer) call(o, "size");
}


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



static String getServerTranspiled2(String id) {
  String transpiled = loadCachedTranspilation(id);
  String md5 = null;
  if (isOfflineMode()) return transpiled;
  if (transpiled != null)
    md5 = md5(transpiled);
  String transpiledSrc = getServerTranspiled(formatSnippetID(id), md5);
  if (eq(transpiledSrc, "SAME")) {
    if (!isTrue(loadPage_silent.get())) print("SAME");
    return transpiled;
  }
  return transpiledSrc;
}
// 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 <A> void addAll(Collection<A> c, Iterable<A> b) {
  if (c != null && b != null) for (A a : b) c.add(a);
}

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

static <A> boolean addAll(Collection<A> c, A... b) {
  return c != null && c.addAll(Arrays.asList(b));
}
// 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 boolean includeInMainLoaded_safe;

static List<String> includeInMainLoaded(List<String> tok, String text) {
  List<String> main = findMainClass(tok);
  if (main == null) {
    print(join(tok));
    throw fail("no main class");
  }
  int i = main.lastIndexOf("}");
  main.set(i, "\n" + text + "\n}");
  i += magicIndexOfSubList(tok, main);
  return includeInMainLoaded_safe ? includeInMainLoaded_reTok(tok, 0, l(tok)) : includeInMainLoaded_reTok(tok, i, i+1);
}
static void tok_p_old(List<String> tok) {
  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);
}
// 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 format3(String pat, Object... args) {
    if (args.length == 0) return pat;
    
    List<String> tok = javaTokPlusPeriod(pat);
    int argidx = 0;
    for (int i = 1; i < tok.size(); i += 2)
      if (tok.get(i).equals("*"))
        tok.set(i, format3_formatArg(argidx < args.length ? args[argidx++] : "null"));
    return join(tok);
  }
  
  static String format3_formatArg(Object arg) {
    if (arg == null) return "null";
    if (arg instanceof String) {
      String s = (String) arg;
      return isIdentifier(s) || isNonNegativeInteger(s) ? s : quote(s);
    }
    if (arg instanceof Integer || arg instanceof Long) return String.valueOf(arg);
    return quote(structure(arg));
  }
  

static String sfu(Object o) { return structureForUser(o); }
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 HashSet<String> allClasses_keywords = lithashset("class", "interface", "enum", "sclass");

// 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();
  for (int i = 1; i < tok.size(); i += 2) {
    if (eq(tok.get(i), "{")) // skip functions
      i = findEndOfBlock(tok, i)-1;
    else if (allClasses_keywords.contains(tok.get(i)) && (i == 1 || !tok.get(i-2).equals("."))) {
      int j = i;
      while (j < tok.size() && !tok.get(j).equals("{"))
        j += 2;
      j = findEndOfBlock(tok, j)+1;
      i = leftScanModifiers(tok, i);
      l.add(tok.subList(i-1, Math.min(tok.size(), j)));
      i = j-2;
    }
  }
  return l;
}

static List<List<String>> allClasses(String text) {
  return allClasses(javaTok(text));
}
static boolean empty(Collection c) { return c == null || c.isEmpty(); }
static boolean empty(CharSequence s) { return s == null || s.length() == 0; }
static boolean empty(Map map) { return map == null || map.isEmpty(); }
static boolean empty(Object[] o) { return o == null || o.length == 0; }
static boolean empty(Object o) {
  if (o instanceof Collection) return empty((Collection) o);
  if (o instanceof String) return empty((String) o);
  if (o instanceof Map) return empty((Map) o);
  if (o instanceof Object[]) return empty((Object[]) o);
  if (o instanceof byte[]) return empty((byte[]) o);
  if (o == null) return true;
  throw fail("unknown type for 'empty': " + getType(o));
}

static boolean empty(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(MultiSet ms) { return ms == null || ms.isEmpty(); }

static boolean isInteger(String s) {
  if (s == null) return false;
  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 Class javax() {
  return getJavaX();
}
// 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 String formatSnippetID(String id) {
  return "#" + parseSnippetID(id);
}

static String formatSnippetID(long id) {
  return "#" + id;
}
static boolean containsToken(List<String> tok, String token) {
  return tok.contains(token);
}
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 vmKeepWithProgramMD5_get(String varName) {
  String struct = (String) ( callOpt(javax(), "vmKeep_get", programID(), md5OfMyJavaSource(), varName));
  if (struct != null)
    setOpt(mc(), varName, unstructure(struct));
}
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\r\n"));

static Set<String> usualJavaClassNames() {
  return usualJavaClassNames_set;
}
static <A> A last(List<A> l) {
  return empty(l) ? null : l.get(l.size()-1);
}

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

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

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

static <A> A last(Iterator<A> it) {
  A a = null;
  while  (it.hasNext()) { ping(); a = it.next(); }
  return a;
}
static long psI(String snippetID) {
  return parseSnippetID(snippetID);
}
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 boolean eq(Object a, Object b) {
  return a == null ? b == null : a == b || a.equals(b);
}


  static void clearTokens(List<String> tok) {
    clearAllTokens(tok);
  }
  
  static void clearTokens(List<String> tok, int i, int j) {
    clearAllTokens(tok, i, j);
  }
static void tokSet(List<String> tok, int idx, String token) {
  tok.set(idx, token);
  reTok(tok, idx);
}
static <A> HashSet<A> lithashset(A... items) {
  HashSet<A> set = new HashSet();
  for (A a : items) set.add(a);
  return set;
}
static List<String> splitAtJavaToken(String s, String splitToken) {
  return splitByJavaToken(s, splitToken);
}
  static String defaultTranslate(String text) {
    Class javax = getJavaX();
    File x = makeTempDir();
    saveTextFile(new File(x, "main.java"), text);
    List<File> libraries_out = new ArrayList<File>();
    File y = (File) call(javax, "defaultTranslate", x, libraries_out);
    if (y == null) return null;
    return loadTextFile(new File(y, "main.java"));
  }

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 (eq(t, "final")) t = get(tok, i += 2);
      
      assertTrue(isIdentifier(t));
      i += 2;
      String type = t;
      
      while (eq(get(tok, i), ".")) {
        type += "." + assertIdentifier(get(tok, i+2));
        i += 4;
      }
      
      // 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> 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 <A> A _get(A[] l, int idx) {
  return idx >= 0 && idx < l(l) ? l[idx] : null;
}
static boolean cic(Collection<String> l, String s) {
  return containsIgnoreCase(l, s);
}



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

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

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

static 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 <A> A printStruct(String prefix, A a) {
  printStructure(prefix, a);
  return a;
}

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

// optionally convert expression to return statement
static String tok_addReturn(List<String> tok) {
  String lastToken = get(tok, l(tok)-2);
  //print("addReturn: " + structure(tok) + ", lastToken: " + quote(lastToken));
  if (eq(lastToken, "}") || eq(lastToken, ";")) return join(tok);
  return "ret " + join(tok) + ";";
}

static String tok_addReturn(String s) {
  return tok_addReturn(javaTok(s));
}
static void tok_replaceWith(List<String> tok) {
  int i;
  while ((i = jfind(tok, "replace <id> with")) >= 0) {
    String token = tok.get(i+2);
    int repStart = i+6;
    int repEnd = smartIndexOf(tok, repStart, ".");
    String replacement = joinSubList(tok, repStart, repEnd-1);
    clearTokens(tok, i, repEnd+1);
    print("Replacing " + token + " with " + replacement + ".");
    int end = findEndOfBlock(tok, repEnd)-1;
    for (int j = repEnd+2; j < end; j += 2)
      if (eq(tok.get(j), token)) tok.set(j, replacement);
    reTok(tok, i, end);
  }
}

static String 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);
  jreplace1(tok, in, out, condition);
  return join(tok);
}

// 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;
static boolean eqGet(List l, int i, Object o) {
  return eq(get(l, i), o);
}
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 long now_virtualTime;
static long now() {
  return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis();
}

static boolean loadSnippets_verbose;

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))),
    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 boolean nempty(Collection c) {
  return !isEmpty(c);
}

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

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

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

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

static boolean nempty(Object o) { return !empty(o); }
static boolean reTok_modify_check;

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

static volatile boolean readLine_noReadLine;

static String readLine_lastInput;
static String readLine_prefix = "[] ";

static String readLine() {
  if (readLine_noReadLine) return null;
  String s = readLineHidden();
  if (s != null) {
    readLine_lastInput = s;
    print(readLine_prefix + s);
  }
  return s;
}
static <A, B> Pair<A, B> pair(A a, B b) {
  return new Pair(a, b);
}

static <A> Pair<A, A> pair(A a) {
  return new Pair(a, a);
}
static int parseInt(String s) {
  return empty(s) ? 0 : Integer.parseInt(s);
}

static int parseInt(char c) {
  return Integer.parseInt(str(c));
}
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 int print_maxLineLength = 0; // 0 = unset

static boolean print_silent; // 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 void print() {
  print("");
}

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

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

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

  print_raw(s);
}

static void print_raw(String s) {
  s = fixNewLines(s);
  // TODO if (print_maxLineLength != 0)
  StringBuffer 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);
}
// will create the file or update its last modified timestamp
static void touchFile(File file) { try {
  closeRandomAccessFile(newRandomAccessFile(mkdirsForFile(file), "rw"));
} catch (Exception __e) { throw rethrow(__e); } }
// out: func(L<S> tok, int cIndex) -> S
// condition: func(L<S> tok, int nIndex) -> S  [yeah it's inconsistent]
static String jreplace_dyn(String s, String in, Object out) {
  return jreplace_dyn(s, in, out, null);
}

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

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

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

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

  boolean anyChange = false;
  for (int n = 0; n < 10000; n++) {
    int i = findCodeTokens(tok, 1, ignoreCase, toks, condition);
    if (i < 0)
      return anyChange;
    String expansion = (String) ( callF(out, tok, i));
    int end = i+l(tokin)-2;
    clearAllTokens(tok, i, end); // C to C
    tok.set(i, expansion);
    if (reTok) // would this ever be false??
      reTok(tok, i, end);
    anyChange = true;
  }
  throw fail("woot? 10000! " + quote(in) + " => " + quote(out));
}
static Object getOpt(Object o, String field) {
  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;
    f.setAccessible(true);
    return f.get(o);
  } catch (Exception e) {
    throw new RuntimeException(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;
    f.setAccessible(true);
    return f.get(null);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

static Field getOpt_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}

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 boolean tok_classHasModifier(List<String> classDecl, String modifier) {
  int i = classDecl.indexOf("class");
  return subList(classDecl, 0, i).contains(modifier);
}
static String javaParser_makeAllPublic_keepComments(String src) {
  //CompilationUnit cu = javaParseCompilationUnit(src);
  CompilationUnit cu = JavaParser.parse(src);
  PrettyPrinterConfiguration ppconf = new PrettyPrinterConfiguration();
  ppconf.setIndent("  ");
  new javaParser_makeAllPublic_Visitor().visit(cu, null);
  return cu.toString(ppconf);
}
static boolean tok_sameTest(List<String> tok, String text) {
  int iTok = 0, iText = 0, nTok = l(tok), nText = l(text);
  while (iTok < nTok) {
    String t = tok.get(iTok++);
    int n = l(t);
    if (n == 0) continue;
    if (!t.regionMatches(0, text, iText, n)) return false;
    iText += n;
  }
  return iText == nText;
}
static String concatMap_strings(Object f, Iterable l) {
  return join((List<String>) map(f, l));
}

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

static String concatMap_strings(Iterable l, Object f) {
  return concatMap_strings(f, l);
}
static String struct(Object o) {
  return structure(o);
}

static String struct(Object o, structure_Data data) {
  return structure(o, data);
}
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 boolean containsOneOfIC(Collection<String> l, String... x) {
  Set<String> set = asCISet(x);
  for (String a : unnull(l))
    if (set.contains(a))
      return true;
  return false;
}
// 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) {
  TreeMap<Integer,Integer> map = new TreeMap();
  List<Integer> stack = new ArrayList();
  for (int i = 1; i < l(tok); 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 String joinWithSpace(Collection<String> c) {
  return join(" ", c);
}

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

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

static void tok_doubleFor_v2(List<String> tok) {
  for (int i : reversed(indexesOf(tok, "for"))) {
    if (neq(get(tok, i+2), "(")) continue;
    int argsFrom = i+4;
    int iColon = indexOfAny(tok, argsFrom, ":", ")");
    if (neq(get(tok, iColon), ":")) continue;
    if (tok_doubleFor_v2_debug) print("have colon");
    tok_typesAndNamesOfParams_keepModifiers.set(true);
    List<Pair<String, String>> args = tok_typesAndNamesOfParams(subList(tok, argsFrom-1, iColon-1));
    if (l(args) != 2) continue; // simple for or a three-argument for (out of this world!)
    
    // S a, b => S a, S b
    if (eq(second(args).a, "?")) second(args).a = first(args).a;
    
    if (tok_doubleFor_v2_debug) print("have 2 args: " + sfu(args));
    int iClosing = tok_findEndOfForExpression(tok, iColon+2);
    if (iClosing < 0) continue;
    if (tok_doubleFor_v2_debug) print("have closing");
    String exp = trimJoinSubList(tok, iColon+2, iClosing-1);
    if (tok_doubleFor_v2_debug) print("Expression: " + exp);
    int iCurly = iClosing+2;
    if (neq(get(tok, iCurly), "{")) throw fail("please use { with double for");
    int iCurlyEnd = findEndOfStatement(tok, iCurly)-1;
    if (iCurlyEnd < 0) continue;
    if (tok_doubleFor_v2_debug) print("have curly end");
    
    // complicated expression? evaluate to variable first
    
    if (!isIdentifier(exp)) {
      String expVar = makeVar();
      String mapType = "Map<" + first(args).a + ", " + second(args).a + ">";
      tokPrepend(tok, i, "{ final " + mapType + " " + expVar + " = " + exp + "; ");
      tokAppend(tok, iCurlyEnd, "}");
      replaceTokens(tok, iColon+2, iClosing-1, expVar);
      exp = expVar;
    }
    
    tokAppend(tok, iColon, " keys(");
    tokPrepend(tok, iClosing, ")");
    
    replaceTokens(tok, argsFrom, iColon-1, joinPairWithSpace(first(args)));
    tokAppend(tok, iCurly, " " + joinPairWithSpace(second(args)) + " =" + exp + ".get(" + first(args).b + "); ");
    reTok(tok, i, iCurlyEnd+1);
  }
}
// process scope x. ... end scope blocks
static void tok_scopes(List<String> tok) {
  if (!tok.contains("scope")) return;
  
  int i;
  while ((i = rjfind(tok, "scope <id> .")) >= 0) {
    int j = jfind(tok, i+6, "end scope");
    if (j < 0) j = l(tok)-1;
    String scopeName = tok.get(i+2);
    HashSet<String> names = new HashSet();
    HashSet<String> functions = new HashSet();
    
    clearTokens(tok, i, i+5);
    clearTokens(tok, j, j+3);
    List<String> subTok = subList(tok, i+5, j);
    
    // auto-make #lock variable
    if (jfind(subTok, "lock #lock") >= 0
      && jfind(subTok, "Lock #lock") < 0)
        tok.set(i, "static Lock " + scopeName + "_lock = lock();\n");
    
    // first pass (find # declarations)
    for (int k = i+6; k < j-2; k += 2) {
      String t = get(tok, k+2);
      if (eqGet(tok, k, "#") && isIdentifier(t)) {
        names.add(t);
        if (eqGet(tok, k+4, "(")) functions.add(t);
        replaceTokens(tok, k, k+3, scopeName + "_" + t);
      }
    }
    
    // second pass (references to scoped variables)
    for (int k = i+6; k < j; k += 2) {
      String t = get(tok, k);
      if (isIdentifier(t)) {
        if (names.contains(t)) {
          if (eqGet(tok, k-2, ".")) {}
          else if (eqGet(tok, k+2, "(") && !functions.contains(t)) {}
          // avoid lock ... and map ...
          else if (eqOneOf(t, "lock", "map") && isIdentifier(get(tok, k+2))) {}
          else
            tok.set(k, scopeName + "_" + t);
        } else if (eq(t, "__scope"))
          tok.set(k, quote(scopeName));
      }
    }
    
    reTok(tok, i, j+1);
  }
}


static int shorten_default = 100;

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

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

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

static String shorten(int max, String s) { return shorten(s, max); }
static <A> NowFuture<A> nowFuture(A a) {
  return new NowFuture(a);
}
static int identityHashCode(Object o) {
  return System.identityHashCode(o);
}
static void lock(Lock lock) { try {
  ping();
  if (lock == null) return;
  try {
    lock.lockInterruptibly();
  } catch (InterruptedException e) {
    print("Locking interrupted! I probably deadlocked, oops.");
    printStackTrace(e);
    rethrow(e);
  }
  ping();
} catch (Exception __e) { throw rethrow(__e); } }

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

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

static ReentrantLock lock() {
  return fairLock();
}
public static String join(String glue, Iterable<String> strings) {
  if (strings == null) return "";
  StringBuilder buf = new StringBuilder();
  Iterator<String> 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 String join(Iterable<String> strings) {
  return join("", strings);
}

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

static String fsI(long id) {
  return formatSnippetID(id);
}
static String dropPrefix(String prefix, String s) {
  return s == null ? null : s.startsWith(prefix) ? s.substring(l(prefix)) : s;
}
static List<Integer> jfindAll(List<String> tok, String pat) {
  return jfindAll(tok, jfind_preprocess(javaTok(pat)));
}

// tokPat must be jfind_preprocess'd
static List<Integer> jfindAll(List<String> tok, List<String> tokPat) {
  String[] toks = toStringArray(codeTokensOnly(tokPat));
  
  int i = -1;
  List<Integer> l = new ArrayList();
  while ((i = findCodeTokens(tok, i+1, false, toks)) >= 0)
    l.add(i);
  return l;
}
static boolean useDummyMainClasses() {
  return true;
  //ret eq("1", trim(loadTextFile(getProgramFile(#1008755, "use-dummy-main-classes"))));
}
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 List<String> tok_importedClassNames(List<String> tok) {
  List<String> names = new ArrayList();
  for (int i = 1; i < l(tok); 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 (isIdentifier (s))
      names.add(s);
      i = j-1;
    }
  return names;
}
static int jfind(String s, String in) {
  return jfind(javaTok(s), in);
}

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

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

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

static int jfind(List<String> tok, int startIdx, String in, Object condition) {
  List<String> tokin = javaTok(in);
  jfind_preprocess(tokin);
  return jfind(tok, startIdx, tokin, 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, List<String> tokin, Object condition) {
  return findCodeTokens(tok, startIdx, false, toStringArray(codeTokensOnly(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;
}
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 RuntimeException fail() { throw new RuntimeException("fail"); }
static RuntimeException fail(Throwable e) { throw asRuntimeException(e); }
static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); }
static RuntimeException fail(String msg) { throw new RuntimeException(msg == null ? "" : msg); }
static RuntimeException fail(String msg, Throwable innerException) { throw new RuntimeException(msg, innerException); }

static boolean tok_repeatWithSleep_verbose;

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 void tok_listComprehensions(List<String> tok) {
  if (!tok.contains("[")) return;
  for (int i = 1; i < l(tok); i += 2) {
    { if (!(eq(tok.get(i), "["))) continue; }
    int iOp = indexOfAny(tok, i+2, "?", ":", "in", "[", "]");
    if (iOp < 0) return;
    if (!eqOneOf(tok.get(iOp), ":", "in")) continue; // not a list comprehension
    
    Map<Integer, Integer> bracketMap = getBracketMap(tok); // XXX - optimize
    
    String type = joinSubList(tok, i+2, iOp-3), id = tok.get(iOp-2);
    int j = scanOverExpression(tok, bracketMap, iOp+2, "|");
    String exp = join(tok.subList(iOp+2, j));
    j += 2;
    int k = scanOverExpression(tok, bracketMap, j, "]");
    String where = join(tok.subList(j, k));
    ++k;
      
    String code = "filter(" + exp + ", func(" + type + " " + id + ") -> Bool { " + where + " })";
    replaceTokens(tok, i, k, code);
    reTok(tok, i, k);
  }
}


static String fsi(String id) {
  return formatSnippetID(id);
}
//sbool ping_actions_shareable = true;
static volatile boolean ping_pauseAll;
static int ping_sleep = 100; // poll pauseAll flag every 100

static volatile boolean ping_anyActions;
static Map<Thread, Object> ping_actions = newWeakHashMap();


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

// returns true when it slept
static boolean ping_impl() { try {
  if (ping_pauseAll && !isAWTThread()) {
    do
      Thread.sleep(ping_sleep);
    while (ping_pauseAll);
    return true;
  }
  
  
  if (ping_anyActions) { // don't allow sharing ping_actions
    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 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 boolean allVarNames_debug;

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 (eq(t, "{"))
      i = findEndOfBlock(tok, i)+1; // skip function/class bodies
  }
  return varNames;
}

static List<String> allVarNames(String text) {
  return allVarNames(javaTok(text));
}
static File getCacheProgramDir() {
  return getCacheProgramDir(getProgramID());
}

static File getCacheProgramDir(String snippetID) {
  return new File(userHome(), "JavaX-Caches/" + formatSnippetIDOpt(snippetID));
}
  public static boolean isSnippetID(String s) {
    try {
      parseSnippetID(s);
      return true;
    } catch (RuntimeException e) {
      return false;
    }
  }
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> Set<A> setIntersection(Set<A> a, Collection<A> b) {
  Set<A> set = similarEmptySet(a);
  Set<A> bSet = asSet(b);
  for (A x : a) if (bSet.contains(x)) set.add(x);
  return set;
}
static 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> ArrayList<A> litlist(A... a) {
  ArrayList l = new ArrayList(a.length);
  for (A x : a) l.add(x);
  return l;
}
// 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 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) {
      f.setAccessible(true);
      return f.get(o);
    }
      
    
      if (o instanceof DynamicObject)
        return ((DynamicObject) o).fieldValues.get(field);
    
  } catch (Exception e) {
    throw asRuntimeException(e);
  }
  throw new RuntimeException("Field '" + field + "' not found in " + o.getClass().getName());
}

static Object get_raw(Object o, String field) {
  try {
    Field f = get_findField(o.getClass(), field);
    f.setAccessible(true);
    return f.get(o);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

static Object get(Class c, String field) {
  try {
    Field f = get_findStaticField(c, field);
    f.setAccessible(true);
    return f.get(null);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

static Field get_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & 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 Set asSet(Object[] array) {
  HashSet set = new HashSet();
  for (Object o : array)
    if (o != null)
      set.add(o);
  return set;
}

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

static <A> Set<A> asSet(Iterable<A> l) {
  if (l instanceof Set) return (Set) l;
  HashSet<A> set = new HashSet();
  for (A o : unnull(l))
    if (o != null)
      set.add(o);
  return set;
}
static List<String> tok_moveImportsUp(List<String> tok) {
  StringBuilder buf = new StringBuilder();
  
  //print("tok_moveImportsUp n=" + l(tok));
  //print("Source begins: " + quote(shorten(join(tok), 200)));
  
  // scan imports on top
  boolean change = false;
  int i;
  Set<String> have = new HashSet();
  for (i = 1; i < l(tok); i += 2)
    if (eq(tok.get(i), "import")) {
      int j = indexOf(tok, i+2, ";");
      if (j < 0) break;
      String s = joinSubList(tok, i, j+1);
      have.add(s);
      i = j;
    } else break;
  
  //print("tok_moveImportsUp have " + l(have) + " after " + i);
  
  // scan imports in text
  for (; i < l(tok); i += 2)
    if (eq(tok.get(i), "import")) {
      int j = indexOf(tok, i+2, ";");
      if (j < 0) continue; // huh?
      String s = joinSubList(tok, i, j+1);
      //print("Found import at " + i + ": " + s);
      if (!have.contains(s)) {
        buf.append(s).append("\n");
        have.add(s);
      }
      replaceTokens/*_reTok*/(tok, i, j+1, "");
      change = true;
      i -= 2;
    }
    
  if (nempty(buf)) {
    if (l(tok) == 1) addAll(tok, "", "");
    tok.set(1, str(buf)+"\n"+tok.get(1));
    change = true;
    //reTok(tok, 1, 2);
  }
    
  if (change) reTok(tok);
  return tok;
}
static 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(t, "ifclass", "ifndef")) ++level;
    else if (eqOneOf(t, "endif", "endifndef")) --level;
    else if (level <= 0 && startsWithJavaIdentifierStart(t)
      && neqGet(tok, i-2, ".")) set.add(t);
  }
  return set;
}
static String f2s(File f) {
  return f == null ? null : f.getAbsolutePath();
}

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

    // scan for non-whitespace
    if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        if (s.charAt(j) == opener || s.charAt(j) == '\n') { // end at \n to not propagate unclosed string literal errors
          ++j;
          break;
        } else if (s.charAt(j) == '\\' && j+1 < l)
          j += 2;
        else
          ++j;
      }
    } else if (Character.isJavaIdentifierStart(c))
      do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || "'".indexOf(s.charAt(j)) >= 0)); // for stuff like "don't"
    else if (Character.isDigit(c)) {
      do ++j; while (j < l && Character.isDigit(s.charAt(j)));
      if (j < l && s.charAt(j) == 'L') ++j; // Long constants like 1L
    } else if (c == '[' && d == '[') {
      do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]"));
      j = Math.min(j+2, l);
    } else if (c == '[' && d == '=' && i+2 < l && s.charAt(i+2) == '[') {
      do ++j; while (j+2 < l && !s.substring(j, j+3).equals("]=]"));
      j = Math.min(j+3, l);
    } else
      ++j;
      
    tok.add(javaTok_substringC(s, i, j));
    i = j;
  }
  
  return tok;
}
static void change() {
  //mainConcepts.allChanged();
  // safe version for now cause function is sometimes included unnecessarily (e.g. by EGDiff)
  callOpt(getOptMC("mainConcepts"), "allChanged");
}
static boolean isSortedList(List l) {
  for (int i = 0; i < l(l)-1; i++)
    if (cmp(l.get(i), l.get(i+1)) > 0)
      return false;
  return true;
}
  static void clearAllTokens(List<String> tok) {
    for (int i = 0; i < tok.size(); i++)
      tok.set(i, "");
  }
  
  static void clearAllTokens(List<String> tok, int i, int j) {
    for (; i < j; i++)
      tok.set(i, "");
  }





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

static class javaParser_makeAllPublic_Visitor extends VoidVisitorAdapter {
  void makePublic(Set<com.github.javaparser.ast.Modifier> modifiers) {
    modifiers.remove(com.github.javaparser.ast.Modifier.PRIVATE);
    modifiers.remove(com.github.javaparser.ast.Modifier.PROTECTED);
    modifiers.add(com.github.javaparser.ast.Modifier.PUBLIC);
  }

  public void visit(ClassOrInterfaceDeclaration n, Object arg) {
    Node parent = n.getParentNode().get();
    if (!(parent instanceof LocalClassDeclarationStmt
      || parent instanceof CompilationUnit && neq(n.getName().asString(), "main")))
      makePublic(n.getModifiers());
    super.visit(n, arg);
  }
  
  public void visit(FieldDeclaration n, Object arg) {
    makePublic(n.getModifiers());
    super.visit(n, arg);
  }
  
  public void visit(ConstructorDeclaration n, Object arg) {
    Node parent = n.getParentNode().get();
    if (!(parent instanceof EnumDeclaration))
      makePublic(n.getModifiers());
    super.visit(n, arg);
  }
  
  public void visit(MethodDeclaration n, Object arg) {
    EnumSet<com.github.javaparser.ast.Modifier> m = n.getModifiers();
    //print("Method found: " + n.getName() + " with modifiers: " + m + ", position: " + n.getRange()->begin);
    if (m.contains(com.github.javaparser.ast.Modifier.PRIVATE) && !m.contains(com.github.javaparser.ast.Modifier.STATIC)) m.add(com.github.javaparser.ast.Modifier.FINAL);
    makePublic(m);
    super.visit(n, arg);
  }
}
static String 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, 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;
    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);
    anyChange = true;
  }
  throw fail("woot? 10000! " + quote(in) + " => " + quote(out));
}

static boolean jreplace_debug;
static <A> A getFuture(Future<A> f) { try {
  return f == null ? null : f.get();
} catch (Exception __e) { throw rethrow(__e); } }
static long parseLong(String s) {
  if (s == null) return 0;
  return Long.parseLong(dropSuffix("L", s));
}

static long parseLong(Object s) {
  return Long.parseLong((String) s);
}
  static String mainJava;
  
  static String loadMainJava() throws IOException {
    if (mainJava != null) return mainJava;
    return loadTextFile("input/main.java", "");
  }
static String md5(String text) { try {
  if (text == null) return "-";
  return bytesToHex(md5_impl(text.getBytes("UTF-8"))); // 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 MessageDigest md5_md;

/*static byte[] md5_impl(byte[] data) ctex {
  if (md5_md == null)
    md5_md = MessageDigest.getInstance("MD5");
  return ((MessageDigest) md5_md.clone()).digest(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 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, 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);
}

// 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 <A> List<A> ll(A... a) {
  ArrayList l = new ArrayList(a.length);
  for (A x : a) l.add(x);
  return l;
}
static void tok_recordDecls(List<String> tok) {
  int i;
  while ((i = jfind(tok, "record <id>(")) >= 0) {
    int argsFrom = i+6, argsTo = findCodeTokens(tok, i, false, ")");
    int idx = findCodeTokens(tok, argsTo, false, "{");
    int j = findEndOfBracketPart(tok, idx);
    String name = tok.get(i+2);
    boolean noEq = eq(get(tok, i-2), "noeq");
    if (noEq) tok.set(i-2, "");
    
    StringBuilder buf = new StringBuilder();
    
    List<String> tArgs = subList(tok, argsFrom-1, argsTo);
    List<Pair<String, String>> args = tok_typesAndNamesOfParams(tArgs);
    List<String> contents = subList(tok, idx+1, j);
    
    for (Pair<String, String> p : args)
      buf.append("\n  " + p.a + " " + p.b + ";");
      
    buf.append("\n  *() {}");
    buf.append("\n  *(" + joinWithComma(map(args, new F1<Pair<String, String>, String>() { String get(Pair<String, String> p) { try { return 
      dropPrefix("new ", p.a) + " *" + p.b ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "dropPrefix(\"new \", p.a) + \" *\" + p.b"; }})) + ") {}");
      
    if (!(jfind(contents, "toString {") >= 0 || jfind(contents, "toString()") >= 0))
      buf.append("\n  toString { ret " 
        + quote(name + "(")
        + " + "
        + join(" + \", \" + ", secondOfPairs(args))
        + " + \")\"; }");
      
    if (!noEq) {
      //buf.append("\n  [stdEq]");
      buf.append(tok_genEqualsAndHashCode(name, args));
    }
    
    tok.set(idx, "{" + buf);
    tok.set(i, "class");
    clearTokens(tok, argsFrom-2, argsTo+1);
    reTok(tok, i, idx+1);
  }
}
static boolean isIdentifier(String s) {
  return isJavaIdentifier(s);
}
static String getSnippetTitle(String id) { try {
  if (id == null) return null;
  if (!isSnippetID(id)) return "?";
  long parsedID = parseSnippetID(id);
  String url;
  if (isImageServerSnippet(parsedID))
    url = "http://ai1.space/images/raw/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();
  return or2(trim(loadPageSilently(url)), "?");
} catch (Exception __e) { throw rethrow(__e); } }

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

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);
}
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 int countChar(String s, char c) {
  int n = 0, l = l(s);
  for (int i = 0; i < l; i++)
    if (s.charAt(i) == c)
      ++n;
  return n;
}
static boolean tok_tokenBeforeLonelyReturnValue(String t) {
  return l(t) == 1 && "{};)".contains(t) || eq(t, "else");
}
static boolean tok_hasClassRef2(List<String> tok, String className) {
  int n = l(tok);
  int level = 0;
  for (int i = 1; i < n; i += 2) {
    String t = tok.get(i);
    if (eqOneOf(t, "ifclass", "ifndef")) ++level;
    else if (eqOneOf(t, "endif", "endifndef")) --level;
    else if (level <= 0 && eq(t, className)) return true;
  }
  return false;
}
static boolean neqOneOf(Object o, Object... l) {
  for (Object x : l) if (eq(o, x)) return false; return true;
}
static void replaceTokens(List<String> tok, int i, int j, String s) {
  clearAllTokens(subList(tok, i, j));
  tok.set(i, s);
}

static void replaceTokens(List<String> tok, String s) {
  clearAllTokens(tok);
  tok.set(0, s);
}
static boolean hasUnclosedStringLiterals(String s) {
  for (String t : javaTokC(s))
    if (isUnproperlyQuoted(t))
      return true;
  return false;
}
static Set<String> findFunctionDefs_keywords = new HashSet(splitAtSpace("static svoid ssvoid ssynchronized sbool sS sO sL"));

static List<String> findFunctionDefs(List<String> tok) {
  int n = l(tok);
  List<String> functions = new ArrayList();
  for (int i = 1; i < n; i += 2) {
    String t = tok.get(i);
    if (findFunctionDefs_keywords.contains(t)) {
      int j = i+2;
      while (j < n && !eqOneOf(tok.get(j), ";", "=", "(", "{"))
        j += 2;
      if (eq(get(tok, j), "(") && isIdentifier(tok.get(j-2)))
        functions.add(tok.get(j-2));
    }
  }
  return functions;
}
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 List<String> findCodeTokens_specials = litlist("*", "<quoted>", "<id>", "<int>", "\\*");
static boolean findCodeTokens_debug;
static int findCodeTokens_indexed, findCodeTokens_unindexed;
static int findCodeTokens_bails, findCodeTokens_nonbails;

static int findCodeTokens(List<String> tok, int startIdx, boolean ignoreCase, String[] tokens, Object condition) {
  if (findCodeTokens_debug) {
    if (eq(getClassName(tok), "main$IndexedList2"))
      findCodeTokens_indexed++;
    else
      findCodeTokens_unindexed++;
  }
  // bail out early if first token not found (works great with IndexedList)
  if (!findCodeTokens_specials.contains(tokens[0])
    && !tok.contains(tokens[0] /*, startIdx << no signature in List for this, unfortunately */)) {
      ++findCodeTokens_bails;
      return -1;
    }
  ++findCodeTokens_nonbails;
  
  outer: for (int i = startIdx | 1; i+tokens.length*2-2 < tok.size(); i += 2) {
    for (int j = 0; j < tokens.length; j++) {
      String p = tokens[j], t = tok.get(i+j*2);
      boolean match;
      if (eq(p, "*")) match = true;
      else if (eq(p, "<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 neq(Object a, Object b) {
  return !eq(a, b);
}
static <A> int indexOf(List<A> l, A a, int startIndex) {
  if (l == null) return -1;
  for (int i = startIndex; i < l(l); 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) {
  if (x == null) return -1;
  for (int i = 0; i < l(x); i++)
    if (eq(x[i], a))
      return i;
  return -1;
}
static String javaParser_reparse_keepComments(String src) {
  CompilationUnit cu = JavaParser.parse(src);
  PrettyPrinterConfiguration ppconf = new PrettyPrinterConfiguration();
  ppconf.setIndent("  ");
  return cu.toString(ppconf);
}
static boolean eqOneOf(Object o, Object... l) {
  for (Object x : l) if (eq(o, x)) return true; return false;
}
static boolean findFunctionInvocations_debug;

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

static Set<String> findFunctionInvocations(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) {
  int i;
  LinkedHashSet l = new LinkedHashSet();
  while ((i = jfind(tok, "please include function *.")) >= 0) {
    String fname = tok.get(i+6);
    l.add(fname);
    hardReferences.add(fname);
    clearAllTokens(tok.subList(i, i+10));
  }

  boolean result = false;
  for (i = 1; i+2 < tok.size(); i += 2) {
    String f = tok.get(i);
    if (!isIdentifier(f)) continue;
    
    // main.<A>bla()
    if (eq(f, "main") && 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);
    if (!eqGet(tok, i+2, "(")) continue;
    if (i == 1
        || !findFunctionInvocations_pre.contains(tok.get(i-2))
        || eq(get(tok, i-2), ".") && eq(get(tok, i-4), "main")) {
      boolean inSF = sf == null || sf.containsKey(f);
      if (findFunctionInvocations_debug)
        print("Possible invocation: " + f + ", inSF: " + inSF);
      if (inSF && !contains(haveFunctions, f) && l.add(f))
        print("Found reference to standard function " + f + ": " + lineAroundToken(tok, i));
    }
  }
  
  return l;
}
static List<String> tlft_j(String text) {
  return toLinesFullTrim_java(text);
}
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);
}

// keyword can comprise multiple tokens now (like "p-awt"}
static List<String> replaceKeywordBlock(List<String> tok, String keyword, String beg, String end) {
  return replaceKeywordBlock(tok, keyword, beg, end, false, null);
}

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

static List<String> replaceKeywordBlock(List<String> tok, String keyword, String beg, String end,
  boolean debug, Object cond) {
  for (int n = 0; n < 1000; n++) {
    int i = jfind(tok, keyword + " {", cond);
    if (i < 0)
      break;
    int idx = findCodeTokens(tok, i, false, "{");
    int j = findEndOfBracketPart(tok, idx);
    
    if (debug) {
      print(toUpper(keyword) + " BEFORE\n" + join(subList(tok, i, j)));
      print("  THEN " + join(subList(tok, j, j+10)));
    }
    //assertEquals("}", tok.get(j-1));
    tok.set(j-1, end);
    replaceTokens(tok, i, idx+1, beg);
    reTok(tok, i, j);
    if (debug) print(toUpper(keyword) + "\n" + join(subList(tok, i, j)) + "\n");
  }
  return tok;
}
static void tok_unpair(List<String> tok) {
  if (!tok.contains("unpair")) return;
  
  int i;
  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(i+2, "Pair<" + type1 + "," + type2 + "> " + v + "=");
    tok.set(j-1, "; " + var1 + " = " + v + ".a; " + var2 + " = " + v + ".b;");
    reTok(tok, i, j);
  }
  
  while ((i = jfind(tok, "<id> <id>, <id> < <id>,<id> > <id> = unpair")) >= 0 || (i = jfind(tok, "<id> <id>, <id><<id>> <id> = unpair")) >= 0) {
    print("unpair");
    int idx = indexOf(tok, "unpair", i);
    int j = findEndOfStatement(tok, idx);
    String type1 = tok.get(i), var1 = tok.get(i+2);
    String type2 = joinSubList(tok, i+5, idx-5), var2 = tok.get(idx-4);
    String v = makeVar();
    tok.set(i+4, ";");
    tok.set(idx-2, ";");
    tok.set(idx-1, "");
    tok.set(idx, "Pair<" + type1 + "," + type2 + "> " + v + "=");
    tok.set(j-1, "; " + var1 + " = " + v + ".a; " + var2 + " = " + v + ".b;");
    reTok(tok, i, j);
  }
}
static List<String> tok_importedStaticFunctionNames(List<String> tok) {
  List<String> names = new ArrayList();
  for (int i = 1; i < l(tok); i += 2)
    if (eq(tok.get(i), "import") && eq(get(tok, i+2), "static")) {
      int j = findEndOfStatement(tok, i); // index of ;+1
      String s = get(tok, j-3);
      if (isIdentifier(s))
      names.add(s);
      i = j-1;
    }
  return names;
}
static 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 volatile int numberOfCores_value;

static int numberOfCores() {
  if (numberOfCores_value == 0)
    numberOfCores_value = Runtime.getRuntime().availableProcessors();
  return numberOfCores_value;
}
static TokCondition tokCondition_beginningOfMethodDeclaration() {
  return new TokCondition() { boolean get(final List<String> tok, final int i) {
    return eqOneOf(_get(tok, i-1), "}", ";", "{", null);
  }};
}
static String trimJoin(List<String> s) {
  return trim(join(s));
}
static <A> List<A> concatLists(Collection<A>... lists) {
  List<A> l = new ArrayList();
  for (Collection<A> list : lists)
    if (list != null)
      l.addAll(list);
  return l;
}

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

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

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 TreeSet<String> ciSet() {
  return caseInsensitiveSet();
}
  static boolean hasCodeTokens(List<String> tok, String... tokens) {
    return findCodeTokens(tok, tokens) >= 0;
  }
static String getClassName(Object o) {
  return o == null ? "null" : o instanceof Class ? ((Class) o).getName() : o.getClass().getName();
}
  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));
  }

static boolean startsWithLowerCaseOrUnderscore(String s) {
  return nempty(s) && (s.startsWith("_") || Character.isLowerCase(s.charAt(0)));
}
static void replaceTokens_reTok(List<String> tok, int i, int j, String s) {
  replaceTokens(tok, i, j, s);
  reTok(tok, i, j);
}
static <A, B> Set<A> keys(Map<A, B> map) {
  return map == null ? new HashSet() : map.keySet();
}

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


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





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




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

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


// 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 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 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 long sysNow() {
  return System.nanoTime()/1000000;
}
static List<String> includeInMainLoaded_stdReTok(List<String> tok, String text) {
  List<String> main = findMainClass(tok);
  if (main == null) {
    print(join(tok));
    throw fail("no main class");
  }
  int i = main.lastIndexOf("}");
  main.set(i, "\n" + text + "\n}");
  i += magicIndexOfSubList(tok, main);
  return reTok(tok, i, i+1);
}
static <A> IndexedList2<A> indexedList2(List<A> l) {
  return l instanceof IndexedList2 ? ((IndexedList2) l) : new IndexedList2(l);
}
static boolean preferCached = false;
static boolean loadSnippet_debug = false;
static ThreadLocal<Boolean> loadSnippet_silent = new ThreadLocal();
static int loadSnippet_timeout = 30000;



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

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

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

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

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

  return text;
}

static File DiskSnippetCache_dir;

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

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

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

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

public static File DiskSnippetCache_getDir() {
  return DiskSnippetCache_dir;
}

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

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

static String dropPrefixTrim(String prefix, String s) {
  return trim(dropPrefix(prefix, s));
}
static Object loadVariableDefinition(String varName) {
  return loadVariableDefinition(getProgramID(), varName);
}

// currently only works with string lists ("= litlist(...)")
// and strings.
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> 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 RuntimeException rethrow(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  throw t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}
static <A> A or(A a, A b) {
  return a != null ? a : b;
}
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);
    
    replaceTokens(tok, i, bracket-1, ("static Cache<" + (type) + "> " + (fName) + "_cache = new Cache(f " + (fName) + "_load);\n")
      + ("static " + (type) + " " + (fName) + "() { ret " + (fName) + "_cache!; }\n\n") + ("static " + (type) + " " + (fName) + "_load"));
    reTok(tok, i, bracket-3);
  }
} catch (Throwable __e) { printStackTrace2(__e); }}
static void clear(Collection c) {
  if (c != null) c.clear();
}
static String dropSuffix(String suffix, String s) {
  return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}
// i must point at the opening bracket ("<")
// index returned is index of closing bracket + 1
static int findEndOfTypeArgs(List<String> cnc, int i) {
  int j = i+2, level = 1;
  while (j < cnc.size()) {
    if (cnc.get(j).equals("<")) ++level;
    else if (cnc.get(j).equals(">")) --level;
    if (level == 0)
      return j+1;
    ++j;
  }
  return cnc.size();
}

static boolean isMD5(String s) {
  return l(s) == 32 && isLowerHexString(s);
}
static boolean structure_showTiming, structure_checkTokenCount;

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

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

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

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

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

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

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

static void structure_1(final Object o, final structure_Data d) {
  if (o == null) { d.append("null"); return; }
  
  Class c = o.getClass();
  boolean concept = false;
  
  List<Field> lFields = d.fieldsByClass.get(c);
  
  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 ", 2); 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 HashSet) {
      d.append(o instanceof LinkedHashSet ? "lhs " : "hashset ");
      structure_1(new ArrayList((Set) o), d);
      return;
    }
  
    if (o instanceof TreeSet) {
      d.append("treeset ");
      structure_1(new ArrayList((Set) o), d);
      return;
    }
    
    String name = c.getName();
    if (o instanceof Collection
      && !startsWith(name, "main$")
      /* && neq(name, "main$Concept$RefL") */) {
      d.append("[");
      final int l = d.n;
      final Iterator it = ((Collection) o).iterator();
      d.stack.add(new Runnable() { public void run() { try { 
        if (!it.hasNext())
          d.append("]");
        else {
          d.stack.add(this);
          if (d.n != l) d.append(", ");
          structure_1(it.next(), d);
        }
      
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (!it.hasNext())\r\n          d.append(\"]\");\r\n        else {\r\n          d.sta..."; }});
      return;
    }
    
    if (o instanceof Map && !startsWith(name, "main$")) {
      if (o instanceof LinkedHashMap) d.append("lhm");
      else if (o instanceof HashMap) d.append("hm");
      else if (name.equals("java.util.Collections$SynchronizedMap")) d.append("sync");
      else if (name.equals("java.util.Collections$SynchronizedSortedMap")) { d.append("sync tm", 2); }
      
      d.append("{");
      final int l = d.n;
      final Iterator it = ((Map) o).entrySet().iterator();
      
      d.stack.add(new Runnable() {
        boolean v;
        Map.Entry e;
        
        public void run() {
          if (v) {
            d.append("=");
            v = false;
            d.stack.add(this);
            structure_1(e.getValue(), d);
          } else {
            if (!it.hasNext())
              d.append("}");
            else {
              e = (Map.Entry) it.next();
              v = true;
              d.stack.add(this);
              if (d.n != l) d.append(", ");
              structure_1(e.getKey(), d);
            }
          }
        }
      });
      return;
    }
    
    if (c.isArray()) {
      if (o instanceof byte[]) {
        d.append("ba ").append(quote(bytesToHex((byte[]) o))); return;
      }
  
      final int n = Array.getLength(o);
  
      if (o instanceof boolean[]) {
        String hex = boolArrayToHex((boolean[]) o);
        int i = l(hex);
        while (i > 0 && hex.charAt(i-1) == '0' && hex.charAt(i-2) == '0') i -= 2;
        d.append("boolarray ").append(n).app(" ").append(quote(substring(hex, 0, i))); return;
      }
      
      String atype = "array", sep = ", ";
  
      if (o instanceof int[]) {
        //ret "intarray " + quote(intArrayToHex((int[]) o));
        atype = "intarray";
        sep = " ";
      }
      
      d.append(atype).append("{");
      d.stack.add(new Runnable() {
        int i;
        public void run() {
          if (i >= n)
            d.append("}");
          else {
            d.stack.add(this);
            if (i > 0) d.append(", ");
            structure_1(Array.get(o, i++), d);
          }
        }
      });
      return;
    }
  
    if (o instanceof Class) {
      d.append("class(", 2).append(quote(((Class) o).getName())).append(")"); return;
    }
      
    if (o instanceof Throwable) {
      d.append("exception(", 2).append(quote(((Throwable) o).getMessage())).append(")"); return;
    }
      
    if (o instanceof BitSet) {
      BitSet bs = (BitSet) o;
      d.append("bitset{", 2);
      int l = d.n;
      for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
        if (d.n != l) d.append(", ");
        d.append(i);
      }
      d.append("}"); return;
    }
      
    // Need more cases? This should cover all library classes...
    if (name.startsWith("java.") || name.startsWith("javax.")) {
      d.append("j ").append(quote(str(o))); return; // Hm. this is not unstructure-able
    }
    
    
      
    /*if (name.equals("main$Lisp")) {
      fail("lisp not supported right now");
    }*/
    
    String dynName = shortDynamicClassName(o);
    if (concept && !d.concepts.contains(dynName)) {
      d.concepts.add(dynName);
      d.append("c ");
    }
    
    // serialize an object with fields.
    // first, collect all fields and values in fv.
    
    TreeSet<Field> fields = new TreeSet<Field>(new Comparator<Field>() {
      public int compare(Field a, Field b) {
        return stdcompare(a.getName(), b.getName());
      }
    });
    
    Class cc = c;
    while (cc != Object.class) {
      for (Field field : getDeclaredFields_cached(cc)) {
        if ((field.getModifiers() & (java.lang.reflect.Modifier.STATIC | java.lang.reflect.Modifier.TRANSIENT)) != 0)
          continue;
        String fieldName = field.getName();
        
        fields.add(field);
        
        // put special cases here...
      }
        
      cc = cc.getSuperclass();
    }
    
    lFields = asList(fields);
    
    // Render this$1 first because unstructure needs it for constructor call.
    
    for (int i = 0; i < l(lFields); i++) {
      Field f = lFields.get(i);
      if (f.getName().equals("this$1")) {
        lFields.remove(i);
        lFields.add(0, f);
        break;
      }
    }
  
    
    d.fieldsByClass.put(c, 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
  }

  LinkedHashMap<String,Object> fv = new LinkedHashMap();
  for (Field f : lFields) {
    Object value;
    try {
      value = f.get(o);
    } catch (Exception e) {
      value = "?";
    }
      
    if (value != null)
      fv.put(f.getName(), value);
    
  }
  
  String name = c.getName();
  String shortName = dropPrefix("main$", name);
    
  // 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) {
    fv.putAll((Map) fv.get("fieldValues"));
    fv.remove("fieldValues");
    shortName = shortDynamicClassName(o);
    fv.remove("className");
  }
  
  String singleField = fv.size() == 1 ? first(fv.keySet()) : null;
  
  d.append(shortName);
  
  
  final int l = d.n;
  final Iterator it = fv.entrySet().iterator();
  
  d.stack.add(new Runnable() { public void run() { try { 
    if (!it.hasNext()) {
      if (d.n != l)
        d.append(")");
    } else {
      Map.Entry e = (Map.Entry) it.next();
      d.append(d.n == l ? "(" : ", ");
      d.append((String) e.getKey()).append("=");
      d.stack.add(this);
      structure_1(e.getValue(), d);
    }
  
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (!it.hasNext()) {\r\n      if (d.n != l)\r\n        d.append(\")\");\r\n    } else..."; }});
}

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 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 <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 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 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> void callF(VF1<A> f, A a) {
    if (f != null) f.get(a);
  }


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

// used internally
static ArrayList<Method> callF_makeCache(Class c) {
  ArrayList<Method> l = new ArrayList();
  Class _c = c;
  do {
    for (Method m : _c.getDeclaredMethods())
      if (m.getName().equals("get")) {
        m.setAccessible(true);
        l.add(m);
      }
    if (!l.isEmpty()) break;
    _c = _c.getSuperclass();
  } while (_c != null);
  callF_cache.put(c, l);
  return l;
}
static String getNameOfAnyClass(List<String> tok) {
  return getClassDeclarationName(first(allClasses(tok)));
}

static <A, B> Map<A, B> newWeakHashMap() {
  return _registerWeakMap(synchroMap(new WeakHashMap()));
}
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) {
  ArrayList<Integer> l = new ArrayList();
  for (int i : a) l.add(i);
  return l;
}

static <A> ArrayList<A> asList(Iterable<A> s) {
  if (s instanceof ArrayList) return (ArrayList) s;
  ArrayList l = new ArrayList();
  if (s != null)
    for (A a : s)
      l.add(a);
  return l;
}


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


static <A> ArrayList<A> asList(Enumeration<A> e) {
  ArrayList l = new ArrayList();
  if (e != null)
    while (e.hasMoreElements())
      l.add(e.nextElement());
  return l;
}
static void replaceListPart(List l, int i, int j, List l2) {
  l.subList(i, j).clear();
  l.addAll(i, l2);
}
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 + "'";
}

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

// 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 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 String shortDynamicClassName(Object o) {
 if (o instanceof DynamicObject && ((DynamicObject) o).className != null)
    return ((DynamicObject) o).className;
  return shortClassName(o);
}
static Thread currentThread() {
  return Thread.currentThread();
}
static Throwable printStackTrace(Throwable 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(S indent, Throwable e) {
  if (endsWithLetter(indent)) indent += " ";
  printIndent(indent, getStackTrace(e));
}*/
static boolean neqGet(List l, int i, Object o) {
  return neq(get(l, i), o);
}
static <A> void replaceCollection(Collection<A> dest, Collection<A> src) {
  dest.clear();
  dest.addAll(src);
}
static String standardCredentials() {
  String user = standardCredentialsUser();
  String pass = standardCredentialsPass();
  if (nempty(user) && nempty(pass))
    return "&_user=" + urlencode(user) + "&_pass=" + urlencode(pass);
  return "";
}
static AutoInitVar < String > md5OfMyJavaSource_cache = new AutoInitVar(new F0<String>() { String get() { try { return  md5(myJavaSource()) ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "md5(myJavaSource())"; }});

static String md5OfMyJavaSource() {
  return md5OfMyJavaSource_cache.get();
}
static String getType(Object o) {
  return getClassName(o);
}
static String standardCredentials_noCookies() {
  return standardCredentials() + "&noCookies=1";
}
static boolean endsWith(String a, String b) {
  return a != null && a.endsWith(b);
}

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


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


static int deleteDirectory_minPathLength = 10;

static void deleteDirectory(File dir) {
  deleteDirectory(dir, false, false);
}

static void deleteDirectory(File dir, boolean verbose, boolean testRun) {
  dir = getCanonicalFile(dir);
  assertTrue(f2s(dir), l(f2s(dir)) >= deleteDirectory_minPathLength);
  File[] files = dir.listFiles();
  if (files == null) return;
  for (File f : files) {
    if (f.isDirectory())
      deleteDirectory(f, verbose, testRun);
    else {
      if (verbose)
        print((testRun ? "Would delete " : "Deleting ") + f.getAbsolutePath());
      if (!testRun)
        f.delete();
    }
  }
  if (verbose) print((testRun ? "Would delete " : "Deleting ") + dir.getAbsolutePath());
  if (!testRun)
    dir.delete();
}

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 String md5OfFile(File f) { try {
  if (!f.exists()) return "-";
  
  MessageDigest md5 = MessageDigest.getInstance("MD5");
  try ( FileInputStream in = new FileInputStream(f)) {

  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());
}} catch (Exception __e) { throw rethrow(__e); } }
static <A> A liftLast(List<A> l) {
  if (l.isEmpty()) return null;
  int i = l(l)-1;
  A a = l.get(i);
  l.remove(i);
  return a;
}
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 unnull(String s) {
  return s == null ? "" : s;
}

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

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

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

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

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



//ifclass Symbol

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

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

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

static int unstructure_tokrefs; // stats

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

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

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

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

    // look at current token
    String t() {
      return curT;
    }
    
    // get current token, move to next
    String tpp() {
      String t = curT;
      consume();
      return t;
    }
    
    void parse(final unstructure_Receiver out) {
      String t = t();
      
      int refID = 0;
      if (structure_isMarker(t, 0, l(t))) {
        refID = parseInt(t.substring(1));
        consume();
      }
      final int _refID = refID;
      
      // if (debug) print("parse: " + quote(t));
      
      final int tokIndex = i;  
      parse_inner(refID, tokIndex, new unstructure_Receiver() {
        void set(Object o) {
          if (_refID != 0)
            refs.put(_refID, o);
          if (o != null)
            tokrefs.put(tokIndex, o);
          out.set(o);
        }
      });
    }
    
    void parse_inner(int refID, int tokIndex, final unstructure_Receiver out) {
      String t = t();
      
      // if (debug) print("parse_inner: " + quote(t));
      
      Class c = classesMap.get(t);
      if (c == null) {
        if (t.startsWith("\"")) {
          String s = internIfLongerThan(unquote(tpp()), structure_internStringsLongerThan);
          out.set(s); return;
        }
        
        if (t.startsWith("'")) {
          out.set(unquoteCharacter(tpp())); return;
        }
        if (t.equals("bigint")) {
          out.set(parseBigInt()); return;
        }
        if (t.equals("d")) {
          out.set(parseDouble()); return;
        }
        if (t.equals("fl")) {
          out.set(parseFloat()); return;
        }
        if (t.equals("sh")) {
          consume();
          t = tpp();
          if (t.equals("-")) {
            t = tpp();
            out.set((short) (-parseInt(t))); return;
          }
          out.set((short) parseInt(t)); return;
        }
        if (t.equals("-")) {
          consume();
          t = tpp();
          out.set(isLongConstant(t) ? (Object) (-parseLong(t)) : (Object) (-parseInt(t))); return;
        }
        if (isInteger(t) || isLongConstant(t)) {
          consume();
          //if (debug) print("isLongConstant " + quote(t) + " => " + isLongConstant(t));
          if (isLongConstant(t)) {
            out.set(parseLong(t)); return;
          }
          long l = parseLong(t);
          boolean isInt = l == (int) l;
          if (debug)
            print("l=" + l + ", isInt: " + isInt);
          out.set(isInt ? (Object) new Integer((int) l) : (Object) new Long(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;
          if (debug)
            print("l=" + l + ", isInt: " + isInt);
          out.set(isInt ? (Object) new Integer((int) l) : (Object) new Long(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)
            print("Warning: 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)
            print("Warning: unsatisfied token reference " + ref);
          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 (eqOneOf(t, "hashmap", "hm")) {
          consume();
          parseMap(new HashMap(), out);
          return;
        }
        if (t.equals("lhm")) {
          consume();
          parseMap(new LinkedHashMap(), out);
          return;
        }
        if (t.equals("sync")) {
          consume();
          if (t().equals("tm")) {
            consume();
            { parseMap(synchronizedTreeMap(), out); return; }
          }
          { parseMap(synchronizedMap(), out); return; }
        }
        if (t.equals("{")) {
          parseMap(out); return;
        }
        if (t.equals("[")) {
          this.parseList(out); return;
        }
        if (t.equals("bitset")) {
          parseBitSet(out); return;
        }
        if (t.equals("array") || t.equals("intarray")) {
          parseArray(out); return;
        }
        if (t.equals("ba")) {
          consume();
          String hex = unquote(tpp());
          out.set(hexToBytes(hex)); return;
        }
        if (t.equals("boolarray")) {
          consume();
          int n = parseInt(tpp());
          String hex = unquote(tpp());
          out.set(boolArrayFromBytes(hexToBytes(hex), n)); return;
        }
        if (t.equals("class")) {
          out.set(parseClass()); return;
        }
        if (t.equals("l")) {
          parseLisp(out); return;
        }
        if (t.equals("null")) {
          consume(); out.set(null); return;
        }
        
        if (eq(t, "c")) {
          consume("c");
          t = t();
          assertTrue(isJavaIdentifier(t));
          concepts.add(t);
        }
      }
      
      if (eq(t, "j")) {
        consume("j");
        out.set(parseJava()); return;
      }

      if (c == null && !isJavaIdentifier(t))
        throw new RuntimeException("Unknown token " + (i+1) + ": " + t);
        
      // any other class name
      if (c == null) {
        // First, find class
        if (allDynamic) c = null;
        else c = classFinder != null ? (Class) callF(classFinder, "main$" + t) : findClass(t);
        if (c != null)
          classesMap.put(t, c);
      }
          
      // Check if it has an outer reference
      consume();
      boolean hasBracket = eq(t(), "(");
      if (hasBracket) consume();
      boolean hasOuter = hasBracket && eq(t(), "this$1");
      
      DynamicObject dO = null;
      Object o = null;
      final String thingName = t;
      if (c != null) {
        o = hasOuter ? nuStubInnerObject(c, classFinder) : nuEmptyObject(c);
        if (o instanceof DynamicObject) dO = (DynamicObject) o;
      } else {
        if (concepts.contains(t) && (c = findClass("Concept")) != null)
          o = dO = (DynamicObject) nuEmptyObject(c);
        else
          dO = new DynamicObject();
        dO.className = t;
        if (debug) print("Made dynamic object " + t + " " + shortClassName(dO));
      }
      
      // Save in references list early because contents of object
      // might link back to main object
      
      if (refID != 0)
        refs.put(refID, o != null ? o : dO);
      tokrefs.put(tokIndex, o != null ? o : dO);
      
      // NOW parse the fields!
      
      final LinkedHashMap<String,Object> fields = new LinkedHashMap(); // preserve order
      final Object _o = o;
      final DynamicObject _dO = dO;
      if (hasBracket) {
        stack.add(new Runnable() { public void run() { try { 
          if (eq(t(), ")")) {
            consume(")");
            objRead(_o, _dO, fields);
            out.set(_o != null ? _o : _dO);
          } else {
            final String key = unquote(tpp());
            if (!eq(tpp(), "="))
              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);
                if (eq(t(), ",")) consume();
              }
            });
          }
        
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (eq(t(), \")\")) {\r\n            consume(\")\");\r\n            objRead(_o, _dO, ..."; }});
      } else {
        objRead(o, dO, fields);
        out.set(o != null ? o : dO);
      }
    }
    
    void objRead(Object o, DynamicObject dO, Map<String, Object> fields) {
      
      if (o != null)
        if (dO != null) {
          if (debug)
            printStructure("setOptAllDyn", fields);
          setOptAllDyn(dO, fields);
        } else {
          setOptAll_pcall(o, fields);
          
        }
      else for (String field : keys(fields))
        dO.fieldValues.put(intern(field), fields.get(field));

      if (o != null)
        pcallOpt_noArgs(o, "_doneLoading");
    }
    
    void parseSet(final Set set, final unstructure_Receiver out) {
      this.parseList(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 unstructure_Receiver out) {
      consume("[");
      final ArrayList list = new ArrayList();
      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          out.set(list);\r\n     ..."; }});
    }
    
    void parseArray(final unstructure_Receiver out) {
      final String type = tpp();
      consume("{");
      final List list = new ArrayList();
      
      stack.add(new Runnable() { public void run() { try { 
        if (eq(t(), "}")) {
          consume("}");
          out.set(type.equals("intarray") ? toIntArray(list) : list.toArray());
        } else {
          stack.add(this);
          parse(new unstructure_Receiver() {
            void set(Object o) {
              list.add(o);
              if (eq(t(), ",")) consume();
            }
          });
        }
      
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (eq(t(), \"}\")) {\r\n          consume(\"}\");\r\n          out.set(type.equals(\"..."; }});
    }
    
    Object parseClass() {
      consume("class");
      consume("(");
      String name = unquote(tpp());
      consume(")");
      name = dropPrefix("main$", name);
      Class c = allDynamic ? null : classFinder != null ? (Class) callF(classFinder, name) : findClass(name);
      if (c != null) return c;
      DynamicObject dO = new DynamicObject();
      dO.className = "java.lang.Class";
      dO.fieldValues.put("name", name);
      return dO;
    }
    
    Object parseBigInt() {
      consume("bigint");
      consume("(");
      String val = 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 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;
        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 (debug)
                  print("parseMap: Got value " + getClassName(value) + ", next token: " + quote(t()));
                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();
    }
    
    void parse_x(unstructure_Receiver out) {
      consume(); // get first token
      parse(out);
      while (nempty(stack))
        popLast(stack).run();
    }
  }
  
  Boolean b = DynamicObject_loading.get();
  DynamicObject_loading.set(true);
  try {
    final Var v = new Var();
    X x = new X();
    x.parse_x(new unstructure_Receiver() {
      void set(Object o) { v.set(o); }
    });
    unstructure_tokrefs = x.tokrefs.size();
    return v.get();
  } finally {
    DynamicObject_loading.set(b);
  }
}

static boolean unstructure_debug;
static boolean isImageServerSnippet(long id) {
  return id >= 1100000 && id < 1200000;
}
static void tokPrepend(List<String> tok, int i, String s) {
  tok.set(i, s + tok.get(i));
}
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 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 pathToJavaxJar() { 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 = "http://tinybrain.de/x30.jar";
    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 Class __javax;

static Class getJavaX() { try {
  
  return __javax;
} catch (Exception __e) { throw rethrow(__e); } }
static boolean dir2zip_recurse_verbose;

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 void set(Object o, String field, Object value) {
  if (o instanceof Class) set((Class) o, field, value);
  else try {
    Field f = set_findField(o.getClass(), field);
    f.setAccessible(true);
    smartSet(f, o, value);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

static void set(Class c, String field, Object value) {
  try {
    Field f = set_findStaticField(c, field);
    f.setAccessible(true);
    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 File tempDirPossiblyInRAMDisk() {
  File f = linux_fileInRamDisk(aGlobalID());
  if (f != null) { f.mkdirs(); return f; }
  return makeTempDir();
}
static TreeSet<String> asCISet(Collection<String> c) {
  return toCaseInsensitiveSet(c);
}

static TreeSet<String> asCISet(String... x) {
  return toCaseInsensitiveSet(x);
}
static void closeRandomAccessFile(RandomAccessFile f) {
  if (f != null) try {
    f.close();
    callJavaX("dropIO", f);
  } catch (Throwable e) {
    printStackTrace(e);
  }
}
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));
}

// TODO: remove
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) { try {
  if (o == null) return null;
  if (o instanceof Class) {
    Method m = call_findStaticMethod((Class) o, method, args, false);
    m.setAccessible(true);
    return invokeMethod(m, null, args);
  /*} else if (o instanceof DynamicMethods) {
    ret ((DynamicMethods) o)._dynCall(method, args);*/
  } else {
    Method m = call_findMethod(o, method, args, false);
    m.setAccessible(true);
    return invokeMethod(m, o, args);
  }
} catch (Exception __e) { throw rethrow(__e); } }

static Method call_findStaticMethod(Class c, String method, Object[] args, boolean debug) {
  Class _c = c;
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (debug)
        System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
      if (!m.getName().equals(method)) {
        if (debug) System.out.println("Method name mismatch: " + method);
        continue;
      }

      if ((m.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0 || !call_checkArgs(m, args, debug))
        continue;

      return m;
    }
    c = c.getSuperclass();
  }
  throw new RuntimeException("Method '" + method + "' (static) with " + args.length + " parameter(s) not found in " + _c.getName());
}

static Method call_findMethod(Object o, String method, Object[] args, boolean debug) {
  Class c = o.getClass();
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (debug)
        System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
      if (m.getName().equals(method) && call_checkArgs(m, args, debug))
        return m;
    }
    c = c.getSuperclass();
  }
  throw new RuntimeException("Method '" + method + "' (non-static) with " + args.length + " parameter(s) not found in " + o.getClass().getName());
}
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)
        f.setAccessible(true);
    }
  }
  return fields;
}
static boolean vmExiting() {
  return isTrue(getOpt(javax(), "killing"));
}
static File getGlobalCache() {
  File file = new File(javaxCachesDir(), "Binary Snippets");
  file.mkdirs();
  return file;
}

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

static List<String> toUpper(Collection<String> s) {
  return allToUpper(s);
}
static String boolArrayToHex(boolean[] a) {
  return bytesToHex(boolArrayToBytes(a));
}
static void lockOrFail(Lock lock, long timeout) { try {
  ping();
  if (!lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
    String s = "Couldn't acquire lock after " + timeout + " ms.";
    if (lock instanceof ReentrantLock) {
      ReentrantLock l = (ReentrantLock) ( lock);
      s += " Hold count: " + l.getHoldCount() + ", owner: " + call(l, "getOwner");
    }
    throw fail(s);
  }
  ping();
} catch (Exception __e) { throw rethrow(__e); } }
static Iterator emptyIterator() {
  return Collections.emptyIterator();
}
static <A> A callMain(A c, String... args) {
  callOpt(c, "main", new Object[] {args});
  return c;
}

static void callMain() {
  callMain(mc());
}
static String readLineHidden() { try {
  if (get(javax(), "readLine_reader") == null)
    set(javax(), "readLine_reader" , new BufferedReader(new InputStreamReader(System.in, "UTF-8")));
  try {
    return ((BufferedReader) get(javax(), "readLine_reader")).readLine();
  } finally {
    consoleClearInput();
  }
} catch (Exception __e) { throw rethrow(__e); } }
  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 getServerTranspiled(String snippetID) {
  return getServerTranspiled(snippetID, null);
}

// returns "SAME" if md5 matches
static String getServerTranspiled(String snippetID, String expectedMD5) { try {
  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 print_append(Appendable _buf, String s, int max) { try {
  synchronized(_buf) {
    _buf.append(s);
    if (!(_buf instanceof StringBuilder)) return;
    StringBuilder buf = (StringBuilder) ( _buf);
    max /= 2;
    if (buf.length() > max) try {
      int newLength = max/2;
      int ofs = buf.length()-newLength;
      String newString = buf.substring(ofs);
      buf.setLength(0);
      buf.append("[...] ").append(newString);
    } catch (Exception e) {
      buf.setLength(0);
    }
  }
} catch (Exception __e) { throw rethrow(__e); } }
  // 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()
  
  static int magicIndexOfSubList(List<String> list, List<String> sublist) {
    Integer o1 = (Integer) ( getOpt(list, "offset"));
    int o2 = (Integer) get(sublist, "offset");
    return o2-(o1 != null ? o1 : 0);
  }
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 boolean isNonNegativeInteger(String s) {
  return s != null && Pattern.matches("\\d+", s);
}
static String getNameOfPublicClass(List<String> tok) {
  for (List<String> c : allClasses(tok))
    if (hasModifier(c, "public"))
      return getClassDeclarationName(c);
  return null;
}

static TreeSet<String> caseInsensitiveSet() {
  return new TreeSet(caseInsensitiveComparator());
}

static TreeSet<String> caseInsensitiveSet(Collection<String> c) {
  return toCaseInsensitiveSet(c);
}
static boolean isFalse(Object o) {
  return eq(false, o);
}
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 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);
}
/** 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);
        
    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 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
}
// 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>();
  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;
}

// 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;
}
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 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 String joinPairWithSpace(Pair p) {
  return str(p.a) + " " + str(p.b);
}
static String loadCachedTranspilation(String id) {
  return loadTextFilePossiblyGZipped(new File(getCodeProgramDir(id), "Transpilation"));
}

static RandomAccessFile newRandomAccessFile(File path, String mode) throws IOException {
  RandomAccessFile f = new RandomAccessFile(path, mode);
  callJavaX("registerIO", f, path, mode.indexOf('w') >= 0);
  return f;
}
static List emptyList() {
  return new ArrayList();
  //ret Collections.emptyList();
}

static List emptyList(int capacity) {
  return new ArrayList(capacity);
}

// Try to match capacity
static List emptyList(Iterable l) {
  return l instanceof Collection ? emptyList(((Collection) l).size()) : emptyList();
}
// returns index of trailing N token
static int scanToEndOfInitializer(List<String> tok, Map<Integer, Integer> bracketMap, int i) {
  while (i < l(tok)) {
    if (litlist(";", ",", ")", "}").contains(tok.get(i)))
      return i-1;
    Integer j = bracketMap.get(i);
    if (j != null)
      i = j+1;
    else
      i++;
  }
  return i;
}
static String lines(Collection lines) { return fromLines(lines); }
static List<String> lines(String s) { return toLines(s); }
static <A> boolean setAdd(Collection<A> c, A a) {
  if (c == null || c.contains(a)) return false;
  c.add(a);
  return true;
}
static String programID() {
  return getProgramID();
}

static String programID(Object o) {
  return getProgramID(o);
}
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);
}
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 ReentrantLock fairLock() {
  return new ReentrantLock(true);
}
static boolean isOfflineMode() {
  return eq("1", trim(loadProgramTextFile("#1005806", "offline-mode")));
}
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 ThreadLocal<Object> print_byThread() {
  synchronized(print_byThread_lock) {
    if (print_byThread == null)
      print_byThread = new ThreadLocal();
  }
  return print_byThread;
}
static boolean eqic(String a, String b) {
  
  
    if ((a == null) != (b == null)) return false;
    if (a == null) return true;
    return a.equalsIgnoreCase(b);
  
}



static boolean eqic(char a, char b) {
  if (a == b) return true;
  
    char u1 = Character.toUpperCase(a);
    char u2 = Character.toUpperCase(b);
    if (u1 == u2) return true;
  
  return Character.toLowerCase(u1) == Character.toLowerCase(u2);
}
static boolean containsIgnoreCase(Collection<String> l, String s) {
  for (String x : l)
    if (eqic(x, s))
      return true;
  return false;
}

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

static boolean 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 int lastIndexOf(String a, String b) {
  return a == null || b == null ? -1 : a.lastIndexOf(b);
}

static int lastIndexOf(String a, char b) {
  return a == null ? -1 : a.lastIndexOf(b);
}
static 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); // possible improvement: skip setAccessible
} 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);
    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) {
        f.setAccessible(true);
        return f;
      }
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}
static String addSlash(String s) {
  return empty(s) || s.endsWith("/") ? s : s + "/";
}
static <A> A popLast(List<A> l) {
  return liftLast(l);
}
  static File tempDir() {
    return makeTempDir();
  }
static int parseIntOpt(String s) {
  return isInteger(s) ? parseInt(s) : 0;
}
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 String javaTok_substringC(String s, int i, int j) {
  return s.substring(i, j);
}
static String urlencode(String x) {
  try {
    return URLEncoder.encode(unnull(x), "UTF-8");
  } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); }
}
static List<String> splitAtSpace(String s) {
  return asList(s.split("\\s+"));
}
static String javaTok_substringN(String s, int i, int j) {
  if (i == j) return "";
  if (j == i+1 && s.charAt(i) == ' ') return " ";
  return s.substring(i, j);
}
static 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 <A> List<Integer> indexesOf(List<A> l, A 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;
}
 // JavaParser







static CompilationUnit javaParseCompilationUnit(String java) {
  JavaParser.getStaticConfiguration().setAttributeComments(false);
  return JavaParser.parse(java);
}
static <A> WeakReference<A> weakRef(A a) {
  return newWeakReference(a);
}
static String getStackTrace2(Throwable e) {
  return hideCredentials(getStackTrace(unwrapTrivialExceptionWraps(e)) + replacePrefix("java.lang.RuntimeException: ", "FAIL: ",
    hideCredentials(str(getInnerException(e)))) + "\n");
}
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;
    
    // It's inefficient :D
    for (int j = 0; j < l(x); j++) l.remove(i);
    l.addAll(i, y);
    i += l(y);
  }
  return l;
}

static <A> List<A> replaceSublist(List<A> l, int fromIndex, int toIndex, List<A> y) {
  // inefficient
  while (toIndex > fromIndex) l.remove(--toIndex);
  l.addAll(fromIndex, y);
  return l;
}
  static List<String> codeTokensOnly(List<String> tok) {
    List<String> l = new ArrayList();
    for (int i = 1; i < tok.size(); i += 2)
      l.add(tok.get(i));
    return l;
  }
static HashMap litmap(Object... x) {
  HashMap map = new HashMap();
  litmap_impl(map, x);
  return map;
}

static void litmap_impl(Map map, Object... x) {
  for (int i = 0; i < x.length-1; i += 2)
    if (x[i+1] != null)
      map.put(x[i], x[i+1]);
}
static ThreadLocal<Boolean> doPost_silently = new ThreadLocal();
static ThreadLocal<Long> doPost_timeout = new ThreadLocal();

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

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

static String doPost(String urlParameters, URLConnection conn, URL url) { try {
  boolean silently = isTrue(optParam(doPost_silently));
  Long timeout = optParam(doPost_timeout);
  setHeaders(conn);

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

  loadPage_charset.set("UTF-8");
  String contents = loadPage(conn, url, false);
  writer.close();
  return contents;
} catch (Exception __e) { throw rethrow(__e); } }
static String firstLine(String text) {
  int i = text.indexOf('\n');
  return i >= 0 ? text.substring(0, i) : text;
}
static boolean odd(int i) {
  return (i & 1) != 0;
}

static boolean odd(long i) {
  return (i & 1) != 0;
}
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 String dropTrailingSquareBracketStuff(String s) {
  return trimSubstring(s, 0, smartLastIndexOf(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 Object getOptMC(String field) {
  return getOpt(mc(), field);
}
static List<String> cncSubList(List<String> tok, int from, int to) {
  from &= ~1;
  to |= 1;
  return subList(tok, from, to);
}
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 <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 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(String a, String b) {
  return a == null ? b == null ? 0 : -1 : a.compareTo(b);
}

static int cmp(Object a, Object b) {
  if (a == null) return b == null ? 0 : -1;
  if (b == null) return 1;
  return ((Comparable) a).compareTo(b);
}
static String dummyMainClassName(String progID) {
  return "m" + psI(progID);
}
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 String className(Object o) {
  return getClassName(o);
}
static int loadPage_defaultTimeout = 60000;
static ThreadLocal<String> loadPage_charset = new ThreadLocal();
static boolean loadPage_allowGzip = true, loadPage_debug;
static boolean loadPage_anonymous; // don't send computer ID
static int loadPage_verboseness = 100000;
static int loadPage_retries = 1; //60; // seconds
static ThreadLocal<Boolean> loadPage_silent = new ThreadLocal();
static volatile int loadPage_forcedTimeout; // ms
static ThreadLocal<Integer> loadPage_forcedTimeout_byThread = new ThreadLocal(); // ms
static ThreadLocal<Map<String, List<String>>> loadPage_responseHeaders = new ThreadLocal();
static ThreadLocal<Map<String, String>> loadPage_extraHeaders = new ThreadLocal();

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

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

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

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

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

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

static String loadPage(URLConnection con, URL url, boolean addHeaders) throws IOException {
  Map<String, String> extraHeaders = getAndClearThreadLocal(loadPage_extraHeaders);
  if (addHeaders) try {
    if (!loadPage_anonymous)
      setHeaders(con);
    if (loadPage_allowGzip)
      con.setRequestProperty("Accept-Encoding", "gzip");
    con.setRequestProperty("X-No-Cookies", "1");
    for (String key : keys(extraHeaders))
      con.setRequestProperty(key, extraHeaders.get(key));
  } catch (Throwable e) {} // fails if within doPost
  
  vm_generalSubMap("URLConnection per thread").put(currentThread(), con);
  loadPage_responseHeaders.set(con.getHeaderFields());
  InputStream in = null;
  try {
    in = con.getInputStream();
  //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 = new InputStreamReader(in, charset);
    
    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 {
    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) {
  Pattern p = Pattern.compile("text/[a-z]+;\\s*charset=([^\\s]+)\\s*");
  Matcher m = p.matcher(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 List<String> toLinesFullTrim_java(String text) {
  return toLinesFullTrim(joinLines(map("javaDropComments", toLinesFullTrim(text))));
}

static String _userHome;
static String userHome() {
  if (_userHome == null) {
    if (isAndroid())
      _userHome = "/storage/sdcard0/";
    else
      _userHome = System.getProperty("user.home");
    //System.out.println("userHome: " + _userHome);
  }
  return _userHome;
}

static File userHome(String path) {
  return new File(userDir(), path);
}
static ThreadLocal<Boolean> tok_typesAndNamesOfParams_keepModifiers = new ThreadLocal();

static List<Pair<String, String>> tok_typesAndNamesOfParams(List<String> tok) {
  boolean keepModifiers = isTrue(optParam(tok_typesAndNamesOfParams_keepModifiers));
  
  try {
    List < Pair < String , String > > out = new ArrayList();
    for (int i = 1; i < l(tok); ) {
      String t = tok.get(i);
      String pre = "";
      
      if (eq(t, "final")) {
        if (keepModifiers) pre += "final ";
        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), ".")) {
        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 += "[]";
        }
        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 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);
  }
}

static boolean isLongConstant(String s) {
  if (!s.endsWith("L")) return false;
  s = s.substring(0, l(s)-1);
  return isInteger(s);
}
static List map(Iterable l, Object f) {
  return map(f, l);
}

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


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


static List map(Object f, Object[] l) { return map(f, asList(l)); }
static List map(Object[] l, Object f) { return map(f, l); }

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

// map: func(key, value) -> list element
static List map(Map map, Object f) {
  List x = new ArrayList();
  if (map != null) for (Object _e : map.entrySet()) {
    Map.Entry e = (Map.Entry) _e;
    x.add(callF(f, e.getKey(), e.getValue()));
  }
  return x;
}
static 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 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, B> List<B> secondOfPairs(Collection<Pair<A, B>> l) {
  return map("secondOfPair", l);
}

static <A> A assertEquals(Object x, A y) {
  return assertEquals(null, x, y);
}

static <A> A assertEquals(String msg, Object x, A y) {
  if (assertVerbose()) return assertEqualsVerbose(msg, x, y);
  if (!(x == null ? y == null : x.equals(y)))
    throw fail((msg != null ? msg + ": " : "") + y + " != " + x);
  return y;
}
static 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 boolean isNormalQuoted_dirty(String s) {
  return startsWith(s, "\"") && endsWith(s, "\"") && l(s) >= 2;
}
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 boolean endsWithLetterOrDigit(String s) {
  return s != null && s.length() > 0 && Character.isLetterOrDigit(s.charAt(s.length()-1));
}
static Object first(Object list) {
  return empty((List) list) ? null : ((List) list).get(0);
}

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(Iterable<A> i) {
  if (i == null) return null;
  Iterator<A> it = i.iterator();
  return it.hasNext() ? it.next() : null;
}

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


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



static void _handleError(Error e) {
  call(javax(), "_handleError", e);
}
static String tok_genEqualsAndHashCode(String className, List<Pair<String, String>> fields) {
  return "public bool equals(O o) {\r\n    if (!o instanceof " + className + ") false;\r\n    " + className + " x = cast o;\r\n    ret " + join(" && ", map(fields, new F1<Pair<String, String>, Object>() { Object get(Pair<String, String> p) { try { return 
      // TODO: use == for primitive fields
      "eq(" + p.b + ", x." + p.b + ")"
    ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "// TODO: use == for primitive fields\r\n      \"eq(\" + p.b + \", x.\" + p.b + \")\""; }})) + ";\r\n  }\r\n  \r\n  public int hashCode() {\r\n    int h = " + hashCode(className) + ";\r\n    " + concatMap_strings(fields, new F1<Pair<String, String>, Object>() { Object get(Pair<String, String> p) { try { return 
      "h = h*2+_hashCode(" + p.b + ");\r\n    "; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "\"h = h*2+_hashCode(\" + p.b + \");\\r\\n    \""; }}) + "ret h;\r\n  }\r\n  ";
}
static boolean isGeneralFileServerSnippet(long id) {
  return id >= 1400000 && id < 1500000;
}
static Set similarEmptySet(Set m) {
  if (m instanceof TreeSet) return new TreeSet(((TreeSet) m).comparator());
  if (m instanceof LinkedHashSet) return new LinkedHashSet();
  
  return new HashSet();
}
static <A> A second(List<A> l) {
  return get(l, 1);
}

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 RuntimeException asRuntimeException(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}
static Class mc() {
  return main.class;
}
static <A, B> void mapPut(Map<A, B> map, A key, B value) {
  if (map != null && key != null && value != null) map.put(key, value);
}
// 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 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 void printStructure(String prefix, Object o) {
  if (endsWithLetter(prefix)) prefix += ": ";
  print(prefix + structureForUser(o));
}

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

static File loadLibrary(String snippetID) {
  return loadBinarySnippet(snippetID);
}
static void tokAppend(List<String> tok, int i, String s) {
  tok.set(i, tok.get(i)+s);
}
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;
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) {
  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 = tok.size()-tokens.length*2; i >= startIdx; i -= 2) {
    for (int j = 0; j < tokens.length; j++) {
      String p = tokens[j], t = tok.get(i+j*2);
      boolean match;
      if (eq(p, "*")) match = true;
      else if (eq(p, "<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 String regexpFirstGroupIC(String pat, String s) {
  Matcher m = regexpIC(pat, s);
  if (m.find()) return m.group(1); else return null;
}
static Object collectionMutex(Object 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 boolean isUnproperlyQuoted(String s) {
  return isNormallyQuoted(s) && !isProperlyQuoted(s);
}
static boolean containsNewLine(String s) {
  return contains(s, '\n'); // screw \r, nobody needs it
}
static Object callOpt(Object o) {
  if (o == null) return null;
  return callF(o);
}

static Object callOpt(Object o, String method, Object... args) {
  try {
    if (o == null) return null;
    if (o instanceof Class) {
      Method m = callOpt_findStaticMethod((Class) o, method, args, false);
      if (m == null) return null;
      m.setAccessible(true);
      return invokeMethod(m, null, args);
    /*} else if (o instanceof DynamicMethods) {
      ret ((DynamicMethods) o)._dynCall(method, args);*/
    } else {
      Method m = callOpt_findMethod(o, method, args, false);
      if (m == null) return null;
      m.setAccessible(true);
      return invokeMethod(m, o, args);
    }
  } catch (Exception e) {
    //fail(e.getMessage() + " | Method: " + method + ", receiver: " + className(o) + ", args: (" + join(", ", map(f className, args) + ")");
    throw rethrow(e);
  }
}

static Method callOpt_findStaticMethod(Class c, String method, Object[] args, boolean debug) {
  Class _c = c;
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (debug)
        System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
      if (!m.getName().equals(method)) {
        if (debug) System.out.println("Method name mismatch: " + method);
        continue;
      }

      if ((m.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0 || !callOpt_checkArgs(m, args, debug))
        continue;

      return m;
    }
    c = c.getSuperclass();
  }
  return null;
}

static Method callOpt_findMethod(Object o, String method, Object[] args, boolean debug) {
  Class c = o.getClass();
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (debug)
        System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
      if (m.getName().equals(method) && callOpt_checkArgs(m, args, debug))
        return m;
    }
    c = c.getSuperclass();
  }
  return null;
}

private static boolean callOpt_checkArgs(Method m, Object[] args, boolean debug) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length) {
    if (debug)
      System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
    return false;
  }
  for (int i = 0; i < types.length; i++)
    if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
      if (debug)
        System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
      return false;
    }
  return true;
}


static String innerMessage(Throwable e) {
  return getInnerMessage(e);
}
static boolean startsWithJavaIdentifierStart(String s) {
  return nempty(s) && Character.isJavaIdentifierStart(s.charAt(0));
}
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 String assertIdentifier(String s) {
  return assertIsIdentifier(s);
}
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 final Map<Class, HashMap<S, Field>> getOpt_cache = newDangerousWeakHashMap(f getOpt_special_init);

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

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

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

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

  Class c = o.getClass();
  HashMap<String, Field> map;
  synchronized(getOpt_cache) {
    map = getOpt_cache.get(c);
    if (map == null)
      map = getOpt_makeCache(c);
  }
  
  if (map == getOpt_special) {
    if (o instanceof Class)
      return getOpt((Class) o, field);
    /*if (o instanceof S)
      ret getOpt(getBot((S) o), field);*/
    if (o instanceof Map)
      return ((Map) o).get(field);
  }
    
  Field f = map.get(field);
  if (f != null) return f.get(o);
  
    if (o instanceof DynamicObject)
      return ((DynamicObject) o).fieldValues.get(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();
    Class _c = c;
    do {
      for (Field f : _c.getDeclaredFields()) {
        f.setAccessible(true);
        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 String structureForUser(Object o) {
  return beautifyStructure(struct_noStringSharing(o));
}
static String formatSnippetIDOpt(String s) {
  return isSnippetID(s) ? formatSnippetID(s) : s;
}
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 Class<?> hotwire(String src) {
  assertFalse(_inCore());
  Class j = getJavaX();
  if (isAndroid()) {
    synchronized(j) { // hopefully this goes well...
      List<File> libraries = new ArrayList<File>();
      File srcDir = (File) call(j, "transpileMain", src, libraries);
      if (srcDir == null)
        throw fail("transpileMain returned null (src=" + quote(src) + ")");
    
      Object androidContext = get(j, "androidContext");
      return (Class) call(j, "loadx2android", srcDir, src);
    }
  } else {
    
    
    Class c = (Class) ( call(j, "hotwire", src));
    hotwire_copyOver(c);
    return c;
    
  }
}
static String loadTextFile(String fileName) {
  return loadTextFile(fileName, null);
}

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

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

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

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

static String loadTextFile(Reader reader) throws IOException {
  StringBuilder builder = new StringBuilder();
  try {
    char[] buffer = new char[1024];
    int n;
    while (-1 != (n = reader.read(buffer)))
      builder.append(buffer, 0, n);
  } finally {
    reader.close();
  }
  return str(builder);
}
// 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;
  while (j < cnc.size()) {
    if (cnc.get(j).equals("{")) ++level;
    else if (cnc.get(j).equals("}")) --level;
    if (level == 0)
      return j+1;
    ++j;
  }
  return cnc.size();
}
static String tb_mainServer_default = "http://tinybrain.de:8080";
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