├── scripts ├── imported │ ├── config.js │ ├── package.json │ └── index.js ├── example.js ├── log-version.js ├── nop │ ├── package.json │ └── index.js ├── correct-import-extensions.js └── post-build.js ├── .npmignore ├── .gitignore ├── .prettierignore ├── src ├── index.ts ├── circuit │ ├── adder │ │ ├── index.ts │ │ ├── half.tsx │ │ ├── full.tsx │ │ └── eight-adder.tsx │ ├── math │ │ ├── index.ts │ │ ├── add.ts │ │ ├── minus.ts │ │ ├── divide.ts │ │ └── multiply.ts │ ├── pair.ts │ ├── index.ts │ ├── eight.ts │ ├── triple.ts │ ├── count.ts │ └── promise-rest.tsx ├── tests │ ├── circuit │ │ ├── index.tsx │ │ ├── adder.tsx │ │ └── adder-chain.tsx │ ├── combinational │ │ ├── index.tsx │ │ ├── boolean.tsx │ │ └── combination.tsx │ ├── index.tsx │ ├── dom-document.ts │ └── dom-h.ts ├── trying-to-write-documentation │ ├── index.ts │ ├── readme-generator.ts │ └── combinational-how.tsx ├── types │ ├── jsx.d.ts │ ├── dom-lite.d.ts │ └── deno-dom.d.ts ├── combinational │ ├── boolean.tsx │ ├── index.ts │ ├── nor.tsx │ ├── or.tsx │ ├── nand.tsx │ ├── xnor.tsx │ ├── not.tsx │ ├── and.tsx │ ├── xor.tsx │ └── combination.ts ├── is.ts └── like.ts ├── .nycrc ├── CONTRIBUTING.md ├── tsconfig.json ├── .github └── workflows │ ├── test-actions.yml │ ├── release-actions.yml │ └── codeql-analysis.yml ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── LICENSE.md ├── import-map-deno.json ├── package.json ├── CODE-OF-CONDUCT.md └── README.md /scripts/imported/config.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | node_modules 4 | coverage -------------------------------------------------------------------------------- /scripts/example.js: -------------------------------------------------------------------------------- 1 | import "../esnext/example/index.js"; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | node_modules 4 | esnext 5 | coverage -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | node_modules 4 | esnext 5 | coverage 6 | *.md -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./like"; 2 | export * from "./combinational"; 3 | export * from "./circuit"; -------------------------------------------------------------------------------- /src/circuit/adder/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./half"; 2 | export * from "./full"; 3 | export * from "./eight-adder"; -------------------------------------------------------------------------------- /src/tests/circuit/index.tsx: -------------------------------------------------------------------------------- 1 | await import("./adder"); 2 | await import("./adder-chain"); 3 | 4 | export default 1; -------------------------------------------------------------------------------- /src/tests/combinational/index.tsx: -------------------------------------------------------------------------------- 1 | export default 1; 2 | 3 | await import("./boolean"); 4 | await import("./combination"); -------------------------------------------------------------------------------- /src/circuit/math/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./minus"; 2 | export * from "./add"; 3 | export * from "./multiply"; 4 | export * from "./divide"; -------------------------------------------------------------------------------- /src/trying-to-write-documentation/index.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | await import("./combinational-how"); 4 | 5 | export default 1; 6 | -------------------------------------------------------------------------------- /src/circuit/pair.ts: -------------------------------------------------------------------------------- 1 | import {count} from "./count"; 2 | 3 | export async function *pair(input: unknown) { 4 | yield * count(input, 2, false); 5 | } -------------------------------------------------------------------------------- /src/types/jsx.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | interface IntrinsicElements 3 | extends Record> {} 4 | } 5 | -------------------------------------------------------------------------------- /src/circuit/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./adder"; 2 | export * from "./pair"; 3 | export * from "./promise-rest"; 4 | export * from "./eight"; 5 | export * from "./math"; -------------------------------------------------------------------------------- /scripts/log-version.js: -------------------------------------------------------------------------------- 1 | import("fs") 2 | .then(({ promises }) => promises.readFile("package.json")) 3 | .then(JSON.parse) 4 | .then(({ version }) => console.log(version)); 5 | -------------------------------------------------------------------------------- /src/tests/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | await import("../trying-to-write-documentation"); 4 | 5 | await import("./circuit"); 6 | await import("./combinational"); 7 | 8 | export default 1; 9 | -------------------------------------------------------------------------------- /scripts/nop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "main": "./index.js", 4 | "type": "module", 5 | "exports": { 6 | ".": "./index.js", 7 | "./": "./index.js" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /scripts/imported/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "main": "./index.js", 4 | "type": "module", 5 | "exports": { 6 | ".": "./index.js", 7 | "./": "./index.js" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/combinational/boolean.tsx: -------------------------------------------------------------------------------- 1 | import {h} from "@virtualstate/focus"; 2 | import {And} from "./and"; 3 | 4 | export function Boolean(options: unknown, input?: unknown) { 5 | return {input} 6 | } 7 | -------------------------------------------------------------------------------- /src/circuit/eight.ts: -------------------------------------------------------------------------------- 1 | import {count} from "./count"; 2 | 3 | export async function *Eight(options: unknown, input?: unknown) { 4 | for await (const snapshot of count(input, 8, true)) { 5 | yield snapshot; 6 | } 7 | } -------------------------------------------------------------------------------- /src/circuit/triple.ts: -------------------------------------------------------------------------------- 1 | import {children} from "@virtualstate/focus"; 2 | import {ok} from "../like"; 3 | import {count} from "./count"; 4 | 5 | export async function *triple(input: unknown) { 6 | yield * count(input, 3, false); 7 | } -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | 4 | ], 5 | "reporter": [ 6 | "clover", 7 | "json-summary", 8 | "html", 9 | "text-summary" 10 | ], 11 | "branches": 80, 12 | "lines": 80, 13 | "functions": 80, 14 | "statements": 80 15 | } 16 | -------------------------------------------------------------------------------- /src/combinational/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./boolean"; 2 | export * from "./and"; 3 | export * from "./nand"; 4 | export * from "./nor"; 5 | export * from "./not"; 6 | export * from "./or"; 7 | export * from "./xnor"; 8 | export * from "./xor"; 9 | export * from "./combination"; -------------------------------------------------------------------------------- /src/combinational/nor.tsx: -------------------------------------------------------------------------------- 1 | import {h} from "@virtualstate/focus"; 2 | import {Not} from "./not"; 3 | import {Or} from "./or"; 4 | 5 | export function Nor(options: unknown, input?: unknown) { 6 | return ( 7 | 8 | {input} 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/combinational/or.tsx: -------------------------------------------------------------------------------- 1 | import {children, h} from "@virtualstate/focus"; 2 | import {isTruthy} from "../like"; 3 | 4 | export async function *Or(options: unknown, input?: unknown) { 5 | for await (const snapshot of children(input)) { 6 | yield snapshot.find(isTruthy) ?? false; 7 | } 8 | } -------------------------------------------------------------------------------- /src/combinational/nand.tsx: -------------------------------------------------------------------------------- 1 | import {h} from "@virtualstate/focus"; 2 | import { Boolean } from "./boolean"; 3 | import {Not} from "./not"; 4 | 5 | export function Nand(options: unknown, input?: unknown) { 6 | return ( 7 | 8 | {input} 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/circuit/math/add.ts: -------------------------------------------------------------------------------- 1 | import {isNumber} from "../../is"; 2 | import {children} from "@virtualstate/focus"; 3 | 4 | export async function *Add(options: unknown, input?: unknown) { 5 | for await (const [base, ...rest] of children(input).filter(isNumber)) { 6 | yield rest.reduce((base, value) => base + value, base); 7 | } 8 | } -------------------------------------------------------------------------------- /src/circuit/math/minus.ts: -------------------------------------------------------------------------------- 1 | import {isNumber} from "../../is"; 2 | import {children} from "@virtualstate/focus"; 3 | 4 | export async function *Minus(options: unknown, input?: unknown) { 5 | for await (const [base, ...rest] of children(input).filter(isNumber)) { 6 | yield rest.reduce((base, value) => base - value, base); 7 | } 8 | } -------------------------------------------------------------------------------- /scripts/nop/index.js: -------------------------------------------------------------------------------- 1 | export const listenAndServe = undefined; 2 | export const createServer = undefined; 3 | 4 | /** 5 | * @type {any} 6 | */ 7 | const on = () => {}; 8 | /** 9 | * @type {any} 10 | */ 11 | const env = {}; 12 | 13 | /*** 14 | * @type {any} 15 | */ 16 | export default { 17 | env, 18 | on, 19 | exit(arg) {}, 20 | }; 21 | -------------------------------------------------------------------------------- /src/combinational/xnor.tsx: -------------------------------------------------------------------------------- 1 | import {children, h} from "@virtualstate/focus"; 2 | import {isTruthy} from "../like"; 3 | 4 | export async function *Xnor(options: unknown, input?: unknown) { 5 | for await (const snapshot of children(input)) { 6 | const truthy = snapshot.map(isTruthy); 7 | yield truthy.every(value => truthy[0] === value); 8 | } 9 | } -------------------------------------------------------------------------------- /src/combinational/not.tsx: -------------------------------------------------------------------------------- 1 | import {children, h} from "@virtualstate/focus"; 2 | import { Boolean } from "./boolean"; 3 | import {isBooleanFalseArray} from "../like"; 4 | 5 | export async function *Not(options: unknown, input?: unknown) { 6 | for await (const snapshot of children({input})) { 7 | yield isBooleanFalseArray(snapshot); 8 | } 9 | } -------------------------------------------------------------------------------- /src/is.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | export function isArray(value: unknown): value is T[]; 3 | export function isArray(value: unknown): value is unknown[]; 4 | export function isArray(value: unknown): boolean { 5 | return Array.isArray(value); 6 | } 7 | 8 | export function isNumber(value: unknown): value is number { 9 | return typeof value === "number"; 10 | } -------------------------------------------------------------------------------- /src/circuit/math/divide.ts: -------------------------------------------------------------------------------- 1 | import {isNumber} from "../../is"; 2 | import {children} from "@virtualstate/focus"; 3 | 4 | export async function *Divide(options: unknown, input?: unknown) { 5 | for await (const [base, ...rest] of children(input).filter(isNumber)) { 6 | yield (rest.length ? rest : [0]) .reduce((base, value) => base / value, base); 7 | } 8 | } -------------------------------------------------------------------------------- /src/circuit/math/multiply.ts: -------------------------------------------------------------------------------- 1 | import {isNumber} from "../../is"; 2 | import {children} from "@virtualstate/focus"; 3 | 4 | export async function *Multiply(options: unknown, input?: unknown) { 5 | for await (const [base, ...rest] of children(input).filter(isNumber)) { 6 | yield (rest.length ? rest : [0]) .reduce((base, value) => base * value, base); 7 | } 8 | } -------------------------------------------------------------------------------- /src/combinational/and.tsx: -------------------------------------------------------------------------------- 1 | import {children, h} from "@virtualstate/focus"; 2 | import {isTruthy} from "../like"; 3 | 4 | export async function *And(options: unknown, input?: unknown) { 5 | for await (const snapshot of children(input)) { 6 | if (snapshot.length) { 7 | yield snapshot.every(isTruthy); 8 | } else { 9 | yield false; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/types/dom-lite.d.ts: -------------------------------------------------------------------------------- 1 | declare module "dom-lite" { 2 | namespace Default { 3 | export const document: Document; 4 | 5 | export const HTMLElement: { 6 | prototype: HTMLElement; 7 | new (): HTMLElement; 8 | }; 9 | export const Node: { 10 | prototype: Node; 11 | new (): Node; 12 | }; 13 | } 14 | 15 | export type DOMNamespace = typeof Default; 16 | 17 | export default Default; 18 | } 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make by way of an issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | We are open to all ideas big or small, and are greatly appreciative of any and all contributions. 7 | 8 | Please note we have a code of conduct, please follow it in all your interactions with the project. 9 | -------------------------------------------------------------------------------- /src/combinational/xor.tsx: -------------------------------------------------------------------------------- 1 | import {children, h} from "@virtualstate/focus"; 2 | import { isTruthy} from "../like"; 3 | 4 | export async function *Xor(options: unknown, input?: unknown) { 5 | for await (const snapshot of children(input)) { 6 | const every = snapshot.every(isTruthy); 7 | if (every) { 8 | yield false; 9 | } else { 10 | yield snapshot.find(isTruthy) ?? false; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/types/deno-dom.d.ts: -------------------------------------------------------------------------------- 1 | declare module "https://deno.land/x/deno_dom/deno-dom-wasm.ts" { 2 | export class DOMParser { 3 | parseFromString(string: string, type: string): Document; 4 | } 5 | 6 | class ElementImpl extends Element {} 7 | class NodeImpl extends Node {} 8 | class HTMLElementImpl extends HTMLElement {} 9 | class DocumentImpl extends Document {} 10 | 11 | export { 12 | ElementImpl as Element, 13 | NodeImpl as Node, 14 | HTMLElementImpl as HTMLElement, 15 | DocumentImpl as Document, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/circuit/adder/half.tsx: -------------------------------------------------------------------------------- 1 | import {pair} from "../pair"; 2 | import {createFragment, h} from "@virtualstate/focus"; 3 | import {And, Xor} from "../../combinational"; 4 | 5 | export async function *HalfAdder(options: unknown, input?: unknown) { 6 | for await (const snapshot of pair(input)) { 7 | yield ( 8 | <> 9 | 10 | {...snapshot} 11 | 12 | 13 | {...snapshot} 14 | 15 | 16 | ) 17 | } 18 | } -------------------------------------------------------------------------------- /scripts/imported/index.js: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | const initialImportPath = 4 | getConfig()["@virtualstate/app-history/test/imported/path"] ?? 5 | "@virtualstate/app-history"; 6 | 7 | if (typeof initialImportPath !== "string") 8 | throw new Error("Expected string import path"); 9 | 10 | export const { AppHistory } = await import(initialImportPath); 11 | 12 | export function getConfig() { 13 | return { 14 | ...getNodeConfig(), 15 | }; 16 | } 17 | 18 | function getNodeConfig() { 19 | if (typeof process === "undefined") return {}; 20 | return JSON.parse(process.env.TEST_CONFIG ?? "{}"); 21 | } 22 | /* c8 ignore end */ 23 | -------------------------------------------------------------------------------- /src/circuit/count.ts: -------------------------------------------------------------------------------- 1 | import {children} from "@virtualstate/focus"; 2 | import {ok} from "../like"; 3 | 4 | export async function *count(input: unknown, count: number, allowMore?: boolean) { 5 | for await (const snapshot of children(input)) { 6 | if (snapshot.length < count) { 7 | // Wait for count be available 8 | continue; 9 | } 10 | if (allowMore && snapshot.length > count) { 11 | yield snapshot.slice(0, count); 12 | } else { 13 | ok(snapshot.length === count, `Expected ${count}, got ${snapshot.length}`); 14 | yield snapshot; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/circuit/promise-rest.tsx: -------------------------------------------------------------------------------- 1 | import {children, h} from "@virtualstate/focus"; 2 | import {ok} from "../like"; 3 | 4 | export interface Rest extends AsyncIterable{ 5 | promise: Promise 6 | } 7 | 8 | export function rest(input: unknown): Rest { 9 | let resolve: (value: unknown[]) => void; 10 | const promise = new Promise((fn) => { 11 | resolve = fn; 12 | }) 13 | ok(resolve); 14 | return { 15 | async *[Symbol.asyncIterator]() { 16 | const [value, ...rest] = await children(input); 17 | ok(rest.length); 18 | resolve(rest); 19 | yield value; 20 | }, 21 | promise 22 | }; 23 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "lib": ["es2018", "esnext", "dom"], 5 | "types": ["jest", "node"], 6 | "esModuleInterop": true, 7 | "target": "esnext", 8 | "noImplicitAny": false, 9 | "downlevelIteration": true, 10 | "moduleResolution": "node", 11 | "declaration": true, 12 | "sourceMap": true, 13 | "outDir": "esnext", 14 | "baseUrl": "./src", 15 | "jsx": "react", 16 | "jsxFactory": "h", 17 | "jsxFragmentFactory": "createFragment", 18 | "allowJs": true, 19 | "paths": { 20 | "@virtualstate/combinational": ["./"] 21 | } 22 | }, 23 | "include": ["src/**/*"], 24 | "typeRoots": ["./node_modules/@types", "src/types"], 25 | "exclude": ["node_modules/@opennetwork/vdom"] 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/test-actions.yml: -------------------------------------------------------------------------------- 1 | name: test-actions 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | env: 8 | NO_COVERAGE_BADGE_UPDATE: 1 9 | FLAGS: FETCH_SERVICE_DISABLE,POST_CONFIGURE_TEST,PLAYWRIGHT,CONTINUE_ON_ERROR 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: "16.x" 17 | registry-url: "https://registry.npmjs.org" 18 | - uses: denoland/setup-deno@v1 19 | with: 20 | deno-version: "v1.x" 21 | - run: | 22 | yarn install 23 | npx playwright install-deps 24 | - run: yarn build 25 | # yarn coverage === c8 + yarn test 26 | - run: yarn coverage 27 | - run: yarn test:deno 28 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.192.0/containers/typescript-node/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Node.js version: 16, 14, 12 4 | ARG VARIANT="16-buster" 5 | FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT} 6 | 7 | # [Optional] Uncomment this section to install additional OS packages. 8 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 9 | # && apt-get -y install --no-install-recommends 10 | 11 | # [Optional] Uncomment if you want to install an additional version of node using nvm 12 | # ARG EXTRA_NODE_VERSION=10 13 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 14 | 15 | # [Optional] Uncomment if you want to install more global node packages 16 | # RUN su node -c "npm install -g " 17 | -------------------------------------------------------------------------------- /src/circuit/adder/full.tsx: -------------------------------------------------------------------------------- 1 | import {createFragment, h} from "@virtualstate/focus"; 2 | import {And, Xor, Or} from "../../combinational"; 3 | import {triple} from "../triple"; 4 | 5 | export async function *FullAdder(options: unknown, input?: unknown) { 6 | for await (const [a, b, carry] of triple(input)) { 7 | const inputXor = ( 8 | 9 | {a} 10 | {b} 11 | 12 | ) 13 | yield ( 14 | <> 15 | 16 | {inputXor} 17 | {carry} 18 | 19 | 20 | 21 | {inputXor} 22 | {carry} 23 | 24 | 25 | {a} 26 | {b} 27 | 28 | 29 | 30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.192.0/containers/typescript-node 3 | { 4 | "name": "Node.js & TypeScript", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick a Node version: 12, 14, 16 8 | "args": { 9 | "VARIANT": "16" 10 | } 11 | }, 12 | 13 | // Set *default* container specific settings.json values on container create. 14 | "settings": {}, 15 | 16 | // Add the IDs of extensions you want installed when the container is created. 17 | "extensions": ["dbaeumer.vscode-eslint"], 18 | 19 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 20 | // "forwardPorts": [], 21 | 22 | // Use 'postCreateCommand' to run commands after the container is created. 23 | // "postCreateCommand": "yarn install", 24 | 25 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 26 | "remoteUser": "node" 27 | } 28 | -------------------------------------------------------------------------------- /src/tests/dom-document.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | import type { DOMNamespace } from "dom-lite"; 4 | import { ok } from "@virtualstate/focus"; 5 | 6 | let exportDefault: DOMNamespace; 7 | 8 | try { 9 | // @ts-ignore 10 | const { DOMParser, Node, HTMLElement, Element } = await import( 11 | "https://deno.land/x/deno_dom/deno-dom-wasm.ts" 12 | ); 13 | 14 | const document = new DOMParser().parseFromString("", "text/html"); 15 | 16 | const nextDefault = { 17 | document, 18 | Node, 19 | HTMLElement: HTMLElement ?? Element, 20 | Element, 21 | }; 22 | 23 | ok(nextDefault); 24 | 25 | exportDefault = nextDefault; 26 | } catch { 27 | if (typeof window !== "undefined" && window.document) { 28 | exportDefault = { 29 | document: window.document, 30 | HTMLElement: window.HTMLElement, 31 | Node: window.Node, 32 | }; 33 | } else { 34 | try { 35 | const dom = await import("dom-lite"); 36 | exportDefault = dom.default; 37 | } catch {} 38 | } 39 | } 40 | 41 | ok(exportDefault); 42 | 43 | export default exportDefault; 44 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Axiom Applied Technologies and Development Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/like.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | import {isStaticChildNode, isUnknownJSXNode, name} from "@virtualstate/focus"; 3 | import {isArray} from "./is"; 4 | 5 | export function isLike(value: unknown, ...and: unknown[]): value is T { 6 | if (!and.length) return !!value; 7 | return !!value && and.every((value) => !!value); 8 | } 9 | 10 | export function ok(value: unknown, message?: string): asserts value; 11 | export function ok(value: unknown, message?: string): asserts value is T; 12 | export function ok(value: unknown, message?: string): asserts value { 13 | if (!value) { 14 | throw new Error(message ?? "Expected value"); 15 | } 16 | } 17 | 18 | export function isBooleanTrueArray(array: unknown): array is true[] { 19 | return isArray(array) && array.every(value => value === true); 20 | } 21 | 22 | export function isBooleanFalseArray(array: unknown): array is true[] { 23 | return isArray(array) && array.every(value => value === false); 24 | } 25 | 26 | export function isBooleanArray(array: unknown): array is boolean[] { 27 | return isArray(array) && array.every(value => typeof value === "boolean") 28 | } 29 | 30 | export function isTruthy(input: unknown) { 31 | if (isStaticChildNode(input)) { 32 | return !!input; 33 | } 34 | if (!input) return false; 35 | // If it's not a node we don't know what it is 36 | if (!isUnknownJSXNode(input)) { 37 | return false; 38 | } 39 | // console.log(input) 40 | // Expect a name to exist if we have a value and 41 | ok(name(input), "Expected name for truthy value"); 42 | return true; 43 | } 44 | -------------------------------------------------------------------------------- /src/circuit/adder/eight-adder.tsx: -------------------------------------------------------------------------------- 1 | import {rest} from "../promise-rest"; 2 | import {FullAdder} from "./full"; 3 | import {h, createFragment} from "@virtualstate/focus"; 4 | import {count} from "../count"; 5 | 6 | export async function *EightAdder(options: unknown, input?: unknown) { 7 | for await (const snapshot of count(input, 16)) { 8 | const left = snapshot.slice(0, 8); 9 | const right = snapshot.slice(8); 10 | 11 | const a1 = rest( 12 | 13 | {left[0]} 14 | {right[0]} 15 | {false} 16 | 17 | ) 18 | const a2 = rest( 19 | 20 | {left[1]} 21 | {right[1]} 22 | {a1.promise} 23 | 24 | ) 25 | const a3 = rest( 26 | 27 | {left[2]} 28 | {right[2]} 29 | {a2.promise} 30 | 31 | ) 32 | const a4 = rest( 33 | 34 | {left[3]} 35 | {right[3]} 36 | {a3.promise} 37 | 38 | ) 39 | const a5 = rest( 40 | 41 | {left[4]} 42 | {right[4]} 43 | {a4.promise} 44 | 45 | ) 46 | const a6 = rest( 47 | 48 | {left[5]} 49 | {right[5]} 50 | {a5.promise} 51 | 52 | ) 53 | const a7 = rest( 54 | 55 | {left[6]} 56 | {right[6]} 57 | {a6.promise} 58 | 59 | ) 60 | const a8 = ( 61 | 62 | {left[7]} 63 | {right[7]} 64 | {a7.promise} 65 | 66 | ) 67 | yield [ 68 | a1, 69 | a2, 70 | a3, 71 | a4, 72 | a5, 73 | a6, 74 | a7, 75 | a8, 76 | ] 77 | } 78 | } -------------------------------------------------------------------------------- /src/combinational/combination.ts: -------------------------------------------------------------------------------- 1 | import {children, getChildrenFromRawNode, h} from "@virtualstate/focus"; 2 | import {ok} from "../like"; 3 | import {And} from "./and"; 4 | import {Or} from "./or"; 5 | import {Not} from "./not"; 6 | import {Xor} from "./xor"; 7 | import {Xnor} from "./xnor"; 8 | import {Nand} from "./nand"; 9 | import {Add, Divide, EightAdder, FullAdder, HalfAdder, Minus, Multiply} from "../circuit"; 10 | 11 | const operations: Record = { 12 | "&": And, 13 | "&&": And, 14 | "|": Or, 15 | "||": Or, 16 | "~": Not, 17 | "!": Not, 18 | "^": Xor, 19 | "~^": Xnor, 20 | "^~": Xnor, 21 | "~&": Nand, 22 | "/": Divide, 23 | "*": Multiply, 24 | "+": Add, 25 | "-": Minus, 26 | } 27 | 28 | export async function Combination(options?: Record, input?: unknown) { 29 | const raw = getChildrenFromRawNode(input); 30 | 31 | ok(Array.isArray(raw)); 32 | 33 | const flat = raw 34 | .flat() 35 | .flatMap(value => { 36 | if (typeof value !== "string") return value; 37 | return value 38 | .split(/\s+/g) 39 | .filter(value => value) 40 | }) 41 | 42 | const operators = flat 43 | .filter((value): value is (string | symbol) => ( 44 | typeof value === "string" || 45 | typeof value === "symbol" 46 | )) 47 | 48 | // console.log(operators); 49 | 50 | let remaining = [...flat]; 51 | 52 | for (const [operatorIndex, operatorKey] of operators.entries()) { 53 | const operator = typeof operatorKey === "string" && !operators[operatorKey] ? operatorKey.trim() : operatorKey; 54 | const node = options?.[operator] ?? operations[operator]; 55 | ok(node, `Unknown operation ${String(operator)}`); 56 | const index = remaining.indexOf(operatorKey); 57 | const parts = remaining.slice(0, operatorIndex === operators.length - 1 ? remaining.length : index + 2) 58 | .filter(value => value !== operatorKey); 59 | const result = h(node, options, ...parts) 60 | remaining.splice(0, index + 2, result) 61 | // console.log({ remaining, operatorKey, parts, index }); 62 | } 63 | 64 | return remaining; 65 | 66 | } -------------------------------------------------------------------------------- /.github/workflows/release-actions.yml: -------------------------------------------------------------------------------- 1 | name: release-actions 2 | on: 3 | push: 4 | branches: 5 | - main 6 | release: 7 | types: 8 | - created 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | env: 13 | NO_COVERAGE_BADGE_UPDATE: 1 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | - uses: actions/setup-node@v2 19 | with: 20 | node-version: "16.x" 21 | registry-url: "https://registry.npmjs.org" 22 | - uses: denoland/setup-deno@v1 23 | with: 24 | deno-version: "v1.x" 25 | - run: | 26 | yarn install 27 | - run: yarn build 28 | # yarn coverage === c8 + yarn test 29 | - run: yarn coverage 30 | - run: yarn test:deno 31 | - name: Package Registry Publish - npm 32 | run: | 33 | git config user.name "${{ github.actor }}" 34 | git config user.email "${{ github.actor}}@users.noreply.github.com" 35 | npm set "registry=https://registry.npmjs.org/" 36 | npm set "@virtualstate:registry=https://registry.npmjs.org/" 37 | npm set "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" 38 | npm publish --access=public 39 | continue-on-error: true 40 | env: 41 | YARN_TOKEN: ${{ secrets.YARN_TOKEN }} 42 | NPM_TOKEN: ${{ secrets.YARN_TOKEN }} 43 | NODE_AUTH_TOKEN: ${{ secrets.YARN_TOKEN }} 44 | - uses: actions/setup-node@v2 45 | with: 46 | node-version: "16.x" 47 | registry-url: "https://npm.pkg.github.com" 48 | - name: Package Registry Publish - GitHub 49 | run: | 50 | git config user.name "${{ github.actor }}" 51 | git config user.email "${{ github.actor}}@users.noreply.github.com" 52 | npm set "registry=https://npm.pkg.github.com/" 53 | npm set "@virtualstate:registry=https://npm.pkg.github.com/virtualstate" 54 | npm set "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" 55 | npm publish --access=public 56 | env: 57 | YARN_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | continue-on-error: true 61 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [main] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [main] 20 | schedule: 21 | - cron: "15 10 * * 5" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["javascript"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /src/trying-to-write-documentation/readme-generator.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | import { promises as fs } from "fs"; 4 | import { extname } from "path"; 5 | 6 | const readme = await fs.readFile("README.md", "utf-8"); 7 | let lines = readme.split("\n"); 8 | 9 | const badgeMatch = /^\[\/\/]:\s+#\s+\(([^)]+)\)\s*$/; 10 | 11 | const badges = lines.filter((line) => badgeMatch.test(line)); 12 | 13 | const replacedNames = new Set(); 14 | 15 | for (const badge of badges) { 16 | const [, name] = badge.match(badgeMatch); 17 | if (replacedNames.has(name)) continue; 18 | if (!/\.[tj]sx?$/.test(name)) continue; 19 | replacedNames.add(name); 20 | 21 | const contents = await fs.readFile(name, "utf-8").catch(() => ""); 22 | if (!contents) continue; 23 | 24 | const extension = extname(name).replace(/^\./, ""); 25 | const codeType = 26 | { 27 | tsx: "typescript jsx", 28 | ts: "typescript", 29 | jsx: "javascript jsx", 30 | js: "javascript", 31 | }[extension] || "typescript"; 32 | 33 | const markdown = splitAndJoin(contents, codeType); 34 | 35 | const startIndex = lines.indexOf(badge) + 1; 36 | const remaining = lines.slice(startIndex); 37 | const endIndex = remaining.indexOf(badge); 38 | lines = [ 39 | ...lines.slice(0, startIndex), 40 | `\n${markdown}\n`, 41 | ...remaining.slice(endIndex), 42 | ]; 43 | } 44 | 45 | await fs.writeFile("README.md", lines.join("\n"), "utf-8"); 46 | 47 | function splitAndJoin(code: string, codeType: string) { 48 | const lines = code.split("\n"); 49 | const indexed = Object.entries(lines); 50 | const commentStart = indexed 51 | .filter(([, line]) => /^\s*\/\*/.test(line) && !/^\s*\/\*{2}/.test(line)) 52 | .map(([index]) => +index); 53 | 54 | const blocks = []; 55 | 56 | // Start at the first comment 57 | for (let index = commentStart[0]; index < lines.length; index += 1) { 58 | if (commentStart[0] === index) { 59 | commentStart.shift(); 60 | const endIndex = lines.findIndex( 61 | (line, lineIndex) => lineIndex > index && /\*\/\s*$/.test(line) 62 | ); 63 | if (endIndex === -1) { 64 | throw new Error("Expected to find end of comment"); 65 | } 66 | const comment = lines.slice(index + 1, endIndex).join("\n"); 67 | blocks.push(comment.trim()); 68 | index = endIndex; 69 | } else { 70 | const block = lines 71 | .slice(index, commentStart[0] ?? lines.length + 1) 72 | .join("\n") 73 | .replace(/^\s*export default 1;?\s*$/m, "") 74 | .trim(); 75 | 76 | if (!block) continue; 77 | blocks.push(`\`\`\`${codeType}\n${block}\n\`\`\``); 78 | index = commentStart[0] - 1; 79 | } 80 | } 81 | 82 | return blocks.join("\n\n"); 83 | } 84 | -------------------------------------------------------------------------------- /import-map-deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "@opennetwork/http-representation": "https://cdn.skypack.dev/@opennetwork/http-representation", 4 | "@virtualstate/astro-renderer": "https://cdn.skypack.dev/@virtualstate/astro-renderer", 5 | "@virtualstate/dom": "https://cdn.skypack.dev/@virtualstate/dom", 6 | "@virtualstate/examples": "https://cdn.skypack.dev/@virtualstate/examples", 7 | "@virtualstate/fringe": "https://cdn.skypack.dev/@virtualstate/fringe", 8 | "@virtualstate/hooks": "https://cdn.skypack.dev/@virtualstate/hooks", 9 | "@virtualstate/memo": "https://cdn.skypack.dev/@virtualstate/memo", 10 | "@virtualstate/hooks-extended": "https://cdn.skypack.dev/@virtualstate/hooks-extended", 11 | "@virtualstate/union": "https://cdn.skypack.dev/@virtualstate/union", 12 | "@virtualstate/x": "https://cdn.skypack.dev/@virtualstate/x", 13 | "@virtualstate/promise": "https://cdn.skypack.dev/@virtualstate/promise", 14 | "@virtualstate/promise/the-thing": "https://cdn.skypack.dev/@virtualstate/promise/the-thing", 15 | "@virtualstate/app-history": "./src/app-history.ts", 16 | "@virtualstate/app-history/polyfill": "./src/polyfill.ts", 17 | "@virtualstate/app-history/event-target/sync": "./src/event-target/sync-event-target.ts", 18 | "@virtualstate/app-history/event-target/async": "./src/event-target/async-event-target.ts", 19 | "@virtualstate/app-history/event-target": "./src/event-target/sync-event-target.ts", 20 | "@virtualstate/app-history-imported": "./src/app-history.ts", 21 | "@virtualstate/app-history-imported/polyfill": "./src/polyfill.ts", 22 | "@virtualstate/app-history-imported/event-target/sync": "./src/event-target/sync-event-target.ts", 23 | "@virtualstate/app-history-imported/event-target/async": "./src/event-target/async-event-target.ts", 24 | "@virtualstate/app-history-imported/event-target": "./src/event-target/sync-event-target.ts", 25 | "@virtualstate/focus": "https://cdn.skypack.dev/@virtualstate/focus", 26 | "dom-lite": "https://cdn.skypack.dev/dom-lite", 27 | "iterable": "https://cdn.skypack.dev/iterable@6.0.1-beta.5", 28 | "https://cdn.skypack.dev/-/iterable@v5.7.0-CNtyuMJo9f2zFu6CuB1D/dist=es2019,mode=imports/optimized/iterable.js": "https://cdn.skypack.dev/iterable@6.0.1-beta.5", 29 | "uuid": "./src/util/deno-uuid.ts", 30 | "whatwg-url": "https://cdn.skypack.dev/whatwg-url", 31 | "abort-controller": "https://cdn.skypack.dev/abort-controller", 32 | "deno:std/uuid/mod": "https://deno.land/std@0.113.0/uuid/mod.ts", 33 | "deno:std/uuid/v4": "https://deno.land/std@0.113.0/uuid/v4.ts", 34 | "deno:deno_dom/deno-dom-wasm.ts": "https://deno.land/x/deno_dom/deno-dom-wasm.ts", 35 | "urlpattern-polyfill": "https://cdn.skypack.dev/urlpattern-polyfill", 36 | "./src/tests/config": "./src/tests/config.ts", 37 | "./src/tests/util": "./src/tests/util.ts", 38 | "./src/index": "./src/index.ts", 39 | "./src": "./src/index.ts", 40 | "@virtualstate/combinational": "./esnext/index.js", 41 | "cheerio": "./scripts/nop/index.js" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scripts/correct-import-extensions.js: -------------------------------------------------------------------------------- 1 | import FileHound from "filehound"; 2 | import { promises as fs } from "fs"; 3 | import path from "path"; 4 | import { promisify } from "util"; 5 | // 6 | // const packages = await FileHound.create() 7 | // .paths(`packages`) 8 | // .directory() 9 | // .depth(1) 10 | // .find(); 11 | 12 | const buildPaths = ["esnext"]; 13 | 14 | for (const buildPath of buildPaths) { 15 | const filePaths = await FileHound.create() 16 | .paths(buildPath) 17 | .discard("node_modules") 18 | .ext("js") 19 | .find(); 20 | 21 | await Promise.all( 22 | filePaths.map(async (filePath) => { 23 | const initialContents = await fs.readFile(filePath, "utf-8"); 24 | 25 | const statements = initialContents.match( 26 | /(?:(?:import|export)(?: .+ from)? ".+";|(?:import\(".+"\)))/g 27 | ); 28 | 29 | if (!statements) { 30 | return; 31 | } 32 | 33 | const importMap = process.env.IMPORT_MAP 34 | ? JSON.parse(await fs.readFile(process.env.IMPORT_MAP, "utf-8")) 35 | : undefined; 36 | const contents = await statements.reduce( 37 | async (contentsPromise, statement) => { 38 | const contents = await contentsPromise; 39 | const url = statement.match(/"(.+)"/)[1]; 40 | if (importMap?.imports?.[url]) { 41 | const replacement = importMap.imports[url]; 42 | if (!replacement.includes("./src")) { 43 | return contents.replace( 44 | statement, 45 | statement.replace(url, replacement) 46 | ); 47 | } 48 | const shift = filePath 49 | .split("/") 50 | // Skip top folder + file 51 | .slice(2) 52 | // Replace with shift up directory 53 | .map(() => "..") 54 | .join("/"); 55 | return contents.replace( 56 | statement, 57 | statement.replace( 58 | url, 59 | replacement.replace("./src", shift).replace(/\.tsx?$/, ".js") 60 | ) 61 | ); 62 | } else { 63 | return contents.replace(statement, await getReplacement(url)); 64 | } 65 | 66 | async function getReplacement(url) { 67 | const [stat, indexStat] = await Promise.all([ 68 | fs 69 | .stat(path.resolve(path.dirname(filePath), url + ".js")) 70 | .catch(() => {}), 71 | fs 72 | .stat(path.resolve(path.dirname(filePath), url + "/index.js")) 73 | .catch(() => {}), 74 | ]); 75 | 76 | if (stat && stat.isFile()) { 77 | return statement.replace(url, url + ".js"); 78 | } else if (indexStat && indexStat.isFile()) { 79 | return statement.replace(url, url + "/index.js"); 80 | } 81 | return statement; 82 | } 83 | }, 84 | Promise.resolve(initialContents) 85 | ); 86 | 87 | await fs.writeFile(filePath, contents, "utf-8"); 88 | }) 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@virtualstate/combinational", 3 | "version": "1.0.1-alpha.3", 4 | "main": "./esnext/index.js", 5 | "module": "./esnext/index.js", 6 | "types": "./esnext/index.d.ts", 7 | "typesVersions": { 8 | "*": { 9 | "*": [ 10 | "./esnext/index.d.ts" 11 | ], 12 | "tests": [ 13 | "./esnext/tests/index.d.ts" 14 | ], 15 | "tests/dom-h": [ 16 | "./esnext/tests/dom-h.d.ts" 17 | ] 18 | } 19 | }, 20 | "type": "module", 21 | "sideEffects": false, 22 | "keywords": [], 23 | "exports": { 24 | ".": "./esnext/index.js", 25 | "./tests": "./esnext/tests/index.js", 26 | "./tests/dom-h": "./esnext/tests/dom-h.js" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/virtualstate/combinational.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/virtualstate/combinational/issues" 34 | }, 35 | "homepage": "https://github.com/virtualstate/combinational#readme", 36 | "author": "Fabian Cook ", 37 | "license": "MIT", 38 | "dependencies": { 39 | "@virtualstate/memo": "^1.7.1" 40 | }, 41 | "devDependencies": { 42 | "@babel/cli": "^7.15.4", 43 | "@babel/core": "^7.15.4", 44 | "@babel/preset-env": "^7.15.4", 45 | "@opennetwork/http-representation": "^3.0.0", 46 | "@rollup/plugin-node-resolve": "^13.1.1", 47 | "@rollup/plugin-typescript": "^8.3.0", 48 | "@types/chance": "^1.1.3", 49 | "@types/jest": "^27.0.1", 50 | "@types/mkdirp": "^1.0.2", 51 | "@types/node": "^17.0.1", 52 | "@types/rimraf": "^3.0.2", 53 | "@types/uuid": "^8.3.3", 54 | "@types/whatwg-url": "^8.2.1", 55 | "@virtualstate/focus": "^1.1.1", 56 | "@virtualstate/promise": "^1.1.6", 57 | "@virtualstate/union": "^2.48.1", 58 | "c8": "^7.11.3", 59 | "chance": "^1.1.8", 60 | "cheerio": "^1.0.0-rc.10", 61 | "core-js": "^3.17.2", 62 | "dom-lite": "^20.2.0", 63 | "filehound": "^1.17.4", 64 | "jest": "^27.1.0", 65 | "jest-playwright-preset": "^1.7.0", 66 | "mkdirp": "^1.0.4", 67 | "playwright": "^1.17.1", 68 | "rimraf": "^3.0.2", 69 | "rollup": "^2.61.1", 70 | "rollup-plugin-babel": "^4.4.0", 71 | "rollup-plugin-ignore": "^1.0.10", 72 | "ts-jest": "^27.0.5", 73 | "ts-node": "^10.2.1", 74 | "typescript": "^4.7.4", 75 | "urlpattern-polyfill": "^1.0.0-rc2", 76 | "v8-to-istanbul": "^8.1.0" 77 | }, 78 | "scripts": { 79 | "build": "rm -rf esnext && tsc", 80 | "postbuild": "mkdir -p coverage && node scripts/post-build.js", 81 | "generate": "yarn build && node esnext/generate.js", 82 | "prepublishOnly": "npm run build", 83 | "test": "yarn build && node --enable-source-maps esnext/tests/index.js", 84 | "test:deno": "yarn build && deno run --allow-read --allow-net --import-map=import-map-deno.json esnext/tests/index.js", 85 | "test:deno:r": "yarn build && deno run -r --allow-read --allow-net --import-map=import-map-deno.json esnext/tests/index.js", 86 | "test:inspect": "yarn build && node --enable-source-maps --inspect-brk esnext/tests/index.js", 87 | "coverage": "yarn build && c8 node esnext/tests/index.js && yarn postbuild" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [conduct+axiom@fabiancook.dev](mailto:conduct+axiom@fabiancook.dev). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/tests/circuit/adder.tsx: -------------------------------------------------------------------------------- 1 | import {HalfAdder} from "@virtualstate/combinational"; 2 | import {h, createFragment} from "@virtualstate/focus"; 3 | import {anAsyncThing} from "@virtualstate/promise/the-thing"; 4 | import {pair} from "@virtualstate/combinational"; 5 | import {ok} from "@virtualstate/combinational"; 6 | import {FullAdder} from "@virtualstate/combinational"; 7 | 8 | export function assertPair(result: unknown[], expected?: T): asserts result is T { 9 | if (!expected) { 10 | ok(!result); 11 | return; 12 | } 13 | ok(result.length === expected.length, "Expected lengths to match"); 14 | for (const [index, value] of Object.entries(result)) { 15 | ok(expected[index] === value, "Expected values to match"); 16 | } 17 | } 18 | 19 | assertPair( 20 | await anAsyncThing(pair( 21 | 22 | {false} 23 | {false} 24 | 25 | )), 26 | [ 27 | false, 28 | false 29 | ] 30 | ); 31 | assertPair( 32 | await anAsyncThing(pair( 33 | 34 | {false} 35 | {true} 36 | 37 | )), 38 | [ 39 | true, 40 | false 41 | ] 42 | ); 43 | assertPair( 44 | await anAsyncThing(pair( 45 | 46 | {true} 47 | {false} 48 | 49 | )), 50 | [ 51 | true, 52 | false 53 | ] 54 | ); 55 | assertPair( 56 | await anAsyncThing(pair( 57 | 58 | {true} 59 | {true} 60 | 61 | )), 62 | [ 63 | false, 64 | true 65 | ] 66 | ); 67 | assertPair( 68 | await anAsyncThing(pair( 69 | 70 | 71 | )), 72 | undefined 73 | ); 74 | assertPair( 75 | await anAsyncThing(pair( 76 | 77 | {false} 78 | 79 | )), 80 | undefined 81 | ); 82 | 83 | assertPair( 84 | await anAsyncThing(pair( 85 | 86 | {false} 87 | {false} 88 | {false} 89 | 90 | )), 91 | [ 92 | false, 93 | false 94 | ] 95 | ); 96 | assertPair( 97 | await anAsyncThing(pair( 98 | 99 | {false} 100 | {false} 101 | {true} 102 | 103 | )), 104 | [ 105 | true, 106 | false 107 | ] 108 | ); 109 | assertPair( 110 | await anAsyncThing(pair( 111 | 112 | {false} 113 | {true} 114 | {false} 115 | 116 | )), 117 | [ 118 | true, 119 | false 120 | ] 121 | ); 122 | assertPair( 123 | await anAsyncThing(pair( 124 | 125 | {false} 126 | {true} 127 | {true} 128 | 129 | )), 130 | [ 131 | false, 132 | true 133 | ] 134 | ); 135 | assertPair( 136 | await anAsyncThing(pair( 137 | 138 | {true} 139 | {false} 140 | {false} 141 | 142 | )), 143 | [ 144 | true, 145 | false 146 | ] 147 | ); 148 | assertPair( 149 | await anAsyncThing(pair( 150 | 151 | {true} 152 | {false} 153 | {true} 154 | 155 | )), 156 | [ 157 | false, 158 | true 159 | ] 160 | ); 161 | assertPair( 162 | await anAsyncThing(pair( 163 | 164 | {true} 165 | {true} 166 | {false} 167 | 168 | )), 169 | [ 170 | false, 171 | true 172 | ] 173 | ); 174 | assertPair( 175 | await anAsyncThing(pair( 176 | 177 | {true} 178 | {true} 179 | {true} 180 | 181 | )), 182 | [ 183 | true, 184 | true 185 | ] 186 | ); 187 | assertPair( 188 | await anAsyncThing(pair( 189 | 190 | {true} 191 | {true} 192 | 193 | )), 194 | undefined 195 | ); 196 | assertPair( 197 | await anAsyncThing(pair( 198 | 199 | {true} 200 | 201 | )), 202 | undefined 203 | ); 204 | assertPair( 205 | await anAsyncThing(pair( 206 | 207 | )), 208 | undefined 209 | ); -------------------------------------------------------------------------------- /scripts/post-build.js: -------------------------------------------------------------------------------- 1 | import "./correct-import-extensions.js"; 2 | import { promises as fs } from "fs"; 3 | import { rollup } from "rollup"; 4 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 5 | import ignore from "rollup-plugin-ignore"; 6 | import babel from "rollup-plugin-babel"; 7 | import { dirname, resolve } from "path"; 8 | 9 | await import("../esnext/trying-to-write-documentation/readme-generator.js"); 10 | 11 | const { pathname } = new URL(import.meta.url); 12 | const cwd = resolve(dirname(pathname), ".."); 13 | 14 | { 15 | // /Volumes/Extreme/Users/fabian/src/virtualstate/esnext/tests/app-history.playwright.wpt.js 16 | // /Volumes/Extreme/Users/fabian/src/virtualstate/app-history/esnext/tests/app-history.playwright.wpt.js 17 | 18 | console.log({ 19 | cwd, 20 | path: 21 | `/Volumes/Extreme/Users/fabian/src/virtualstate/app-history/esnext/tests/app-history.playwright.wpt.js` === 22 | `${cwd}/esnext/tests/app-history.playwright.wpt.js`, 23 | p: `${cwd}/esnext/tests/app-history.playwright.wpt.js`, 24 | }); 25 | 26 | const bundle = await rollup({ 27 | input: "./esnext/tests/index.js", 28 | plugins: [ 29 | ignore([ 30 | "playwright", 31 | "fs", 32 | "path", 33 | "uuid", 34 | "cheerio", 35 | "@virtualstate/app-history", 36 | "@virtualstate/app-history-imported", 37 | `${cwd}/esnext/tests/app-history.playwright.js`, 38 | `${cwd}/esnext/tests/app-history.playwright.wpt.js`, 39 | `${cwd}/esnext/tests/dependencies-input.js`, 40 | `${cwd}/esnext/tests/dependencies.js`, 41 | "./app-history.playwright.js", 42 | "./app-history.playwright.wpt.js", 43 | ]), 44 | nodeResolve(), 45 | ], 46 | inlineDynamicImports: true, 47 | treeshake: { 48 | preset: "smallest", 49 | moduleSideEffects: "no-external", 50 | }, 51 | }); 52 | await bundle.write({ 53 | sourcemap: true, 54 | output: { 55 | file: "./esnext/tests/rollup.js", 56 | }, 57 | inlineDynamicImports: true, 58 | format: "cjs", 59 | interop: "auto", 60 | globals: { 61 | "esnext/tests/app-history.playwright.js": "globalThis", 62 | }, 63 | }); 64 | } 65 | 66 | if (!process.env.NO_COVERAGE_BADGE_UPDATE) { 67 | const badges = []; 68 | 69 | const { name } = await fs.readFile("package.json", "utf-8").then(JSON.parse); 70 | 71 | badges.push( 72 | "### Support\n\n", 73 | "![Node.js supported](https://img.shields.io/badge/node-%3E%3D16.0.0-blue)", 74 | "![Deno supported](https://img.shields.io/badge/deno-%3E%3D1.17.0-blue)" 75 | // "![Chromium supported](https://img.shields.io/badge/chromium-%3E%3D98.0.4695.0-blue)", 76 | // "![Webkit supported](https://img.shields.io/badge/webkit-%3E%3D15.4-blue)", 77 | // "![Firefox supported](https://img.shields.io/badge/firefox-%3E%3D94.0.1-blue)" 78 | ); 79 | 80 | badges.push( 81 | "\n\n### Test Coverage\n\n" 82 | // `![nycrc config on GitHub](https://img.shields.io/nycrc/${name.replace(/^@/, "")})` 83 | ); 84 | 85 | // const wptResults = await fs 86 | // .readFile("coverage/wpt.results.json", "utf8") 87 | // .then(JSON.parse) 88 | // .catch(() => ({})); 89 | // if (wptResults?.total) { 90 | // const message = `${wptResults.pass}/${wptResults.total}`; 91 | // const name = "Web Platform Tests"; 92 | // badges.push( 93 | // `![${name} ${message}](https://img.shields.io/badge/${encodeURIComponent( 94 | // name 95 | // )}-${encodeURIComponent(message)}-brightgreen)` 96 | // ); 97 | // } 98 | 99 | const coverage = await fs 100 | .readFile("coverage/coverage-summary.json", "utf8") 101 | .then(JSON.parse) 102 | .catch(() => ({})); 103 | const coverageConfig = await fs.readFile(".nycrc", "utf8").then(JSON.parse); 104 | for (const [name, { pct }] of Object.entries(coverage?.total ?? {})) { 105 | const good = coverageConfig[name]; 106 | if (!good) continue; // not configured 107 | const color = pct >= good ? "brightgreen" : "yellow"; 108 | const message = `${pct}%25`; 109 | badges.push( 110 | `![${message} ${name} covered](https://img.shields.io/badge/${name}-${message}-${color})` 111 | ); 112 | } 113 | 114 | const tag = "[//]: # (badges)"; 115 | 116 | const readMe = await fs.readFile("README.md", "utf8"); 117 | const badgeStart = readMe.indexOf(tag); 118 | const badgeStartAfter = badgeStart + tag.length; 119 | if (badgeStart === -1) { 120 | throw new Error(`Expected to find "${tag}" in README.md`); 121 | } 122 | const badgeEnd = badgeStartAfter + readMe.slice(badgeStartAfter).indexOf(tag); 123 | const badgeEndAfter = badgeEnd + tag.length; 124 | const readMeBefore = readMe.slice(0, badgeStart); 125 | const readMeAfter = readMe.slice(badgeEndAfter); 126 | 127 | const readMeNext = `${readMeBefore}${tag}\n\n${badges.join( 128 | " " 129 | )}\n\n${tag}${readMeAfter}`; 130 | await fs.writeFile("README.md", readMeNext); 131 | console.log("Wrote coverage badges!"); 132 | } 133 | -------------------------------------------------------------------------------- /src/tests/combinational/boolean.tsx: -------------------------------------------------------------------------------- 1 | import {descendants, h, ok} from "@virtualstate/focus"; 2 | import {And, Boolean, Nand, Nor, Xnor, Xor} from "@virtualstate/combinational"; 3 | import {isBooleanFalseArray, isBooleanTrueArray} from "@virtualstate/combinational"; 4 | import {Not} from "@virtualstate/combinational"; 5 | import {Or} from "@virtualstate/combinational"; 6 | 7 | export default 1; 8 | 9 | 10 | const nodeTrue = {true} 11 | const nodeTrueNumber = {1} 12 | const nodeTrueString = {" "} 13 | const nodeTrueNode = 14 | 15 | const nodeFalse = {false} 16 | const nodeFalseNumber = {0} 17 | const nodeFalseString = {""} 18 | 19 | async function assertTrue(input: unknown): Promise { 20 | const booleans = await descendants(input); 21 | ok(isBooleanTrueArray(booleans), `expected all to be true, got ${booleans}`); 22 | } 23 | async function assertFalse(input: unknown): Promise { 24 | const booleans = await descendants(input); 25 | ok(isBooleanFalseArray(booleans), `expected all to be false, got ${booleans}`); 26 | } 27 | 28 | await assertTrue(nodeTrue); 29 | await assertTrue(nodeTrueNumber); 30 | await assertTrue(nodeTrueString); 31 | await assertTrue(nodeTrueNode); 32 | 33 | await assertFalse(nodeFalse); 34 | await assertFalse(nodeFalseNumber); 35 | await assertFalse(nodeFalseString); 36 | 37 | const nodeAndTrue = ( 38 | 39 | {nodeTrue} 40 | {nodeTrueNumber} 41 | {nodeTrueString} 42 | {nodeTrueNode} 43 | {true} 44 | {" "} 45 | {1} 46 | 47 | 48 | ) 49 | const nodeAndFalse = ( 50 | 51 | {nodeAndTrue} 52 | {false} 53 | 54 | ); 55 | 56 | await assertFalse(); 57 | await assertTrue(nodeAndTrue); 58 | await assertFalse(nodeAndFalse); 59 | 60 | const not = ( 61 | 62 | {nodeAndTrue} 63 | 64 | ) 65 | const notTrue = ( 66 | 67 | {nodeAndFalse} 68 | 69 | ); 70 | 71 | await assertTrue(); 72 | await assertFalse(not); 73 | await assertTrue(notTrue); 74 | 75 | const or = ( 76 | 77 | {nodeAndTrue} 78 | {nodeAndFalse} 79 | 80 | ) 81 | const orFalse = ( 82 | 83 | {nodeFalse} 84 | {nodeAndFalse} 85 | 86 | ); 87 | 88 | await assertFalse(); 89 | await assertTrue(or); 90 | await assertFalse(orFalse); 91 | 92 | const nor = ( 93 | 94 | {nodeAndTrue} 95 | {nodeAndFalse} 96 | 97 | ) 98 | const norTrue = ( 99 | 100 | {nodeFalse} 101 | {nodeAndFalse} 102 | 103 | ); 104 | 105 | await assertTrue(); 106 | await assertFalse(nor); 107 | await assertTrue(norTrue); 108 | 109 | const xor = ( 110 | 111 | {false} 112 | {nodeFalse} 113 | 114 | ) 115 | await assertFalse(); 116 | await assertFalse(xor); 117 | const xorTrueOne = ( 118 | 119 | {nodeFalse} 120 | {nodeTrue} 121 | 122 | ) 123 | const xorTrueOther = ( 124 | 125 | {nodeAndTrue} 126 | {orFalse} 127 | 128 | ); 129 | await assertTrue(xorTrueOne); 130 | await assertTrue(xorTrueOther); 131 | const xorFalseAll = ( 132 | 133 | {nodeAndTrue} 134 | {true} 135 | {nodeTrueString} 136 | 137 | ); 138 | await assertFalse(xorFalseAll); 139 | 140 | const xnor = ( 141 | 142 | {false} 143 | {nodeFalse} 144 | {nodeAndFalse} 145 | {orFalse} 146 | 147 | ); 148 | await assertTrue(); 149 | await assertTrue(xnor); 150 | const xnorTrue = ( 151 | 152 | {true} 153 | {nodeTrue} 154 | {nodeAndTrue} 155 | {or} 156 | 157 | ); 158 | await assertTrue(xnorTrue); 159 | 160 | const xnorFalse = ( 161 | 162 | {true} 163 | {nodeFalse} 164 | {nodeAndTrue} 165 | {or} 166 | 167 | ); 168 | await assertFalse(xnorFalse); 169 | const xnorFalseOther = ( 170 | 171 | {true} 172 | {nodeFalse} 173 | {nodeAndTrue} 174 | {orFalse} 175 | 176 | ); 177 | await assertFalse(xnorFalseOther); 178 | 179 | const nand = ( 180 | 181 | {false} 182 | {nodeFalse} 183 | {orFalse} 184 | 185 | ); 186 | await assertTrue(); 187 | await assertTrue(nand); 188 | const nandTrue = ( 189 | 190 | {true} 191 | {nodeFalse} 192 | {orFalse} 193 | 194 | ); 195 | await assertTrue(nandTrue); 196 | const nandTrueOther = ( 197 | 198 | {true} 199 | {nodeAndTrue} 200 | {orFalse} 201 | 202 | ); 203 | await assertTrue(nandTrueOther); 204 | const nandTrueAnother = ( 205 | 206 | {true} 207 | {nodeAndTrue} 208 | {orFalse} 209 | {or} 210 | 211 | ); 212 | await assertTrue(nandTrueAnother); 213 | const nandFalse = ( 214 | 215 | {true} 216 | {nodeAndTrue} 217 | {nodeTrue} 218 | {or} 219 | 220 | ); 221 | await assertFalse(nandFalse); 222 | -------------------------------------------------------------------------------- /src/trying-to-write-documentation/combinational-how.tsx: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | import {ok, h, descendants} from "@virtualstate/focus"; 4 | import { 5 | And, 6 | Boolean, 7 | isBooleanFalseArray, 8 | isBooleanTrueArray, Nand, 9 | Nor, 10 | Not, 11 | Or, 12 | Xnor, 13 | Xor 14 | } from "@virtualstate/combinational"; 15 | 16 | /** 17 | * This is living documentation, change this code, and on build, README.md will be updated 18 | * 19 | * Comments starting with /* (and not /**) will be treated as markdown 20 | * Code is treated as codeblocks 21 | * 22 | * To split code up, add an empty comment 23 | * A comment must have its starting & ending markers on their own lines 24 | */ 25 | 26 | /* 27 | # Hello 28 | */ 29 | 30 | const nodeTrue = {true} 31 | const nodeTrueNumber = {1} 32 | const nodeTrueString = {" "} 33 | const nodeTrueNode = 34 | 35 | const nodeFalse = {false} 36 | const nodeFalseNumber = {0} 37 | const nodeFalseString = {""} 38 | 39 | async function assertTrue(input: unknown): Promise { 40 | const booleans = await descendants(input); 41 | ok(isBooleanTrueArray(booleans), `expected all to be true, got ${booleans}`); 42 | } 43 | async function assertFalse(input: unknown): Promise { 44 | const booleans = await descendants(input); 45 | ok(isBooleanFalseArray(booleans), `expected all to be false, got ${booleans}`); 46 | } 47 | 48 | await assertTrue(nodeTrue); 49 | await assertTrue(nodeTrueNumber); 50 | await assertTrue(nodeTrueString); 51 | await assertTrue(nodeTrueNode); 52 | 53 | await assertFalse(nodeFalse); 54 | await assertFalse(nodeFalseNumber); 55 | await assertFalse(nodeFalseString); 56 | 57 | const nodeAndTrue = ( 58 | 59 | {nodeTrue} 60 | {nodeTrueNumber} 61 | {nodeTrueString} 62 | {nodeTrueNode} 63 | {true} 64 | {" "} 65 | {1} 66 | 67 | 68 | ) 69 | const nodeAndFalse = ( 70 | 71 | {nodeAndTrue} 72 | {false} 73 | 74 | ); 75 | 76 | await assertFalse(); 77 | await assertTrue(nodeAndTrue); 78 | await assertFalse(nodeAndFalse); 79 | 80 | const not = ( 81 | 82 | {nodeAndTrue} 83 | 84 | ) 85 | const notTrue = ( 86 | 87 | {nodeAndFalse} 88 | 89 | ); 90 | 91 | await assertTrue(); 92 | await assertFalse(not); 93 | await assertTrue(notTrue); 94 | 95 | const or = ( 96 | 97 | {nodeAndTrue} 98 | {nodeAndFalse} 99 | 100 | ) 101 | const orFalse = ( 102 | 103 | {nodeFalse} 104 | {nodeAndFalse} 105 | 106 | ); 107 | 108 | await assertFalse(); 109 | await assertTrue(or); 110 | await assertFalse(orFalse); 111 | 112 | const nor = ( 113 | 114 | {nodeAndTrue} 115 | {nodeAndFalse} 116 | 117 | ) 118 | const norTrue = ( 119 | 120 | {nodeFalse} 121 | {nodeAndFalse} 122 | 123 | ); 124 | 125 | await assertTrue(); 126 | await assertFalse(nor); 127 | await assertTrue(norTrue); 128 | 129 | const xor = ( 130 | 131 | {false} 132 | {nodeFalse} 133 | 134 | ) 135 | await assertFalse(); 136 | await assertFalse(xor); 137 | const xorTrueOne = ( 138 | 139 | {nodeFalse} 140 | {nodeTrue} 141 | 142 | ) 143 | const xorTrueOther = ( 144 | 145 | {nodeAndTrue} 146 | {orFalse} 147 | 148 | ); 149 | await assertTrue(xorTrueOne); 150 | await assertTrue(xorTrueOther); 151 | const xorFalseAll = ( 152 | 153 | {nodeAndTrue} 154 | {true} 155 | {nodeTrueString} 156 | 157 | ); 158 | await assertFalse(xorFalseAll); 159 | 160 | const xnor = ( 161 | 162 | {false} 163 | {nodeFalse} 164 | {nodeAndFalse} 165 | {orFalse} 166 | 167 | ); 168 | await assertTrue(); 169 | await assertTrue(xnor); 170 | const xnorTrue = ( 171 | 172 | {true} 173 | {nodeTrue} 174 | {nodeAndTrue} 175 | {or} 176 | 177 | ); 178 | await assertTrue(xnorTrue); 179 | 180 | const xnorFalse = ( 181 | 182 | {true} 183 | {nodeFalse} 184 | {nodeAndTrue} 185 | {or} 186 | 187 | ); 188 | await assertFalse(xnorFalse); 189 | const xnorFalseOther = ( 190 | 191 | {true} 192 | {nodeFalse} 193 | {nodeAndTrue} 194 | {orFalse} 195 | 196 | ); 197 | await assertFalse(xnorFalseOther); 198 | 199 | const nand = ( 200 | 201 | {false} 202 | {nodeFalse} 203 | {orFalse} 204 | 205 | ); 206 | await assertTrue(); 207 | await assertTrue(nand); 208 | const nandTrue = ( 209 | 210 | {true} 211 | {nodeFalse} 212 | {orFalse} 213 | 214 | ); 215 | await assertTrue(nandTrue); 216 | const nandTrueOther = ( 217 | 218 | {true} 219 | {nodeAndTrue} 220 | {orFalse} 221 | 222 | ); 223 | await assertTrue(nandTrueOther); 224 | const nandTrueAnother = ( 225 | 226 | {true} 227 | {nodeAndTrue} 228 | {orFalse} 229 | {or} 230 | 231 | ); 232 | await assertTrue(nandTrueAnother); 233 | const nandFalse = ( 234 | 235 | {true} 236 | {nodeAndTrue} 237 | {nodeTrue} 238 | {or} 239 | 240 | ); 241 | await assertFalse(nandFalse); 242 | 243 | export default 1; 244 | -------------------------------------------------------------------------------- /src/tests/combinational/combination.tsx: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | import {children, h} from "@virtualstate/focus"; 4 | import {Combination, ok} from "@virtualstate/combinational"; 5 | import {isNumber} from "../../is"; 6 | import {memo} from "@virtualstate/memo"; 7 | 8 | export default 1; 9 | 10 | function *Changes() { 11 | yield true; 12 | yield false; 13 | yield true; 14 | } 15 | 16 | { 17 | 18 | const root = ( 19 | 20 | {true} 21 | & 22 | 23 | 24 | ) 25 | 26 | for await (const snapshot of children(root)) { 27 | console.log(snapshot); 28 | } 29 | } 30 | 31 | { 32 | 33 | async function *Pick(o?: unknown, input?: unknown) { 34 | for await (const snapshot of children(input)) { 35 | if (!snapshot.length) continue; 36 | const index = Math.round((snapshot.length - 1) * Math.random()); 37 | console.log({ picked: index, snapshot }); 38 | yield snapshot[index]; 39 | } 40 | } 41 | 42 | const PickSymbol = Symbol("Pick"); 43 | 44 | const root = ( 45 | 46 | {true} 47 | & 48 | 49 | || 50 | {false} && {true} || {false}{PickSymbol}{true} 51 | 52 | ) 53 | 54 | for await (const snapshot of children(root)) { 55 | console.log(snapshot); 56 | } 57 | } 58 | 59 | { 60 | const UnknownSymbol = Symbol("Unknown"); 61 | const root = ( 62 | 63 | {true} | {UnknownSymbol} 64 | 65 | ) 66 | 67 | ok(await children(root).catch(error => error) instanceof Error); 68 | } 69 | 70 | { 71 | const root = ( 72 | 73 | {1} + {2} 74 | 75 | ) 76 | 77 | const result = await children(root); 78 | console.log(result); 79 | ok(result.length === 1); 80 | ok(result[0] === 3); 81 | } 82 | 83 | { 84 | const root = ( 85 | 86 | {1} - {2} 87 | 88 | ) 89 | 90 | const result = await children(root); 91 | console.log(result); 92 | ok(result.length === 1); 93 | ok(result[0] === -1); 94 | } 95 | 96 | { 97 | const root = ( 98 | 99 | {1} * {2} * {5} 100 | 101 | ) 102 | 103 | const result = await children(root); 104 | console.log(result); 105 | ok(result.length === 1); 106 | ok(result[0] === 10); 107 | } 108 | 109 | { 110 | const root = ( 111 | 112 | {1} / {2} * {5} 113 | 114 | ) 115 | 116 | const result = await children(root); 117 | console.log(result); 118 | ok(result.length === 1); 119 | ok(result[0] === 2.5); 120 | } 121 | 122 | { 123 | const root = ( 124 | 125 | {1} / {2} * {5} + {1} 126 | 127 | ) 128 | 129 | const result = await children(root); 130 | console.log(result); 131 | ok(result.length === 1); 132 | ok(result[0] === 3.5); 133 | } 134 | 135 | { 136 | let called = 0; 137 | async function *A() { 138 | called += 1; 139 | yield 1; 140 | yield 0.01; 141 | yield 2; 142 | } 143 | async function *B() { 144 | called += 1; 145 | yield 4; 146 | yield 1; 147 | yield 5; 148 | } 149 | async function *C() { 150 | called += 1; 151 | yield 3; 152 | yield 0.3; 153 | yield 0.1; 154 | yield 2 155 | } 156 | async function *D() { 157 | called += 1; 158 | yield 3; 159 | yield 9; 160 | yield 2; 161 | yield 2 162 | } 163 | 164 | const root = memo( 165 | 166 | / * + 167 | 168 | ) 169 | 170 | { 171 | const result = await children(root); 172 | console.log(result); 173 | ok(result.length === 1); 174 | ok(result[0] === 2.8); 175 | } 176 | 177 | let seenLength = 0; 178 | { 179 | const seen = []; 180 | 181 | for await (const snapshot of children(root)) { 182 | ok(snapshot.length === 1); 183 | seen.push(snapshot); 184 | } 185 | 186 | console.log(seen); 187 | ok(seen.length >= 4); 188 | ok(seen.at(-1).at(0) === 2.8); 189 | 190 | seenLength = seen.length; 191 | } 192 | 193 | { 194 | 195 | const value = await children(root) 196 | .filter(isNumber) 197 | .at(-1); 198 | console.log(value); 199 | ok(value === 2.8); 200 | } 201 | 202 | { 203 | 204 | const valueSeen: number[] = []; 205 | for await ( 206 | const value of children(root) 207 | .filter(isNumber) 208 | .at(-1) 209 | ) { 210 | valueSeen.push(value); 211 | } 212 | console.log(valueSeen); 213 | ok(valueSeen.length === seenLength); 214 | ok(valueSeen.at(-1) === 2.8); 215 | } 216 | 217 | ok(called === 4); 218 | 219 | } 220 | 221 | 222 | { 223 | const root = ( 224 | 225 | {1} / {undefined} 226 | 227 | ) 228 | 229 | const result = await children(root); 230 | console.log(result); 231 | ok(result.length === 1); 232 | ok(isNumber(result[0])) 233 | ok(!isNaN(result[0])); 234 | ok(!isFinite(result[0])); 235 | } 236 | 237 | 238 | { 239 | const root = ( 240 | 241 | {1} / {0} 242 | 243 | ) 244 | 245 | const result = await children(root); 246 | console.log(result); 247 | ok(result.length === 1); 248 | ok(isNumber(result[0])) 249 | ok(!isNaN(result[0])); 250 | ok(!isFinite(result[0])); 251 | } 252 | 253 | { 254 | const root = ( 255 | 256 | {1} * {0} 257 | 258 | ) 259 | 260 | const result = await children(root); 261 | console.log(result); 262 | ok(result.length === 1); 263 | ok(isNumber(result[0])) 264 | ok(result[0] === 0); 265 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@virtualstate/combinational` 2 | 3 | This project is in semver alpha stage 4 | 5 | [//]: # (badges) 6 | 7 | ### Support 8 | 9 | ![Node.js supported](https://img.shields.io/badge/node-%3E%3D16.0.0-blue) ![Deno supported](https://img.shields.io/badge/deno-%3E%3D1.17.0-blue) 10 | 11 | ### Test Coverage 12 | 13 | ![100%25 lines covered](https://img.shields.io/badge/lines-100%25-brightgreen) ![100%25 statements covered](https://img.shields.io/badge/statements-100%25-brightgreen) ![100%25 functions covered](https://img.shields.io/badge/functions-100%25-brightgreen) ![100%25 branches covered](https://img.shields.io/badge/branches-100%25-brightgreen) 14 | 15 | [//]: # (badges) 16 | 17 | [//]: # (src/trying-to-write-documentation/combinational-how.tsx) 18 | 19 | import {ok, h, descendants} from "@virtualstate/focus"; 20 | import { 21 | And, 22 | Boolean, 23 | isBooleanFalseArray, 24 | isBooleanTrueArray, Nand, 25 | Nor, 26 | Not, 27 | Or, 28 | Xnor, 29 | Xor 30 | } from "@virtualstate/combinational"; 31 | 32 | /** 33 | * This is living documentation, change this code, and on build, README.md will be updated 34 | * 35 | * Comments starting with /* (and not /**) will be treated as markdown 36 | * Code is treated as codeblocks 37 | * 38 | * To split code up, add an empty comment 39 | * A comment must have its starting & ending markers on their own lines 40 | 41 | # Hello 42 | 43 | ```typescript jsx 44 | const nodeTrue = {true} 45 | const nodeTrueNumber = {1} 46 | const nodeTrueString = {" "} 47 | const nodeTrueNode = 48 | 49 | const nodeFalse = {false} 50 | const nodeFalseNumber = {0} 51 | const nodeFalseString = {""} 52 | 53 | async function assertTrue(input: unknown): Promise { 54 | const booleans = await descendants(input); 55 | ok(isBooleanTrueArray(booleans), `expected all to be true, got ${booleans}`); 56 | } 57 | async function assertFalse(input: unknown): Promise { 58 | const booleans = await descendants(input); 59 | ok(isBooleanFalseArray(booleans), `expected all to be false, got ${booleans}`); 60 | } 61 | 62 | await assertTrue(nodeTrue); 63 | await assertTrue(nodeTrueNumber); 64 | await assertTrue(nodeTrueString); 65 | await assertTrue(nodeTrueNode); 66 | 67 | await assertFalse(nodeFalse); 68 | await assertFalse(nodeFalseNumber); 69 | await assertFalse(nodeFalseString); 70 | 71 | const nodeAndTrue = ( 72 | 73 | {nodeTrue} 74 | {nodeTrueNumber} 75 | {nodeTrueString} 76 | {nodeTrueNode} 77 | {true} 78 | {" "} 79 | {1} 80 | 81 | 82 | ) 83 | const nodeAndFalse = ( 84 | 85 | {nodeAndTrue} 86 | {false} 87 | 88 | ); 89 | 90 | await assertFalse(); 91 | await assertTrue(nodeAndTrue); 92 | await assertFalse(nodeAndFalse); 93 | 94 | const not = ( 95 | 96 | {nodeAndTrue} 97 | 98 | ) 99 | const notTrue = ( 100 | 101 | {nodeAndFalse} 102 | 103 | ); 104 | 105 | await assertTrue(); 106 | await assertFalse(not); 107 | await assertTrue(notTrue); 108 | 109 | const or = ( 110 | 111 | {nodeAndTrue} 112 | {nodeAndFalse} 113 | 114 | ) 115 | const orFalse = ( 116 | 117 | {nodeFalse} 118 | {nodeAndFalse} 119 | 120 | ); 121 | 122 | await assertFalse(); 123 | await assertTrue(or); 124 | await assertFalse(orFalse); 125 | 126 | const nor = ( 127 | 128 | {nodeAndTrue} 129 | {nodeAndFalse} 130 | 131 | ) 132 | const norTrue = ( 133 | 134 | {nodeFalse} 135 | {nodeAndFalse} 136 | 137 | ); 138 | 139 | await assertTrue(); 140 | await assertFalse(nor); 141 | await assertTrue(norTrue); 142 | 143 | const xor = ( 144 | 145 | {false} 146 | {nodeFalse} 147 | 148 | ) 149 | await assertFalse(); 150 | await assertFalse(xor); 151 | const xorTrueOne = ( 152 | 153 | {nodeFalse} 154 | {nodeTrue} 155 | 156 | ) 157 | const xorTrueOther = ( 158 | 159 | {nodeAndTrue} 160 | {orFalse} 161 | 162 | ); 163 | await assertTrue(xorTrueOne); 164 | await assertTrue(xorTrueOther); 165 | const xorFalseAll = ( 166 | 167 | {nodeAndTrue} 168 | {true} 169 | {nodeTrueString} 170 | 171 | ); 172 | await assertFalse(xorFalseAll); 173 | 174 | const xnor = ( 175 | 176 | {false} 177 | {nodeFalse} 178 | {nodeAndFalse} 179 | {orFalse} 180 | 181 | ); 182 | await assertTrue(); 183 | await assertTrue(xnor); 184 | const xnorTrue = ( 185 | 186 | {true} 187 | {nodeTrue} 188 | {nodeAndTrue} 189 | {or} 190 | 191 | ); 192 | await assertTrue(xnorTrue); 193 | 194 | const xnorFalse = ( 195 | 196 | {true} 197 | {nodeFalse} 198 | {nodeAndTrue} 199 | {or} 200 | 201 | ); 202 | await assertFalse(xnorFalse); 203 | const xnorFalseOther = ( 204 | 205 | {true} 206 | {nodeFalse} 207 | {nodeAndTrue} 208 | {orFalse} 209 | 210 | ); 211 | await assertFalse(xnorFalseOther); 212 | 213 | const nand = ( 214 | 215 | {false} 216 | {nodeFalse} 217 | {orFalse} 218 | 219 | ); 220 | await assertTrue(); 221 | await assertTrue(nand); 222 | const nandTrue = ( 223 | 224 | {true} 225 | {nodeFalse} 226 | {orFalse} 227 | 228 | ); 229 | await assertTrue(nandTrue); 230 | const nandTrueOther = ( 231 | 232 | {true} 233 | {nodeAndTrue} 234 | {orFalse} 235 | 236 | ); 237 | await assertTrue(nandTrueOther); 238 | const nandTrueAnother = ( 239 | 240 | {true} 241 | {nodeAndTrue} 242 | {orFalse} 243 | {or} 244 | 245 | ); 246 | await assertTrue(nandTrueAnother); 247 | const nandFalse = ( 248 | 249 | {true} 250 | {nodeAndTrue} 251 | {nodeTrue} 252 | {or} 253 | 254 | ); 255 | await assertFalse(nandFalse); 256 | ``` 257 | 258 | [//]: # (src/trying-to-write-documentation/combinational-how.tsx) 259 | -------------------------------------------------------------------------------- /src/tests/dom-h.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | import dom from "./dom-document"; 3 | import { 4 | isDescendantFulfilled, 5 | isKeyIn, 6 | isLike, 7 | proxy, 8 | } from "@virtualstate/focus"; 9 | import { descendantsSettled } from "@virtualstate/focus"; 10 | import { ok, isStaticChildNode } from "@virtualstate/focus"; 11 | 12 | const { document, HTMLElement } = dom; 13 | 14 | export { document, HTMLElement }; 15 | 16 | export function isNode(node: unknown): node is Node { 17 | return isLike(node) && typeof node.nodeType === "number"; 18 | } 19 | 20 | export function createElement( 21 | tagName: string, 22 | options?: unknown, 23 | ...children: unknown[] 24 | ): HTMLElement { 25 | let instance: HTMLElement; 26 | const proxied = proxy({ 27 | tagName, 28 | options, 29 | get instance() { 30 | if (instance) return instance; 31 | instance = document.createElement(tagName, options); 32 | setOptions(instance, options); 33 | return instance; 34 | }, 35 | children, 36 | }); 37 | ok(proxied); 38 | return proxied; 39 | } 40 | 41 | export function setOptions(element: HTMLElement, options: unknown) { 42 | setAttributes(element, options); 43 | for (const [key, value] of Object.entries(options ?? {})) { 44 | if (set(`${key}List`, value)) continue; 45 | if (key.endsWith("Name")) { 46 | const withoutName = key.replace(/Name$/, ""); 47 | if (set(`${withoutName}List`, value)) continue; 48 | } 49 | set(key, value); 50 | } 51 | 52 | function isDOMTokenList(value: unknown): value is DOMTokenList { 53 | return isLike(value) && typeof value.add === "function"; 54 | } 55 | 56 | function isArrayLike(value: unknown): value is unknown[] { 57 | return isKeyIn(value, "length"); 58 | } 59 | 60 | function isObject(value: unknown): value is Record { 61 | return typeof value === "object"; 62 | } 63 | 64 | function set(key: string | symbol, value: unknown) { 65 | if (!isKeyIn(element, key)) return false; 66 | if ( 67 | typeof key === "string" && 68 | key.endsWith("List") && 69 | typeof value === "string" 70 | ) { 71 | value = value.split(/\s+/g).filter(Boolean); 72 | } 73 | const elementValue: unknown = element[key]; 74 | if (isArrayLike(value)) { 75 | // const add = Array.isArray(element[key]) ? 76 | // ((target: unknown[]) => (value: unknown) => target.push(value))(element[key]) : 77 | // Object.assign(element[key], { length: value.length }, { ...value }); 78 | const list: DOMTokenList = isDOMTokenList(elementValue) 79 | ? elementValue 80 | : undefined; 81 | const array = 82 | !list && Array.isArray(elementValue) ? elementValue : undefined; 83 | 84 | const items = Array.from(value); 85 | if (list) { 86 | const strings = items.map((item) => `${item}`); 87 | for (const existing of Array.from(list)) { 88 | if (!strings.includes(existing)) { 89 | list.remove(existing); 90 | } 91 | } 92 | for (const string of strings) { 93 | if (!list.contains(string)) { 94 | list.add(string); 95 | } 96 | } 97 | } else if (array) { 98 | for (const existing of array) { 99 | if (items.includes(existing)) continue; 100 | const index = array.indexOf(existing); 101 | if (index === -1) continue; 102 | array.splice(index, 1); 103 | } 104 | for (const item of items) { 105 | if (!array.includes(item)) { 106 | array.push(item); 107 | } 108 | } 109 | } 110 | } else if (isObject(value) && isObject(elementValue)) { 111 | const keys = Object.keys(value); 112 | const existingKeys = Object.keys(elementValue); 113 | const removeKeys = existingKeys.filter((key) => !keys.includes(key)); 114 | for (const key of removeKeys) { 115 | elementValue[key] = undefined; 116 | } 117 | for (const key of keys) { 118 | elementValue[key] = value[key]; 119 | } 120 | } else { 121 | element[key] = value; 122 | } 123 | return true; 124 | } 125 | } 126 | 127 | export function setAttributes(element: HTMLElement, options: unknown) { 128 | const attributes = Object.entries(options ?? {}) 129 | .filter( 130 | ([key, value]) => typeof key === "string" && isStaticChildNode(value) 131 | ) 132 | .filter(([, value]) => value !== false) 133 | .map(([key, value]): [string, string] => [ 134 | key, 135 | value === true ? "" : `${value}`, 136 | ]); 137 | for (const [key, value] of attributes) { 138 | element.setAttribute(key, value); 139 | } 140 | } 141 | 142 | export const h = createElement; 143 | 144 | export async function appendChild(node: HTMLElement, parent: HTMLElement) { 145 | const appended = appendDescendants(node); 146 | await Promise.any([appended, new Promise(queueMicrotask)]); 147 | if (node instanceof HTMLElement) { 148 | if (node.parentElement) { 149 | if (node.parentElement !== parent) { 150 | node.parentElement.removeChild(node); 151 | parent.appendChild(node); 152 | } 153 | } else { 154 | parent.appendChild(node); 155 | } 156 | } 157 | await appended; 158 | } 159 | 160 | export async function appendDescendants(node: unknown) { 161 | for await (const descendants of descendantsSettled(node)) { 162 | const parents = descendants 163 | .filter(isDescendantFulfilled) 164 | .reduce((map, { value, parent }) => { 165 | let array = map.get(parent); 166 | if (!array) { 167 | array = []; 168 | map.set(parent, array); 169 | } 170 | array.push(value); 171 | return map; 172 | }, new Map()); 173 | for (const parent of parents.keys()) { 174 | if (!isNode(parent)) continue; 175 | const descendants = parents.get(parent); 176 | const nodes = descendants.map((node) => { 177 | if (isStaticChildNode(node)) { 178 | return document.createTextNode(`${node}`); 179 | } else if (node instanceof HTMLElement) { 180 | return proxy(node).instance ?? node; 181 | } else { 182 | return undefined; 183 | } 184 | }); 185 | const childNodes = parent.childNodes; 186 | const reverseNodes = [...nodes].reverse(); 187 | const maxChildNodes = Math.max(nodes.length, childNodes.length); 188 | for (let index = 0; index < maxChildNodes; index += 1) { 189 | const expected = nodes[index]; 190 | const current = childNodes[index]; 191 | const previous = reverseNodes.find( 192 | (node, nodeIndex) => !!node && nodeIndex < index 193 | ); 194 | if (expected === current) continue; 195 | if (current) { 196 | parent.removeChild(current); 197 | } 198 | if (isNode(previous) && isNode(expected)) { 199 | if (previous.nextSibling) { 200 | parent.insertBefore(expected, previous.nextSibling); 201 | } else { 202 | parent.appendChild(expected); 203 | } 204 | } else { 205 | if (childNodes.length) { 206 | for (const node of Array.from(childNodes)) { 207 | parent.removeChild(node); 208 | } 209 | } 210 | if (isNode(expected)) { 211 | parent.appendChild(expected); 212 | } 213 | } 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/tests/circuit/adder-chain.tsx: -------------------------------------------------------------------------------- 1 | import {Eight, HalfAdder, Rest} from "@virtualstate/combinational"; 2 | import {h, createFragment, children} from "@virtualstate/focus"; 3 | import {anAsyncThing} from "@virtualstate/promise/the-thing"; 4 | import {pair} from "../../circuit"; 5 | import {ok} from "@virtualstate/combinational"; 6 | import {FullAdder, rest} from "@virtualstate/combinational"; 7 | import {assertPair} from "./adder"; 8 | import {EightAdder} from "@virtualstate/combinational"; 9 | 10 | const split1 = rest( 11 | 12 | {true} 13 | {false} 14 | {false} 15 | 16 | ) 17 | 18 | 19 | async function assertRest(rest: Rest, expected?: unknown[]) { 20 | console.group({ 21 | expected 22 | }); 23 | const got = await children([ 24 | rest, 25 | rest.promise 26 | ]) 27 | console.log({ got }); 28 | assertPair( 29 | got, 30 | expected 31 | ); 32 | console.groupEnd() 33 | } 34 | 35 | /* 36 | [ 37 | false, 38 | false 39 | ] 40 | */ 41 | await assertRest( 42 | rest( 43 | 44 | {false} 45 | {false} 46 | {false} 47 | 48 | ), 49 | [ 50 | false, 51 | false 52 | ] 53 | ); 54 | 55 | /* 56 | [ 57 | true, 58 | false 59 | ] 60 | */ 61 | await assertRest( 62 | rest( 63 | 64 | {false} 65 | {false} 66 | {true} 67 | 68 | ), 69 | [ 70 | true, 71 | false 72 | ] 73 | ); 74 | 75 | 76 | /* 77 | [ 78 | true, 79 | false 80 | ] 81 | */ 82 | await assertRest( 83 | rest( 84 | 85 | {false} 86 | {true} 87 | {false} 88 | 89 | ), 90 | [ 91 | true, 92 | false 93 | ] 94 | ); 95 | /* 96 | [ 97 | false, 98 | true 99 | ] 100 | */ 101 | await assertRest( 102 | rest( 103 | 104 | {false} 105 | {true} 106 | {true} 107 | 108 | ), 109 | [ 110 | false, 111 | true 112 | ] 113 | ); 114 | 115 | 116 | /* 117 | [ 118 | true, 119 | false 120 | ] 121 | */ 122 | await assertRest( 123 | rest( 124 | 125 | {true} 126 | {false} 127 | {false} 128 | 129 | ), 130 | [ 131 | true, 132 | false 133 | ] 134 | ); 135 | 136 | /* 137 | [ 138 | false, 139 | true 140 | ] 141 | */ 142 | await assertRest( 143 | rest( 144 | 145 | {true} 146 | {false} 147 | {true} 148 | 149 | ), 150 | [ 151 | false, 152 | true 153 | ] 154 | ); 155 | /* 156 | [ 157 | false, 158 | true 159 | ] 160 | */ 161 | await assertRest( 162 | rest( 163 | 164 | {true} 165 | {true} 166 | {false} 167 | 168 | ), 169 | [ 170 | false, 171 | true 172 | ] 173 | ); 174 | 175 | 176 | /* 177 | [ 178 | true, 179 | true 180 | ] 181 | */ 182 | await assertRest( 183 | rest( 184 | 185 | {true} 186 | {true} 187 | {true} 188 | 189 | ), 190 | [ 191 | true, 192 | true 193 | ] 194 | ); 195 | 196 | // 0 + 0 = 0 197 | console.log(await children( 198 | 199 | {false} 200 | {false} 201 | {false} 202 | {false} 203 | {false} 204 | {false} 205 | {false} 206 | {false} 207 | {false} 208 | {false} 209 | {false} 210 | {false} 211 | {false} 212 | {false} 213 | {false} 214 | {false} 215 | 216 | )) 217 | // 1 + 0 = 1 218 | const one = 219 | {true} 220 | {false} 221 | {false} 222 | {false} 223 | {false} 224 | {false} 225 | {false} 226 | {false} 227 | {false} 228 | {false} 229 | {false} 230 | {false} 231 | {false} 232 | {false} 233 | {false} 234 | {false} 235 | ; 236 | console.log(await children(one)) 237 | // 1 + 1 = 2 238 | // 1 + 1 = 10 239 | const two = 240 | {true} 241 | {false} 242 | {false} 243 | {false} 244 | {false} 245 | {false} 246 | {false} 247 | {false} 248 | 249 | {true} 250 | {false} 251 | {false} 252 | {false} 253 | {false} 254 | {false} 255 | {false} 256 | {false} 257 | 258 | console.log(await children(two)) 259 | // 3 + 1 = 4 260 | // 11 + 1 = 100 261 | const four = 262 | {true} 263 | {true} 264 | {false} 265 | {false} 266 | {false} 267 | {false} 268 | {false} 269 | {false} 270 | 271 | {true} 272 | {false} 273 | {false} 274 | {false} 275 | {false} 276 | {false} 277 | {false} 278 | {false} 279 | 280 | console.log(await children(four)) 281 | // 3 + 3 = 6 282 | // 11 + 11 = 110 283 | const six = 284 | {true} 285 | {true} 286 | {false} 287 | {false} 288 | {false} 289 | {false} 290 | {false} 291 | {false} 292 | 293 | {true} 294 | {true} 295 | {false} 296 | {false} 297 | {false} 298 | {false} 299 | {false} 300 | {false} 301 | ; 302 | console.log(await children(six)) 303 | 304 | function assertBooleanResult(binaryBooleans: unknown[], expectedNumberResult: number) { 305 | const expectedBinary = expectedNumberResult.toString(2).padStart(8, "0"); 306 | 307 | const binary = binaryBooleans 308 | .slice(0, 8) 309 | .reverse() 310 | .map(value => value ? "1" : "0") 311 | .join(""); 312 | 313 | ok(binary === expectedBinary, `Expected ${expectedBinary} got ${binary}`); 314 | 315 | const parsed = parseInt(binary, 2); 316 | 317 | ok(parsed === expectedNumberResult, `Expected ${expectedNumberResult} got ${parsed}`); 318 | } 319 | 320 | const seven = ( 321 | 322 | 323 | {one} 324 | 325 | 326 | {six} 327 | 328 | 329 | ) 330 | 331 | assertBooleanResult( 332 | await children( 333 | seven, 334 | ), 335 | 7 336 | ); 337 | const fourteen = ( 338 | 339 | 340 | {seven} 341 | 342 | 343 | {seven} 344 | 345 | 346 | ) 347 | assertBooleanResult(await children(fourteen), 14); 348 | const twentyEight = ( 349 | 350 | 351 | {fourteen} 352 | 353 | 354 | {fourteen} 355 | 356 | 357 | ); 358 | assertBooleanResult(await children(twentyEight), 28); 359 | 360 | async function add(a: number, b: number) { 361 | const expectedNumberResult = a + b; 362 | 363 | ok(expectedNumberResult <= 255); 364 | 365 | const aBinary = a.toString(2).padStart(8, "0"); 366 | const bBinary = b.toString(2).padStart(8, "0"); 367 | const aBinaryBooleans = [...aBinary].reverse().map(value => value === "1"); 368 | const bBinaryBooleans = [...bBinary].reverse().map(value => value === "1"); 369 | 370 | const expectedBinary = expectedNumberResult.toString(2).padStart(8, "0"); 371 | 372 | console.group(`${a} + ${b} = ${expectedNumberResult}\n${aBinary} + ${bBinary} = ${expectedBinary}`); 373 | 374 | const result = await children( 375 | 376 | {...aBinaryBooleans} 377 | {...bBinaryBooleans} 378 | 379 | ); 380 | 381 | assertBooleanResult(result, expectedNumberResult); 382 | 383 | console.groupEnd(); 384 | } 385 | 386 | for (let i = 0; i < 255; i += Math.random()) { 387 | const a = Math.min(255, Math.round(i + (Math.random() * 5))); 388 | const b = Math.min(255, Math.round(i + (Math.random() * 5))); 389 | /* c8 ignore start */ 390 | if (a + b > 255) break; 391 | /* c8 ignore stop */ 392 | await add(a, b); 393 | /* c8 ignore start */ 394 | if (a + b === 255) break; 395 | /* c8 ignore stop */ 396 | } --------------------------------------------------------------------------------