// adapters are immutable! (plus returns a modified clone) abstract sclass Adapter { abstract bool canMatch(S in, S out); Adapter plus(S in, S out) { ret this; } S get(S in) { ret in; } double size() { ret 0; } } abstract sclass GenericAdapter { void modify(OccTree2 node) {} } // node in flight (+ optional adapter) sclass InFlight { OccTree2 node; Adapter adapter; *(OccTree2 *node) {} *(OccTree2 *node, Adapter *adapter) {} E adapted() { // TODO: security when rewriting states ret adapter == null ? node.e : new E(adapter.get(node.e.text()), node.e.type()); } } sclass LooseBot { sbool debug, repeat = true; OccTree2 tree; new L nodes; new L rewindStack; O newAdapter; O grabber; // func(S -> E) GenericAdapter genericAdapter; // WordAdapter now default *(OccTree2 tree) { this(tree, func { new WordAdapter }); } // main constructor *(OccTree2 *tree, O *newAdapter) { expandEllipsis(tree); } *(S theme) { this(makeOccTree(theme)); } *(S theme, O newAdapter) { this(makeOccTree(theme), newAdapter); } Adapter makeAdapter() { ret newAdapter == null ? null : (Adapter) call(newAdapter); } void take(E in) { remember(); // try starting over from beginning of tree too if (repeat) setAdd(nodes, new InFlight(tree, makeAdapter())); new L l; // try grabber if (grabber != null && in.q()) try { E grabbed = cast callFunction(grabber, in.text()); if (grabbed != null) { if (debug) print("Grabber worked! " + in.text() + " => " + grabbed); // make a mini tree with Q + A so it is matched in // next call to getSingleOutput() OccTree2 node = new OccTree2(in); node.next.add(new OccTree2(grabbed)); l.add(new InFlight(node)); } else if (debug) print("Grabber didnt't return anything. " + in.text()); } catch e { if (debug) printStackTrace(e); } for (InFlight flight : nodes) for (OccTree2 n : flight.node.next) { if (genericAdapter != null) genericAdapter.modify(n); if (eq(n.e.type(), in.type()) && (flight.adapter != null ? flight.adapter.canMatch(n.e.text(), in.text()) : matchE(n.e, in))) { l.add(new InFlight(n, flight.adapter == null ? null : flight.adapter.plus(n.e.text(), in.text()))); } } nodes = l; } L possibleNextInput() { new HashSet l; if (repeat) l.add(tree.e); L ns = cloneList(nodes); if (repeat) setAdd(ns, new InFlight(tree, makeAdapter())); for (InFlight flight : ns) for (OccTree2 n : flight.node.next) l.add(new InFlight(n, flight.adapter).adapted()); ret asList(l); } void remember() { if (rewindStack != null) rewindStack.add(cloneList(nodes)); } E getSingleOutput() { remember(); new L l; for (InFlight flight : nodes) for (OccTree2 n : flight.node.next) if (n.e.a() || isCommand(n.e)) l.add(new InFlight(n, flight.adapter)); if (empty(l)) { rewind(); null; } nodes = l; ret first(nodes).adapted(); } void rewind() { nodes = popLast(rewindStack); } bool isCommand(E e) { ret e.state() && matchStart("bot", e.state); } void noRewind() { rewindStack = null; } bool matchE(E a, E b) { // standard NL matching ret eq(a.type(), b.type()) && match(a.text(), b.text()); } S nodesToString(L nodes) { new L l; l.add(n(l(nodes), "node")); for (InFlight flight : nodes) { l.add(str(flight.node.e)); if (flight.adapter != null) l.add(" " + flight.adapter); } ret rtrim(fromLines(l)); } S thoughts() { new L l; l.add("Expecting input:\n"); for (E e : possibleNextInput()) if (e != null) l.add(" " + e); l.add(""); l.add(nodesToString(nodes)); if (rewindStack != null) { l.add("\nHistory\n"); for (L nodes : reversedList(rewindStack)) l.add(indent(nodesToString(nodes))); } ret fromLines(l); } LooseBot debugOn() { debug = true; ret this; } } sclass WordAdapter extends Adapter { new Map wordMap; L tok(S s) { ret nlTok2(dropPunctuation2(s)); } bool canMatch(S in, S out) { L t1 = tok(in), t2 = tok(out); ret l(t1) == l(t2); } Adapter plus(S in, S out) { L t1 = tok(in), t2 = tok(out); if (l(t1) != l(t2)) ret this; WordAdapter a = cast nuObject(getClass()); a.wordMap = cloneMap(wordMap); for (int i = 1; i < l(t1); i += 2) { S w1 = t1.get(i), w2 = t2.get(i); if (!eqic(w1, w2)) // just overwrite - be flexible! a.wordMap.put(w1.toLowerCase(), w2.toLowerCase()); } ret a; } S get(S s) { L tok = nlTok2(s); for (int i = 1; i < l(tok); i += 2) { S w = lookupToken(tok.get(i)); if (nempty(w)) tok.set(i, w); } ret join(tok); } S lookupToken(S s) { ret wordMap.get(s.toLowerCase()); } double size() { ret l(wordMap); } public S toString() { ret structure(wordMap); } } sclass WordAdapter2 extends WordAdapter { new Map wordMap; L tok(S s) { ret nlTok2(s); } }