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

13133
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 (13133L/96K/278K).

!1011230

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.printer.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.CompilationUnit;
import java.security.SecureRandom;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.stmt.LocalClassDeclarationStmt;
import com.github.javaparser.*;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.visitor.*;
import javax.net.ssl.*;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
class main {
static boolean autoQuine = true;
static int maxQuineLength = 80;

// _registerThread usually costs nothing because we need
// the registerWeakHashMap mechanism anyway for ping().
static List<String> functionsToAlwaysInclude = ll(
  "_registerThread");

// 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", "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;
    
    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 {
      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 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(Collection<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(Collection<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 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;

// Remember these variables are NOT cleared automatically

static HashSet<Long> included = new HashSet();
static Set<String> definitions = ciSet();
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 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());
  
  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;
  }
}

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();
  definitions.add("SymbolAsString");
  shouldNotIncludeFunction.clear();
  shouldNotIncludeClass.clear();
  doNotIncludeFunction.clear();
  addedFunctions.clear();
  addedClasses.clear();
  hardFunctionReferences.clear();
  libs.clear();
  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 (!hasCodeTokens(tok, "m", "{") && !hasCodeTokens(tok, "main", "{") && !hasCodeTokens(tok, "class", "main")) {
    //tok = jtok(moveImportsUp("m {\n" + in + "\n}"));
    replaceTokens_reTok(tok, 1, 2, "m {\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);
  
  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() {");
    
    tok_definitions(tok);
    tok_ifndef(tok);
    tok_ifdef(tok);
    if (safety == 0) tok = quickmain(tok);
    tok = standardFunctions(tok);
    tok = stdstuff(tok); // standard functions and all the keywords
    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.");
  
  print("moveImportsUp"); tok_moveImportsUp(tok);
  
  print("Indexing"); tok = indexedList2(tok);
  
  // POST-PROCESSING after stdstuff loop
  
  //print("Type<A, A>"); // Type<A> to Type<A, A>
  
  print("extendClasses"); tok = extendClasses(tok);
  print("libs"); libs(tok);
  print("sourceCodeLine"); sourceCodeLine(tok);

  // Stuff that depends on the list of inner classes (haveClasses)
  HashSet<String> haveClasses = haveClasses(tok);
  print("innerClassesVar"); innerClassesVar(tok, haveClasses);
  print("ifclass"); tok_ifclass(tok, haveClasses);
  print("slashCasts"); slashCasts(tok, haveClasses);
  print("newWithoutNew"); newWithoutNew(tok, haveClasses);
  
  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"));
  }
  jreplace(tok, "T3< <id> >", "T3<$3, $3, $3>");
  
  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 (containsIC(definitions, "allpublic")) {
    // Fire up the Java parser & pretty printer!
    print("Making all public.");
    try {
      tok = jtok(javaParser_makeAllPublic(join(tok)));
    } catch (Throwable e) {
      String src = join(tok);
      print(src);
      print(f2s(saveProgramTextFile("error.java", src)));
      throw rethrow(e);
    }
  }
  
  if (nempty(libs))
    for (String lib : libs)
      tok.add(concatMap_strings(new Object() { Object get(String s) { try { return  "\n!" + s ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "\"\\n!\" + s"; }}, libs));
  
  /*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);
    listComprehensions(tok);
    tok_doubleFor_v2(tok);
    forPing(tok);
    directSnippetRefs(tok);
    quicknu(tok);
    //tok_juxtaposeCalls(tok);
    expandVarCopies(tok);
    
    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() { Object get(List<String> tok, int i) { try { 
        String text = tok.get(i+2);
        return new String[] {
          "{ JWindow _loading_window = showLoadingAnimation(" + text + "); try {",
          "} finally { disposeWindow(_loading_window); }}" };
       } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S text = tok.get(i+2);\r\n        ret new S[] {\r\n          \"{ JWindow _loading_win..."; }});
  
    replaceKeywordBlock(tok, "html",
      "static O html(S uri, fMap<S, S> params) ctex " + "{\n", "}");
    
    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>");
  
    // "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 { printException(e); "
    jreplace(tok, "catch print <id> {", "catch $3 { printException($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 Object() {
      boolean get(List<String> tok, int i) {
        String word = tok.get(i+3);
        return startsWithLowerCaseOrUnderscore(word);
      }
    });
    
    jreplace(tok, "+ +", "+", new Object() {
      boolean get(List<String> tok, 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 Object() { Object get(List<String> tok, int i) { try { 
      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;
     } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S l = tok.get(i+1);\r\n      if (!(isIdentifier(l) || eq(l, \")\"))) false;\r\n      i..."; }});
    
    // [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))");

    // more shortening
  
    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");
    
    // "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 Object() { Object get(List<String> tok, int i) { try { return  eqOneOf(tok.get(i+1), "return", "continue") ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { 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 = jfind(tok, "return <quoted> 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, "map <id>(", "map(f $2,");
    jreplace(tok, "filter <id>(", "filter(f $2,");
  
    // func keyword for lambdas - now automatically quines toString() if enabled
    
    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 = join(subList(tok, argsTo+6, idx-1));
        
      String toString = autoQuine ? "  public S toString() { ret " + quote(shorten(maxQuineLength, trim(join(contents)))) + "; }" : "";
      
      List<String> args = subList(tok, argsFrom-1, argsTo);
      List<String> types = tok_typesOfParams(args);
      Object type = "O";
      if (l(types) == 1)
        type = "F1<" + first(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 = subList(tok, argsFrom-1, argsTo);
      List<String> types = tok_typesOfParams(args);
      Object type = "O";
      if (l(types) == 1)
        type = "VF1<" + first(types) + ">";

      replaceTokens(tok, i, j, "new " + type + "() { void get(" + join(subList(tok, argsFrom, argsTo-1)) + ") 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 = 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);
      }
    }
  
    // "ref -> bla" for dereferencing Concept.Ref or ThreadLocal or other
    //jreplace(tok, "<id> ->", "$1.get().");
    jreplace(tok, "->", ".get().", new Object() { Object get(List<String> tok, int i) { try { return 
      empty(tok.get(i+2))
        && !(empty(_get(tok, i)) && eq(_get(tok, i-1), "-")) // i-->0
    ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "empty(tok.get(i+2))\r\n        && !(empty(_get(tok, i)) && eq(_get(tok, i-1), \"-\")..."; }});
    
    // 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);
    
    // Do this BEFORE awt replacement! ("p-awt" contains "awt" token)
    if (hasCodeTokens(tok, "p", "-")) {
      jreplace(tok, "p-pretty {", "p-noconsole {");
      replaceKeywordBlock(tok, "p-awt-noconsole", "p-awt {", "\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(); ", "}");
      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);
    }
  
    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", "runnableThread(r " + "{", "})");
    rNamedThread(tok);
    
    // 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 Object() { Object get(List<String> tok, int i) { try { return 
      tok_tokenBeforeLonelyReturnValue(_get(tok, i-1))
    ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { 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);
  
    // "myFunction;" instead of "myFunction();" - quite rough
    cond = new Object() {
      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 (matchStartX($2, s, m))",
      new Object() { boolean get(List<String> tok, 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 Object() { Object get(List<String> tok, int i) { try { return 
      javaTokC(unquote(tok.get(i+3))).contains("|")
    ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "javaTokC(unquote(tok.get(i+3))).contains(\"|\")"; }});
    
    // "bla"
    jreplace(tok, "if <quoted>", "if (match($2, s))",
      new Object() { boolean get(List<String> tok, 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) { printStackTrace2(__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() { Object get(List<String> tok, int i) { try { 
        String var = makeVar("startTime");
        return new String[] {
          "long " + var + " = sysNow(); ",
          " done2_always(" + tok.get(i+2) + ", " + var + "); "};
       } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "S var = makeVar(\"startTime\");\r\n        ret new S[] {\r\n          \"long \" + 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 Object() { Object get(List<String> tok, int i) { try { return 
        neqOneOf(_get(tok, i-1), "svoid", "void")
      ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { 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 {",
      "}}}");
      
    namedThreads(tok);
    threads(tok);
  
    // try answer
    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);
    }
  
    // 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);
    }
    
    // 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);
    
    // end of localStuff1

    same = tok_sameTest(tok, before);
    /*if (!same)
      print("local not same " + safety + " (" + l(tok) + " tokens)");*/
    if (safety++ >= 10) {
      print("----");
      print(join(tok));
      print("----");
      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);
    
  // STANDARD CLASSES & INTERFACES

  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)));
    }
  }
  
  //final Set<S> haveClasses = addStandardClasses(tok, toStringArray(lclasses));
  print("standard classes");
  final Set<String> haveClasses = addStandardClasses_v2(tok, lclasses);
  
  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);
    List<String> contents = subList(tok, idx+1, j-1);
    tok.set(i, "public static void main(final String[] args) throws Exception");
    replaceTokens(tok, idx+1, j-1, tok_addSemicolon(contents));
    reTok(tok, i, j-1);
    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 Object() { Object get(List<String> tok, int i) { try { return 
    eqOneOf(_get(tok, i+5), "{", ",", ")", ";", ":")
  ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "eqOneOf(_get(tok, i+5), \"{\", \",\", \")\", \";\", \":\")"; }});
  
  jreplace1(tok, "new <id> < <id> >", "new $2<$4>()", new Object() { Object get(List<String> tok, int i) { try { return 
    eqOneOf(_get(tok, i+11), "{", ",", ")", ";", ":")
  ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { 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 Object() { Object get(List<String> tok, int i) { try { return 
    eqOneOf(_get(tok, i+9), "(", ";", ",", ")")
  ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { 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 Object() { Object get(List<String> tok, int i) { try { return 
    eqOneOf(_get(tok, i+9+3*2), "(", ";", ",", ")")
  ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { 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 Object() { Object get(List<String> tok, int i) { try { return 
    eqOneOf(_get(tok, i+9+5*2), "(", ";", ",", ")")
  ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { 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;
}

static void listComprehensions(List<String> tok) {
  int i;
  for (String op : ll(":", "in"))
    while ((i = jfind(tok, "[<id> <id> " + op)) >= 0) {
      Map<Integer, Integer> bracketMap = getBracketMap(tok); // XXX - optimize
      String type = tok.get(i+2), id = tok.get(i+4);
      int j = scanOverExpression(tok, bracketMap, i+8, "|");
      String exp = join(tok.subList(i+8, 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);
    }
}

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

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));
    }
    
    // changes tok
    Set<String> invocations = findFunctionInvocations(tok, sf, hardFunctionReferences);
    /*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: " + structure(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(List<String> tok) {
  HashSet<String> have = new HashSet();
  for (List<String> c : innerClassesOfMain(tok))
    have.add(getClassDeclarationName(c));
  have.addAll(tok_importedClassNames(tok));
  have.add("String"); // for S => S.class I guess?
  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, List<String> definitions) {
  for (int safety = 0; safety < 10; safety++) {
    HashSet<String> have = haveClasses(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(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(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)", new O() {
    O get(L<S> tok, int i) {
      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 Object() { Object get(List<String> tok, int i) { try { 
    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;
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "if (!classNames.contains(tok.get(i+1))) false;\r\n    bool ok = neqOneOf(_get(tok,..."; }});
}

// +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;
  String keyword = "f";
  while ((i = jfind(tok, keyword + " <id>", new Object() {
    Object get(List<String> tok, 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));
  }
}

// # 123 => "#123"
static void directSnippetRefs(List<String> tok) {
  int i;
  while ((i = jfind(tok, "#<int>", new Object() {
    boolean get(List<String> tok, 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);
}

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

// 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 Object() {
      Object get(List<String> tok, 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 Object() { Object get(List<String> tok, 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 Object() { Object get(List<String> tok, int i) { try { return  neq(tok.get(i+3), "instanceof") ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "neq(tok.get(i+3), \"instanceof\")"; }})) >= 0) {
    int semicolon = findEndOfStatement(tok, i)-1;
    String var = makeVar("lock");
    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;
    
    // e.g. temp newThoughtSpace();
    if (!contains(subList(tok, i+2, semicolon), "="))
      tok.set(i+2, "AutoCloseable " + makeVar("") + " = " + tok.get(i+2));
      
    tok.set(i, "try (");
    tok.set(semicolon, ") {");
    tok.set(endOfOuterBlock, "}}");
    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 = func(L<S> tok, int i) { 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 Object() { Object get(List<String> tok, int i) { try { return 
      haveClasses.contains(tok.get(i+7))
    ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "haveClasses.contains(tok.get(i+7))"; }});
}

static boolean hasDef(String s) {
  return definitions.contains(s);
}
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;
}
// map: index of opening bracket -> index of closing bracket
static Map<Integer, Integer> getBracketMap(List tok) {
  TreeMap<Integer,Integer> map = new TreeMap();
  List<Integer> stack = new ArrayList();
  for (int i = 1; i < l(tok); i+= 2) {
    if (getBracketMap_opening.contains(tok.get(i)))
      stack.add(i);
    else if (getBracketMap_closing.contains(tok.get(i))) {
      if (!empty(stack))
        map.put(liftLast(stack), i);
    }
  }
  return map;
}

static List<String> getBracketMap_opening = ll("{", "(");
static List<String> getBracketMap_closing = ll("}", ")");
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;
}
static List<List<String>> innerClassesOfMain(List<String> tok) {
  return innerClasses(findMainClass(tok));
}
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 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!)
    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);
  }
}
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 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
      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);
}
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);
}
// 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 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) {
  List<String> tokin = javaTok(pat);
  jfind_preprocess(tokin);
  String[] toks = toStringArray(codeTokensOnly(tokin));
  
  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 String getClassDeclarationName(List<String> c) {
  if (c != null)
    for (int i = 1; i+2 < c.size(); i += 2)
      if (eqOneOf(c.get(i), "class", "interface", "enum"))
        return c.get(i+2);
  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")) {
      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 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 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(Collection<A> l) {
  if (l == null) return new ArrayList();
  // TODO: is this correct when l is a keySet?
  synchronized(l) {
    return new ArrayList<A>(l);
  }
}
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);
}
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); }

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

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

static String fsi(String id) {
  return formatSnippetID(id);
}
// 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;
}
static Set<String> tokenIndexWithoutIfclass(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) set.add(t);
  }
  return set;
}
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));
}
// 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.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) {
        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;
      
    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 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 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 String tok_addSemicolon(List<String> tok) {
  String lastToken = get(tok, l(tok)-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 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(Collection<A> l) {
  if (l instanceof Set) return (Set) l;
  HashSet<A> set = new HashSet();
  for (A o : 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)+tok.get(1));
    change = true;
    //reTok(tok, 1, 2);
  }
    
  if (change) reTok(tok);
  return tok;
}
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 = max(0, min(l(l), startIndex));
  endIndex = max(0, min(l(l), endIndex));
  if (startIndex > endIndex) return litlist();
  return l.subList(startIndex, endIndex);
}


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);
    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);
    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 <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(map)
      : map instanceof LinkedHashMap ? new LinkedHashMap(map)
      : new HashMap(map);
  }
}
static String f2s(File f) {
  return f == null ? null : f.getAbsolutePath();
}
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 Map<Thread, Boolean> _registerThread_threads = newWeakHashMap();

static Thread _registerThread(Thread t) {
  _registerThread_threads.put(t, true);
  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 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 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(int[] a) { return a == null ? 0 : a.length; }
static int l(float[] 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)
    : (int) call(o, "size");
}


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



  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 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 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))
      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> void addAll(Collection<A> c, Collection<A> b) {
  if (c != null && b != null) c.addAll(b);
}

static <A> void addAll(Collection<A> c, A... b) {
  if (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 <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 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 String mainJava;
  
  static String loadMainJava() throws IOException {
    if (mainJava != null) return mainJava;
    return loadTextFile("input/main.java", "");
  }
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 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) { try {
  return md5(loadBinaryFile(file));
} catch (Exception __e) { throw rethrow(__e); } }
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 Object() { Object 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]");
    
    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);
}
// 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 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 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 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 <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;
}
// 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 (eqOneOf(tok.get(i), "class", "interface", "enum") && (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(String 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 == 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(MultiSet ms) { return ms == null || ms.isEmpty(); }

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 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 RuntimeException asRuntimeException(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}
static boolean neqOneOf(Object o, Object... l) {
  for (Object x : l) if (eq(o, x)) return false; return true;
}
// 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 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 String formatSnippetID(String id) {
  return "#" + parseSnippetID(id);
}

static String formatSnippetID(long id) {
  return "#" + id;
}
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 || checkCondition(condition, tok, i-1)) // pass N index
      return i;
  }
  return -1;
}
static boolean neq(Object a, Object b) {
  return !eq(a, b);
}
static boolean containsToken(List<String> tok, String token) {
  return tok.contains(token);
}
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, 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 void vmKeepWithProgramMD5_get(String varName) {
  String struct = (String) ( callOpt(javax(), "vmKeep_get", programID(), md5OfMyJavaSource(), varName));
  if (struct != null)
    setOpt(mc(), varName, unstructure(struct));
}
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
static Set<String> findFunctionInvocations_pre = new HashSet(litlist(".", "void", "S", "String", "int", "bool", "boolean", "A"));

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) {
  int i;
  Set<String> l = new HashSet();
  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;
    if (findFunctionInvocations_debug)
      System.out.println("Testing identifier " + f);
    if (!tok.get(i+2).equals("(")) 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)
        System.out.println("Possible invocation: " + f + ", inSF: " + inSF);
      if (inSF)
        l.add(f);
    }
  }
  
  return l;
}
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 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);
}

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);
}
// 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 volatile int numberOfCores_value;

static int numberOfCores() {
  if (numberOfCores_value == 0)
    numberOfCores_value = Runtime.getRuntime().availableProcessors();
  return numberOfCores_value;
}
static boolean eq(Object a, Object b) {
  return a == null ? b == null : a == b || a.equals(b);
}

// a little kludge for stuff like eq(symbol, "$X")

static boolean eq(Symbol a, String b) {
  return eq(str(a), b);
}

  static void clearTokens(List<String> tok) {
    clearAllTokens(tok);
  }
  
  static void clearTokens(List<String> tok, int i, int j) {
    clearAllTokens(tok, i, j);
  }
static String trimJoin(List<String> s) {
  return trim(join(s));
}
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 <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 TreeSet<String> ciSet() {
  return caseInsensitiveSet();
}
  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 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 <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(Collection<Symbol> l, Symbol s) {
  return contains(l, s);
}


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

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

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

static void splitJavaFiles(List<String> tok) { 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 (List<String> c : classes) {
      //print("  Class: " + shorten(join(c), 80));
      print("  Class: " + quote(getClassDeclarationName(c)));
    }
    if (empty(classes))
      print("No classes?? " + quote(src));
    else if (l(classes) == 1) {
      String fileName = addSlash(pack.replace('.', '/')) + getClassDeclarationName(first(classes)) + ".java";
      print("File name: " + fileName);
      saveTextFile("output/" + fileName, join(subtok));
    } else
      throw fail("Not supported");
    print();
  }
} catch (Exception __e) { throw rethrow(__e); } }
static boolean startsWithLowerCaseOrUnderscore(String s) {
  return nempty(s) && (s.startsWith("_") || Character.isLowerCase(s.charAt(0)));
}
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 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 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 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 <A> void remove(List<A> l, int i) {
  if (l != null && i >= 0 && i < l(l))
    l.remove(i);
}

static <A> void remove(Collection<A> l, A a) {
  if (l != null) l.remove(a);
}
static <A, B> 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 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);
}


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 long now_virtualTime;
static long now() {
  return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis();
}

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 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 <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);
  List<String> tok = javaTok(progIDOrSrc);
  
  int i = findCodeTokens(tok, varName, "=");
  if (i < 0) return null;
  
  i += 4;
  if (isQuoted(tok.get(i)))
    return unquote(tok.get(i));
  
  if (eq(get(tok, i), "litlist") && eq(get(tok, i+2), "(")) {
    int opening = i+2;
    int closing = findEndOfBracketPart(tok, opening)-1;
    List l = new ArrayList();
    for (i = opening+2; i < closing; i += 4)
      l.add(unquote(tok.get(i)));
    return l;
  }
  
  throw fail("Unknown variable type or no definition in source: " + shorten(progIDOrSrc, 100) + "/" + varName);
}


static RuntimeException rethrow(Throwable e) {
  throw asRuntimeException(e);
}
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 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(Map map) {
  return map == null || map.isEmpty();
}
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(Map m) {
  return !isEmpty(m);
}

static boolean nempty(Iterator i) {
  return i != null && i.hasNext();
}
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 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 volatile boolean readLine_noReadLine;

static String readLine() {
  if (readLine_noReadLine) return null;
  return (String) call(getJavaX(), "readLine");
}
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 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("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.stack...."; }});
      return;
    }
    
    if (o instanceof Map && !startsWith(name, "main$")) {
      if (o instanceof LinkedHashMap) d.append("lhm");
      else if (o instanceof HashMap) d.append("hm");
      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 {\r..."; }});
}

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 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 StringBuffer print_log = local_log; // might be redirected, e.g. to main bot

// in bytes - will cut to half that
static volatile int print_log_max = 1024*1024;
static volatile int local_log_max = 100*1024;
//static 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 void print() {
  print("");
}

// 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) {
  
  if (print_byThread != null) {
    Object f = print_byThread.get();
    if (f != null)
      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;
  StringBuffer buf = print_log;
  int loc_max = print_log_max;
  if (buf != loc && buf != null) {
    print_append(buf, s, print_log_max);
    loc_max = local_log_max;
  }
  if (loc != null) 
    print_append(loc, s, loc_max);
  System.out.print(s);
}

static void print(long l) {
  print(String.valueOf(l));
}

static void print(char c) {
  print(String.valueOf(c));
}

static void print_append(StringBuffer buf, String s, int max) {
  synchronized(buf) {
    buf.append(s);
    max /= 2;
    if (buf.length() > max) try {
      int newLength = max/2;
      int ofs = buf.length()-newLength;
      String newString = buf.substring(ofs);
      buf.setLength(0);
      buf.append("[...] ").append(newString);
    } catch (Exception e) {
      buf.setLength(0);
    }
  }
}
// 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 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 void printStackTrace2(S indent, Throwable e) {
  if (endsWithLetter(indent)) indent += " ";
  printIndent(indent, getStackTrace2(e));
}*/
static boolean tok_classHasModifier(List<String> classDecl, String modifier) {
  int i = classDecl.indexOf("class");
  return subList(classDecl, 0, i).contains(modifier);
}
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 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 WeakHashMap<Class, ArrayList<Method>> callF_cache = new WeakHashMap();


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

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 <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 void deleteDirectory(File dir) {
  deleteDirectory(dir, false, false);
}

static void deleteDirectory(File dir, boolean verbose, boolean testRun) {
  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) {
  return s.replace("\r\n", "\n").replace("\r", "\n");
}
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;
}




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

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

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

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_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;
    
    // 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("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("{")) {
          parseMap(out); return;
        }
        if (t.equals("[")) {
          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, 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;
      if (c != null) {
        o = hasOuter ? nuStubInnerObject(c) : 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());
            consume("=");
            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, fie..."; }});
      } 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) {
      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(\"int..."; }});
    }
    
    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 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);
            consume("=");
            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 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 Class __javax;

static Class getJavaX() {
  return __javax;
}
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, 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 File tempDirPossiblyInRAMDisk() {
  File f = linux_fileInRamDisk(aGlobalID());
  if (f != null) { f.mkdirs(); return f; }
  return makeTempDir();
}
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));
}

static Object call(Object o) {
  return callFunction(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 instanceof Class) {
      Method m = call_findStaticMethod((Class) o, method, args, false);
      m.setAccessible(true);
      return invokeMethod(m, null, args);
    } else {
      Method m = call_findMethod(o, method, args, false);
      m.setAccessible(true);
      return invokeMethod(m, o, args);
    }
  } catch (Exception e) {
    throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
  }
}

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

      if ((m.getModifiers() & 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());
}

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

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 String boolArrayToHex(boolean[] a) {
  return bytesToHex(boolArrayToBytes(a));
}
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());
}
  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 Class javax() {
  return getJavaX();
}
  // 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 isNonNegativeInteger(String s) {
  return s != null && Pattern.matches("\\d+", s);
}
static TreeSet<String> caseInsensitiveSet() {
  return new TreeSet(caseInsensitiveComparator());
}

static TreeSet<String> caseInsensitiveSet(Collection<String> c) {
  return toCaseInsensitiveSet(c);
}
static boolean checkCondition(Object condition, Object... args) {
  return isTrue(callF(condition, args));
}
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();
}
/** 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);
    File parentFile = file.getParentFile();
    if (parentFile != null)
      parentFile.mkdirs();
    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 == '\u201C' || c == '\u201D') c = '"'; // normalize quotes
    if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        char _c = s.charAt(j);
        if (_c == '\u201C' || _c == '\u201D') _c = '"'; // normalize quotes
        if (_c == opener) {
          ++j;
          break;
        } else if (s.charAt(j) == '\\' && j+1 < l)
          j += 2;
        else
          ++j;
      }
      if (j-1 >= i+1) {
        tok.add(opener + s.substring(i+1, j-1) + opener);
        i = j;
        continue;
      }
    } else if (Character.isJavaIdentifierStart(c))
      do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || s.charAt(j) == '\'')); // for things like "this one's"
    else if (Character.isDigit(c))
      do ++j; while (j < l && Character.isDigit(s.charAt(j)));
    else if (cc.equals("[[")) {
      do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]"));
      j = Math.min(j+2, l);
    } else if (cc.equals("[=") && i+2 < l && s.charAt(i+2) == '[') {
      do ++j; while (j+2 < l && !s.substring(j, j+3).equals("]=]"));
      j = Math.min(j+3, l);
    } else if (s.substring(j, Math.min(j+3, l)).equals("..."))
      j += 3;
    else if (c == '$' || c == '#')
      do ++j; while (j < l && Character.isDigit(s.charAt(j)));
    else
      ++j;

    tok.add(s.substring(i, j));
    i = j;
  }
  
  if ((tok.size() % 2) == 0) tok.add("");
  return tok;
}

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

public static String mkdirsForFile(String path) {
  mkdirsForFile(new File(path));
  return path;
}
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();
}
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 <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 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 boolean isOfflineMode() {
  return eq("1", trim(loadProgramTextFile("#1005806", "offline-mode")));
}
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(Symbol a, Symbol b) {
  return eq(a, b);
}

static boolean eqic(Symbol a, String b) {
  return eqic(asString(a), b);
}


static boolean eqic(char a, char b) {
  if (a == b) return true;
  
    char u1 = Character.toUpperCase(a);
    char u2 = Character.toUpperCase(b);
    if (u1 == u2) return true;
  
  return Character.toLowerCase(u1) == Character.toLowerCase(u2);
}
static 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;
  
  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)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}
public static byte[] loadBinaryFile(String fileName) {
 try {
  if (!new File(fileName).exists())
    return null;

  FileInputStream in = new FileInputStream(fileName);

  byte buf[] = new byte[1024];
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  int l;
  while (true) {
    l = in.read(buf);
    if (l <= 0) break;
    out.write(buf, 0, l);
  }
  in.close();
  return out.toByteArray();
 } catch (IOException e) { throw new RuntimeException(e); }
}

public static byte[] loadBinaryFile(File file) {
  return loadBinaryFile(file.getPath());
}

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 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 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 String getStackTrace2(Throwable throwable) {
  return hideCredentials(getStackTrace(throwable) + replacePrefix("java.lang.RuntimeException: ", "FAIL: ",
    hideCredentials(str(getInnerException(throwable)))) + "\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 Map 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 String doPost(Map urlParameters, String url) {
  return doPost(makePostData(urlParameters), url);
}

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

static String doPost(String urlParameters, URLConnection conn, URL url) { try {
  setHeaders(conn);

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

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

  String contents = loadPage(conn, url, false);
  writer.close();
  return contents;
} catch (Exception __e) { throw rethrow(__e); } }
static boolean odd(int i) {
  return (i & 1) != 0;
}

static boolean odd(long i) {
  return (i & 1) != 0;
}
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 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();
  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) {
    Object action;
    synchronized(ping_actions) {
      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 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 List<String> toLinesFullTrim_java(String text) {
  return toLinesFullTrim(joinLines(map("javaDropComments", toLinesFullTrim(text))));
}

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(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
  loadPage_responseHeaders.set(con.getHeaderFields());
  String contentType = con.getContentType();
  if (contentType == null) {
    //printStruct("Headers: ", con.getHeaderFields());
    throw new IOException("Page could not be read: " + url);
  }
  //print("Content-Type: " + contentType);
  String charset = loadPage_charset == null ? null : loadPage_charset.get();
  if (charset == null) charset = loadPage_guessCharset(contentType);
  
  InputStream in = con.getInputStream();
  try {
    if ("gzip".equals(con.getContentEncoding())) {
      if (loadPage_debug)
        print("loadPage: Using gzip.");
      in = new GZIPInputStream(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 { 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);
  return con;
}
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 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 <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 (!(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;
}
// TODO: send hard errors to core

static AtomicLong _handleError_nonVMErrors = new AtomicLong();
static AtomicLong _handleError_vmErrors = new AtomicLong();
static AtomicLong _handleError_outOfMemoryErrors = new AtomicLong();
static volatile long _handleError_lastOutOfMemoryError;
static volatile Error _handleError_lastHardError;

static void _handleError(Error e) {
  if (!(e instanceof VirtualMachineError)) {
    incAtomicLong(_handleError_nonVMErrors);
    return;
  }
  
  print("\nHARD ERROR\n");
  printStackTrace2(e);
  print("\nHARD ERROR\n");
  _handleError_lastHardError = e;
  
  incAtomicLong(_handleError_vmErrors);
  if (e instanceof OutOfMemoryError) {
    incAtomicLong(_handleError_outOfMemoryErrors);
    _handleError_lastOutOfMemoryError = sysNow();
  }
}
static Set similarEmptySet(Set m) {
  if (m instanceof TreeSet) return new TreeSet();
  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 <A, B, C> B second(T3<A, B, C> t) {
  return t == null ? null : t.b;
}

static Class mc() {
  return main.class;
}
// 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) {
  String t = getUnclosedStringLiteral(s);
  if (t != null) {
    if (msg != null)
      msg.set("Unclosed string literal: " + quote(t));
    return false;
  }
  
  List<String> tok = javaTok(s);
  Map<Integer,Integer> map = new HashMap();
  List<Integer> stack = getBracketMap2(tok, map, op, close);
  if (nempty(stack)) {
    if (msg != null)
      msg.set("Bad hygiene - " + n(l(stack), "bracket") + " not closed");
    return false;
  }
  
  if (map.containsKey(0)) {
    if (msg != null)
      msg.set("Bad hygiene - bracket not opened (" + quote(tok.get(map.get(0))) + ")");
    return false;
  }
  
  for (int i : keys(map)) {
    int j = map.get(i);
    String a = tok.get(i), b = tok.get(j);
    int ai = op.indexOf(a), bi = close.indexOf(b);
    if (ai != bi) {
      if (msg != null)
        msg.set("Bad hygiene - brackets don't match (" + quote(a) + " vs " + quote(b) + ")");
      return false;
    }
  }
  
  if (msg != null)
    msg.set("Hygiene OK!");
  return true;
}
static <A, B> void mapPut(Map<A, B> map, A key, B value) {
  if (map != null && key != null && value != null) map.put(key, value);
}
static 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
      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 || checkCondition(condition, tok, i-1)) // pass N index
      return i;
  }
  return -1;
}
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 {
      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 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 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;
    
  }
}
  public static String loadTextFile(String fileName) {
    return loadTextFile(fileName, null);
  }
  
  public static String loadTextFile(File fileName, String defaultContents) {
    ping();
    try {
      if (fileName == null || !fileName.exists())
        return defaultContents;
  
      FileInputStream fileInputStream = new FileInputStream(fileName);
      InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");
      return loadTextFile(inputStreamReader);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  
  public static String loadTextFile(File fileName) {
    return loadTextFile(fileName, null);
  }

  public static String loadTextFile(String fileName, String defaultContents) {
    return fileName == null ? defaultContents : loadTextFile(newFile(fileName), defaultContents);
  }
  
  public static String loadTextFile(Reader reader) throws IOException {
    StringBuilder builder = new StringBuilder();
    try {
      char[] buffer = new char[1024];
      int n;
      while (-1 != (n = reader.read(buffer)))
        builder.append(buffer, 0, n);
        
    } finally {
      reader.close();
    }
    return builder.toString();
  }
static void smartSet(Field f, Object o, Object value) throws Exception {
  f.setAccessible(true);
  
  // take care of common case (long to int)
  if (f.getType() == int.class && value instanceof Long)
    value = ((Long) value).intValue();
    
  try {
    f.set(o, value);
  } catch (Exception e) {
    
    throw e;
  }
}
// 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 tok_packageName(List<String> tok) {
  int i = jfind(tok, "package");
  if (i < 0) return "";
  i += 2;
  int j = jfind(tok, i, ";");
  if (j < 0) return "";
  return join(codeTokensOnly(subList(tok, i-1, j)));
}
  static File makeTempDir() {
    return (File) call(getJavaX(), "TempDirMaker_make");
  }
static <A> A setThreadLocal(ThreadLocal<A> tl, A value) {
  A old = tl.get();
  tl.set(value);
  return old;
}
static boolean isInRAMDisk(File f) {
  return startsWith(f2s(f), "/dev/shm");
}
static boolean isSingleQuoteIdentifier(String s) {
  if (l(s) < 2 || s.charAt(0) != '\'' || !Character.isJavaIdentifierStart(s.charAt(1))) return false;
  for (int i = 2; i < l(s); i++)
    if (!Character.isJavaIdentifierPart(s.charAt(i))) return false;
  return true;
}
static <A> List<A> reversed(Collection<A> l) {
  return reversedList(l);
}

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

static String reversed(String s) {
  return reversedString(s);
}
static int stdcompare(Number a, Number b) {
  return cmp(a, b);
}

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

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

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

static List<List<String>> innerClasses(List<String> tok) {
  if (tok == null) return emptyList();
  int i = 1;
  while (i < tok.size() && !tok.get(i).equals("{"))
    i += 2;
  return allClasses(tok.subList(i+1, tok.size()));
}
static boolean startsWith(String a, String b) {
  return a != null && a.startsWith(b);
}

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


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


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




static List<CriticalAction> beginCriticalAction_inFlight = synchroList();

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

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

static void cleanMeUp_beginCriticalAction() {
  int n = 0;
  while (nempty(beginCriticalAction_inFlight)) {
    int m = l(beginCriticalAction_inFlight);
    if (m != n) {
      n = m;
      try {
        print("Waiting for " + n(n, "critical actions") + ": " + join(", ", collect(beginCriticalAction_inFlight, "description")));
      } catch (Throwable __e) { printStackTrace2(__e); }
    }
    sleepInCleanUp(10);
  }
}
static void printWithTime(String s) {
  print(hmsWithColons() + ": " + s);
}
static String loadPage_utf8(URL url) {
  return loadPage_utf8(url.toString());
}

static String loadPage_utf8(String url) {
  loadPage_charset.set("UTF-8");
  try {
    return loadPage(url);
  } finally {
    loadPage_charset.set(null);
  }
}
static String shortenSnippetID(String snippetID) {
  if (snippetID.startsWith("#"))
    snippetID = snippetID.substring(1);
  String httpBlaBla = "http://tinybrain.de/";
  if (snippetID.startsWith(httpBlaBla))
    snippetID = snippetID.substring(httpBlaBla.length());
  return "" + parseLong(snippetID);
}
static 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 byte[] hexToBytes(String s) {
  int n = l(s) / 2;
  byte[] bytes = new byte[n];
  for (int i = 0; i < n; i++) {
    String hex = substring(s, i*2, i*2+2);
    try {
      bytes[i] = (byte) parseHexByte(hex);
    } catch (Throwable _e) {
      throw fail("Bad hex byte: " + quote(hex) + " at " + i*2 + "/" + l(s));
    }
  }
  return bytes;
}
static String replacePrefix(String prefix, String replacement, String s) {
  if (!startsWith(s, prefix)) return s;
  return replacement + substring(s, l(prefix));
}
static boolean isProperlyQuoted(String s) {
  return s.length() >= 2
    && s.startsWith("\"")
    && s.endsWith("\"")
    && (!s.endsWith("\\\"") || s.endsWith("\\\\\""));
}
static HashMap<Class,Constructor> nuEmptyObject_cache = new HashMap();

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

  return (A) ctr.newInstance();
} catch (Exception __e) { throw rethrow(__e); } }

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

static 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 boolean structure_isMarker(String s, int i, int j) {
  if (i >= j) return false;
  if (s.charAt(i) != 'm') return false;
  ++i;
  while (i < j) {
    char c = s.charAt(i);
    if (c < '0' || c > '9') return false;
    ++i;
  }
  return true;
}
static void incAtomicLong(AtomicLong l) {
  l.incrementAndGet();
}
static void setHeaders(URLConnection con) throws IOException {
  
  String computerID = getComputerID_quick();
  if (computerID != null) try {
    con.setRequestProperty("X-ComputerID", computerID);
    con.setRequestProperty("X-OS", System.getProperty("os.name") + " " + System.getProperty("os.version"));
  } catch (Throwable e) {
    //printShortException(e);
  }
  
}
static String exceptionToStringShort(Throwable e) {
  lastException(e);
  e = getInnerException(e);
  String msg = unnull(e.getMessage());
  if (msg.indexOf("Error") < 0 && msg.indexOf("Exception") < 0)
    return baseClassName(e) + prependIfNempty(": ", msg);
  else
    return msg;
}
static Throwable getInnerException(Throwable e) {
  while (e.getCause() != null)
    e = e.getCause();
  return e;
}
static String javaDropComments(String s) {
  return javaDropAllComments(s);
}
static String n(long l, String name) {
  return l + " " + trim(l == 1 ? singular(name) : getPlural(name));
}

static String n(Collection l, String name) {
  return n(l(l), name);
}

static String n(Map m, String name) {
  return n(l(m), name);
}

static String n(Object[] a, String name) {
  return n(l(a), name);
}


  static String n(MultiSet ms, String name) {
    return n(l(ms), name);
  }

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

static <A> A optParam(ThreadLocal<A> tl) {
  return optPar(tl);
}
static void saveTranspiledCode(String progID, String code) {
  File dir = getCodeProgramDir(progID);
  new File(dir, "Transpilation").delete();
  saveGZTextFile(new File(dir, "Transpilation.gz"), code);
}
static <A> A getAndClearThreadLocal(ThreadLocal<A> tl) {
  A a = tl.get();
  tl.set(null);
  return a;
}
static boolean jsonDecode_useOrderedMaps = true;

static Object jsonDecode(final String text) {
  final List<String> tok = jsonTok(text);
  
  class Y {
    int i = 1;

    Object parse() {
      String t = tok.get(i);
      if (t.startsWith("\"")) {
        String s = unquote(tok.get(i));
        i += 2;
        return s;
      }
      if (t.equals("{"))
        return parseMap();
      if (t.equals("["))
        return parseList();
      if (t.equals("null")) {
        i += 2; return null;
      }
      if (t.equals("false")) {
        i += 2; return false;
      }
      if (t.equals("true")) {
        i += 2; return true;
      }
      boolean minus = false;
      if (t.equals("-")) {
        minus = true;
        i += 2;
        t = get(tok, i);
      }
      if (isInteger(t)) {
        i += 2;
        if (eq(get(tok, i), ".")) {
          String x = t + "." + get(tok, i+2);
          i += 4;
          double d = parseDouble(x);
          if (minus) d = -d;
          return d;
        } else {
          long l = parseLong(t);
          if (minus) l = -l;
          return l != (int) l ? new Long(l) : new Integer((int) l);
        }
      }
      
      throw new RuntimeException("Unknown token " + (i+1) + ": " + t + ": " + text);
    }
    
    Object parseList() {
      consume("[");
      List list = new ArrayList();
      while (!tok.get(i).equals("]")) {
        list.add(parse());
        if (tok.get(i).equals(",")) i += 2;
      }
      consume("]");
      return list;
    }
    
    Object parseMap() {
      consume("{");
      Map map = jsonDecode_useOrderedMaps ? new LinkedHashMap() : new TreeMap();
      while (!tok.get(i).equals("}")) {
        String key = unquote(tok.get(i));
        i += 2;
        consume(":");
        Object value = parse();
        map.put(key, value);
        if (tok.get(i).equals(",")) i += 2;
      }
      consume("}");
      return map;
    }
    
    void consume(String s) {
      if (!tok.get(i).equals(s)) {
        String prevToken = i-2 >= 0 ? tok.get(i-2) : "";
        String nextTokens = join(tok.subList(i, Math.min(i+4, tok.size())));
        throw fail(quote(s) + " expected: " + prevToken + " " + nextTokens + " (" + i + "/" + tok.size() + ")");
      }
      i += 2;
    }
  }
  
  return new Y().parse();
}
static Comparator<String> caseInsensitiveComparator() {
  
  
  return String.CASE_INSENSITIVE_ORDER;
  
}
static boolean jmatch(String pat, String s) {
  return jmatch(pat, s, null);
}

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

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

static boolean jmatch(String pat, List<String> toks, Matches matches) {
  List<String> tokpat = javaTok(pat);
  String[] m = match2(tokpat, toks);
  //print(structure(tokpat) + " on " + structure(toks) + " => " + structure(m));
  if (m == null)
    return false;
  else {
    if (matches != null) matches.m = m;
    return true;
  }
}
static String getStackTrace(Throwable throwable) {
  lastException(throwable);
  return getStackTrace_noRecord(throwable);
}

static String getStackTrace_noRecord(Throwable throwable) {
  StringWriter writer = new StringWriter();
  throwable.printStackTrace(new PrintWriter(writer));
  return hideCredentials(writer.toString());
}

static String getStackTrace() {
  return getStackTrace_noRecord(new Throwable());
}
static String rep(int n, char c) {
  return repeat(c, n);
}

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

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

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

static File getCodeProgramDir() {
  return getCodeProgramDir(getProgramID());
}

static File getCodeProgramDir(String snippetID) {
  return new File(javaxCodeDir(), formatSnippetID(snippetID));
}

static File getCodeProgramDir(long snippetID) {
  return getCodeProgramDir(formatSnippetID(snippetID));
}
static URLConnection openConnection(URL url) { try {
  ping();
  
  callOpt(javax(), "recordOpenURLConnection", str(url));
  
  return url.openConnection();
} catch (Exception __e) { throw rethrow(__e); } }
static <A, B> B secondOfPair(Pair<A, B> p) {
  return p == null ? null : p.b;
}
static FileOutputStream newFileOutputStream(File path) throws IOException {
  return newFileOutputStream(path.getPath());
}

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

static FileOutputStream newFileOutputStream(String path, boolean append) throws IOException {
  mkdirsForFile(path);
  FileOutputStream f = new // Line break for ancient translator
    FileOutputStream(path, append);
  
  callJavaX("registerIO", f, path, true);
  
  return f;
}
static String shortClassName(Object o) {
  if (o == null) return null;
  Class c = o instanceof Class ? (Class) o : o.getClass();
  String name = c.getName();
  return shortenClassName(name);
}
static Producer<String> javaTokC_noMLS_onReader(final BufferedReader r) {
  final class X implements Producer<String> {
    StringBuilder buf = new StringBuilder(); // stores from "i"
    char c, d, e = 'x'; // just not '\0'
    
    X() {
      // fill c, d and e
      nc();
      nc();
      nc();
    }
    
    // get next character(s) into c, d and e
    void nc() { try {
      c = d;
      d = e;
      if (e == '\0') return;
      int i = r.read();
      e = i < 0 ? '\0' : (char) i;
    } catch (Exception __e) { throw rethrow(__e); } }
    
    void ncSave() {
      if (c != '\0') {
        buf.append(c);
        nc();
      }
    }
    
    public String next() {
      // scan for whitespace
      while (c != '\0') {
        if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
          nc();
        else if (c == '/' && d == '*') {
          do nc(); while (c != '\0' && !(c == '*' && d == '/'));
          nc(); nc();
        } else if (c == '/' && d == '/') {
          do nc(); while (c != '\0' && "\r\n".indexOf(c) < 0);
        } else
          break;
      }
      
      if (c == '\0') return null;

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

static TreeSet<String> toCaseInsensitiveSet(Collection<String> c) {
  // TODO: check if it already is a CI set
  TreeSet<String> set = caseInsensitiveSet();
  if (c != null) set.addAll(c);
  return set;
}
static String asString(Object o) {
  return o == null ? null : o.toString();
}
static List _registerWeakMap_preList;

static <A> A _registerWeakMap(A map) {
  if (javax() == null) {
    // We're in class init
    if (_registerWeakMap_preList == null) _registerWeakMap_preList = synchroList();
    _registerWeakMap_preList.add(map);
    return map;
  }
  
  try {
    call(javax(), "_registerWeakMap", map);
  } catch (Throwable e) {
    printException(e);
    print("Upgrade JavaX!!");
  }
  return map;
}

static void _onLoad_registerWeakMap() {
  assertNotNull(javax());
  if (_registerWeakMap_preList == null) return;
  for (Object o : _registerWeakMap_preList)
    _registerWeakMap(o);
  _registerWeakMap_preList = null;
}
static String myJavaSource_code;

static String myJavaSource() {
  return myJavaSource_code;
}
static boolean _inCore() {
  return false;
}
static String assertIsIdentifier(String s) {
  if (!isIdentifier(s))
    throw fail("Not an identifier: " + quote(s));
  return s;
}
static boolean isLongConstant(String s) {
  if (!s.endsWith("L")) return false;
  s = s.substring(0, l(s)-1);
  return isInteger(s);
}
static boolean endsWithLetter(String s) {
  return nempty(s) && isLetter(last(s));
}




static volatile boolean disableCertificateValidation_attempted;

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

static List<String> getJavaModifiers() {
  return getJavaModifiers_list;
}
static int[] toIntArray(List<Integer> l) {
  int[] a = new int[l(l)];
  for (int i = 0; i < a.length; i++)
    a[i] = l.get(i);
  return a;
}
static boolean isNormallyQuoted(String s) {
  return startsWith(s, "\"");
}
static Map synchroMap() {
  return synchroHashMap();
}

static <A, B> Map<A, B> synchroMap(Map<A, B> map) {
  return Collections.synchronizedMap(map);
}
// 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, 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, A sub, int start) {
  int i = indexOf(l, sub, start);
  return i < 0 ? l(l) : i;
}
static double parseDouble(String s) {
  return Double.parseDouble(s);
}
//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);
  }
  getOpt_cache.put(c, map);
  return map;
}
static File[] listFiles(File dir) {
  File[] files = dir.listFiles();
  return files == null ? new File[0] : files;
}

static File[] listFiles(String dir) {
  return listFiles(new File(dir));
}
static int toInt(Object o) {
  if (o == null) return 0;
  if (o instanceof Number)
    return ((Number) o).intValue();
  if (o instanceof String)
    return parseInt((String) o);
  throw fail("woot not int: " + getClassName(o));
}

static int toInt(long l) {
  if (l != (int) l) throw fail("Too large for int: " + l);
  return (int) l;
}
static <A> A or(A a, A b) {
  return a != null ? a : b;
}
static String aGlobalID() {
  return randomID(16);
}
static Object callFunction(Object f, Object... args) {
  return callF(f, args);
}
static HashMap<String, List<Method>> callMC_cache = new HashMap();
static String callMC_key;
static Method callMC_value;

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

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

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

static void callMC_makeCache() {
  synchronized(callMC_cache) {
    callMC_cache.clear();
    Class _c = (Class) mc(), c = _c;
    while (c != null) {
      for (Method m : c.getDeclaredMethods())
        if ((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0) {
          m.setAccessible(true);
          multiMapPut(callMC_cache, m.getName(), m);
        }
      c = c.getSuperclass();
    }
  }
}
// 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 Thread currentThread() {
  return Thread.currentThread();
}
static void setOptAll_pcall(Object o, Map<String, Object> fields) {
  if (fields == null) return;
  for (String field : keys(fields))
    try { setOpt(o, field, fields.get(field)); } catch (Throwable __e) { print(exceptionToStringShort(__e)); }
}

static void setOptAll_pcall(Object o, Object... values) {
  //values = expandParams(c.getClass(), values);
  warnIfOddCount(values);
  for (int i = 0; i+1 < l(values); i += 2) {
    String field = (String) values[i];
    Object value = values[i+1];
    try { setOpt(o, field, value); } catch (Throwable __e) { print(exceptionToStringShort(__e)); }
  }
}
static List<String> toLinesFullTrim(String s) {
  List<String> l = new ArrayList();
  for (String line : toLines(s)) if (nempty(line = trim(line))) l.add(line);
  return l;
}

static List<String> toLinesFullTrim(File f) {
  List<String> l = new ArrayList();
  for (String line : linesFromFile(f)) if (nempty(line = trim(line))) l.add(line);
  return l;
}

static boolean setOptAllDyn_debug;

static void setOptAllDyn(DynamicObject o, Map<String, Object> fields) {
  if (fields == null) return;
  for (String field : keys(fields)) {
    Object val = fields.get(field);
    boolean has = hasField(o, field);
    if (has)
      setOpt(o, field, val);
    else {
      o.fieldValues.put(intern(field), val);
      if (setOptAllDyn_debug) print("setOptAllDyn added dyn " + field + " to " + o + " [value: " + val + ", fieldValues = " + systemHashCode(o.fieldValues) + ", " + struct(keys(o.fieldValues)) + "]");
    }
  }
}
static File loadBinarySnippet(String snippetID) { try {
  long id = parseSnippetID(snippetID);
  File f = DiskSnippetCache_getLibrary(id);
  if (fileSize(f) == 0)
    f = loadDataSnippetToFile(snippetID);
  return f;
} catch (Exception __e) { throw rethrow(__e); } }
static String hideCredentials(URL url) { return url == null ? null : hideCredentials(str(url)); }

static String hideCredentials(String url) {
  return url.replaceAll("([&?])_pass=[^&\\s\"]*", "$1_pass=<hidden>");
}

static String hideCredentials(Object o) {
  return hideCredentials(str(o));
}
static Class getMainClass() {
  return main.class;
}

static Class getMainClass(Object o) { try {
  return (o instanceof Class ? (Class) o : o.getClass()).getClassLoader().loadClass("main");
} catch (Exception __e) { throw rethrow(__e); } }
static String intern(String s) {
  return fastIntern(s);
}
static String reversedString(String s) {
  return reverseString(s);
}
static File linux_fileInRamDisk(String name) {
  if (!isLinux()) return null;
  File dir = newFile("/dev/shm");
  if (dir.isDirectory()) return newFile(dir, name);
  return null;
}
static String joinLines(List<String> lines) {
  return fromLines(lines);
}

static String joinLines(String glue, String text) {
  return join(glue, toLines(text));
}
static Producer<String> javaTokC_noMLS_iterator(final String s) {
  return new Producer<String>() {
    final int l = s.length();
    int i = 0;
    
    public String next() {
      if (i >= l) return null;
      
      int j = i;
      char c, d;
      
      // scan for whitespace
      while (j < l) {
        c = s.charAt(j);
        d = j+1 >= l ? '\0' : s.charAt(j+1);
        if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
          ++j;
        else if (c == '/' && d == '*') {
          do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
          j = Math.min(j+2, l);
        } else if (c == '/' && d == '/') {
          do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
        } else
          break;
      }
      
      i = j;
      if (i >= l) return null;
      c = s.charAt(i);
      d = i+1 >= l ? '\0' : s.charAt(i+1);
  
      // scan for non-whitespace
      if (c == '\'' || c == '"') {
        char opener = c;
        ++j;
        while (j < l) {
          if (s.charAt(j) == opener || s.charAt(j) == '\n') { // end at \n to not propagate unclosed string literal errors
            ++j;
            break;
          } else if (s.charAt(j) == '\\' && j+1 < l)
            j += 2;
          else
            ++j;
        }
      } else if (Character.isJavaIdentifierStart(c))
        do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || "'".indexOf(s.charAt(j)) >= 0)); // for stuff like "don't"
      else if (Character.isDigit(c)) {
        do ++j; while (j < l && Character.isDigit(s.charAt(j)));
        if (j < l && s.charAt(j) == 'L') ++j; // Long constants like 1L
      } else
        ++j;
        
      String t = quickSubstring(s, i, j);
      i = j;
      return t;
    }
  };
}

static boolean warn_on = true;

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

static void warn(String s, List<String> warnings) {
  warn(s);
  if (warnings != null)
    warnings.add(s);
}
static String beautifyStructure(String s) {
  return structure_addTokenMarkers(s);
}
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 Object callJavaX(String method, Object... args) {
  return callOpt(getJavaX(), method, args);
}
static String loadProgramTextFile(String name) {
  return loadTextFile(getProgramFile(name));
}

static String loadProgramTextFile(String progID, String name) {
  return loadTextFile(getProgramFile(progID, name));
}

static String loadProgramTextFile(String progID, String name, String defaultText) {
  return loadTextFile(getProgramFile(progID, name), defaultText);
}
static List<Integer> getBracketMap2(List<String> tok, Map<Integer, Integer> map, String openingBrackets, String closingBrackets) {
  map.clear();
  List<Integer> stack = new ArrayList();
  for (int i = 1; i < l(tok); i+= 2) {
    String t = tok.get(i);
    if (l(t) == 1)
      if (openingBrackets.contains(t))
        stack.add(i);
      else if (closingBrackets.contains(tok.get(i)))
        map.put(empty(stack) ? 0 : liftLast(stack), i);
  }
  return stack;
}
static char unquoteCharacter(String s) {
  assertTrue(s.startsWith("'") && s.length() > 1);
  return unquote("\"" + s.substring(1, s.endsWith("'") ? s.length()-1 : s.length()) + "\"").charAt(0);
}
static void copyStream(InputStream in, OutputStream out) { try {
  byte[] buf = new byte[65536];
  while (true) {
    int n = in.read(buf);
    if (n <= 0) return;
    out.write(buf, 0, n);
  }
} catch (Exception __e) { throw rethrow(__e); } }
static <A> A nuStubInnerObject(Class<A> c) { try {
  Class outerType = getOuterClass(c);
  Constructor m = c.getDeclaredConstructor(outerType);
  m.setAccessible(true);
  return (A) m.newInstance(new Object[] {null});
} catch (Exception __e) { throw rethrow(__e); } }
static String struct_noStringSharing(Object o) {
  structure_Data d = new structure_Data();
  d.noStringSharing = true;
  return structure(o, d);
}
static boolean[] boolArrayFromBytes(byte[] a, int n) {
  boolean[] b = new boolean[n];
  int m = min(n, l(a)*8);
  for (int i = 0; i < m; i++)
    b[i] = (a[i/8] & 1 << (i & 7)) != 0;
  return b;
}
static String loadTextFilePossiblyGZipped(String fileName) {
  return loadTextFilePossiblyGZipped(fileName, null);
}
  
static String loadTextFilePossiblyGZipped(String fileName, String defaultContents) {
  File gz = new File(fileName + ".gz");
  return gz.exists() ? loadGZTextFile(gz) : loadTextFile(fileName, defaultContents);
}

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

static String loadTextFilePossiblyGZipped(File fileName, String defaultContents) {
  return loadTextFilePossiblyGZipped(fileName.getPath(), defaultContents);
}

static URLConnection setURLConnectionTimeouts(URLConnection con, long timeout) {
  con.setConnectTimeout(toInt(timeout));
  con.setReadTimeout(toInt(timeout));
  if (con.getConnectTimeout() != timeout || con.getReadTimeout() != timeout)
    print("Warning: Timeouts not set by JDK.");
  return con;
}
static void setOpt_raw(Object o, String field, Object value) { try {
  if (o == null) return;
  if (o instanceof Class) setOpt_raw((Class) o, field, value);
  else {
    Field f = setOpt_raw_findField(o.getClass(), field);
    if (f != null)
      smartSet(f, o, value);
  }
} catch (Exception __e) { throw rethrow(__e); } }

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

static Field setOpt_raw_findField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field))
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}
static byte[] boolArrayToBytes(boolean[] a) {
  byte[] b = new byte[(l(a)+7)/8];
  for (int i = 0; i < l(a); i++)
    if (a[i])
      b[i/8] |= 1 << (i & 7);
  return b;
}
static HashMap<String,Class> findClass_cache = new HashMap();

// currently finds only inner classes of class "main"
// returns null on not found
// this is the simple version that is not case-tolerant
static Class findClass(String name) {
  synchronized(findClass_cache) {
    if (findClass_cache.containsKey(name))
      return findClass_cache.get(name);
      
    if (!isJavaIdentifier(name)) return null;
    
    Class c;
    try {
      c = Class.forName("main$" + name);
    } catch (ClassNotFoundException e) {
      c = null;
    }
    findClass_cache.put(name, c);
    return c;
  }
}
static String standardCredentialsUser() {
  return trim(loadTextFile(new File(userHome(), ".tinybrain/username")));
}
public static void copyFile(File src, File dest) { try {
  mkdirsForFile(dest);
  FileInputStream inputStream = new FileInputStream(src.getPath());
  FileOutputStream outputStream = newFileOutputStream(dest.getPath());
  try {
    copyStream(inputStream, outputStream);
    inputStream.close();
  } finally {
    outputStream.close();
  }
} catch (Exception __e) { throw rethrow(__e); } }
static float parseFloat(String s) {
  return Float.parseFloat(s);
}
static List parseList(String s) {
  return (List) safeUnstructure(s);
}
static File javaxCachesDir_dir; // can be set to work on different base dir

static File javaxCachesDir() {
  return javaxCachesDir_dir != null ? javaxCachesDir_dir : new File(userHome(), "JavaX-Caches");
}
static Map emptyMap() {
  return new HashMap();
}
static String internIfLongerThan(String s, int l) {
  return s == null ? null : l(s) >= l ? intern(s) : s;
}
static void pcallOpt_noArgs(Object o, String method) {
  try { callOpt_noArgs(o, method); } catch (Throwable __e) { printStackTrace2(__e); }
}
static File getProgramDir() {
  return programDir();
}

static File getProgramDir(String snippetID) {
  return programDir(snippetID);
}
// works on lists and strings and null

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

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

static int indexOfIgnoreCase(String a, String b) {
  return indexOfIgnoreCase_manual(a, b);
  /*Matcher m = Pattern.compile(b, Pattern.CASE_INSENSITIVE + Pattern.LITERAL).matcher(a);
  if (m.find()) return m.start(); else ret -1;*/
}
static final boolean loadPageThroughProxy_enabled = false;

