chalk.js
· 3.5 KiB · JavaScript
Originalformat
Playground
(() =>
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); }
}
})();
| 1 | (() => |
| 2 | globalThis.chalk = () => { |
| 3 | // A naive tuplespace. Mostly for global event coordination |
| 4 | let messages = []; |
| 5 | let handlers = []; |
| 6 | const rm = (from, idx) => { |
| 7 | for (let i = idx.length - 1; i >= 0; i--) { from.splice(idx[i], 1) } |
| 8 | } |
| 9 | const mutate = () => { |
| 10 | let matched = false; |
| 11 | let toRm = []; |
| 12 | let idx = 0; |
| 13 | let msgToRm = new Set(); |
| 14 | for (let h of handlers) { |
| 15 | const isMatch = h.doQuery(msgToRm); |
| 16 | matched ||= isMatch; |
| 17 | if (isMatch && h.isOnce) { |
| 18 | toRm.push(idx); |
| 19 | } |
| 20 | idx++; |
| 21 | } |
| 22 | let rmIdx = Array.from(msgToRm); |
| 23 | rmIdx.sort((a,b) => a-b); |
| 24 | rm(handlers, toRm); |
| 25 | rm(messages, rmIdx); |
| 26 | }; |
| 27 | |
| 28 | const query = (vars, predicates, callback, toRm, pIdx = 0) => { |
| 29 | let matched = false; |
| 30 | let mIdx = 0; |
| 31 | for (const m of messages) { |
| 32 | if (predicates[pIdx](vars, m)) { |
| 33 | let keep = predicates[pIdx].keep; |
| 34 | if (pIdx < predicates.length - 1) { |
| 35 | let didMatch = query(vars, predicates, callback, toRm, pIdx+1); |
| 36 | matched ||= didMatch; |
| 37 | if (matched && !keep) { toRm.add(mIdx); } |
| 38 | } else { |
| 39 | callback(vars); |
| 40 | if (!keep) { toRm.add(mIdx); } |
| 41 | matched = true; |
| 42 | } |
| 43 | } |
| 44 | mIdx++; |
| 45 | } |
| 46 | return matched; |
| 47 | } |
| 48 | |
| 49 | const doWhen = (patterns, handler, isOnce=false) => { |
| 50 | const predicates = []; |
| 51 | const vars = new Set(); |
| 52 | for (let patt of patterns) { |
| 53 | let keep = false; |
| 54 | if (patt.endsWith("?")) { patt = patt.slice(0,-1); keep = true; } |
| 55 | const init = []; |
| 56 | const conditions = []; |
| 57 | const cleanup = []; |
| 58 | let idx = 0; |
| 59 | for (const p of patt.split(" ")) { |
| 60 | const k = p.slice(1); |
| 61 | if (p.startsWith("~$") && vars.has(p)) { |
| 62 | if (vars.has(p)) { |
| 63 | conditions.push(`t[${idx}] !== v["${k.slice(1)}"]`); |
| 64 | } else { |
| 65 | throw new Error("Cannot negate a var until after it has been captured"); |
| 66 | } |
| 67 | } else if (p.startsWith("~")) { |
| 68 | conditions.push(`t[${idx}] !== "${k}"`); |
| 69 | } else if (p.startsWith("$")) { |
| 70 | if (vars.has(k)) { |
| 71 | conditions.push(`t[${idx}] === v["${k}"]`); |
| 72 | } else { |
| 73 | init.push(`v["${k}"] = t[${idx}]`) |
| 74 | cleanup.push(`delete v["${k}"]`) |
| 75 | vars.add(k); |
| 76 | } |
| 77 | } else { |
| 78 | conditions.push(`t[${idx}] === "${p}"`); |
| 79 | } |
| 80 | idx++; |
| 81 | } |
| 82 | let fn = new Function('v', 't', |
| 83 | `${init.join(';')}; let ret = t.length === ${idx} && ${conditions.join(' && ')}; if (!ret) { ${cleanup.join('; ')}; } return ret;` |
| 84 | ); |
| 85 | fn.keep = keep; |
| 86 | predicates.push(fn); |
| 87 | } |
| 88 | handlers.push({ doQuery(toRm) { |
| 89 | return query({}, predicates, handler, toRm); |
| 90 | }, isOnce }); |
| 91 | mutate(); |
| 92 | } |
| 93 | return { _:{ messages, handlers }, |
| 94 | msg(...data) { |
| 95 | messages.push(data); |
| 96 | mutate(); |
| 97 | }, when(...args) { doWhen(args.slice(0,-1), args.at(-1)); }, |
| 98 | once(patt, handler) { doWhen(patt, handler, true); } |
| 99 | } |
| 100 | })(); |
| 101 |
chalk.tests.js
· 2.1 KiB · JavaScript
Originalformat
Playground
import "./test.js";
import "./chalk.js";
test("chalk", {
basicTest() {
const board = globalThis.chalk();
let didFire = false;
board.when("test", (_) => didFire = true);
board.msg("test");
assert(didFire, "Did not fire");
},
basicTest2() {
const board = globalThis.chalk();
let didFire = false;
board.when("test $x", (_) => { didFire = (_.x === "yo"); })
board.msg("test", "yo");
assert(didFire, "Did not fire");
},
joinTest(){
const board = chalk();
let didFire = false;
board.when("test $a", "taste $a", (_) => { didFire = _.a == "yo"})
board.msg("test", "yo");
assert(!didFire, "Fired prematurely");
board.msg("taste", "ey");
assert(!didFire, "Fired prematurely");
board.msg("taste", "yo");
assert(didFire, "Did not fire when it should have");
},
keepTest() {
const board = chalk();
let didFire = false;
board.when("test $a?", "taste $a", (_) => { didFire = _.a == "yo"})
board.msg("test", "yo");
assert(!didFire, "Fired prematurely");
board.msg("taste", "ey");
assert(!didFire, "Fired prematurely");
board.msg("taste", "yo");
assert(didFire, "Did not fire when it should have");
assert(board._.messages.find((x) => x[0] === "test" && x[1] === "yo"), "Message was removed and shouldn't have been")
},
negationTest(){
const board = chalk();
let didFire = false;
board.when("test ~foo", (_) => { didFire = true})
board.msg("test", "foo");
assert(!didFire, "Fired prematurely");
board.msg("test", "yo");
assert(didFire, "Did not fire when it should have");
},
saturationTest() {
const board = chalk();
let $aFired = false;
let fooFired = false;
board.when("test $a", (_) => { $aFired = true; })
board.when("test foo", (_) => { fooFired = true; })
board.msg("test", "foo");
assert($aFired && fooFired, "Two handlers should have fired");
}
});
| 1 | import "./test.js"; |
| 2 | import "./chalk.js"; |
| 3 | |
| 4 | test("chalk", { |
| 5 | basicTest() { |
| 6 | const board = globalThis.chalk(); |
| 7 | |
| 8 | let didFire = false; |
| 9 | board.when("test", (_) => didFire = true); |
| 10 | board.msg("test"); |
| 11 | assert(didFire, "Did not fire"); |
| 12 | }, |
| 13 | basicTest2() { |
| 14 | const board = globalThis.chalk(); |
| 15 | |
| 16 | let didFire = false; |
| 17 | board.when("test $x", (_) => { didFire = (_.x === "yo"); }) |
| 18 | board.msg("test", "yo"); |
| 19 | assert(didFire, "Did not fire"); |
| 20 | }, |
| 21 | joinTest(){ |
| 22 | const board = chalk(); |
| 23 | |
| 24 | let didFire = false; |
| 25 | board.when("test $a", "taste $a", (_) => { didFire = _.a == "yo"}) |
| 26 | board.msg("test", "yo"); |
| 27 | assert(!didFire, "Fired prematurely"); |
| 28 | board.msg("taste", "ey"); |
| 29 | assert(!didFire, "Fired prematurely"); |
| 30 | board.msg("taste", "yo"); |
| 31 | assert(didFire, "Did not fire when it should have"); |
| 32 | }, |
| 33 | keepTest() { |
| 34 | const board = chalk(); |
| 35 | |
| 36 | let didFire = false; |
| 37 | board.when("test $a?", "taste $a", (_) => { didFire = _.a == "yo"}) |
| 38 | board.msg("test", "yo"); |
| 39 | assert(!didFire, "Fired prematurely"); |
| 40 | board.msg("taste", "ey"); |
| 41 | assert(!didFire, "Fired prematurely"); |
| 42 | board.msg("taste", "yo"); |
| 43 | assert(didFire, "Did not fire when it should have"); |
| 44 | assert(board._.messages.find((x) => x[0] === "test" && x[1] === "yo"), "Message was removed and shouldn't have been") |
| 45 | }, |
| 46 | negationTest(){ |
| 47 | const board = chalk(); |
| 48 | |
| 49 | let didFire = false; |
| 50 | board.when("test ~foo", (_) => { didFire = true}) |
| 51 | board.msg("test", "foo"); |
| 52 | assert(!didFire, "Fired prematurely"); |
| 53 | board.msg("test", "yo"); |
| 54 | assert(didFire, "Did not fire when it should have"); |
| 55 | }, |
| 56 | saturationTest() { |
| 57 | const board = chalk(); |
| 58 | |
| 59 | let $aFired = false; |
| 60 | let fooFired = false; |
| 61 | board.when("test $a", (_) => { $aFired = true; }) |
| 62 | board.when("test foo", (_) => { fooFired = true; }) |
| 63 | board.msg("test", "foo"); |
| 64 | assert($aFired && fooFired, "Two handlers should have fired"); |
| 65 | } |
| 66 | }); |
| 67 | |
| 68 |
test.js
· 431 B · JavaScript
Originalformat
Playground
globalThis.test = (suite, tests) => {
process.stdout.write(suite + ": ");
for (const [name, t] of Object.entries(tests)) {
try {
t();
process.stdout.write(".");
} catch(ex) {
console.error("In " + suite +", test " + name + " failed:", ex)
}
}
console.log();
}
// For global scope
globalThis.assert = (cond, msg) => { if (!cond) throw new Error(msg); };
| 1 | globalThis.test = (suite, tests) => { |
| 2 | process.stdout.write(suite + ": "); |
| 3 | for (const [name, t] of Object.entries(tests)) { |
| 4 | try { |
| 5 | t(); |
| 6 | process.stdout.write("."); |
| 7 | } catch(ex) { |
| 8 | console.error("In " + suite +", test " + name + " failed:", ex) |
| 9 | } |
| 10 | } |
| 11 | console.log(); |
| 12 | } |
| 13 | |
| 14 | // For global scope |
| 15 | globalThis.assert = (cond, msg) => { if (!cond) throw new Error(msg); }; |
| 16 |