!759 static Map> thescripts = synchroTreeMap(); static class PerUser { S dialogID; ScriptState scriptState; } static class ScriptState { S scriptID; long position; *(S *scriptID) {} *() {} } static new Map perUserMap; static new ThreadLocal puCurrent; static new ThreadLocal scriptStateCurrent; static new L snippetIDs; static O mainBot; p { load("snippetIDs"); reload(); load("perUserMap"); makeBot("A Dialog Bot"); } // does not increase the index static FC parseFunctionCall() { ret parseFunctionCall(currentScriptLine()); } static S currentScriptLine() { int l = l(thescript()); int idx = (int) (scriptState().position % l); ret thescript().get(idx); } synchronized answer { // init per-user stuff S dialogID = cast call(mainBot, "getDialogID"); if "get dialog id" { ret structure(dialogID); } if "dialog engine: number of users" { ret str(l(perUserMap)); } if (dialogID == null) ret null; // need a dialog id now PerUser pu = perUserMap.get(dialogID); if (pu == null) { pu = new PerUser; pu.dialogID = dialogID; printFormat("Created new dialog: *", dialogID); perUserMap.put(dialogID, pu); } puCurrent.set(pu); // admin stuff if "print scripts" ret structure(thescripts); if "list scripts" ret structure(snippetIDs); if "add dialog script *" { setAdd(snippetIDs, formatSnippetID(m.unq(0))); save("snippetIDs"); reload(); ret "OK, scripts now: " + structure(snippetIDs); } if "remove dialog script *" { snippetIDs.remove(formatSnippetID(m.unq(0))); save("snippetIDs"); reload(); ret "OK, scripts now: " + structure(snippetIDs); } if "reload dialogs" { reload(); ret "OK, " + l(snippetIDs) + " dialog scripts reloaded."; } // try current script first pcall { if (u().scriptState != null && thescript() != null) { S answer = answerUsingScript(u().scriptState, s); if (!empty(answer)) ret answer; } } // try all scripts for (S scriptID : thescripts.keySet()) pcall { ScriptState scriptState = new ScriptState(scriptID); S answer = answerUsingScript(scriptState, s); if (!empty(answer)) { u().scriptState = scriptState; // Switch to new script save("perUserMap"); ret answer; } } } static synchronized S answerUsingScript(ScriptState scriptState, S s) { scriptStateCurrent.set(scriptState); int safety = 0; while (safety++ < 1000) try { FC fc = parseFunctionCall(); S f = fc.f; if (eq(f, "match")) { S pat = getString(fc.args, 0); if (!match(pat, s)) { softFail("I only understand: *", pat); null; } nextPosition(); // only after match succeeds, otherwise stay at that point } else if (eq(f, "say")) { nextPosition(); S answer = getString(fc.args, 0); ret answer; } else throw fail("Unknown function in script: *", fc.f); } catch (Throwable e) { printFormat("Was parsing: *", currentScriptLine()); throw asRuntimeException(e); } throw fail("hard limit reached - 1000 script lines at once - possible endless loop?"); } // a parsed function call static class FC { int idx; // index of next token S f; new L args; } static FC parseFunctionCall(S s) { L tok = javaTok(s); new FC fc; fc.f = assertIsIdentifier(tok.get(1)); int i = 5; assertEquals("(", tok.get(3)); while (i < l(tok) && !eq(tok.get(i), ")")) { fc.args.add(unquote(tok.get(i))); i += 2; if (tok.get(i).equals(",")) // otherwise it's kinda mangled, eh. well! we just keep on parsing... i += 2; } if (eq(tok.get(i), ")")) // we even allow a missing closing bracket! i += 2; fc.idx = i; // save index so some other parser can continue parsing ret fc; } static void nextPosition() { ++scriptState().position; save("perUserMap"); // might not always be necessary print("Position in script now: " + scriptState().position % l(thescript())); } // get per-user data for current thread static PerUser u() { ret puCurrent.get(); } static ScriptState scriptState() { ret scriptStateCurrent.get(); } static L thescript() { S scriptID = scriptState().scriptID; ret thescripts.get(scriptID); } static void loadMore() { for (S snippetID : snippetIDs) pcall { L lines = toLinesFullTrim(loadSnippet(snippetID)); new L currentScript; int i = 0; for (; i < l(lines); i += 2) { S q = lines.get(i); if (q.startsWith("-")) { if (nempty(currentScript)) thescripts.put(snippetID + "-" + i, currentScript); currentScript = litlist(); --i; } else { S a = lines.get(i+1); a = dropPrefix("eleu:", a).trim(); currentScript.addAll(litlist( "match(" + quote(q) + ");", "say(" + quote(a) + ");")); } } if (nempty(currentScript)) thescripts.put(snippetID + "-" + i, currentScript); } } static void reload() { thescripts.clear(); thescripts.put("1", toLinesFullTrim([[ match("Hi Eleutheria"); say("Hi there!"); match("Hi Eleutheria"); say("You have said that before :)"); ]])); thescripts.put("2", toLinesFullTrim([[ match("how old are you"); say("less than one year i believe"); match("really?"); say("yeah"); ]])); loadMore(); }