Zuletzt aktiv 12 hours ago

yumaikas's Avatar yumaikas hat die Gist bearbeitet 12 hours ago. Zu Änderung gehen

2 files changed, 82 insertions

chalk.tests.js(Datei erstellt)

@@ -0,0 +1,67 @@
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 +

test.js(Datei erstellt)

@@ -0,0 +1,15 @@
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); };

yumaikas's Avatar yumaikas hat die Gist bearbeitet 12 hours ago. Zu Änderung gehen

1 file changed, 100 insertions

chalk.js(Datei erstellt)

@@ -0,0 +1,100 @@
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 + })();
Neuer Älter