chalk.tests.js(檔案已創建)
| @@ -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(檔案已創建)
| @@ -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); }; | |
chalk.js(檔案已創建)
| @@ -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 | + | })(); | |
上一頁
下一頁