(() => globalThis.chalk = () => { // A naive tuplespace. Mostly for global event coordination let messages = []; let handlers = []; const rm = (from, idx) => { for (let i = idx.length - 1; i >= 0; i--) { from.splice(idx[i], 1) } } const mutate = () => { let matched = false; let toRm = []; let idx = 0; let msgToRm = new Set(); for (let h of handlers) { const isMatch = h.doQuery(msgToRm); matched ||= isMatch; if (isMatch && h.isOnce) { toRm.push(idx); } idx++; } let rmIdx = Array.from(msgToRm); rmIdx.sort((a,b) => a-b); rm(handlers, toRm); rm(messages, rmIdx); }; const query = (vars, predicates, callback, toRm, pIdx = 0) => { let matched = false; let mIdx = 0; for (const m of messages) { if (predicates[pIdx](vars, m)) { let keep = predicates[pIdx].keep; if (pIdx < predicates.length - 1) { let didMatch = query(vars, predicates, callback, toRm, pIdx+1); matched ||= didMatch; if (matched && !keep) { toRm.add(mIdx); } } else { callback(vars); if (!keep) { toRm.add(mIdx); } matched = true; } } mIdx++; } return matched; } const doWhen = (patterns, handler, isOnce=false) => { const predicates = []; const vars = new Set(); for (let patt of patterns) { let keep = false; if (patt.endsWith("?")) { patt = patt.slice(0,-1); keep = true; } const init = []; const conditions = []; const cleanup = []; let idx = 0; for (const p of patt.split(" ")) { const k = p.slice(1); if (p.startsWith("~$") && vars.has(p)) { if (vars.has(p)) { conditions.push(`t[${idx}] !== v["${k.slice(1)}"]`); } else { throw new Error("Cannot negate a var until after it has been captured"); } } else if (p.startsWith("~")) { conditions.push(`t[${idx}] !== "${k}"`); } else if (p.startsWith("$")) { if (vars.has(k)) { conditions.push(`t[${idx}] === v["${k}"]`); } else { init.push(`v["${k}"] = t[${idx}]`) cleanup.push(`delete v["${k}"]`) vars.add(k); } } else { conditions.push(`t[${idx}] === "${p}"`); } idx++; } let fn = new Function('v', 't', `${init.join(';')}; let ret = t.length === ${idx} && ${conditions.join(' && ')}; if (!ret) { ${cleanup.join('; ')}; } return ret;` ); fn.keep = keep; predicates.push(fn); } handlers.push({ doQuery(toRm) { return query({}, predicates, handler, toRm); }, isOnce }); mutate(); } return { _:{ messages, handlers }, msg(...data) { messages.push(data); mutate(); }, when(...args) { doWhen(args.slice(0,-1), args.at(-1)); }, once(patt, handler) { doWhen(patt, handler, true); } } })();