abstract sclass DialogBot { sbool debug; OccTree node; *(OccTree *node) {} L handle(L in) { int i = 0; while (i < l(in) && in.get(i).q != null) { if (!selectMatching(in.get(i))) break; ++i; } new L out; E e; while ((e = nextBotOutput()) != null) { out.add(e); select(e); } ret out; } void take(E e) { selectMatching(e); } E getSingleOutput() { E e = nextBotOutput(); if (e != null) select(e); ret e; } void rewind() { node = node.parent; } bool selectMatching(E in) { for (E e : keys(node.followUp)) if (matchE(e, in)) { if (debug) print("Selecting " + struct(e) + " matching " + struct(in)); select(e); true; } if (debug) print("No match for " + structure(in) + " in " + structure(node.followUpKeys())); false; } abstract bool matchE(E a, E b); E nextBotOutput() { new L l; L follows = viableFollowUps(); for (E e : follows) if (e.a() || isCommand(e)) l.add(e); if (empty(l)) null; // fail("no good bot output: " + struct(node.followUpKeys())); if (l(l) > 1) fail("unclear bot output: " + struct(follows)); ret first(l); } void select(E e) { node = node.followUp(e); if (node == null) fail("can't select " + structure(e)); } L viableFollowUps() { new L l; for (E e : keys(node.followUp)) if (isViable(node.followUp(e))) l.add(e); ret l; } bool isCommand(E e) { ret e.state() && matchStart("bot", e.state); } } sclass LooseBot extends DialogBot { *(OccTree node) { super(node); } bool matchE(E a, E b) { // standard NL matching if (a.q()) ret match(a.q, b.q); if (a.a()) ret match(a.a, b.a); ret match(a.state, b.state); } }