// parser explainer
static class Explain {
  static L<S> primitiveClasses = litlist("quoted", "identifier", "any", "int");
  
  O parseResult;
  L<S> tok;
  L e;
  new L<Explain> subs;
  
  L<S> fullMatchClasses() { ret (L<S>) call(parseResult, "fullMatchClasses"); }
  
  static boolean debug;
  
  *(O *parseResult, L *e) {
    tok = (L) get(parseResult, "tok");
    _makeSubExplains();
  }
  
  void _makeSubExplains() {
    for (int i = 4; i < l(e); i++) {
      L sub = cast get(e, i);
      int t1 = (int) get(sub, 0);
      int t2 = (int) get(sub, 1);
      S className = getString(sub, 2);
      L subE = sub;
      if (!primitiveClasses.contains(className))
        subE = (L) call(parseResult, "explainFull", t1, t2, className);
      if (debug)
        printF("Explaining * * * => *", t1, t2, className, subE);
      if (subE == null)
        subs.add(null);
      else
        subs.add(new Explain(parseResult, subE));
    }
  }
  
  S className() {
    ret getString(e, 2);
  }
  
  int fromToken() {
    ret (int) get(e, 0);
  }
  
  int toToken() {
    ret (int) get(e, 1);
  }
  
  // return tokens, padded with empty non-code tokens first and last
  // to make actual CNC
  L<S> tok() {
    ret concatLists(
      litlist(""),
      subList(tok, fromToken(), toToken()-1),
      litlist(""));
  }
  
  S string() {
    ret join(subList(tok, fromToken(), toToken()-1));
  }
  
  boolean containsToken(S t) {
    ret main.containsToken(tok(), t);
  }
  
  void findAll(S className, L<S> out) {
    if (eq(className, className()))
      out.add(string());
    else // << this is new - don't recurse
      for (Explain e : subs)
        if (e != null)
          e.findAll(className, out);
  }
  
  L<S> findAll(S className) {
    new L<S> l;
    findAll(className, l);
    ret l;
  }
  
  // short for findFirst
  Explain find(S className) {
    ret findFirst(className);
  }
  
  Explain findFirst(S className) {
    if (eq(className, className()))
      ret this;
    ret findFirstSub(className);
  }
  
  // find class, but exclude myself
  Explain findFirstSub(S className) {
    for (Explain e : subs)
      if (e != null) {
        Explain result = e.findFirst(className);
        if (result != null) ret result;
      }
    ret null;
  }
  
  boolean has(S className) {
    ret findFirst(className) != null;
  }
  
  boolean hasSub(S className) {
    ret findFirstSub(className) != null;
  }
  
  void findAll2(S className, L<Explain> out) {
    if (eq(className, className()))
      out.add(this);
    for (Explain e : subs)
      if (e != null)
        e.findAll2(className, out);
  }
  
  L<Explain> findAll2(S className) {
    new L<Explain> l;
    findAll2(className, l);
    ret l;
  }
  
  // short for pruneSubs
  Explain prune(S className) {
    ret pruneSubs(className);
  }
  
  // returns self after pruning
  Explain pruneSubs(S className) {
    for (int i = 0; i < l(subs); ) {
      Explain e = sub(i);
      if (e == null) ++i;
      else if (eq(e.className(), className))
        subs.remove(i);
      else {
        e.pruneSubs(className);
        ++i;
      }
    }
    ret this;
  }
  
  Explain sub(int i) { ret get(subs, i); }
  
  L<Explain> subs() { ret subs; }
  
  bool singleEqualChild() {
    if (l(subs) != 1) false;
    Explain e = first(subs);
    ret fromToken() == e.fromToken() && toToken() == e.toToken();
  }
}