static String loadPageThroughProxy(String url) {
  return null;
}
static void hotwire_copyOver(Class c) {
  synchronized(StringBuffer.class) {
    for (String field : litlist("print_log", "print_silent", "androidContext")) {
      Object o = getOpt(mc(), field);
      if (o != null)
        setOpt(c, field, o);
    }
      
    Object mainBot = getMainBot();
    if (mainBot != null)
      setOpt(c, "mainBot", mainBot);

    setOpt(c, "creator_class", new WeakReference(mc()));
  }
}
static Symbol emptySymbol_value;

static Symbol emptySymbol() {
  if (emptySymbol_value == null) emptySymbol_value = symbol("");
  return emptySymbol_value;
}
static void sortFilesByName(List<File> l) {
  sort(l, new Comparator<File>() {
    public int compare(File a, File b) {
      return stdcompare(a.getName(), b.getName());
    }
  });
}
static String makePostData(Map<Object, Object> map) {
  List<String> l = new ArrayList();
  for (Map.Entry<Object, Object> e : map.entrySet()) {
    String key = (String) ( e.getKey());
    Object val = e.getValue();
    if (val != null) {
      String value = str(val); //structureOrText(val);
      l.add(urlencode(key) + "=" + urlencode(escapeMultichars(value)));
    }
  }
  return join("&", l);
}

static String makePostData(Object... params) {
  return makePostData(litorderedmap(params));
}

static String getUnclosedStringLiteral(String s) {
  for (String t : javaTokC(s))
    if (isUnproperlyQuoted(t))
      return t;
  return null;
}
// extended over Class.isInstance() to handle primitive types
static boolean isInstanceX(Class type, Object arg) {
  if (type == boolean.class) return arg instanceof Boolean;
  if (type == int.class) return arg instanceof Integer;
  if (type == long.class) return arg instanceof Long;
  if (type == float.class) return arg instanceof Float;
  if (type == short.class) return arg instanceof Short;
  if (type == char.class) return arg instanceof Character;
  if (type == byte.class) return arg instanceof Byte;
  if (type == double.class) return arg instanceof Double;
  return type.isInstance(arg);
}
static Object nuObject(String className, Object... args) { try {
  return nuObject(classForName(className), args);
} catch (Exception __e) { throw rethrow(__e); } }

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

static <A> A