docs: function dfsHelper(states, root, state, depth, tokens) {
    var edges;
  
    // Base case: we finished the root rule
    if (state === root.origin && depth === root.rule.length) {
      return [];
    }
  
    // If the current production symbol is a terminal
    if (root.rule[depth] instanceof RegExp) {
      if (root.rule[depth].test(tokens[state])) {
        let subMatch = dfsHelper(states, root, state + 1, depth + 1, tokens);
  
        if (subMatch) {
          return [tokens[state]].concat(subMatch);
        }
      }
      return null;
    } else if (typeof root.rule[depth] === 'string') {
      if (root.rule[depth] === tokens[state]) {
        let subMatch = dfsHelper(states, root, state + 1, depth + 1, tokens);
  
        if (subMatch) {
          return [tokens[state]].concat(subMatch);
        }
      }
      return null;
    }
  
    // Otherwise, it must be a non-terminal
    edges = states[state]
      .filter((item) => item.rule.lhs === root.rule[depth])
      .map((item) => {
        let subMatch = dfsHelper(states, root, item.origin, depth + 1, tokens);
  
        if (subMatch) {
          return [{
            item    : item.rule,
            children: dfsHelper(states, item, state, 0, tokens)
          }].concat(subMatch);
        }
        return null;
      })
      .filter((list) => list);
  
    if (edges.length > 1) {
      let diffs = edges.filter(
        (tree) => JSON.stringify(tree) !== JSON.stringify(edges[0])
      );
  
      if (diffs.length > 0) {
        //console.log('Ambiguity\n' + JSON.stringify(edges, null, 2));
        console.log('Ambiguous rules');
      }
    }
  
    return edges[0];
  }