diff --git a/README.md b/README.md index 15a50cc8f6..4fb95a3711 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # @thi.ng/umbrella [![Travis status](https://api.travis-ci.org/thi-ng/umbrella.svg?branch=master)](https://travis-ci.org/thi-ng/umbrella) +[![Code Climate](https://api.codeclimate.com/v1/badges/592940419adb5bf8abaf/maintainability)](https://codeclimate.com/github/thi-ng/umbrella/maintainability) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org/) [![Discord chat](https://img.shields.io/discord/445761008837984256.svg)](https://discord.gg/JhYcmBw) [![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?label=%40thing_umbrella&style=social)](https://twitter.com/thing_umbrella) diff --git a/examples/geom-convex-hull/src/webpack.d.ts b/examples/geom-convex-hull/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/geom-convex-hull/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/imgui/README.md b/examples/imgui/README.md index 149ce48398..9a287dd366 100644 --- a/examples/imgui/README.md +++ b/examples/imgui/README.md @@ -1,6 +1,6 @@ # imgui -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/feature/imgui/assets/screenshots/imgui-demo.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/imgui-demo.png) [Live demo](http://demo.thi.ng/umbrella/imgui/) diff --git a/examples/imgui/index.html b/examples/imgui/index.html index 4a1c63715d..7087dbe5f0 100644 --- a/examples/imgui/index.html +++ b/examples/imgui/index.html @@ -39,7 +39,7 @@ Source code / Info diff --git a/examples/imgui/src/webpack.d.ts b/examples/imgui/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/imgui/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/pixel-basics/src/webpack.d.ts b/examples/pixel-basics/src/webpack.d.ts new file mode 100644 index 0000000000..891c762327 --- /dev/null +++ b/examples/pixel-basics/src/webpack.d.ts @@ -0,0 +1,2 @@ +declare module "*.jpg"; +declare module "*.png"; diff --git a/examples/porter-duff/src/webpack.d.ts b/examples/porter-duff/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/porter-duff/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/scenegraph-image/src/webpack.d.ts b/examples/scenegraph-image/src/webpack.d.ts new file mode 100644 index 0000000000..65e20c0a38 --- /dev/null +++ b/examples/scenegraph-image/src/webpack.d.ts @@ -0,0 +1,2 @@ +declare module "*.png"; +declare module "*.jpg"; diff --git a/examples/shader-ast-noise/index.html b/examples/shader-ast-noise/index.html index 7a1fc0bfb0..c95735abab 100644 --- a/examples/shader-ast-noise/index.html +++ b/examples/shader-ast-noise/index.html @@ -27,7 +27,7 @@

Source code

diff --git a/examples/shader-ast-raymarch/index.html b/examples/shader-ast-raymarch/index.html index 4df683ba16..c030e2f6b8 100644 --- a/examples/shader-ast-raymarch/index.html +++ b/examples/shader-ast-raymarch/index.html @@ -27,7 +27,7 @@

Source code

diff --git a/examples/shader-ast-sdf2d/index.html b/examples/shader-ast-sdf2d/index.html index bf6558aac0..b1bb4735af 100644 --- a/examples/shader-ast-sdf2d/index.html +++ b/examples/shader-ast-sdf2d/index.html @@ -27,7 +27,7 @@

Source code

diff --git a/examples/shader-ast-sdf2d/package.json b/examples/shader-ast-sdf2d/package.json index ee609cee03..56f8728490 100644 --- a/examples/shader-ast-sdf2d/package.json +++ b/examples/shader-ast-sdf2d/package.json @@ -7,6 +7,7 @@ "scripts": { "clean": "rm -rf .cache build out", "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report", + "build:webpack": "../../node_modules/.bin/webpack --mode production", "start": "parcel index.html -p 8080 --open" }, "devDependencies": { diff --git a/examples/shader-ast-sdf2d/webpack.config.js b/examples/shader-ast-sdf2d/webpack.config.js new file mode 100644 index 0000000000..bf16021356 --- /dev/null +++ b/examples/shader-ast-sdf2d/webpack.config.js @@ -0,0 +1,23 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: { + process: false + } +}; diff --git a/examples/shader-ast-tunnel/index.html b/examples/shader-ast-tunnel/index.html index c0b8c1f0e7..74e1fac015 100644 --- a/examples/shader-ast-tunnel/index.html +++ b/examples/shader-ast-tunnel/index.html @@ -27,7 +27,7 @@

Source code

diff --git a/examples/shader-ast-tunnel/package.json b/examples/shader-ast-tunnel/package.json index bb0d0ea3c0..375e237d53 100644 --- a/examples/shader-ast-tunnel/package.json +++ b/examples/shader-ast-tunnel/package.json @@ -7,6 +7,7 @@ "scripts": { "clean": "rm -rf .cache build out", "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report", + "build:webpack": "../../node_modules/.bin/webpack --mode production", "start": "parcel index.html -p 8080 --open" }, "devDependencies": { diff --git a/examples/shader-ast-tunnel/src/webpack.d.ts b/examples/shader-ast-tunnel/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/shader-ast-tunnel/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/shader-ast-tunnel/webpack.config.js b/examples/shader-ast-tunnel/webpack.config.js new file mode 100644 index 0000000000..bf16021356 --- /dev/null +++ b/examples/shader-ast-tunnel/webpack.config.js @@ -0,0 +1,23 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: { + process: false + } +}; diff --git a/examples/texgen/src/webpack.d.ts b/examples/texgen/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/texgen/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/packages/adjacency/CHANGELOG.md b/packages/adjacency/CHANGELOG.md index 45f8ab9d53..47b979685e 100644 --- a/packages/adjacency/CHANGELOG.md +++ b/packages/adjacency/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.22...@thi.ng/adjacency@0.1.23) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/adjacency + + + + + +## [0.1.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.21...@thi.ng/adjacency@0.1.22) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/adjacency + + + + + ## [0.1.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.20...@thi.ng/adjacency@0.1.21) (2019-08-16) **Note:** Version bump only for package @thi.ng/adjacency diff --git a/packages/adjacency/package.json b/packages/adjacency/package.json index 07890c9926..8cb5f2c8ae 100644 --- a/packages/adjacency/package.json +++ b/packages/adjacency/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/adjacency", - "version": "0.1.21", + "version": "0.1.23", "description": "Sparse & bitwise adjacency matrices for directed / undirected graphs", "module": "./index.js", "main": "./lib/index.js", @@ -25,7 +25,7 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/vectors": "^3.1.1", + "@thi.ng/vectors": "^3.3.0", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", @@ -34,12 +34,12 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/binary": "^1.1.0", - "@thi.ng/bitfield": "^0.1.11", + "@thi.ng/bitfield": "^0.1.12", "@thi.ng/checks": "^2.3.0", - "@thi.ng/dcons": "^2.1.3", - "@thi.ng/sparse": "^0.1.19" + "@thi.ng/dcons": "^2.1.4", + "@thi.ng/sparse": "^0.1.20" }, "keywords": [ "adjacency", diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index b33dc124e8..e11c49192c 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@6.3.2...@thi.ng/api@6.3.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/api + + + + + ## [6.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@6.3.1...@thi.ng/api@6.3.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/api diff --git a/packages/api/package.json b/packages/api/package.json index 9c226a18bc..e589b0a116 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/api", - "version": "6.3.2", + "version": "6.3.3", "description": "Common, generic types & interfaces for thi.ng projects", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "compare", diff --git a/packages/arrays/CHANGELOG.md b/packages/arrays/CHANGELOG.md index 14232cee7a..68115096f6 100644 --- a/packages/arrays/CHANGELOG.md +++ b/packages/arrays/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/arrays@0.2.3...@thi.ng/arrays@0.2.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/arrays + + + + + ## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/arrays@0.2.2...@thi.ng/arrays@0.2.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/arrays diff --git a/packages/arrays/package.json b/packages/arrays/package.json index bc3c6060a0..b62160d6d2 100644 --- a/packages/arrays/package.json +++ b/packages/arrays/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/arrays", - "version": "0.2.3", + "version": "0.2.4", "description": "Array / Arraylike utilities", "module": "./index.js", "main": "./lib/index.js", @@ -33,12 +33,12 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", "@thi.ng/compare": "^1.0.9", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/random": "^1.1.10" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/random": "^1.1.11" }, "keywords": [ "arrays", diff --git a/packages/associative/CHANGELOG.md b/packages/associative/CHANGELOG.md index 8aedeaa30c..c4abc6a04c 100644 --- a/packages/associative/CHANGELOG.md +++ b/packages/associative/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@2.4.3...@thi.ng/associative@3.0.0) (2019-08-21) + + +### Code Refactoring + +* **associative:** update XXXMap.dissoc() signature to unify API ([632c57a](https://github.com/thi-ng/umbrella/commit/632c57a)) + + +### BREAKING CHANGES + +* **associative:** dissoc() method signature changed from varargs to `Iterable` + +Example: + +- previously: `HashMap.dissoc(1, 2, 3)` +- now: `HashMap.dissoc([1, 2, 3])` + +This new signature is the same as used by `dissoc()` standalone fn and +the `disj()` methods of the various Sets in this package. + + + + + ## [2.4.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@2.4.2...@thi.ng/associative@2.4.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/associative diff --git a/packages/associative/package.json b/packages/associative/package.json index 1c8dd1866d..d650cdc92b 100644 --- a/packages/associative/package.json +++ b/packages/associative/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/associative", - "version": "2.4.3", + "version": "3.0.0", "description": "Alternative Set & Map data type implementations with customizable equality semantics & supporting operations", "module": "./index.js", "main": "./lib/index.js", @@ -33,14 +33,14 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/binary": "^1.1.0", "@thi.ng/checks": "^2.3.0", "@thi.ng/compare": "^1.0.9", - "@thi.ng/dcons": "^2.1.3", + "@thi.ng/dcons": "^2.1.4", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "data structures", diff --git a/packages/associative/src/api.ts b/packages/associative/src/api.ts index 1d2b9d2996..54ff5ea2fb 100644 --- a/packages/associative/src/api.ts +++ b/packages/associative/src/api.ts @@ -40,14 +40,20 @@ export interface EquivMapOpts extends EquivSetOpts { keys: EquivSetConstructor; } +/** + * Hash function for key values of type K + */ +export type HashFn = Fn; + /** * Creation options for HashMap class. */ export interface HashMapOpts { /** - * Function for computing key hash codes. MUST be supplied. + * Function for computing key hash codes. MUST be supplied. Only + * numeric hashes are supported. */ - hash: Fn; + hash: HashFn; /** * Optional key equality predicate. Default: thi.ng/equiv */ diff --git a/packages/associative/src/array-set.ts b/packages/associative/src/array-set.ts index a908aec9be..f3ccb5e9d7 100644 --- a/packages/associative/src/array-set.ts +++ b/packages/associative/src/array-set.ts @@ -6,6 +6,9 @@ import { } from "@thi.ng/api"; import { equiv } from "@thi.ng/equiv"; import { EquivSetOpts, IEquivSet } from "./api"; +import { dissoc } from "./dissoc"; +import { equivSet } from "./internal/equiv"; +import { into } from "./into"; interface ArraySetProps { vals: T[]; @@ -14,6 +17,8 @@ interface ArraySetProps { const __private = new WeakMap, ArraySetProps>(); +const __vals = (inst: ArraySet) => __private.get(inst)!.vals; + /** * An alternative set implementation to the native ES6 Set type. Uses * customizable equality/equivalence predicate and so is more useful @@ -34,7 +39,7 @@ export class ArraySet extends Set implements IEquivSet { } *[Symbol.iterator](): IterableIterator { - yield* __private.get(this)!.vals; + yield* __vals(this); } get [Symbol.species]() { @@ -46,7 +51,7 @@ export class ArraySet extends Set implements IEquivSet { } get size(): number { - return __private.get(this)!.vals.length; + return __vals(this).length; } copy(): ArraySet { @@ -61,56 +66,53 @@ export class ArraySet extends Set implements IEquivSet { } clear() { - __private.get(this)!.vals.length = 0; + __vals(this).length = 0; } first(): T | undefined { if (this.size) { - return __private.get(this)!.vals[0]; + return __vals(this)[0]; } } - add(x: T) { - !this.has(x) && __private.get(this)!.vals.push(x); + add(key: T) { + !this.has(key) && __vals(this).push(key); return this; } - into(xs: Iterable) { - for (let x of xs) { - this.add(x); - } - return this; + into(keys: Iterable) { + return into(this, keys); } - has(x: T) { - return this.get(x, SEMAPHORE) !== SEMAPHORE; + has(key: T) { + return this.get(key, SEMAPHORE) !== SEMAPHORE; } /** * Returns the canonical value for `x`, if present. If the set * contains no equivalent for `x`, returns `notFound`. * - * @param x + * @param key * @param notFound */ - get(x: T, notFound?: T): T | undefined { + get(key: T, notFound?: T): T | undefined { const $this = __private.get(this)!; const eq = $this.equiv; const vals = $this.vals; - for (let i = vals.length - 1; i >= 0; i--) { - if (eq(vals[i], x)) { + for (let i = vals.length; --i >= 0; ) { + if (eq(vals[i], key)) { return vals[i]; } } return notFound; } - delete(x: T) { + delete(key: T) { const $this = __private.get(this)!; const eq = $this.equiv; const vals = $this.vals; - for (let i = vals.length - 1; i >= 0; i--) { - if (eq(vals[i], x)) { + for (let i = vals.length; --i >= 0; ) { + if (eq(vals[i], key)) { vals.splice(i, 1); return true; } @@ -118,34 +120,16 @@ export class ArraySet extends Set implements IEquivSet { return false; } - disj(xs: Iterable) { - for (let x of xs) { - this.delete(x); - } - return this; + disj(keys: Iterable) { + return dissoc(this, keys); } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Set)) { - return false; - } - if (this.size !== o.size) { - return false; - } - const vals = __private.get(this)!.vals; - for (let i = vals.length; --i >= 0; ) { - if (!o.has(vals[i])) { - return false; - } - } - return true; + return equivSet(this, o); } forEach(fn: Fn3, Readonly, Set, void>, thisArg?: any) { - const vals = __private.get(this)!.vals; + const vals = __vals(this); for (let i = vals.length; --i >= 0; ) { const v = vals[i]; fn.call(thisArg, v, v, this); @@ -153,17 +137,17 @@ export class ArraySet extends Set implements IEquivSet { } *entries(): IterableIterator> { - for (let v of __private.get(this)!.vals) { + for (let v of __vals(this)) { yield [v, v]; } } *keys(): IterableIterator { - yield* __private.get(this)!.vals; + yield* __vals(this); } *values(): IterableIterator { - yield* this.keys(); + yield* __vals(this); } opts(): EquivSetOpts { diff --git a/packages/associative/src/dissoc.ts b/packages/associative/src/dissoc.ts new file mode 100644 index 0000000000..c55abf645c --- /dev/null +++ b/packages/associative/src/dissoc.ts @@ -0,0 +1,20 @@ +import { IObjectOf } from "@thi.ng/api"; + +export function dissoc(map: Map, keys: Iterable): Map; +export function dissoc(set: Set, keys: Iterable): Set; +export function dissoc(coll: Map | Set, keys: Iterable) { + for (let k of keys) { + coll.delete(k); + } + return coll; +} + +export const dissocObj = ( + obj: IObjectOf, + keys: Iterable +) => { + for (let k of keys) { + delete obj[k]; + } + return obj; +}; diff --git a/packages/associative/src/equiv-map.ts b/packages/associative/src/equiv-map.ts index 82bfdf6841..753027e765 100644 --- a/packages/associative/src/equiv-map.ts +++ b/packages/associative/src/equiv-map.ts @@ -10,6 +10,9 @@ import { import { equiv } from "@thi.ng/equiv"; import { EquivMapOpts, IEquivSet } from "./api"; import { ArraySet } from "./array-set"; +import { dissoc } from "./dissoc"; +import { equivMap } from "./internal/equiv"; +import { into } from "./into"; interface MapProps { keys: IEquivSet; @@ -19,6 +22,8 @@ interface MapProps { const __private = new WeakMap, MapProps>(); +const __map = (map: EquivMap) => __private.get(map)!.map; + export class EquivMap extends Map implements Iterable>, @@ -110,21 +115,7 @@ export class EquivMap extends Map } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Map)) { - return false; - } - if (this.size !== o.size) { - return false; - } - for (let p of __private.get(this)!.map.entries()) { - if (!equiv(o.get(p[0]), p[1])) { - return false; - } - } - return true; + return equivMap(this, o); } delete(key: K) { @@ -138,15 +129,12 @@ export class EquivMap extends Map return false; } - dissoc(...keys: K[]) { - for (let k of keys) { - this.delete(k); - } - return this; + dissoc(keys: Iterable) { + return dissoc(this, keys); } forEach(fn: Fn3, Map, void>, thisArg?: any) { - for (let pair of __private.get(this)!.map) { + for (let pair of __map(this)) { fn.call(thisArg, pair[1], pair[0], this); } } @@ -177,22 +165,19 @@ export class EquivMap extends Map } into(pairs: Iterable>) { - for (let p of pairs) { - this.set(p[0], p[1]); - } - return this; + return into(this, pairs); } entries(): IterableIterator> { - return __private.get(this)!.map.entries(); + return __map(this).entries(); } keys(): IterableIterator { - return __private.get(this)!.map.keys(); + return __map(this).keys(); } values(): IterableIterator { - return __private.get(this)!.map.values(); + return __map(this).values(); } opts(): EquivMapOpts { diff --git a/packages/associative/src/hash-map.ts b/packages/associative/src/hash-map.ts index 14bffc14a8..21a76a4b0e 100644 --- a/packages/associative/src/hash-map.ts +++ b/packages/associative/src/hash-map.ts @@ -10,6 +10,9 @@ import { import { ceilPow2 } from "@thi.ng/binary"; import { equiv } from "@thi.ng/equiv"; import { HashMapOpts } from "./api"; +import { dissoc } from "./dissoc"; +import { equivMap } from "./internal/equiv"; +import { into } from "./into"; interface HashMapState { hash: Fn; @@ -22,12 +25,19 @@ interface HashMapState { const __private = new WeakMap, HashMapState>(); +const __iterator = (map: HashMap, id: 0 | 1) => + function*() { + for (let p of __private.get(map)!.bins) { + if (p) yield p[id]; + } + }; + const DEFAULT_CAP = 16; /** - * Configurable hash map implementation w/ ES6 Map API and using open + * Configurable hash map implementation w/ ES6 Map API. Uses open * addressing / linear probing to resolve key collisions. Supports any - * key types, via user supplied hash function. + * key types via mandatory user supplied hash function. * * See `HashMapOpts` for further configuration & behavior details. * @@ -87,16 +97,12 @@ export class HashMap extends Map } } - *keys(): IterableIterator { - for (let p of __private.get(this)!.bins) { - if (p) yield p[0]; - } + keys(): IterableIterator { + return __iterator(this, 0)(); } - *values(): IterableIterator { - for (let p of __private.get(this)!.bins) { - if (p) yield p[1]; - } + values(): IterableIterator { + return __iterator(this, 1)(); } forEach(fn: Fn3, Map, void>, thisArg?: any) { @@ -128,21 +134,7 @@ export class HashMap extends Map } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Map)) { - return false; - } - if (this.size !== o.size) { - return false; - } - for (let p of __private.get(this)!.bins) { - if (p && !equiv(o.get(p[0]), p[1])) { - return false; - } - } - return true; + return equivMap(this, o); } has(key: K): boolean { @@ -197,17 +189,11 @@ export class HashMap extends Map } into(pairs: Iterable>) { - for (let p of pairs) { - this.set(p[0], p[1]); - } - return this; + return into(this, pairs); } - dissoc(...keys: K[]) { - for (let k of keys) { - this.delete(k); - } - return this; + dissoc(keys: Iterable) { + return dissoc(this, keys); } opts(overrides?: Partial>): HashMapOpts { diff --git a/packages/associative/src/index.ts b/packages/associative/src/index.ts index 85c818f47c..bb46785349 100644 --- a/packages/associative/src/index.ts +++ b/packages/associative/src/index.ts @@ -1,6 +1,7 @@ export * from "./array-set"; export * from "./common-keys"; export * from "./difference"; +export * from "./dissoc"; export * from "./equiv-map"; export * from "./hash-map"; export * from "./indexed"; diff --git a/packages/associative/src/internal/equiv.ts b/packages/associative/src/internal/equiv.ts new file mode 100644 index 0000000000..cd02679f61 --- /dev/null +++ b/packages/associative/src/internal/equiv.ts @@ -0,0 +1,31 @@ +import { equiv } from "@thi.ng/equiv"; + +export const equivMap = (a: Map, b: any) => { + if (a === b) { + return true; + } + if (!(b instanceof Map) || a.size !== b.size) { + return false; + } + for (let p of a.entries()) { + if (!equiv(b.get(p[0]), p[1])) { + return false; + } + } + return true; +}; + +export const equivSet = (a: Set, b: any) => { + if (a === b) { + return true; + } + if (!(b instanceof Set) || a.size !== b.size) { + return false; + } + for (let k of a.keys()) { + if (!b.has(k)) { + return false; + } + } + return true; +}; diff --git a/packages/associative/src/into.ts b/packages/associative/src/into.ts index c7aac008b7..e68ae0d576 100644 --- a/packages/associative/src/into.ts +++ b/packages/associative/src/into.ts @@ -7,10 +7,8 @@ import { isMap } from "@thi.ng/checks"; * @param dest * @param src */ -export function into( - dest: Map, - src: Iterable> -): Map; +// prettier-ignore +export function into(dest: Map, src: Iterable>): Map; export function into(dest: Set, src: Iterable): Set; export function into(dest: Map | Set, src: Iterable) { if (isMap(dest)) { diff --git a/packages/associative/src/ll-set.ts b/packages/associative/src/ll-set.ts index 7a4a418c7e..700db7bbab 100644 --- a/packages/associative/src/ll-set.ts +++ b/packages/associative/src/ll-set.ts @@ -7,6 +7,9 @@ import { import { DCons } from "@thi.ng/dcons"; import { equiv } from "@thi.ng/equiv"; import { EquivSetOpts, IEquivSet } from "./api"; +import { dissoc } from "./dissoc"; +import { equivSet } from "./internal/equiv"; +import { into } from "./into"; interface SetProps { vals: DCons; @@ -15,6 +18,8 @@ interface SetProps { const __private = new WeakMap, SetProps>(); +const __vals = (inst: LLSet) => __private.get(inst)!.vals; + /** * Similar to `ArraySet`, this class is an alternative implementation of * the native ES6 Set API using a @thi.ng/dcons linked list as backing @@ -38,7 +43,7 @@ export class LLSet extends Set implements IEquivSet { } *[Symbol.iterator](): IterableIterator { - yield* __private.get(this)!.vals; + yield* __vals(this); } get [Symbol.species]() { @@ -50,7 +55,7 @@ export class LLSet extends Set implements IEquivSet { } get size(): number { - return __private.get(this)!.vals.length; + return __vals(this).length; } copy() { @@ -65,44 +70,41 @@ export class LLSet extends Set implements IEquivSet { } clear() { - __private.get(this)!.vals.clear(); + __vals(this).clear(); } first(): T | undefined { if (this.size) { - return __private.get(this)!.vals.head!.value; + return __vals(this).head!.value; } } - add(x: T) { - !this.has(x) && __private.get(this)!.vals.push(x); + add(key: T) { + !this.has(key) && __vals(this).push(key); return this; } - into(xs: Iterable) { - for (let x of xs) { - this.add(x); - } - return this; + into(keys: Iterable) { + return into(this, keys); } - has(x: T) { - return this.get(x, SEMAPHORE) !== SEMAPHORE; + has(key: T) { + return this.get(key, SEMAPHORE) !== SEMAPHORE; } /** - * Returns the canonical value for `x`, if present. If the set - * contains no equivalent for `x`, returns `notFound`. + * Returns the canonical (stored) value for `key`, if present. If + * the set contains no equivalent for `key`, returns `notFound`. * - * @param x + * @param key * @param notFound */ - get(x: T, notFound?: T): T | undefined { + get(key: T, notFound?: T): T | undefined { const $this = __private.get(this)!; const eq = $this.equiv; let i = $this.vals.head; while (i) { - if (eq(i.value, x)) { + if (eq(i.value, key)) { return i.value; } i = i.next; @@ -110,12 +112,12 @@ export class LLSet extends Set implements IEquivSet { return notFound; } - delete(x: T) { + delete(key: T) { const $this = __private.get(this)!; const eq = $this.equiv; let i = $this.vals.head; while (i) { - if (eq(i.value, x)) { + if (eq(i.value, key)) { $this.vals.splice(i, 1); return true; } @@ -124,35 +126,16 @@ export class LLSet extends Set implements IEquivSet { return false; } - disj(xs: Iterable) { - for (let x of xs) { - this.delete(x); - } - return this; + disj(keys: Iterable) { + return dissoc(this, keys); } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Set)) { - return false; - } - if (this.size !== o.size) { - return false; - } - let i = __private.get(this)!.vals.head; - while (i) { - if (!o.has(i.value)) { - return false; - } - i = i.next; - } - return true; + return equivSet(this, o); } forEach(fn: Fn3, Readonly, Set, void>, thisArg?: any) { - let i = __private.get(this)!.vals.head; + let i = __vals(this).head; while (i) { fn.call(thisArg, i.value, i.value, this); i = i.next; @@ -160,17 +143,17 @@ export class LLSet extends Set implements IEquivSet { } *entries(): IterableIterator> { - for (let v of __private.get(this)!.vals) { + for (let v of __vals(this)) { yield [v, v]; } } *keys(): IterableIterator { - yield* __private.get(this)!.vals; + yield* __vals(this); } *values(): IterableIterator { - yield* this.keys(); + yield* __vals(this); } opts(): EquivSetOpts { diff --git a/packages/associative/src/sorted-map.ts b/packages/associative/src/sorted-map.ts index ffb88e8c04..b53513ba7b 100644 --- a/packages/associative/src/sorted-map.ts +++ b/packages/associative/src/sorted-map.ts @@ -6,9 +6,11 @@ import { SEMAPHORE } from "@thi.ng/api"; import { compare } from "@thi.ng/compare"; -import { equiv } from "@thi.ng/equiv"; import { isReduced, map, ReductionFn } from "@thi.ng/transducers"; import { SortedMapOpts } from "./api"; +import { dissoc } from "./dissoc"; +import { equivMap } from "./internal/equiv"; +import { into } from "./into"; interface SortedMapState { head: Node; @@ -161,10 +163,10 @@ export class SortedMap extends Map { let x: IteratorResult>, y: IteratorResult>; let c: number; while (((x = i.next()), (y = j.next()), !x.done && !y.done)) { - if ((c = compare(x.value[0], y.value[0])) !== 0) { - return c; - } - if ((c = compare(x.value[1], y.value[1])) !== 0) { + if ( + (c = compare(x.value[0], y.value[0])) !== 0 || + (c = compare(x.value[1], y.value[1])) !== 0 + ) { return c; } } @@ -172,21 +174,7 @@ export class SortedMap extends Map { } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Map)) { - return false; - } - if (this.size !== o.size) { - return false; - } - for (let p of this.entries()) { - if (!equiv(o.get(p[0]), p[1])) { - return false; - } - } - return true; + return equivMap(this, o); } first(): Pair | undefined { @@ -272,17 +260,11 @@ export class SortedMap extends Map { } into(pairs: Iterable>) { - for (let p of pairs) { - this.set(p[0], p[1]); - } - return this; + return into(this, pairs); } - dissoc(...keys: K[]) { - for (let k of keys) { - this.delete(k); - } - return this; + dissoc(keys: Iterable) { + return dissoc(this, keys); } forEach(fn: Fn3, Map, void>, thisArg?: any) { diff --git a/packages/associative/src/sorted-set.ts b/packages/associative/src/sorted-set.ts index aaca4c4fcb..5e45b78f24 100644 --- a/packages/associative/src/sorted-set.ts +++ b/packages/associative/src/sorted-set.ts @@ -2,6 +2,9 @@ import { Fn3, ICompare, Pair } from "@thi.ng/api"; import { compare } from "@thi.ng/compare"; import { IReducible, map, ReductionFn } from "@thi.ng/transducers"; import { IEquivSet, SortedSetOpts } from "./api"; +import { dissoc } from "./dissoc"; +import { equivSet } from "./internal/equiv"; +import { into } from "./into"; import { SortedMap } from "./sorted-map"; const __private = new WeakMap, SortedMap>(); @@ -87,21 +90,7 @@ export class SortedSet extends Set } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Set)) { - return false; - } - if (this.size !== o.size) { - return false; - } - for (let k of this.keys()) { - if (!o.has(k)) { - return false; - } - } - return true; + return equivSet(this, o); } $reduce(rfn: ReductionFn, acc: any): any { @@ -120,16 +109,13 @@ export class SortedSet extends Set return __private.get(this)!.values(key, max); } - add(value: T) { - __private.get(this)!.set(value, value); + add(key: T) { + __private.get(this)!.set(key, key); return this; } - into(xs: Iterable) { - for (let x of xs) { - this.add(x); - } - return this; + into(keys: Iterable) { + return into(this, keys); } clear(): void { @@ -141,15 +127,12 @@ export class SortedSet extends Set return first ? first[0] : undefined; } - delete(value: T): boolean { - return __private.get(this)!.delete(value); + delete(key: T): boolean { + return __private.get(this)!.delete(key); } - disj(xs: Iterable) { - for (let x of xs) { - this.delete(x); - } - return this; + disj(keys: Iterable) { + return dissoc(this, keys); } forEach( @@ -161,12 +144,12 @@ export class SortedSet extends Set } } - has(value: T): boolean { - return __private.get(this)!.has(value); + has(key: T): boolean { + return __private.get(this)!.has(key); } - get(value: T, notFound?: T): T | undefined { - return __private.get(this)!.get(value, notFound); + get(key: T, notFound?: T): T | undefined { + return __private.get(this)!.get(key, notFound); } opts(): SortedSetOpts { diff --git a/packages/associative/src/sparse-set.ts b/packages/associative/src/sparse-set.ts index af7e96e51b..5c93d8efca 100644 --- a/packages/associative/src/sparse-set.ts +++ b/packages/associative/src/sparse-set.ts @@ -7,6 +7,8 @@ import { import { isNumber } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; import { IEquivSet } from "./api"; +import { dissoc } from "./dissoc"; +import { into } from "./into"; interface SparseSetProps { dense: UIntArray; @@ -53,10 +55,7 @@ export abstract class ASparseSet extends Set if (this === o) { return true; } - if (!(o instanceof Set)) { - return false; - } - if (this.size !== o.size) { + if (!(o instanceof Set) || this.size !== o.size) { return false; } const $this = __private.get(this)!; @@ -69,27 +68,27 @@ export abstract class ASparseSet extends Set return true; } - add(k: number) { + add(key: number) { const $this = __private.get(this)!; const dense = $this.dense; const sparse = $this.sparse; const max = dense.length; - const i = sparse[k]; + const i = sparse[key]; const n = $this.n; - if (k < max && n < max && !(i < n && dense[i] === k)) { - dense[n] = k; - sparse[k] = n; + if (key < max && n < max && !(i < n && dense[i] === key)) { + dense[n] = key; + sparse[key] = n; $this.n++; } return this; } - delete(k: number) { + delete(key: number) { const $this = __private.get(this)!; const dense = $this.dense; const sparse = $this.sparse; - const i = sparse[k]; - if (i < $this.n && dense[i] === k) { + const i = sparse[key]; + if (i < $this.n && dense[i] === key) { const j = dense[--$this.n]; dense[i] = j; sparse[j] = i; @@ -98,14 +97,14 @@ export abstract class ASparseSet extends Set return false; } - has(k: number): boolean { + has(key: number): boolean { const $this = __private.get(this)!; - const i = $this.sparse[k]; - return i < $this.n && $this.dense[i] === k; + const i = $this.sparse[key]; + return i < $this.n && $this.dense[i] === key; } - get(k: number, notFound = -1) { - return this.has(k) ? k : notFound; + get(key: number, notFound = -1) { + return this.has(key) ? key : notFound; } first() { @@ -113,18 +112,12 @@ export abstract class ASparseSet extends Set return $this.n ? $this.dense[0] : undefined; } - into(ks: Iterable) { - for (let k of ks) { - this.add(k); - } - return this; + into(keys: Iterable) { + return into(this, keys); } - disj(ks: Iterable) { - for (let k of ks) { - this.delete(k); - } - return this; + disj(keys: Iterable) { + return dissoc(this, keys); } forEach(fn: Fn3, void>, thisArg?: any) { @@ -159,12 +152,13 @@ export abstract class ASparseSet extends Set return this.keys(); } - protected __copy(c: ASparseSet) { + protected __copyTo>(dest: S) { const $this = __private.get(this)!; - const $c = __private.get(c)!; + const $c = __private.get(dest)!; $c.dense = $this.dense.slice(); $c.sparse = $this.sparse.slice(); $c.n = $this.n; + return dest; } } @@ -189,9 +183,7 @@ export class SparseSet8 extends ASparseSet } copy() { - const c = new SparseSet8(0); - this.__copy(c); - return c; + return this.__copyTo(new SparseSet8(0)); } empty() { @@ -220,9 +212,7 @@ export class SparseSet16 extends ASparseSet } copy() { - const c = new SparseSet16(0); - this.__copy(c); - return c; + return this.__copyTo(new SparseSet16(0)); } empty() { @@ -251,9 +241,7 @@ export class SparseSet32 extends ASparseSet } copy() { - const c = new SparseSet8(0); - this.__copy(c); - return c; + return this.__copyTo(new SparseSet32(0)); } empty() { diff --git a/packages/atom/CHANGELOG.md b/packages/atom/CHANGELOG.md index 846d51d0ea..ae682b7639 100644 --- a/packages/atom/CHANGELOG.md +++ b/packages/atom/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@3.0.3...@thi.ng/atom@3.0.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/atom + + + + + ## [3.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@3.0.2...@thi.ng/atom@3.0.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/atom diff --git a/packages/atom/package.json b/packages/atom/package.json index e7d80537da..50c5869218 100644 --- a/packages/atom/package.json +++ b/packages/atom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/atom", - "version": "3.0.3", + "version": "3.0.4", "description": "Mutable wrappers for nested immutable values w/ optional undo/redo history", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/paths": "^2.1.4" }, "keywords": [ "derived views", diff --git a/packages/bencode/CHANGELOG.md b/packages/bencode/CHANGELOG.md index e031538b1a..2b7e9139f3 100644 --- a/packages/bencode/CHANGELOG.md +++ b/packages/bencode/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/bencode@0.3.3...@thi.ng/bencode@0.3.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/bencode + + + + + ## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/bencode@0.3.2...@thi.ng/bencode@0.3.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/bencode diff --git a/packages/bencode/package.json b/packages/bencode/package.json index 48b3427a52..eccb9052cf 100644 --- a/packages/bencode/package.json +++ b/packages/bencode/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bencode", - "version": "0.3.3", + "version": "0.3.4", "description": "Bencode binary encoder / decoder with optional UTF8 encoding", "module": "./index.js", "main": "./lib/index.js", @@ -33,13 +33,13 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/arrays": "^0.2.4", "@thi.ng/checks": "^2.3.0", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/transducers-binary": "^0.4.3" + "@thi.ng/defmulti": "^1.1.3", + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/transducers-binary": "^0.4.4" }, "keywords": [ "bencode", diff --git a/packages/bitfield/CHANGELOG.md b/packages/bitfield/CHANGELOG.md index 1f59075fda..14c85b771a 100644 --- a/packages/bitfield/CHANGELOG.md +++ b/packages/bitfield/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitfield@0.1.11...@thi.ng/bitfield@0.1.12) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/bitfield + + + + + ## [0.1.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitfield@0.1.10...@thi.ng/bitfield@0.1.11) (2019-07-31) **Note:** Version bump only for package @thi.ng/bitfield diff --git a/packages/bitfield/package.json b/packages/bitfield/package.json index 9a11b37e52..8db8bda3d0 100644 --- a/packages/bitfield/package.json +++ b/packages/bitfield/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bitfield", - "version": "0.1.11", + "version": "0.1.12", "description": "1D / 2D bit field implementations", "module": "./index.js", "main": "./lib/index.js", @@ -33,9 +33,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/binary": "^1.1.0", - "@thi.ng/strings": "^1.2.2" + "@thi.ng/strings": "^1.2.3" }, "keywords": [ "1D", diff --git a/packages/bitstream/CHANGELOG.md b/packages/bitstream/CHANGELOG.md index 2bda5d565e..5e4cfec3da 100644 --- a/packages/bitstream/CHANGELOG.md +++ b/packages/bitstream/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@1.1.2...@thi.ng/bitstream@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/bitstream + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@1.1.1...@thi.ng/bitstream@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/bitstream diff --git a/packages/bitstream/package.json b/packages/bitstream/package.json index 6ba9a47329..d333af133c 100644 --- a/packages/bitstream/package.json +++ b/packages/bitstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bitstream", - "version": "1.1.2", + "version": "1.1.3", "description": "ES6 iterator based read/write bit streams & support for variable word widths", "module": "./index.js", "main": "./lib/index.js", @@ -25,7 +25,7 @@ "pub": "yarn build:release && yarn publish --access public" }, "dependencies": { - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.0" }, "devDependencies": { "@types/mocha": "^5.2.6", diff --git a/packages/cache/CHANGELOG.md b/packages/cache/CHANGELOG.md index 7e23849aeb..2208727a9d 100644 --- a/packages/cache/CHANGELOG.md +++ b/packages/cache/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@1.0.23...@thi.ng/cache@1.0.24) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/cache + + + + + ## [1.0.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@1.0.22...@thi.ng/cache@1.0.23) (2019-08-16) **Note:** Version bump only for package @thi.ng/cache diff --git a/packages/cache/package.json b/packages/cache/package.json index 04d2725c64..8e3cb9368a 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/cache", - "version": "1.0.23", + "version": "1.0.24", "description": "In-memory cache implementations with ES6 Map-like API and different eviction strategies", "module": "./index.js", "main": "./lib/index.js", @@ -33,9 +33,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/dcons": "^2.1.3", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/api": "^6.3.3", + "@thi.ng/dcons": "^2.1.4", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "cache", diff --git a/packages/cache/src/lru.ts b/packages/cache/src/lru.ts index 1c42b537b6..1d1c479894 100644 --- a/packages/cache/src/lru.ts +++ b/packages/cache/src/lru.ts @@ -100,24 +100,8 @@ export class LRUCache implements ICache { set(key: K, value: V) { const size = this.opts.ksize(key) + this.opts.vsize(value); const e = this.map.get(key); - if (e) { - this._size -= e.value.s; - } - this._size += size; - if (this.ensureSize()) { - if (e) { - e.value.v = value; - e.value.s = size; - this.items.asTail(e); - } else { - this.items.push({ - k: key, - v: value, - s: size - }); - this.map.set(key, this.items.tail!); - } - } + this._size += Math.max(0, size - (e ? e.value.s : 0)); + this.ensureSize() && this.doSetEntry(e, key, value, size); return value; } @@ -173,4 +157,20 @@ export class LRUCache implements ICache { this.opts.release && this.opts.release(ee.k, ee.v); this._size -= ee.s; } + + protected doSetEntry( + e: ConsCell> | undefined, + k: K, + v: V, + s: number + ) { + if (e) { + e.value.v = v; + e.value.s = s; + this.items.asTail(e); + } else { + this.items.push({ k, v, s }); + this.map.set(k, this.items.tail!); + } + } } diff --git a/packages/cache/src/mru.ts b/packages/cache/src/mru.ts index e00213cb33..47800530f3 100644 --- a/packages/cache/src/mru.ts +++ b/packages/cache/src/mru.ts @@ -14,32 +14,24 @@ export class MRUCache extends LRUCache { return new MRUCache(null, this.opts); } - set(key: K, value: V) { - const size = this.opts.ksize(key) + this.opts.vsize(value); - const e = this.map.get(key); - if (e) { - this._size -= e.value.s; - } - this._size += size; - if (this.ensureSize()) { - if (e) { - e.value.v = value; - e.value.s = size; - this.items.asHead(e); - } else { - this.items.cons({ - k: key, - v: value, - s: size - }); - this.map.set(key, this.items.head!); - } - } - return value; - } - protected resetEntry(e: ConsCell>) { this.items.asHead(e); return e.value.v; } + + protected doSetEntry( + e: ConsCell> | undefined, + k: K, + v: V, + s: number + ) { + if (e) { + e.value.v = v; + e.value.s = s; + this.items.asHead(e); + } else { + this.items.cons({ k, v, s }); + this.map.set(k, this.items.head!); + } + } } diff --git a/packages/cache/src/tlru.ts b/packages/cache/src/tlru.ts index e21940b3ee..99794b6b67 100644 --- a/packages/cache/src/tlru.ts +++ b/packages/cache/src/tlru.ts @@ -56,10 +56,7 @@ export class TLRUCache extends LRUCache { set(key: K, value: V, ttl = this.opts.ttl) { const size = this.opts.ksize(key) + this.opts.vsize(value); const e = this.map.get(key); - if (e) { - this._size -= e.value.s; - } - this._size += size; + this._size += Math.max(0, size - (e ? e.value.s : 0)); if (this.ensureSize()) { const t = Date.now() + ttl; if (e) { diff --git a/packages/color/CHANGELOG.md b/packages/color/CHANGELOG.md index 8f26d263e8..b3d95e02da 100644 --- a/packages/color/CHANGELOG.md +++ b/packages/color/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.0.3...@thi.ng/color@1.1.0) (2019-08-21) + + +### Features + +* **color:** add resolveAsCSS(), update deps ([f96ac92](https://github.com/thi-ng/umbrella/commit/f96ac92)) + + + + + +## [1.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.0.2...@thi.ng/color@1.0.3) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/color + + + + + ## [1.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.0.1...@thi.ng/color@1.0.2) (2019-08-16) diff --git a/packages/color/package.json b/packages/color/package.json index 0fabc587be..e6fdc3b553 100644 --- a/packages/color/package.json +++ b/packages/color/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/color", - "version": "1.0.2", + "version": "1.1.0", "description": "Raw, array-based, color ops, conversions, opt. type wrappers, multi-color gradients", "module": "./index.js", "main": "./lib/index.js", @@ -33,14 +33,15 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/compose": "^1.3.2", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/errors": "^1.1.2", + "@thi.ng/api": "^6.3.3", + "@thi.ng/checks": "^2.3.0", + "@thi.ng/compose": "^1.3.3", + "@thi.ng/defmulti": "^1.1.3", + "@thi.ng/errors": "^1.2.0", "@thi.ng/math": "^1.4.2", - "@thi.ng/strings": "^1.2.2", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/strings": "^1.2.3", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "alpha", diff --git a/packages/color/src/index.ts b/packages/color/src/index.ts index ad0a393a15..5c9e6aacd9 100644 --- a/packages/color/src/index.ts +++ b/packages/color/src/index.ts @@ -13,6 +13,7 @@ export * from "./hue-rgba"; export * from "./int-css"; export * from "./int-rgba"; export * from "./kelvin-rgba"; +export * from "./resolve"; export * from "./rgba-css"; export * from "./rgba-hcva"; export * from "./rgba-hcya"; diff --git a/packages/color/src/resolve.ts b/packages/color/src/resolve.ts new file mode 100644 index 0000000000..80fa1b4c7c --- /dev/null +++ b/packages/color/src/resolve.ts @@ -0,0 +1,23 @@ +import { isArrayLike, isNumber } from "@thi.ng/checks"; +import { ColorMode, ReadonlyColor } from "./api"; +import { asCSS } from "./convert"; + +/** + * Takes a color in one of the following formats and tries to convert it + * to a CSS string: + * + * - any IColor instance + * - raw RGBA vector + * - number ((A)RGB int) + * - string (unchanged) + * + * @param col + */ +export const resolveAsCSS = (col: any) => + isArrayLike(col) + ? isNumber((col).mode) + ? asCSS(col) + : asCSS(col, ColorMode.RGBA) + : isNumber(col) + ? asCSS(col, ColorMode.INT32) + : col; diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index 6fd888eee3..d691cdb82b 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/compose@1.3.2...@thi.ng/compose@1.3.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/compose + + + + + ## [1.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/compose@1.3.1...@thi.ng/compose@1.3.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/compose diff --git a/packages/compose/package.json b/packages/compose/package.json index 63cf3c8af4..b5d1258b5a 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/compose", - "version": "1.3.2", + "version": "1.3.3", "description": "Arity-optimized functional composition helpers", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.3.3", + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "composition", diff --git a/packages/csp/CHANGELOG.md b/packages/csp/CHANGELOG.md index bbf2147c16..e779c6bad1 100644 --- a/packages/csp/CHANGELOG.md +++ b/packages/csp/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@1.1.3...@thi.ng/csp@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/csp + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@1.1.2...@thi.ng/csp@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/csp diff --git a/packages/csp/package.json b/packages/csp/package.json index a5c98c2d95..1f639e8b6b 100644 --- a/packages/csp/package.json +++ b/packages/csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/csp", - "version": "1.1.3", + "version": "1.1.4", "description": "ES6 promise based CSP implementation", "module": "./index.js", "main": "./lib/index.js", @@ -37,12 +37,12 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/arrays": "^0.2.4", "@thi.ng/checks": "^2.3.0", - "@thi.ng/dcons": "^2.1.3", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/dcons": "^2.1.4", + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "async", diff --git a/packages/dcons/CHANGELOG.md b/packages/dcons/CHANGELOG.md index 0ac855f943..b54a90e2da 100644 --- a/packages/dcons/CHANGELOG.md +++ b/packages/dcons/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@2.1.3...@thi.ng/dcons@2.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/dcons + + + + + ## [2.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@2.1.2...@thi.ng/dcons@2.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/dcons diff --git a/packages/dcons/package.json b/packages/dcons/package.json index 9321e07780..a57577c4f3 100644 --- a/packages/dcons/package.json +++ b/packages/dcons/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dcons", - "version": "2.1.3", + "version": "2.1.4", "description": "Comprehensive doubly linked list structure w/ iterator support", "module": "./index.js", "main": "./lib/index.js", @@ -33,12 +33,12 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", "@thi.ng/compare": "^1.0.9", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "datastructure", diff --git a/packages/defmulti/CHANGELOG.md b/packages/defmulti/CHANGELOG.md index e73de684cd..071ea06608 100644 --- a/packages/defmulti/CHANGELOG.md +++ b/packages/defmulti/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@1.1.2...@thi.ng/defmulti@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/defmulti + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@1.1.1...@thi.ng/defmulti@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/defmulti diff --git a/packages/defmulti/package.json b/packages/defmulti/package.json index b9c630d6a1..d1444d8b92 100644 --- a/packages/defmulti/package.json +++ b/packages/defmulti/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/defmulti", - "version": "1.1.2", + "version": "1.1.3", "description": "Dynamically extensible multiple dispatch via user supplied dispatch function.", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.3.3", + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "ES6", diff --git a/packages/defmulti/src/api.ts b/packages/defmulti/src/api.ts new file mode 100644 index 0000000000..75e504b538 --- /dev/null +++ b/packages/defmulti/src/api.ts @@ -0,0 +1,305 @@ +import { + Fn, + Fn2, + Fn3, + Fn4, + Fn5, + Fn6, + Fn7, + Fn8, + FnAny, + IObjectOf +} from "@thi.ng/api"; + +export const DEFAULT: unique symbol = Symbol(); + +export type DispatchFn = FnAny; +export type DispatchFn1 = Fn; +export type DispatchFn1O = (a: A, b?: B) => PropertyKey; +export type DispatchFn2 = Fn2; +export type DispatchFn2O = (a: A, b: B, c?: C) => PropertyKey; +export type DispatchFn3 = Fn3; +export type DispatchFn3O = (a: A, b: B, c: C, d?: D) => PropertyKey; +export type DispatchFn4 = Fn4; +export type DispatchFn4O = ( + a: A, + b: B, + c: C, + d: D, + e?: E +) => PropertyKey; +export type DispatchFn5 = Fn5; +export type DispatchFn5O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f?: F +) => PropertyKey; +export type DispatchFn6 = Fn6; +export type DispatchFn6O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g?: G +) => PropertyKey; +export type DispatchFn7 = Fn7< + A, + B, + C, + D, + E, + F, + G, + PropertyKey +>; +export type DispatchFn7O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h?: H +) => PropertyKey; +export type DispatchFn8 = Fn8< + A, + B, + C, + D, + E, + F, + G, + H, + PropertyKey +>; +export type DispatchFn8O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i?: I +) => PropertyKey; + +export type Implementation = FnAny; +export type Implementation1 = Fn; +export type Implementation1O = (a: A, b?: B) => T; +export type Implementation2 = Fn2; +export type Implementation2O = (a: A, b: B, c?: C) => T; +export type Implementation3 = Fn3; +export type Implementation3O = (a: A, b: B, c: C, d?: D) => T; +export type Implementation4 = Fn4; +export type Implementation4O = ( + a: A, + b: B, + c: C, + d: D, + e?: E +) => T; +export type Implementation5 = Fn5; +export type Implementation5O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f?: F +) => T; +export type Implementation6 = Fn6; +export type Implementation6O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g?: G +) => T; +export type Implementation7 = Fn7< + A, + B, + C, + D, + E, + F, + G, + T +>; +export type Implementation7O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h?: H +) => T; +export type Implementation8 = Fn8< + A, + B, + C, + D, + E, + F, + G, + H, + T +>; +export type Implementation8O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i?: I +) => T; + +export interface MultiFnBase { + /** + * Registers implementation for dispatch value `id`. Returns true, + * if successful. Returns false if an implementation already exists + * (and does nothing in this case). + * + * @param id + * @param impl + */ + add(id: PropertyKey, impl: I): boolean; + /** + * Takes an object of dispatch values and their implementations and + * calls `.add()` for each KV pair. Returns true, if all impls were + * added successfully. Note: Only numbers or strings are accepted as + * dispatch values here. + * + * @param impls + */ + addAll(impls: IObjectOf): boolean; + /** + * Removes implementation for dispatch value `id`. Returns true, if + * successful. + * + * @param id + */ + remove(id: PropertyKey): boolean; + /** + * Returns true, if the function is callable (has a valid + * implementation) for given arguments. + * + * @param args + */ + callable(...args: any[]): boolean; + /** + * Returns a set of all registered dispatch values. + */ + impls(): Set; + /** + * Updates dispatch hierarchy by declaring dispatch value `id` to + * delegate to `parent`'s implementation. I.e. in terms of dispatch + * logic, `id` is considered the same as `parent. + * + * @param id + * @param parent + */ + isa(id: PropertyKey, parent: PropertyKey): boolean; + /** + * Returns all known dispatch relationships. This is an object with + * all registered dispatch values as keys, each with a set of parent + * dispatch values. + */ + rels(): IObjectOf>; + /** + * Returns a set of immediate parent dispatch values for given + * dispatch value `id`. + * + * @param id + */ + parents(id: PropertyKey): Set; + /** + * Similar to `parents()`, but includes all transitive parent dispatch + * values for given dispatch value `id`. + * @param id + */ + ancestors(id: PropertyKey): Set; +} + +export interface MultiFn + extends Implementation, + MultiFnBase> {} + +export interface MultiFn1 + extends Implementation1, + MultiFnBase> {} + +export interface MultiFn1O + extends Implementation1O, + MultiFnBase> {} + +export interface MultiFn2 + extends Implementation2, + MultiFnBase> {} + +export interface MultiFn2O + extends Implementation2O, + MultiFnBase> {} + +export interface MultiFn3 + extends Implementation3, + MultiFnBase> {} + +export interface MultiFn3O + extends Implementation3O, + MultiFnBase> {} + +export interface MultiFn4 + extends Implementation4, + MultiFnBase> {} + +export interface MultiFn4O + extends Implementation4O, + MultiFnBase> {} + +export interface MultiFn5 + extends Implementation5, + MultiFnBase> {} + +export interface MultiFn5O + extends Implementation5O, + MultiFnBase> {} + +export interface MultiFn6 + extends Implementation6, + MultiFnBase> {} + +export interface MultiFn6O + extends Implementation6O, + MultiFnBase> {} + +export interface MultiFn7 + extends Implementation7, + MultiFnBase> {} + +export interface MultiFn7O + extends Implementation7O, + MultiFnBase> {} + +export interface MultiFn8 + extends Implementation8, + MultiFnBase> {} + +export interface MultiFn8O + extends Implementation8O, + MultiFnBase> {} + +export type AncestorDefs = IObjectOf>; diff --git a/packages/defmulti/src/defmulti-n.ts b/packages/defmulti/src/defmulti-n.ts new file mode 100644 index 0000000000..88214afecd --- /dev/null +++ b/packages/defmulti/src/defmulti-n.ts @@ -0,0 +1,51 @@ +import { illegalArity } from "@thi.ng/errors"; +import { DEFAULT, Implementation } from "./api"; +import { defmulti } from "./defmulti"; + +/** + * Returns a multi-dispatch function which delegates to one of the + * provided implementations, based on the arity (number of args) when + * the function is called. Internally uses `defmulti`, so new arities + * can be dynamically added (or removed) at a later time. If no + * `fallback` is provided, `defmultiN` also registers a `DEFAULT` + * implementation which simply throws an `IllegalArityError` when + * invoked. + * + * **Note:** Unlike `defmulti` no argument type checking is supported, + * however you can specify the return type for the generated function. + * + * ``` + * const foo = defmultiN({ + * 0: () => "zero", + * 1: (x) => `one: ${x}`, + * 3: (x, y, z) => `three: ${x}, ${y}, ${z}` + * }); + * + * foo(); + * // zero + * foo(23); + * // one: 23 + * foo(1, 2, 3); + * // three: 1, 2, 3 + * foo(1, 2); + * // Error: illegal arity: 2 + * + * foo.add(2, (x, y) => `two: ${x}, ${y}`); + * foo(1, 2); + * // two: 1, 2 + * ``` + * + * @param impls + * @param fallback + */ +export const defmultiN = ( + impls: { [id: number]: Implementation }, + fallback?: Implementation +) => { + const fn = defmulti((...args: any[]) => args.length); + fn.add(DEFAULT, fallback || ((...args) => illegalArity(args.length))); + for (let id in impls) { + fn.add(id, impls[id]); + } + return fn; +}; diff --git a/packages/defmulti/src/defmulti.ts b/packages/defmulti/src/defmulti.ts new file mode 100644 index 0000000000..6ccd4885de --- /dev/null +++ b/packages/defmulti/src/defmulti.ts @@ -0,0 +1,188 @@ +import { IObjectOf } from "@thi.ng/api"; +import { unsupported } from "@thi.ng/errors"; +import { + AncestorDefs, + DEFAULT, + DispatchFn, + DispatchFn1, + DispatchFn1O, + DispatchFn2, + DispatchFn2O, + DispatchFn3, + DispatchFn3O, + DispatchFn4, + DispatchFn4O, + DispatchFn5, + DispatchFn5O, + DispatchFn6, + DispatchFn6O, + DispatchFn7, + DispatchFn7O, + DispatchFn8, + DispatchFn8O, + Implementation, + MultiFn, + MultiFn1, + MultiFn1O, + MultiFn2, + MultiFn2O, + MultiFn3, + MultiFn3O, + MultiFn4, + MultiFn4O, + MultiFn5, + MultiFn5O, + MultiFn6, + MultiFn6O, + MultiFn7, + MultiFn7O, + MultiFn8, + MultiFn8O +} from "./api"; + +/** + * Returns a new multi-dispatch function using the provided dispatcher. + * The dispatcher can take any number of arguments and must produce a + * dispatch value (string, number or symbol) used to lookup an + * implementation. If found, the impl is called with the same args and + * its return value used as own return value. If no matching + * implementation is available, attempts to dispatch to DEFAULT impl. If + * none is registered, an error is thrown. + * + * `defmulti` provides generics for type checking up to 8 args (plus the + * return type) and the generics will also apply to all implementations. + * If more than 8 args are required, `defmulti` will fall back to an + * untyped varargs solution. + * + * Implementations for different dispatch values can be added and + * removed dynamically by calling `.add(id, fn)` or `.remove(id)` on the + * returned function. Each returns `true` if the operation was + * successful. + */ +export function defmulti(f: DispatchFn, rels?: AncestorDefs): MultiFn; +// prettier-ignore +export function defmulti(f: DispatchFn1, rels?: AncestorDefs): MultiFn1; +// prettier-ignore +export function defmulti(f: DispatchFn2, rels?: AncestorDefs): MultiFn2; +// prettier-ignore +export function defmulti(f: DispatchFn1O, rels?: AncestorDefs): MultiFn1O; +// prettier-ignore +export function defmulti(f: DispatchFn3, rels?: AncestorDefs): MultiFn3; +// prettier-ignore +export function defmulti(f: DispatchFn2O, rels?: AncestorDefs): MultiFn2O; +// prettier-ignore +export function defmulti(f: DispatchFn4, rels?: AncestorDefs): MultiFn4; +// prettier-ignore +export function defmulti(f: DispatchFn3O, rels?: AncestorDefs): MultiFn3O; +// prettier-ignore +export function defmulti(f: DispatchFn5, rels?: AncestorDefs): MultiFn5; +// prettier-ignore +export function defmulti(f: DispatchFn4O, rels?: AncestorDefs): MultiFn4O; +// prettier-ignore +export function defmulti(f: DispatchFn6, rels?: AncestorDefs): MultiFn6; +// prettier-ignore +export function defmulti(f: DispatchFn5O, rels?: AncestorDefs): MultiFn5O; +// prettier-ignore +export function defmulti(f: DispatchFn7, rels?: AncestorDefs): MultiFn7; +// prettier-ignore +export function defmulti(f: DispatchFn6O, rels?: AncestorDefs): MultiFn6O; +// prettier-ignore +export function defmulti(f: DispatchFn8, rels?: AncestorDefs): MultiFn8; +// prettier-ignore +export function defmulti(f: DispatchFn7O, rels?: AncestorDefs): MultiFn7O; +// prettier-ignore +export function defmulti(f: DispatchFn8O, rels?: AncestorDefs): MultiFn8O; +export function defmulti(f: any, ancestors?: AncestorDefs) { + const impls: IObjectOf> = {}; + const rels: IObjectOf> = ancestors + ? makeRels(ancestors) + : {}; + const fn: any = (...args: any[]) => { + const id = f(...args); + const g = impls[id] || findImpl(impls, rels, id) || impls[DEFAULT]; + return g + ? g(...args) + : unsupported(`missing implementation for: "${id.toString()}"`); + }; + fn.add = (id: PropertyKey, g: Implementation) => { + if (impls[id]) return false; + impls[id] = g; + return true; + }; + fn.addAll = (_impls: IObjectOf>) => { + let ok = true; + for (let id in _impls) { + ok = fn.add(id, _impls[id]) && ok; + } + return ok; + }; + fn.remove = (id: PropertyKey) => { + if (!impls[id]) return false; + delete impls[id]; + return true; + }; + fn.callable = (...args: any[]) => { + const id = f(...args); + return !!( + impls[id] || + findImpl(impls, rels, id) || + impls[DEFAULT] + ); + }; + fn.isa = (id: PropertyKey, parent: PropertyKey) => { + let val = rels[id]; + !val && (rels[id] = val = new Set()); + val.add(parent); + }; + fn.impls = () => { + const res = new Set(Object.keys(impls)); + for (let id in rels) { + findImpl(impls, rels, id) && res.add(id); + } + impls[DEFAULT] && res.add(DEFAULT); + return res; + }; + fn.rels = () => rels; + fn.parents = (id: PropertyKey) => rels[id]; + fn.ancestors = (id: PropertyKey) => + new Set(findAncestors([], rels, id)); + return fn; +} + +const findImpl = ( + impls: IObjectOf>, + rels: IObjectOf>, + id: PropertyKey +) => { + const parents = rels[id]; + if (!parents) return; + for (let p of parents) { + let impl: Implementation = + impls[p] || findImpl(impls, rels, p); + if (impl) return impl; + } +}; + +const findAncestors = ( + acc: PropertyKey[], + rels: IObjectOf>, + id: PropertyKey +) => { + const parents = rels[id]; + if (parents) { + for (let p of parents) { + acc.push(p); + findAncestors(acc, rels, p); + } + } + return acc; +}; + +const makeRels = (spec: AncestorDefs) => { + const rels: IObjectOf> = {}; + for (let k in spec) { + const val = spec[k]; + rels[k] = val instanceof Set ? val : new Set(val); + } + return rels; +}; diff --git a/packages/defmulti/src/impls.ts b/packages/defmulti/src/impls.ts new file mode 100644 index 0000000000..97be4b1646 --- /dev/null +++ b/packages/defmulti/src/impls.ts @@ -0,0 +1,76 @@ +import { IObjectOf } from "@thi.ng/api"; +import { illegalArgs } from "@thi.ng/errors"; +import { Implementation, MultiFn } from "./api"; + +/** + * Syntax-sugar intended for sets of multi-methods sharing same dispatch + * values / logic. Takes a dispatch value, an object of "is-a" + * relationships and a number of multi-methods, each with an + * implementation for the given dispatch value. + * + * The relations object has dispatch values (parents) as keys and arrays + * of multi-methods as their values. For each multi-method associates + * the given `type` with the related parent dispatch value to delegate + * to its implementation. + * + * The remaining implementations are associated with their related + * multi-method and the given `type` dispatch value. + * + * ``` + * foo = defmulti((x) => x.id); + * bar = defmulti((x) => x.id); + * bax = defmulti((x) => x.id); + * baz = defmulti((x) => x.id); + * + * // define impls for dispatch value `a` + * implementations( + * "a", + * + * // delegate bax & baz impls to dispatch val `b` + * { + * b: [bax, baz] + * }, + * + * // concrete multi-fn impls + * foo, + * (x) => `foo: ${x.val}`, + * + * bar, + * (x) => `bar: ${x.val.toUpperCase()}` + * ); + * + * // add parent impls + * bax.add("b", (x) => `bax: ${x.id}`); + * baz.add("c", (x) => `baz: ${x.id}`); + * // use "c" impl for "b" + * baz.isa("b", "c"); + * + * foo({ id: "a", val: "alice" }); // "foo: alice" + * bar({ id: "a", val: "alice" }); // "bar: ALICE" + * bax({ id: "a", val: "alice" }); // "bax: a" + * baz({ id: "a", val: "alice" }); // "baz: a" + * + * baz.impls(); // Set { "c", "a", "b" } + * ``` + * + * @param type + * @param impls + */ +export const implementations = ( + type: PropertyKey, + rels: IObjectOf[]>, + ...impls: (MultiFn | Implementation)[] +) => { + impls.length & 1 && + illegalArgs("expected an even number of implementation items"); + if (rels) { + for (let parent in rels) { + for (let fn of rels[parent]) { + fn.isa(type, parent); + } + } + } + for (let i = 0; i < impls.length; i += 2) { + (>impls[i]).add(type, impls[i + 1]); + } +}; diff --git a/packages/defmulti/src/index.ts b/packages/defmulti/src/index.ts index 75c6df6e56..5cdab3f896 100644 --- a/packages/defmulti/src/index.ts +++ b/packages/defmulti/src/index.ts @@ -1,606 +1,4 @@ -import { - Fn, - Fn2, - Fn3, - Fn4, - Fn5, - Fn6, - Fn7, - Fn8, - FnAny, - IObjectOf -} from "@thi.ng/api"; -import { illegalArgs, illegalArity, unsupported } from "@thi.ng/errors"; - -export const DEFAULT: unique symbol = Symbol(); - -export type DispatchFn = FnAny; -export type DispatchFn1 = Fn; -export type DispatchFn1O = (a: A, b?: B) => PropertyKey; -export type DispatchFn2 = Fn2; -export type DispatchFn2O = (a: A, b: B, c?: C) => PropertyKey; -export type DispatchFn3 = Fn3; -export type DispatchFn3O = (a: A, b: B, c: C, d?: D) => PropertyKey; -export type DispatchFn4 = Fn4; -export type DispatchFn4O = ( - a: A, - b: B, - c: C, - d: D, - e?: E -) => PropertyKey; -export type DispatchFn5 = Fn5; -export type DispatchFn5O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f?: F -) => PropertyKey; -export type DispatchFn6 = Fn6; -export type DispatchFn6O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g?: G -) => PropertyKey; -export type DispatchFn7 = Fn7< - A, - B, - C, - D, - E, - F, - G, - PropertyKey ->; -export type DispatchFn7O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h?: H -) => PropertyKey; -export type DispatchFn8 = Fn8< - A, - B, - C, - D, - E, - F, - G, - H, - PropertyKey ->; -export type DispatchFn8O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i?: I -) => PropertyKey; - -export type Implementation = FnAny; -export type Implementation1 = Fn; -export type Implementation1O = (a: A, b?: B) => T; -export type Implementation2 = Fn2; -export type Implementation2O = (a: A, b: B, c?: C) => T; -export type Implementation3 = Fn3; -export type Implementation3O = (a: A, b: B, c: C, d?: D) => T; -export type Implementation4 = Fn4; -export type Implementation4O = ( - a: A, - b: B, - c: C, - d: D, - e?: E -) => T; -export type Implementation5 = Fn5; -export type Implementation5O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f?: F -) => T; -export type Implementation6 = Fn6; -export type Implementation6O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g?: G -) => T; -export type Implementation7 = Fn7< - A, - B, - C, - D, - E, - F, - G, - T ->; -export type Implementation7O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h?: H -) => T; -export type Implementation8 = Fn8< - A, - B, - C, - D, - E, - F, - G, - H, - T ->; -export type Implementation8O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i?: I -) => T; - -export interface MultiFnBase { - /** - * Registers implementation for dispatch value `id`. Returns true, - * if successful. Returns false if an implementation already exists - * (and does nothing in this case). - * - * @param id - * @param impl - */ - add(id: PropertyKey, impl: I): boolean; - /** - * Takes an object of dispatch values and their implementations and - * calls `.add()` for each KV pair. Returns true, if all impls were - * added successfully. Note: Only numbers or strings are accepted as - * dispatch values here. - * - * @param impls - */ - addAll(impls: IObjectOf): boolean; - /** - * Removes implementation for dispatch value `id`. Returns true, if - * successful. - * - * @param id - */ - remove(id: PropertyKey): boolean; - /** - * Returns true, if the function is callable (has a valid - * implementation) for given arguments. - * - * @param args - */ - callable(...args: any[]): boolean; - /** - * Returns a set of all registered dispatch values. - */ - impls(): Set; - /** - * Updates dispatch hierarchy by declaring dispatch value `id` to - * delegate to `parent`'s implementation. I.e. in terms of dispatch - * logic, `id` is considered the same as `parent. - * - * @param id - * @param parent - */ - isa(id: PropertyKey, parent: PropertyKey): boolean; - /** - * Returns all known dispatch relationships. This is an object with - * all registered dispatch values as keys, each with a set of parent - * dispatch values. - */ - rels(): IObjectOf>; - /** - * Returns a set of immediate parent dispatch values for given - * dispatch value `id`. - * - * @param id - */ - parents(id: PropertyKey): Set; - /** - * Similar to `parents()`, but includes all transitive parent dispatch - * values for given dispatch value `id`. - * @param id - */ - ancestors(id: PropertyKey): Set; -} - -export interface MultiFn - extends Implementation, - MultiFnBase> {} - -export interface MultiFn1 - extends Implementation1, - MultiFnBase> {} - -export interface MultiFn1O - extends Implementation1O, - MultiFnBase> {} - -export interface MultiFn2 - extends Implementation2, - MultiFnBase> {} - -export interface MultiFn2O - extends Implementation2O, - MultiFnBase> {} - -export interface MultiFn3 - extends Implementation3, - MultiFnBase> {} - -export interface MultiFn3O - extends Implementation3O, - MultiFnBase> {} - -export interface MultiFn4 - extends Implementation4, - MultiFnBase> {} - -export interface MultiFn4O - extends Implementation4O, - MultiFnBase> {} - -export interface MultiFn5 - extends Implementation5, - MultiFnBase> {} - -export interface MultiFn5O - extends Implementation5O, - MultiFnBase> {} - -export interface MultiFn6 - extends Implementation6, - MultiFnBase> {} - -export interface MultiFn6O - extends Implementation6O, - MultiFnBase> {} - -export interface MultiFn7 - extends Implementation7, - MultiFnBase> {} - -export interface MultiFn7O - extends Implementation7O, - MultiFnBase> {} - -export interface MultiFn8 - extends Implementation8, - MultiFnBase> {} - -export interface MultiFn8O - extends Implementation8O, - MultiFnBase> {} - -export type AncestorDefs = IObjectOf>; - -/** - * Returns a new multi-dispatch function using the provided dispatcher. - * The dispatcher can take any number of arguments and must produce a - * dispatch value (string, number or symbol) used to lookup an - * implementation. If found, the impl is called with the same args and - * its return value used as own return value. If no matching - * implementation is available, attempts to dispatch to DEFAULT impl. If - * none is registered, an error is thrown. - * - * `defmulti` provides generics for type checking up to 8 args (plus the - * return type) and the generics will also apply to all implementations. - * If more than 8 args are required, `defmulti` will fall back to an - * untyped varargs solution. - * - * Implementations for different dispatch values can be added and - * removed dynamically by calling `.add(id, fn)` or `.remove(id)` on the - * returned function. Each returns `true` if the operation was - * successful. - */ -export function defmulti(f: DispatchFn, rels?: AncestorDefs): MultiFn; -export function defmulti( - f: DispatchFn1, - rels?: AncestorDefs -): MultiFn1; -export function defmulti( - f: DispatchFn2, - rels?: AncestorDefs -): MultiFn2; -export function defmulti( - f: DispatchFn1O, - rels?: AncestorDefs -): MultiFn1O; -export function defmulti( - f: DispatchFn3, - rels?: AncestorDefs -): MultiFn3; -export function defmulti( - f: DispatchFn2O, - rels?: AncestorDefs -): MultiFn2O; -export function defmulti( - f: DispatchFn4, - rels?: AncestorDefs -): MultiFn4; -export function defmulti( - f: DispatchFn3O, - rels?: AncestorDefs -): MultiFn3O; -export function defmulti( - f: DispatchFn5, - rels?: AncestorDefs -): MultiFn5; -export function defmulti( - f: DispatchFn4O, - rels?: AncestorDefs -): MultiFn4O; -export function defmulti( - f: DispatchFn6, - rels?: AncestorDefs -): MultiFn6; -export function defmulti( - f: DispatchFn5O, - rels?: AncestorDefs -): MultiFn5O; -export function defmulti( - f: DispatchFn7, - rels?: AncestorDefs -): MultiFn7; -export function defmulti( - f: DispatchFn6O, - rels?: AncestorDefs -): MultiFn6O; -export function defmulti( - f: DispatchFn8, - rels?: AncestorDefs -): MultiFn8; -export function defmulti( - f: DispatchFn7O, - rels?: AncestorDefs -): MultiFn7O; -export function defmulti( - f: DispatchFn8O, - rels?: AncestorDefs -): MultiFn8O; -export function defmulti(f: any, ancestors?: AncestorDefs) { - const impls: IObjectOf> = {}; - const rels: IObjectOf> = ancestors - ? makeRels(ancestors) - : {}; - const fn: any = (...args: any[]) => { - const id = f(...args); - const g = impls[id] || findImpl(impls, rels, id) || impls[DEFAULT]; - return g - ? g(...args) - : unsupported(`missing implementation for: "${id.toString()}"`); - }; - fn.add = (id: PropertyKey, g: Implementation) => { - if (impls[id]) return false; - impls[id] = g; - return true; - }; - fn.addAll = (_impls: IObjectOf>) => { - let ok = true; - for (let id in _impls) { - ok = fn.add(id, _impls[id]) && ok; - } - return ok; - }; - fn.remove = (id: PropertyKey) => { - if (!impls[id]) return false; - delete impls[id]; - return true; - }; - fn.callable = (...args: any[]) => { - const id = f(...args); - return !!( - impls[id] || - findImpl(impls, rels, id) || - impls[DEFAULT] - ); - }; - fn.isa = (id: PropertyKey, parent: PropertyKey) => { - let val = rels[id]; - !val && (rels[id] = val = new Set()); - val.add(parent); - }; - fn.impls = () => { - const res = new Set(Object.keys(impls)); - for (let id in rels) { - findImpl(impls, rels, id) && res.add(id); - } - impls[DEFAULT] && res.add(DEFAULT); - return res; - }; - fn.rels = () => rels; - fn.parents = (id: PropertyKey) => rels[id]; - fn.ancestors = (id: PropertyKey) => - new Set(findAncestors([], rels, id)); - return fn; -} - -const findImpl = ( - impls: IObjectOf>, - rels: IObjectOf>, - id: PropertyKey -) => { - const parents = rels[id]; - if (!parents) return; - for (let p of parents) { - let impl: Implementation = - impls[p] || findImpl(impls, rels, p); - if (impl) return impl; - } -}; - -const findAncestors = ( - acc: PropertyKey[], - rels: IObjectOf>, - id: PropertyKey -) => { - const parents = rels[id]; - if (parents) { - for (let p of parents) { - acc.push(p); - findAncestors(acc, rels, p); - } - } - return acc; -}; - -const makeRels = (spec: AncestorDefs) => { - const rels: IObjectOf> = {}; - for (let k in spec) { - const val = spec[k]; - rels[k] = val instanceof Set ? val : new Set(val); - } - return rels; -}; - -/** - * Returns a multi-dispatch function which delegates to one of the - * provided implementations, based on the arity (number of args) when - * the function is called. Internally uses `defmulti`, so new arities - * can be dynamically added (or removed) at a later time. If no - * `fallback` is provided, `defmultiN` also registers a `DEFAULT` - * implementation which simply throws an `IllegalArityError` when - * invoked. - * - * **Note:** Unlike `defmulti` no argument type checking is supported, - * however you can specify the return type for the generated function. - * - * ``` - * const foo = defmultiN({ - * 0: () => "zero", - * 1: (x) => `one: ${x}`, - * 3: (x, y, z) => `three: ${x}, ${y}, ${z}` - * }); - * - * foo(); - * // zero - * foo(23); - * // one: 23 - * foo(1, 2, 3); - * // three: 1, 2, 3 - * foo(1, 2); - * // Error: illegal arity: 2 - * - * foo.add(2, (x, y) => `two: ${x}, ${y}`); - * foo(1, 2); - * // two: 1, 2 - * ``` - * - * @param impls - * @param fallback - */ -export const defmultiN = ( - impls: { [id: number]: Implementation }, - fallback?: Implementation -) => { - const fn = defmulti((...args: any[]) => args.length); - fn.add(DEFAULT, fallback || ((...args) => illegalArity(args.length))); - for (let id in impls) { - fn.add(id, impls[id]); - } - return fn; -}; - -/** - * Syntax-sugar intended for sets of multi-methods sharing same dispatch - * values / logic. Takes a dispatch value, an object of "is-a" - * relationships and a number of multi-methods, each with an - * implementation for the given dispatch value. - * - * The relations object has dispatch values (parents) as keys and arrays - * of multi-methods as their values. For each multi-method associates - * the given `type` with the related parent dispatch value to delegate - * to its implementation. - * - * The remaining implementations are associated with their related - * multi-method and the given `type` dispatch value. - * - * ``` - * foo = defmulti((x) => x.id); - * bar = defmulti((x) => x.id); - * bax = defmulti((x) => x.id); - * baz = defmulti((x) => x.id); - * - * // define impls for dispatch value `a` - * implementations( - * "a", - * - * // delegate bax & baz impls to dispatch val `b` - * { - * b: [bax, baz] - * }, - * - * // concrete multi-fn impls - * foo, - * (x) => `foo: ${x.val}`, - * - * bar, - * (x) => `bar: ${x.val.toUpperCase()}` - * ); - * - * // add parent impls - * bax.add("b", (x) => `bax: ${x.id}`); - * baz.add("c", (x) => `baz: ${x.id}`); - * // use "c" impl for "b" - * baz.isa("b", "c"); - * - * foo({ id: "a", val: "alice" }); // "foo: alice" - * bar({ id: "a", val: "alice" }); // "bar: ALICE" - * bax({ id: "a", val: "alice" }); // "bax: a" - * baz({ id: "a", val: "alice" }); // "baz: a" - * - * baz.impls(); // Set { "c", "a", "b" } - * ``` - * - * @param type - * @param impls - */ -export const implementations = ( - type: PropertyKey, - rels: IObjectOf[]>, - ...impls: (MultiFn | Implementation)[] -) => { - impls.length & 1 && - illegalArgs("expected an even number of implementation items"); - if (rels) { - for (let parent in rels) { - for (let fn of rels[parent]) { - fn.isa(type, parent); - } - } - } - for (let i = 0; i < impls.length; i += 2) { - (>impls[i]).add(type, impls[i + 1]); - } -}; +export * from "./api"; +export * from "./defmulti"; +export * from "./defmulti-n"; +export * from "./impls"; diff --git a/packages/dgraph/CHANGELOG.md b/packages/dgraph/CHANGELOG.md index 4786b4dcd6..1c33810374 100644 --- a/packages/dgraph/CHANGELOG.md +++ b/packages/dgraph/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@1.1.13...@thi.ng/dgraph@1.1.14) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/dgraph + + + + + ## [1.1.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@1.1.12...@thi.ng/dgraph@1.1.13) (2019-08-16) **Note:** Version bump only for package @thi.ng/dgraph diff --git a/packages/dgraph/package.json b/packages/dgraph/package.json index 8184e44382..3b8f8d27c3 100644 --- a/packages/dgraph/package.json +++ b/packages/dgraph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dgraph", - "version": "1.1.13", + "version": "1.1.14", "description": "Type-agnostic directed acyclic graph (DAG) & graph operations", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/associative": "^2.4.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/associative": "^3.0.0", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "data structure", diff --git a/packages/diff/CHANGELOG.md b/packages/diff/CHANGELOG.md index 3970cfb10e..0261375bd5 100644 --- a/packages/diff/CHANGELOG.md +++ b/packages/diff/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@3.2.2...@thi.ng/diff@3.2.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/diff + + + + + ## [3.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@3.2.1...@thi.ng/diff@3.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/diff diff --git a/packages/diff/package.json b/packages/diff/package.json index 03e76d64f5..65e1f07fe2 100644 --- a/packages/diff/package.json +++ b/packages/diff/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/diff", - "version": "3.2.2", + "version": "3.2.3", "description": "Array & object Diff", "module": "./index.js", "main": "./lib/index.js", @@ -32,7 +32,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/equiv": "^1.0.9" }, "keywords": [ diff --git a/packages/dot/CHANGELOG.md b/packages/dot/CHANGELOG.md index 00cfd51fb4..025eb54155 100644 --- a/packages/dot/CHANGELOG.md +++ b/packages/dot/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@1.1.3...@thi.ng/dot@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/dot + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@1.1.2...@thi.ng/dot@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/dot diff --git a/packages/dot/package.json b/packages/dot/package.json index c67125e845..31c080a3d8 100644 --- a/packages/dot/package.json +++ b/packages/dot/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dot", - "version": "1.1.3", + "version": "1.1.4", "description": "Graphviz DOM abstraction as vanilla JS objects & serialization to DOT format", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0" }, "keywords": [ diff --git a/packages/dsp/CHANGELOG.md b/packages/dsp/CHANGELOG.md index 8c3dbe1731..6208cc96e9 100644 --- a/packages/dsp/CHANGELOG.md +++ b/packages/dsp/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/dsp@1.0.14...@thi.ng/dsp@1.0.15) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/dsp + + + + + ## [1.0.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/dsp@1.0.13...@thi.ng/dsp@1.0.14) (2019-07-31) **Note:** Version bump only for package @thi.ng/dsp diff --git a/packages/dsp/package.json b/packages/dsp/package.json index 9f3221dbbd..0a1ef0f2b7 100644 --- a/packages/dsp/package.json +++ b/packages/dsp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dsp", - "version": "1.0.14", + "version": "1.0.15", "description": "Assorted DSP utils, oscillators etc.", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/math": "^1.4.2" }, "keywords": [ diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index 36bb6cf65a..b1f65f58f7 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/errors@1.1.2...@thi.ng/errors@1.2.0) (2019-08-21) + + +### Features + +* **errors:** add defError(), refactor all existing, update readme ([ded89c2](https://github.com/thi-ng/umbrella/commit/ded89c2)) + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/errors@1.1.1...@thi.ng/errors@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/errors diff --git a/packages/errors/README.md b/packages/errors/README.md index b93e0f4ba1..49fd210bea 100644 --- a/packages/errors/README.md +++ b/packages/errors/README.md @@ -9,11 +9,11 @@ This project is part of the ## About -Custom error types and helper fns used by many packages in this repo. +Custom error types (extending `Error`) and helper functions used by most +other packages in this repo. -This feature was previously part of the -[@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) -package. +Additional error types can be defined using +[`defError()`](https://github.com/thi-ng/umbrella/tree/master/packages/errors/src/deferror.ts) ## Installation @@ -41,6 +41,21 @@ err.illegalState("oops"); err.unsupported("TODO not yet implemented") // Error: unsupported operation: TODO not yet implemented + +// define custom error +const MyError = err.defError( + () => "Eeek... ", + (x) => x + " is not allowed!" +); + +try { + throw new MyError(23); +} catch(e) { + console.warn(e.message); + console.log(e instanceof Error); +} +// Eeek... 23 is not allowed! +// true ``` ## Authors diff --git a/packages/errors/package.json b/packages/errors/package.json index 35e9e8b0d5..c27957c642 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/errors", - "version": "1.1.2", + "version": "1.2.0", "description": "Custom error types and helper fns.", "module": "./index.js", "main": "./lib/index.js", diff --git a/packages/errors/src/deferror.ts b/packages/errors/src/deferror.ts new file mode 100644 index 0000000000..a910e73c44 --- /dev/null +++ b/packages/errors/src/deferror.ts @@ -0,0 +1,9 @@ +export const defError = ( + prefix: (msg?: T) => string, + suffix: (msg?: T) => string = (msg) => (msg !== undefined ? ": " + msg : "") +) => + class extends Error { + constructor(msg?: T) { + super(prefix(msg) + suffix(msg)); + } + }; diff --git a/packages/errors/src/illegal-arguments.ts b/packages/errors/src/illegal-arguments.ts index c2807673ac..b803783c50 100644 --- a/packages/errors/src/illegal-arguments.ts +++ b/packages/errors/src/illegal-arguments.ts @@ -1,8 +1,6 @@ -export class IllegalArgumentError extends Error { - constructor(msg?: any) { - super("illegal argument(s)" + (msg !== undefined ? ": " + msg : "")); - } -} +import { defError } from "./deferror"; + +export const IllegalArgumentError = defError(() => "illegal argument(s)"); export const illegalArgs = (msg?: any): never => { throw new IllegalArgumentError(msg); diff --git a/packages/errors/src/illegal-arity.ts b/packages/errors/src/illegal-arity.ts index 3d3d18173d..f4482aaa33 100644 --- a/packages/errors/src/illegal-arity.ts +++ b/packages/errors/src/illegal-arity.ts @@ -1,8 +1,6 @@ -export class IllegalArityError extends Error { - constructor(n: number) { - super(`illegal arity: ${n}`); - } -} +import { defError } from "./deferror"; + +export const IllegalArityError = defError(() => "illegal arity"); export const illegalArity = (n: number): never => { throw new IllegalArityError(n); diff --git a/packages/errors/src/illegal-state.ts b/packages/errors/src/illegal-state.ts index a0aeb8ad40..d5aa166e51 100644 --- a/packages/errors/src/illegal-state.ts +++ b/packages/errors/src/illegal-state.ts @@ -1,8 +1,6 @@ -export class IllegalStateError extends Error { - constructor(msg?: any) { - super("illegal state" + (msg !== undefined ? ": " + msg : "")); - } -} +import { defError } from "./deferror"; + +export const IllegalStateError = defError(() => "illegal state"); export const illegalState = (msg?: any): never => { throw new IllegalStateError(msg); diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts index f4fff5e84e..ac23228467 100644 --- a/packages/errors/src/index.ts +++ b/packages/errors/src/index.ts @@ -1,3 +1,4 @@ +export * from "./deferror"; export * from "./illegal-arguments"; export * from "./illegal-arity"; export * from "./illegal-state"; diff --git a/packages/errors/src/unsupported.ts b/packages/errors/src/unsupported.ts index 88face590c..0f4b6349de 100644 --- a/packages/errors/src/unsupported.ts +++ b/packages/errors/src/unsupported.ts @@ -1,8 +1,8 @@ -export class UnsupportedOperationError extends Error { - constructor(msg?: any) { - super("unsupported operation" + (msg !== undefined ? ": " + msg : "")); - } -} +import { defError } from "./deferror"; + +export const UnsupportedOperationError = defError( + () => "unsupported operation" +); export const unsupported = (msg?: any): never => { throw new UnsupportedOperationError(msg); diff --git a/packages/fsm/CHANGELOG.md b/packages/fsm/CHANGELOG.md index 0e1707ddb4..ec83775642 100644 --- a/packages/fsm/CHANGELOG.md +++ b/packages/fsm/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/fsm@2.2.3...@thi.ng/fsm@2.2.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/fsm + + + + + ## [2.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/fsm@2.2.2...@thi.ng/fsm@2.2.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/fsm diff --git a/packages/fsm/package.json b/packages/fsm/package.json index 76a64b8804..2f99909dee 100644 --- a/packages/fsm/package.json +++ b/packages/fsm/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/fsm", - "version": "2.2.3", + "version": "2.2.4", "description": "Composable primitives for building declarative, transducer based Finite-State machines & parsers for arbitrary data streams", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/arrays": "^0.2.4", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "ES6", diff --git a/packages/fsm/src/fsm.ts b/packages/fsm/src/fsm.ts index f96a12ab53..9b7e79fe80 100644 --- a/packages/fsm/src/fsm.ts +++ b/packages/fsm/src/fsm.ts @@ -5,6 +5,7 @@ import { isReduced, iterator, Reducer, + ReductionFn, Transducer, unreduced } from "@thi.ng/transducers"; @@ -87,12 +88,7 @@ export function fsm( ); } if (res) { - for (let y of unreduced(res)) { - acc = reduce(acc, y); - if (isReduced(acc)) { - break; - } - } + acc = reduceResult(reduce, acc, res); isReduced(res) && (acc = ensureReduced(acc)); } if (type === Match.FULL_NC && !isReduced(acc)) { @@ -100,12 +96,7 @@ export function fsm( } } else if (type === Match.FAIL) { if (res) { - for (let y of unreduced(res)) { - acc = reduce(acc, y); - if (isReduced(acc)) { - break; - } - } + acc = reduceResult(reduce, acc, res); } return ensureReduced(acc); } @@ -116,3 +107,13 @@ export function fsm( ]; }; } + +const reduceResult = (rfn: ReductionFn, acc: any, res: R[]) => { + for (let x of unreduced(res)) { + acc = rfn(acc, x); + if (isReduced(acc)) { + break; + } + } + return acc; +}; diff --git a/packages/geom-accel/CHANGELOG.md b/packages/geom-accel/CHANGELOG.md index b6a5cb3030..6519288410 100644 --- a/packages/geom-accel/CHANGELOG.md +++ b/packages/geom-accel/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.6...@thi.ng/geom-accel@1.2.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-accel + + + + + +## [1.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.5...@thi.ng/geom-accel@1.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-accel + + + + + ## [1.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.4...@thi.ng/geom-accel@1.2.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-accel diff --git a/packages/geom-accel/package.json b/packages/geom-accel/package.json index ffa468af8d..d6856db998 100644 --- a/packages/geom-accel/package.json +++ b/packages/geom-accel/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-accel", - "version": "1.2.5", + "version": "1.2.7", "description": "nD spatial indexing data structures", "module": "./index.js", "main": "./lib/index.js", @@ -33,13 +33,13 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.3", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/heaps": "^1.1.2", + "@thi.ng/api": "^6.3.3", + "@thi.ng/arrays": "^0.2.4", + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/heaps": "^1.1.3", "@thi.ng/math": "^1.4.2", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-accel/src/kdtree.ts b/packages/geom-accel/src/kdtree.ts index f79e45378e..b75793dd0b 100644 --- a/packages/geom-accel/src/kdtree.ts +++ b/packages/geom-accel/src/kdtree.ts @@ -1,4 +1,4 @@ -import { ICopy, Pair } from "@thi.ng/api"; +import { Fn, ICopy, Pair } from "@thi.ng/api"; import { ensureArray } from "@thi.ng/arrays"; import { ISpatialAccel } from "@thi.ng/geom-api"; import { Heap } from "@thi.ng/heaps"; @@ -102,7 +102,7 @@ export class KdTree : parent; let parent: MaybeKdNode; if (this.root) { - parent = nearest1(p, [eps * eps, null], [], this.dim, this.root)[1]; + parent = nearest1(p, [eps * eps, null], this.dim, this.root)[1]; if (parent) { return false; } @@ -154,74 +154,20 @@ export class KdTree has(k: Readonly, eps = EPS) { return ( !!this.root && - !!nearest1(k, [eps * eps, null], [], this.dim, this.root)[1] + !!nearest1(k, [eps * eps, null], this.dim, this.root)[1] ); } select(q: Readonly, maxNum: number, maxDist?: number): Pair[] { - if (!this.root) return []; - const res: Pair[] = []; - if (maxNum === 1) { - const sel = nearest1( - q, - [maxDist != null ? maxDist * maxDist : Infinity, null], - [], - this.dim, - this.root - )[1]; - sel && res.push([sel.k, sel.v]); - } else { - const sel = this.buildSelection(q, maxNum, maxDist); - for (let n = sel.length; --n >= 0; ) { - const nn = sel[n][1]; - nn && res.push([nn.k, nn.v]); - } - } - return res; + return this.doSelect(q, (x) => [x.k, x.v], maxNum, maxDist); } - selectKeys(q: Readonly, maxNum: number, maxDist?: number): K[] { - if (!this.root) return []; - const res: K[] = []; - if (maxNum === 1) { - const sel = nearest1( - q, - [maxDist != null ? maxDist * maxDist : Infinity, null], - [], - this.dim, - this.root - )[1]; - sel && res.push(sel.k); - } else { - const src = this.buildSelection(q, maxNum, maxDist); - for (let n = src.length; --n >= 0; ) { - const nn = src[n][1]; - nn && res.push(nn.k); - } - } - return res; + selectKeys(q: Readonly, maxNum: number, maxDist?: number) { + return this.doSelect(q, (x) => x.k, maxNum, maxDist); } - selectVals(q: Readonly, maxNum: number, maxDist?: number): V[] { - if (!this.root) return []; - const res: V[] = []; - if (maxNum === 1) { - const sel = nearest1( - q, - [maxDist != null ? maxDist * maxDist : Infinity, null], - [], - this.dim, - this.root - )[1]; - sel && res.push(sel.v); - } else { - const src = this.buildSelection(q, maxNum, maxDist); - for (let n = src.length; --n >= 0; ) { - const nn = src[n][1]; - nn && res.push(nn.v); - } - } - return res; + selectVals(q: Readonly, maxNum: number, maxDist?: number) { + return this.doSelect(q, (x) => x.v, maxNum, maxDist); } balanceRatio() { @@ -243,10 +189,36 @@ export class KdTree for (let i = maxNum; --i >= 0; ) { nodes.push(c); } - nearest(q, nodes, [], this.dim, maxNum, this.root!); + nearest(q, nodes, this.dim, maxNum, this.root!); return nodes.values.sort(CMP); } + protected doSelect( + q: Readonly, + f: Fn, T>, + maxNum: number, + maxDist?: number + ): T[] { + if (!this.root) return []; + const res: any[] = []; + if (maxNum === 1) { + const sel = nearest1( + q, + [maxDist != null ? maxDist * maxDist : Infinity, null], + this.dim, + this.root + )[1]; + sel && res.push(f(sel)); + } else { + const sel = this.buildSelection(q, maxNum, maxDist); + for (let n = sel.length; --n >= 0; ) { + const s = sel[n][1]; + s && res.push(f(s)); + } + } + return res; + } + protected buildTree( points: Pair[], depth: number, @@ -344,7 +316,6 @@ const remove = (node: KdNode) => { const nearest = ( q: K, acc: Heap<[number, MaybeKdNode]>, - tmp: Vec, dims: number, maxNum: number, node: KdNode @@ -352,38 +323,16 @@ const nearest = ( const p = node.k; const ndist = distSq(p, q); if (!node.l && !node.r) { - if (!acc.length || ndist < acc.peek()[0]) { - if (acc.length >= maxNum) { - acc.pushPop([ndist, node]); - } else { - acc.push([ndist, node]); - } - } + collect(acc, node, maxNum, ndist); return; } - const ndim = node.d; - for (let i = dims; --i >= 0; ) { - tmp[i] = i === ndim ? q[i] : p[i]; - } - const tdist = distSq(p, tmp); - let best = !node.r - ? node.l - : !node.l - ? node.r - : q[ndim] < p[ndim] - ? node.l - : node.r; - nearest(q, acc, tmp, dims, maxNum, best!); - if (!acc.length || ndist < acc.peek()[0]) { - if (acc.length >= maxNum) { - acc.pushPop([ndist, node]); - } else { - acc.push([ndist, node]); - } - } + const tdist = nodeDist(node, dims, q, p); + let best = bestChild(node, q); + nearest(q, acc, dims, maxNum, best!); + collect(acc, node, maxNum, ndist); if (!acc.length || tdist < acc.peek()[0]) { best = best === node.l ? node.r : node.l; - best && nearest(q, acc, tmp, dims, maxNum, best); + best && nearest(q, acc, dims, maxNum, best); } }; @@ -398,39 +347,64 @@ const nearest = ( const nearest1 = ( q: K, acc: [number, MaybeKdNode], - tmp: Vec, dims: number, node: KdNode ): [number, MaybeKdNode] => { const p = node.k; const ndist = distSq(p, q); if (!node.l && !node.r) { - if (ndist < acc[0]) { - acc[0] = ndist; - acc[1] = node; - } + collect1(acc, node, ndist); return acc; } - const ndim = node.d; - for (let i = dims; --i >= 0; ) { - tmp[i] = i === ndim ? q[i] : p[i]; + const tdist = nodeDist(node, dims, q, p); + let best = bestChild(node, q); + nearest1(q, acc, dims, best!); + collect1(acc, node, ndist); + if (tdist < acc[0]) { + best = best === node.l ? node.r : node.l; + best && nearest1(q, acc, dims, best); } - const tdist = distSq(p, tmp); - let best = !node.r + return acc; +}; + +const bestChild = (node: KdNode, q: K) => { + const d = node.d; + return !node.r ? node.l : !node.l ? node.r - : q[ndim] < p[ndim] + : q[d] < node.k[d] ? node.l : node.r; - nearest1(q, acc, tmp, dims, best!); - if (ndist < acc[0]) { - acc[0] = ndist; - acc[1] = node; - } - if (tdist < acc[0]) { - best = best === node.l ? node.r : node.l; - best && nearest1(q, acc, tmp, dims, best); +}; + +const collect = ( + acc: Heap<[number, MaybeKdNode]>, + node: KdNode, + maxNum: number, + ndist: number +) => + (!acc.length || ndist < acc.peek()[0]) && + (acc.length >= maxNum + ? acc.pushPop([ndist, node]) + : acc.push([ndist, node])); + +const collect1 = ( + acc: [number, MaybeKdNode], + node: KdNode, + ndist: number +) => ndist < acc[0] && ((acc[0] = ndist), (acc[1] = node)); + +const TMP: Vec = []; + +const nodeDist = ( + node: KdNode, + dims: number, + q: K, + p: K +) => { + for (let i = dims, d = node.d; --i >= 0; ) { + TMP[i] = i === d ? q[i] : p[i]; } - return acc; + return distSq(TMP, p); }; diff --git a/packages/geom-api/CHANGELOG.md b/packages/geom-api/CHANGELOG.md index c1df4257c2..8d40f74c59 100644 --- a/packages/geom-api/CHANGELOG.md +++ b/packages/geom-api/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.4...@thi.ng/geom-api@0.3.5) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-api + + + + + +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.3...@thi.ng/geom-api@0.3.4) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-api + + + + + ## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.2...@thi.ng/geom-api@0.3.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-api diff --git a/packages/geom-api/package.json b/packages/geom-api/package.json index d2cf25923d..444e5e4cd7 100644 --- a/packages/geom-api/package.json +++ b/packages/geom-api/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-api", - "version": "0.3.3", + "version": "0.3.5", "description": "Shared type & interface declarations for @thi.ng/geom packages", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/api": "^6.3.3", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "ES6", diff --git a/packages/geom-arc/CHANGELOG.md b/packages/geom-arc/CHANGELOG.md index b4806b6c88..72c49c387b 100644 --- a/packages/geom-arc/CHANGELOG.md +++ b/packages/geom-arc/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.6...@thi.ng/geom-arc@0.2.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-arc + + + + + +## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.5...@thi.ng/geom-arc@0.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-arc + + + + + ## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.4...@thi.ng/geom-arc@0.2.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-arc diff --git a/packages/geom-arc/package.json b/packages/geom-arc/package.json index 51bd90e16a..58a4a98f58 100644 --- a/packages/geom-arc/package.json +++ b/packages/geom-arc/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-arc", - "version": "0.2.5", + "version": "0.2.7", "description": "2D circular / elliptic arc operations", "module": "./index.js", "main": "./lib/index.js", @@ -34,10 +34,10 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/geom-resample": "^0.2.5", + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/geom-resample": "^0.2.7", "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-clip/CHANGELOG.md b/packages/geom-clip/CHANGELOG.md index 5e1be4853e..c7efed37e2 100644 --- a/packages/geom-clip/CHANGELOG.md +++ b/packages/geom-clip/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.6...@thi.ng/geom-clip@0.1.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-clip + + + + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.5...@thi.ng/geom-clip@0.1.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-clip + + + + + ## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.4...@thi.ng/geom-clip@0.1.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-clip diff --git a/packages/geom-clip/package.json b/packages/geom-clip/package.json index 7859c2a0df..8de2327120 100644 --- a/packages/geom-clip/package.json +++ b/packages/geom-clip/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-clip", - "version": "0.1.5", + "version": "0.1.7", "description": "2D line & convex polygon clipping (Liang-Barsky / Sutherland-Hodgeman)", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/geom-isec": "^0.3.5", - "@thi.ng/geom-poly-utils": "^0.1.23", + "@thi.ng/geom-isec": "^0.3.7", + "@thi.ng/geom-poly-utils": "^0.1.25", "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-closest-point/CHANGELOG.md b/packages/geom-closest-point/CHANGELOG.md index 0cafdd5dd1..29591c6c53 100644 --- a/packages/geom-closest-point/CHANGELOG.md +++ b/packages/geom-closest-point/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.6...@thi.ng/geom-closest-point@0.3.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-closest-point + + + + + +## [0.3.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.5...@thi.ng/geom-closest-point@0.3.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-closest-point + + + + + ## [0.3.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.4...@thi.ng/geom-closest-point@0.3.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-closest-point diff --git a/packages/geom-closest-point/package.json b/packages/geom-closest-point/package.json index 001323109b..c63a5a06f8 100644 --- a/packages/geom-closest-point/package.json +++ b/packages/geom-closest-point/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-closest-point", - "version": "0.3.5", + "version": "0.3.7", "description": "Closest point / proximity helpers", "module": "./index.js", "main": "./lib/index.js", @@ -34,7 +34,7 @@ }, "dependencies": { "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "ES6", diff --git a/packages/geom-closest-point/src/index.ts b/packages/geom-closest-point/src/index.ts index a5f35f9924..94883ac778 100644 --- a/packages/geom-closest-point/src/index.ts +++ b/packages/geom-closest-point/src/index.ts @@ -228,19 +228,7 @@ export const closestPointRect = ( bmax: ReadonlyVec, out: Vec = [] ) => { - let minD = Infinity; - let minID: number; - let minW: number; - for (let i = 0; i < 4; i++) { - const j = i >> 1; - const w = (i & 1 ? bmax : bmin)[j]; - const d = Math.abs(p[j] - w); - if (d < minD) { - minD = d; - minID = j; - minW = w; - } - } + const [minID, minW] = closestBoxEdge(p, bmin, bmax, 4); return minID! === 0 ? setC2(out, minW!, clamp(p[1], bmin[1], bmax[1])) : setC2(out, clamp(p[0], bmin[0], bmax[0]), minW!); @@ -252,23 +240,11 @@ export const closestPointAABB = ( bmax: ReadonlyVec, out: Vec = [] ) => { - let minD = Infinity; - let minID: number; - let minW: number; - for (let i = 0; i < 6; i++) { - const j = i >> 1; - const w = (i & 1 ? bmax : bmin)[j]; - const d = Math.abs(p[j] - w); - if (d < minD) { - minD = d; - minID = j; - minW = w; - } - } + const [minID, minW] = closestBoxEdge(p, bmin, bmax, 6); return minID! === 0 ? setC3( out, - minW!, + minW, clamp(p[1], bmin[1], bmax[1]), clamp(p[2], bmin[2], bmax[2]) ) @@ -276,13 +252,35 @@ export const closestPointAABB = ( ? setC3( out, clamp(p[0], bmin[0], bmax[0]), - minW!, + minW, clamp(p[2], bmin[2], bmax[2]) ) : setC3( out, clamp(p[0], bmin[0], bmax[0]), clamp(p[1], bmin[1], bmax[1]), - minW! + minW ); }; + +const closestBoxEdge = ( + p: ReadonlyVec, + bmin: ReadonlyVec, + bmax: ReadonlyVec, + n: number +) => { + let minD = Infinity; + let minID: number; + let minW: number; + for (let i = 0; i < n; i++) { + const j = i >> 1; + const w = (i & 1 ? bmax : bmin)[j]; + const d = Math.abs(p[j] - w); + if (d < minD) { + minD = d; + minID = j; + minW = w; + } + } + return [minID!, minW!]; +}; diff --git a/packages/geom-hull/CHANGELOG.md b/packages/geom-hull/CHANGELOG.md index 9d66acbe16..369f460f69 100644 --- a/packages/geom-hull/CHANGELOG.md +++ b/packages/geom-hull/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.0.27](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.26...@thi.ng/geom-hull@0.0.27) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-hull + + + + + +## [0.0.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.25...@thi.ng/geom-hull@0.0.26) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-hull + + + + + ## [0.0.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.24...@thi.ng/geom-hull@0.0.25) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-hull diff --git a/packages/geom-hull/package.json b/packages/geom-hull/package.json index 8e2d61073a..5d17dc524b 100644 --- a/packages/geom-hull/package.json +++ b/packages/geom-hull/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-hull", - "version": "0.0.25", + "version": "0.0.27", "description": "Fast 2D convex hull (Graham Scan)", "module": "./index.js", "main": "./lib/index.js", @@ -34,7 +34,7 @@ }, "dependencies": { "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-isec/CHANGELOG.md b/packages/geom-isec/CHANGELOG.md index aac4fe6d80..2b689904b6 100644 --- a/packages/geom-isec/CHANGELOG.md +++ b/packages/geom-isec/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.6...@thi.ng/geom-isec@0.3.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-isec + + + + + +## [0.3.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.5...@thi.ng/geom-isec@0.3.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-isec + + + + + ## [0.3.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.4...@thi.ng/geom-isec@0.3.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-isec diff --git a/packages/geom-isec/package.json b/packages/geom-isec/package.json index 726c034211..8d2fe7a563 100644 --- a/packages/geom-isec/package.json +++ b/packages/geom-isec/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-isec", - "version": "0.3.5", + "version": "0.3.7", "description": "2D/3D shape intersection checks", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/geom-closest-point": "^0.3.5", + "@thi.ng/api": "^6.3.3", + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/geom-closest-point": "^0.3.7", "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-isec/src/ray-rect.ts b/packages/geom-isec/src/ray-rect.ts index 05c36d97df..81f98878d5 100644 --- a/packages/geom-isec/src/ray-rect.ts +++ b/packages/geom-isec/src/ray-rect.ts @@ -21,13 +21,14 @@ const rayRect = ( bmin: ReadonlyVec, bmax: ReadonlyVec ) => { - let p = rpos[0], - d = 1 / dir[0]; + let p = rpos[0]; + let d = 1 / dir[0]; let t1 = (bmin[0] - p) * d; let t2 = (bmax[0] - p) * d; let tmin = min(t1, t2); let tmax = max(t1, t2); - (p = rpos[1]), (d = 1 / dir[1]); + p = rpos[1]; + d = 1 / dir[1]; t1 = (bmin[1] - p) * d; t2 = (bmax[1] - p) * d; return <[number, number]>[max(tmin, min(t1, t2)), min(tmax, max(t1, t2))]; @@ -47,16 +48,18 @@ const rayBox = ( bmin: ReadonlyVec, bmax: ReadonlyVec ) => { - let p = rpos[0], - d = 1 / dir[0]; + let p = rpos[0]; + let d = 1 / dir[0]; let t1 = (bmin[0] - p) * d; let t2 = (bmax[0] - p) * d; let tmin = min(t1, t2); let tmax = max(t1, t2); - (p = rpos[1]), (d = 1 / dir[1]); + p = rpos[1]; + d = 1 / dir[1]; t1 = (bmin[1] - p) * d; t2 = (bmax[1] - p) * d; - (p = rpos[2]), (d = 1 / dir[2]); + p = rpos[2]; + d = 1 / dir[2]; t1 = (bmin[2] - p) * d; t2 = (bmax[2] - p) * d; tmin = max(tmin, min(t1, t2)); diff --git a/packages/geom-isoline/CHANGELOG.md b/packages/geom-isoline/CHANGELOG.md index d5fadbbe83..1e9a65d090 100644 --- a/packages/geom-isoline/CHANGELOG.md +++ b/packages/geom-isoline/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.24...@thi.ng/geom-isoline@0.1.25) (2019-08-21) + + +### Performance Improvements + +* **geom-isoline:** refactor contourVertex as jump table, minor updates ([d25827e](https://github.com/thi-ng/umbrella/commit/d25827e)) + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.23...@thi.ng/geom-isoline@0.1.24) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-isoline + + + + + ## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.22...@thi.ng/geom-isoline@0.1.23) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-isoline diff --git a/packages/geom-isoline/package.json b/packages/geom-isoline/package.json index c04437f613..2496e30a47 100644 --- a/packages/geom-isoline/package.json +++ b/packages/geom-isoline/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-isoline", - "version": "0.1.23", + "version": "0.1.25", "description": "Fast 2D contour line extraction / generation", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-isoline/src/index.ts b/packages/geom-isoline/src/index.ts index ca88d0472e..1c1761984d 100644 --- a/packages/geom-isoline/src/index.ts +++ b/packages/geom-isoline/src/index.ts @@ -1,41 +1,13 @@ +import { Fn5 } from "@thi.ng/api"; import { range2d } from "@thi.ng/transducers"; import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -// flattened [to,clear] tuples +// flattened [to, clear] tuples // all positive values are given as times 2 +// prettier-ignore const EDGE_INDEX = [ - -1, - -1, - 4, - 0, - 2, - 0, - 2, - 0, - 0, - 0, - -1, - -1, - 0, - 0, - 0, - 0, - 6, - 0, - 4, - 0, - -1, - -1, - 2, - 0, - 6, - 0, - 4, - 0, - 6, - 0, - -1, - -1 + -1, -1, 4, 0, 2, 0, 2, 0, 0, 0, -1, -1, 0, 0, 0, 0, + 6, 0, 4, 0, -1, -1, 2, 0, 6, 0, 4, 0, 6, 0, -1, -1 ]; // flattened coord offsets [x,y] tuples @@ -99,30 +71,17 @@ const mix = ( return a === b ? 0 : (a - iso) / (a - b); }; -const contourVertex = ( - src: ReadonlyVec, - w: number, - x: number, - y: number, - to: number, - iso: number -) => { - switch (to) { - case 0: - return [x + mix(src, w, x, y, x + 1, y, iso), y]; - case 2: - return [x + 1, y + mix(src, w, x + 1, y, x + 1, y + 1, iso)]; - case 4: - return [x + mix(src, w, x, y + 1, x + 1, y + 1, iso), y + 1]; - case 6: - return [x, y + mix(src, w, x, y, x, y + 1, iso)]; - default: - } -}; +// prettier-ignore +const contourVertex: Fn5[] = [ + (src, w, x, y, iso) => [x + mix(src, w, x, y, x + 1, y, iso), y], + (src, w, x, y, iso) => [x + 1, y + mix(src, w, x + 1, y, x + 1, y + 1, iso)], + (src, w, x, y, iso) => [x + mix(src, w, x, y + 1, x + 1, y + 1, iso), y + 1], + (src, w, x, y, iso) => [x, y + mix(src, w, x, y, x, y + 1, iso)] +]; export function* isolines(src: ReadonlyVec, w: number, h: number, iso: number) { const coded = encodeCrossings(src, w, h, iso); - let curr: number[][] = []; + let curr: Vec[] = []; let from: number; let to = -1; let clear: number; @@ -166,15 +125,15 @@ export function* isolines(src: ReadonlyVec, w: number, h: number, iso: number) { to = EDGE_INDEX[id]; clear = EDGE_INDEX[id + 1]; } - if (curr.length > 0 && from === -1 && to > -1) { + if (from === -1 && to > -1 && curr.length > 0) { yield curr; curr = []; } if (clear !== -1) { - coded[y * w + x] = clear; + coded[i] = clear; } if (to >= 0) { - curr.push(contourVertex(src, w, x, y, to, iso)!); + curr.push(contourVertex[to >> 1](src, w, x, y, iso)); x += NEXT_EDGES[to]; y += NEXT_EDGES[to + 1]; } else { diff --git a/packages/geom-poly-utils/CHANGELOG.md b/packages/geom-poly-utils/CHANGELOG.md index e46b50ff8b..4afc52fa42 100644 --- a/packages/geom-poly-utils/CHANGELOG.md +++ b/packages/geom-poly-utils/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.24...@thi.ng/geom-poly-utils@0.1.25) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-poly-utils + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.23...@thi.ng/geom-poly-utils@0.1.24) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-poly-utils + + + + + ## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.22...@thi.ng/geom-poly-utils@0.1.23) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-poly-utils diff --git a/packages/geom-poly-utils/package.json b/packages/geom-poly-utils/package.json index 51db7f046e..1e71f70fd6 100644 --- a/packages/geom-poly-utils/package.json +++ b/packages/geom-poly-utils/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-poly-utils", - "version": "0.1.23", + "version": "0.1.25", "description": "Polygon / triangle analysis & processing utilities", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/errors": "^1.1.2", - "@thi.ng/geom-api": "^0.3.3", + "@thi.ng/errors": "^1.2.0", + "@thi.ng/geom-api": "^0.3.5", "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-resample/CHANGELOG.md b/packages/geom-resample/CHANGELOG.md index 63d349030d..b865822874 100644 --- a/packages/geom-resample/CHANGELOG.md +++ b/packages/geom-resample/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.6...@thi.ng/geom-resample@0.2.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-resample + + + + + +## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.5...@thi.ng/geom-resample@0.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-resample + + + + + ## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.4...@thi.ng/geom-resample@0.2.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-resample diff --git a/packages/geom-resample/package.json b/packages/geom-resample/package.json index eb08599f87..496301d7a7 100644 --- a/packages/geom-resample/package.json +++ b/packages/geom-resample/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-resample", - "version": "0.2.5", + "version": "0.2.7", "description": "Customizable nD polyline interpolation, re-sampling, splitting & nearest point computation", "module": "./index.js", "main": "./lib/index.js", @@ -34,10 +34,10 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/geom-closest-point": "^0.3.5", + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/geom-closest-point": "^0.3.7", "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-splines/CHANGELOG.md b/packages/geom-splines/CHANGELOG.md index d96bba18b7..15951886f5 100644 --- a/packages/geom-splines/CHANGELOG.md +++ b/packages/geom-splines/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.4.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.3.4...@thi.ng/geom-splines@0.4.0) (2019-08-21) + + +### Features + +* **geom-splines:** add cubicTangentAt / quadraticTangentAt() ([e1cf355](https://github.com/thi-ng/umbrella/commit/e1cf355)) + + + + + +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.3.3...@thi.ng/geom-splines@0.3.4) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-splines + + + + + ## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.3.2...@thi.ng/geom-splines@0.3.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-splines diff --git a/packages/geom-splines/package.json b/packages/geom-splines/package.json index 16301badbe..58877e75c6 100644 --- a/packages/geom-splines/package.json +++ b/packages/geom-splines/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-splines", - "version": "0.3.3", + "version": "0.4.0", "description": "nD cubic & quadratic curve analysis, conversion, interpolation, splitting", "module": "./index.js", "main": "./lib/index.js", @@ -34,11 +34,11 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/geom-arc": "^0.2.5", - "@thi.ng/geom-resample": "^0.2.5", + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/geom-arc": "^0.2.7", + "@thi.ng/geom-resample": "^0.2.7", "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-splines/src/cubic-bounds.ts b/packages/geom-splines/src/cubic-bounds.ts index 54c72f2995..591df19027 100644 --- a/packages/geom-splines/src/cubic-bounds.ts +++ b/packages/geom-splines/src/cubic-bounds.ts @@ -34,18 +34,17 @@ const axisBounds = ( if (h > 0) { h = Math.sqrt(h); - let t = k0 / (-k1 - h); - if (t > 0 && t < 1) { - const q = mixCubic(pa, pb, pc, pd, t); - min[i] = Math.min(min[i], q); - max[i] = Math.max(max[i], q); - } - t = k0 / (-k1 + h); - if (t > 0 && t < 1) { - const q = mixCubic(pa, pb, pc, pd, t); - min[i] = Math.min(min[i], q); - max[i] = Math.max(max[i], q); - } + + const update = (t: number) => { + if (t > 0 && t < 1) { + const q = mixCubic(pa, pb, pc, pd, t); + min[i] = Math.min(min[i], q); + max[i] = Math.max(max[i], q); + } + }; + + update(k0 / (-k1 - h)); + update(k0 / (-k1 + h)); } }; diff --git a/packages/geom-splines/src/cubic-from-controlpoints.ts b/packages/geom-splines/src/cubic-from-controlpoints.ts index 92f6c90cf7..924fd610d9 100644 --- a/packages/geom-splines/src/cubic-from-controlpoints.ts +++ b/packages/geom-splines/src/cubic-from-controlpoints.ts @@ -7,38 +7,43 @@ import { Vec } from "@thi.ng/vectors"; +const buildUniform = (segments: Vec[], t: number) => { + const res: Vec[][] = []; + for (let i = 0, n = segments.length - 2; i < n; i += 2) { + const a = segments[i]; + const b = segments[i + 1]; + const c = segments[i + 2]; + res.push([ + a, + add(null, direction([], a, b, t), a), + add(null, direction([], c, b, t), c), + c + ]); + } + return res; +}; + +const buildNonUniform = (segments: Vec[], t: number) => { + const res: Vec[][] = []; + for (let i = 0, n = segments.length - 2; i < n; i += 2) { + const a = segments[i]; + const b = segments[i + 1]; + const c = segments[i + 2]; + res.push([a, mixN([], a, b, t), mixN([], c, b, t), c]); + } + return res; +}; + export const closedCubicFromControlPoints = ( points: ReadonlyVec[], t = 1, uniform = false ) => { - const segments = []; + const segments: Vec[] = []; for (let i = 0, num = points.length; i < num; i++) { - const a = points[i]; - const b = points[(i + 1) % num]; - segments.push(mixN([], a, b, 0.5), set([], b)); + const q = points[(i + 1) % num]; + segments.push(mixN([], points[i], q, 0.5), set([], q)); } segments.push(segments[0]); - const res: Vec[][] = []; - if (uniform) { - for (let i = 0, n = segments.length - 2; i < n; i += 2) { - const a = segments[i]; - const b = segments[i + 1]; - const c = segments[i + 2]; - res.push([ - a, - add(null, direction([], a, b, t), a), - add(null, direction([], c, b, t), c), - c - ]); - } - } else { - for (let i = 0, n = segments.length - 2; i < n; i += 2) { - const a = segments[i]; - const b = segments[i + 1]; - const c = segments[i + 2]; - res.push([a, mixN([], a, b, t), mixN([], c, b, t), c]); - } - } - return res; + return uniform ? buildUniform(segments, t) : buildNonUniform(segments, t); }; diff --git a/packages/geom-splines/src/cubic-sample.ts b/packages/geom-splines/src/cubic-sample.ts index a178a1de20..ce7a39638c 100644 --- a/packages/geom-splines/src/cubic-sample.ts +++ b/packages/geom-splines/src/cubic-sample.ts @@ -1,40 +1,9 @@ -import { isNumber, isPlainObject } from "@thi.ng/checks"; -import { DEFAULT_SAMPLES, SamplingOpts } from "@thi.ng/geom-api"; -import { Sampler } from "@thi.ng/geom-resample"; -import { - mixCubic, - ReadonlyVec, - set, - Vec -} from "@thi.ng/vectors"; +import { mixCubic } from "@thi.ng/vectors"; +import { __sample } from "./internal/sample"; -export const sampleCubic = ( - pts: ReadonlyVec[], - opts?: number | Partial -): Vec[] => { - if (isPlainObject(opts) && (opts).dist !== undefined) { - return new Sampler( - sampleCubic(pts, (opts).num || DEFAULT_SAMPLES) - ).sampleUniform( - (opts).dist, - (opts).last !== false - ); - } - opts = isNumber(opts) - ? { - num: opts, - last: true - } - : { - num: DEFAULT_SAMPLES, - ...opts - }; - const res: Vec[] = []; - const [a, b, c, d] = pts; - const delta = 1 / opts.num!; - for (let t = 0; t < opts.num!; t++) { +export const sampleCubic = __sample((res, [a, b, c, d], num) => { + const delta = 1 / num; + for (let t = 0; t < num; t++) { res.push(mixCubic([], a, b, c, d, t * delta)); } - opts.last && res.push(set([], d)); - return res; -}; +}); diff --git a/packages/geom-splines/src/cubic-tangent.ts b/packages/geom-splines/src/cubic-tangent.ts new file mode 100644 index 0000000000..eaef8099c5 --- /dev/null +++ b/packages/geom-splines/src/cubic-tangent.ts @@ -0,0 +1,26 @@ +import { + addW4, + normalize, + ReadonlyVec, + Vec +} from "@thi.ng/vectors"; + +export const cubicTangentAt = ( + out: Vec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + t: number, + len = 1 +) => { + const s = 1 - t; + const ss = s * s; + const tt = t * t; + const ts2 = 2 * t * s; + return normalize( + out, + addW4(out, a, b, c, d, -3 * ss, 3 * (ss - ts2), 3 * (ts2 - tt), 3 * tt), + len + ); +}; diff --git a/packages/geom-splines/src/index.ts b/packages/geom-splines/src/index.ts index 97ddd77aa1..0b154dd18e 100644 --- a/packages/geom-splines/src/index.ts +++ b/packages/geom-splines/src/index.ts @@ -7,11 +7,13 @@ export * from "./cubic-line"; export * from "./cubic-quadratic"; export * from "./cubic-sample"; export * from "./cubic-split"; +export * from "./cubic-tangent"; export * from "./quadratic-bounds"; export * from "./quadratic-closest-point"; export * from "./quadratic-line"; export * from "./quadratic-sample"; export * from "./quadratic-split"; +export * from "./quadratic-tangent"; export * from "./point-at"; diff --git a/packages/geom-splines/src/internal/sample.ts b/packages/geom-splines/src/internal/sample.ts new file mode 100644 index 0000000000..3314d4a281 --- /dev/null +++ b/packages/geom-splines/src/internal/sample.ts @@ -0,0 +1,33 @@ +import { Fn3 } from "@thi.ng/api"; +import { isNumber, isPlainObject } from "@thi.ng/checks"; +import { DEFAULT_SAMPLES, SamplingOpts } from "@thi.ng/geom-api"; +import { Sampler } from "@thi.ng/geom-resample"; +import { ReadonlyVec, set, Vec } from "@thi.ng/vectors"; + +export const __sample = (sample: Fn3) => + function $( + pts: ReadonlyVec[], + opts?: number | Partial + ): Vec[] { + if (isPlainObject(opts) && (opts).dist !== undefined) { + return new Sampler( + $(pts, (opts).num || DEFAULT_SAMPLES) + ).sampleUniform( + (opts).dist, + (opts).last !== false + ); + } + opts = isNumber(opts) + ? { + num: opts, + last: true + } + : { + num: DEFAULT_SAMPLES, + ...opts + }; + const res: Vec[] = []; + sample(res, pts, opts.num!); + opts.last && res.push(set([], pts[pts.length - 1])); + return res; + }; diff --git a/packages/geom-splines/src/quadratic-sample.ts b/packages/geom-splines/src/quadratic-sample.ts index c2266fb303..0cc0c00107 100644 --- a/packages/geom-splines/src/quadratic-sample.ts +++ b/packages/geom-splines/src/quadratic-sample.ts @@ -1,40 +1,9 @@ -import { isNumber, isPlainObject } from "@thi.ng/checks"; -import { DEFAULT_SAMPLES, SamplingOpts } from "@thi.ng/geom-api"; -import { Sampler } from "@thi.ng/geom-resample"; -import { - mixQuadratic, - ReadonlyVec, - set, - Vec -} from "@thi.ng/vectors"; +import { mixQuadratic } from "@thi.ng/vectors"; +import { __sample } from "./internal/sample"; -export const sampleQuadratic = ( - points: ReadonlyVec[], - opts?: number | Partial -): Vec[] => { - if (isPlainObject(opts) && (opts).dist !== undefined) { - return new Sampler( - sampleQuadratic(points, (opts).num || DEFAULT_SAMPLES) - ).sampleUniform( - (opts).dist, - (opts).last !== false - ); - } - opts = isNumber(opts) - ? { - num: opts, - last: true - } - : { - num: DEFAULT_SAMPLES, - ...opts - }; - const res: Vec[] = []; - const delta = 1 / opts.num!; - const [a, b, c] = points; - for (let t = 0; t < opts.num!; t++) { +export const sampleQuadratic = __sample((res, [a, b, c], num) => { + const delta = 1 / num; + for (let t = 0; t < num; t++) { res.push(mixQuadratic([], a, b, c, t * delta)); } - opts.last && res.push(set([], c)); - return res; -}; +}); diff --git a/packages/geom-splines/src/quadratic-tangent.ts b/packages/geom-splines/src/quadratic-tangent.ts new file mode 100644 index 0000000000..0676da2e3a --- /dev/null +++ b/packages/geom-splines/src/quadratic-tangent.ts @@ -0,0 +1,21 @@ +import { + addW2, + normalize, + ReadonlyVec, + sub, + Vec +} from "@thi.ng/vectors"; + +export const quadraticTangentAt = ( + out: Vec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + t: number, + len = 1 +) => + normalize( + out, + addW2(out, sub(out, b, a), sub([], c, b), 2 * (1 - t), 2 * t), + len + ); diff --git a/packages/geom-subdiv-curve/CHANGELOG.md b/packages/geom-subdiv-curve/CHANGELOG.md index af8d75b448..18f0fd353f 100644 --- a/packages/geom-subdiv-curve/CHANGELOG.md +++ b/packages/geom-subdiv-curve/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.23...@thi.ng/geom-subdiv-curve@0.1.24) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-subdiv-curve + + + + + +## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.22...@thi.ng/geom-subdiv-curve@0.1.23) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-subdiv-curve + + + + + ## [0.1.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.21...@thi.ng/geom-subdiv-curve@0.1.22) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-subdiv-curve diff --git a/packages/geom-subdiv-curve/package.json b/packages/geom-subdiv-curve/package.json index 6488381bca..5bebac8579 100644 --- a/packages/geom-subdiv-curve/package.json +++ b/packages/geom-subdiv-curve/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-subdiv-curve", - "version": "0.1.22", + "version": "0.1.24", "description": "Freely customizable, iterative subdivision curves for open / closed input geometries", "module": "./index.js", "main": "./lib/index.js", @@ -33,9 +33,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-tessellate/CHANGELOG.md b/packages/geom-tessellate/CHANGELOG.md index b79520f652..399c9f499e 100644 --- a/packages/geom-tessellate/CHANGELOG.md +++ b/packages/geom-tessellate/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.6...@thi.ng/geom-tessellate@0.2.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-tessellate + + + + + +## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.5...@thi.ng/geom-tessellate@0.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-tessellate + + + + + ## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.4...@thi.ng/geom-tessellate@0.2.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-tessellate diff --git a/packages/geom-tessellate/package.json b/packages/geom-tessellate/package.json index 33a4cfdf94..0995f01ef5 100644 --- a/packages/geom-tessellate/package.json +++ b/packages/geom-tessellate/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-tessellate", - "version": "0.2.5", + "version": "0.2.7", "description": "2D/3D polygon tessellators", "module": "./index.js", "main": "./lib/index.js", @@ -34,11 +34,11 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/geom-isec": "^0.3.5", - "@thi.ng/geom-poly-utils": "^0.1.23", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/geom-isec": "^0.3.7", + "@thi.ng/geom-poly-utils": "^0.1.25", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom-voronoi/CHANGELOG.md b/packages/geom-voronoi/CHANGELOG.md index 2bcd370452..9f3158f13b 100644 --- a/packages/geom-voronoi/CHANGELOG.md +++ b/packages/geom-voronoi/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.24...@thi.ng/geom-voronoi@0.1.25) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-voronoi + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.23...@thi.ng/geom-voronoi@0.1.24) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-voronoi + + + + + ## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.22...@thi.ng/geom-voronoi@0.1.23) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom-voronoi diff --git a/packages/geom-voronoi/package.json b/packages/geom-voronoi/package.json index 970fb0bf49..3ba221a104 100644 --- a/packages/geom-voronoi/package.json +++ b/packages/geom-voronoi/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-voronoi", - "version": "0.1.23", + "version": "0.1.25", "description": "Fast, incremental 2D Delaunay & Voronoi mesh implementation", "module": "./index.js", "main": "./lib/index.js", @@ -33,14 +33,14 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/geom-clip": "^0.1.5", - "@thi.ng/geom-isec": "^0.3.5", - "@thi.ng/geom-poly-utils": "^0.1.23", + "@thi.ng/geom-clip": "^0.1.7", + "@thi.ng/geom-isec": "^0.3.7", + "@thi.ng/geom-poly-utils": "^0.1.25", "@thi.ng/math": "^1.4.2", "@thi.ng/quad-edge": "^0.2.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom/CHANGELOG.md b/packages/geom/CHANGELOG.md index 4afd98ac9c..02b33305a5 100644 --- a/packages/geom/CHANGELOG.md +++ b/packages/geom/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.4...@thi.ng/geom@1.7.5) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom + + + + + +## [1.7.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.3...@thi.ng/geom@1.7.4) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom + + + + + ## [1.7.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.2...@thi.ng/geom@1.7.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/geom diff --git a/packages/geom/package.json b/packages/geom/package.json index 3adfc3407e..55992bc9a5 100644 --- a/packages/geom/package.json +++ b/packages/geom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom", - "version": "1.7.3", + "version": "1.7.5", "description": "2D geometry types, polymorphic operations, SVG generation", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib ctors internal ops", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -33,31 +33,31 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/arrays": "^0.2.4", "@thi.ng/checks": "^2.3.0", - "@thi.ng/compose": "^1.3.2", - "@thi.ng/defmulti": "^1.1.2", + "@thi.ng/compose": "^1.3.3", + "@thi.ng/defmulti": "^1.1.3", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/geom-arc": "^0.2.5", - "@thi.ng/geom-clip": "^0.1.5", - "@thi.ng/geom-closest-point": "^0.3.5", - "@thi.ng/geom-hull": "^0.0.25", - "@thi.ng/geom-isec": "^0.3.5", - "@thi.ng/geom-poly-utils": "^0.1.23", - "@thi.ng/geom-resample": "^0.2.5", - "@thi.ng/geom-splines": "^0.3.3", - "@thi.ng/geom-subdiv-curve": "^0.1.22", - "@thi.ng/geom-tessellate": "^0.2.5", - "@thi.ng/hiccup": "^3.2.3", - "@thi.ng/hiccup-svg": "^3.2.5", + "@thi.ng/errors": "^1.2.0", + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/geom-arc": "^0.2.7", + "@thi.ng/geom-clip": "^0.1.7", + "@thi.ng/geom-closest-point": "^0.3.7", + "@thi.ng/geom-hull": "^0.0.27", + "@thi.ng/geom-isec": "^0.3.7", + "@thi.ng/geom-poly-utils": "^0.1.25", + "@thi.ng/geom-resample": "^0.2.7", + "@thi.ng/geom-splines": "^0.4.0", + "@thi.ng/geom-subdiv-curve": "^0.1.24", + "@thi.ng/geom-tessellate": "^0.2.7", + "@thi.ng/hiccup": "^3.2.4", + "@thi.ng/hiccup-svg": "^3.3.0", "@thi.ng/math": "^1.4.2", - "@thi.ng/matrices": "^0.5.5", - "@thi.ng/random": "^1.1.10", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/matrices": "^0.5.7", + "@thi.ng/random": "^1.1.11", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/geom/src/api.ts b/packages/geom/src/api.ts deleted file mode 100644 index 2f26b4fb16..0000000000 --- a/packages/geom/src/api.ts +++ /dev/null @@ -1,573 +0,0 @@ -import { isNumber } from "@thi.ng/checks"; -import { equiv } from "@thi.ng/equiv"; -import { illegalState } from "@thi.ng/errors"; -import { - AABBLike, - Attribs, - IHiccupPathSegment, - IHiccupShape, - IShape, - PathSegment, - PCLike, - Type -} from "@thi.ng/geom-api"; -import { pointAt as arcPointAt, pointAtTheta as arcPointAtTheta } from "@thi.ng/geom-arc"; -import { - add2, - add3, - copyVectors, - maddN2, - set, - Vec -} from "@thi.ng/vectors"; - -export abstract class APC implements PCLike { - points: Vec[]; - attribs?: Attribs; - - constructor(points?: Vec[], attribs?: Attribs) { - this.points = points || []; - this.attribs = attribs; - } - - abstract get type(): number | string; - abstract copy(): IShape; - - *[Symbol.iterator]() { - yield* this.points; - } -} - -export class AABB implements AABBLike { - pos: Vec; - size: Vec; - attribs?: Attribs; - - constructor( - pos: Vec = [0, 0, 0], - size: number | Vec = 1, - attribs?: Attribs - ) { - this.pos = pos; - this.size = isNumber(size) ? [size, size, size] : size; - this.attribs = attribs; - } - - get type() { - return Type.AABB; - } - - copy() { - return new AABB(set([], this.pos), set([], this.size), { - ...this.attribs - }); - } - - max() { - return add3([], this.pos, this.size); - } -} - -export class Arc implements IHiccupShape, IHiccupPathSegment { - pos: Vec; - r: Vec; - start: number; - end: number; - axis: number; - xl: boolean; - cw: boolean; - attribs?: Attribs; - - constructor( - pos: Vec, - r: Vec, - axis: number, - start: number, - end: number, - xl = false, - cw = false, - attribs?: Attribs - ) { - this.pos = pos; - this.r = r; - this.axis = axis; - this.start = start; - this.end = end; - this.xl = xl; - this.cw = cw; - this.attribs = attribs; - } - - get type() { - return Type.ARC; - } - - copy() { - return new Arc( - set([], this.pos), - set([], this.r), - this.axis, - this.start, - this.end, - this.xl, - this.cw, - { ...this.attribs } - ); - } - - equiv(o: any) { - return ( - o instanceof Arc && - equiv(this.pos, o.pos) && - equiv(this.r, o.r) && - this.start === o.start && - this.end === o.end && - this.axis === o.axis && - this.xl === o.xl && - this.cw && - o.cw - ); - } - - pointAt(t: number, out: Vec = []) { - return arcPointAt( - this.pos, - this.r, - this.axis, - this.start, - this.end, - t, - out - ); - } - - pointAtTheta(theta: number, out: Vec = []) { - return arcPointAtTheta(this.pos, this.r, this.axis, theta, out); - } - - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.pointAt(0)], ...this.toHiccupPathSegments()] - ]; - } - - toHiccupPathSegments() { - return [ - [ - "A", - this.r[0], - this.r[1], - this.axis, - this.xl, - this.cw, - this.pointAt(1) - ] - ]; - } -} - -export class Circle implements IHiccupShape { - pos: Vec; - r: number; - attribs?: Attribs; - - constructor(pos: Vec = [0, 0], r = 1, attribs?: Attribs) { - this.pos = pos; - this.r = r; - this.attribs = attribs; - } - - get type() { - return Type.CIRCLE; - } - - copy() { - return new Circle(set([], this.pos), this.r, { ...this.attribs }); - } - - toHiccup() { - return ["circle", this.attribs, this.pos, this.r]; - } -} - -export class Cubic extends APC implements IHiccupPathSegment { - get type() { - return Type.CUBIC; - } - - copy() { - return new Cubic(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.points[0]], ...this.toHiccupPathSegments()] - ]; - } - - toHiccupPathSegments() { - const pts = this.points; - return [["C", pts[1], pts[2], pts[3]]]; - } -} - -export class Ellipse implements IHiccupShape { - pos: Vec; - r: Vec; - attribs?: Attribs; - - constructor( - pos: Vec = [0, 0], - r: number | Vec = [1, 1], - attribs?: Attribs - ) { - this.pos = pos; - this.r = isNumber(r) ? [r, r] : r; - this.attribs = attribs; - } - - get type() { - return Type.ELLIPSE; - } - - copy() { - return new Ellipse(set([], this.pos), set([], this.r), { - ...this.attribs - }); - } - - toHiccup() { - return ["ellipse", this.attribs, this.pos, this.r]; - } -} - -export class Group implements IHiccupShape { - children: IHiccupShape[]; - attribs: Attribs; - - constructor(attribs: Attribs, children?: IHiccupShape[]) { - this.attribs = attribs; - this.children = children || []; - } - - get type() { - return Type.GROUP; - } - - *[Symbol.iterator]() { - yield* this.children; - } - - copy() { - return new Group({ ...this.attribs }, ( - this.children.map((c) => c.copy()) - )); - } - - equiv(o: any) { - return o instanceof Group && equiv(this.children, o.children); - } - - toHiccup() { - return ["g", this.attribs, ...this.children.map((x) => x.toHiccup())]; - } -} - -export class Line extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return Type.LINE; - } - - copy() { - return new Line(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["line", this.attribs, this.points[0], this.points[1]]; - } - - toHiccupPathSegments() { - const [a, b] = this.points; - return [ - a[0] === b[0] ? ["V", b[1]] : a[1] === b[1] ? ["H", b[0]] : ["L", b] - ]; - } -} - -export class Path implements IHiccupShape { - segments: PathSegment[]; - closed: boolean; - attribs?: Attribs; - - constructor(segments?: PathSegment[], attribs?: Attribs) { - this.segments = segments || []; - this.attribs = attribs; - this.closed = false; - } - - get type() { - return Type.PATH; - } - - *[Symbol.iterator]() { - yield* this.segments; - } - - copy() { - const p = new Path([...this.segments], { ...this.attribs }); - p.closed = this.closed; - return p; - } - - equiv(o: any) { - return o instanceof Path && equiv(this.segments, o.segments); - } - - add(s: PathSegment) { - if (this.closed) illegalState("path already closed"); - this.segments.push(s); - } - - toHiccup() { - let dest: any[] = []; - const segments = this.segments; - const n = segments.length; - if (n > 1) { - dest.push(["M", segments[0].point]); - for (let i = 1; i < n; i++) { - dest = dest.concat(segments[i].geo!.toHiccupPathSegments()); - } - if (this.closed) { - dest.push(["Z"]); - } - } - return ["path", this.attribs || {}, dest]; - } -} - -export class Plane implements IHiccupShape { - normal: Vec; - w: number; - attribs?: Attribs; - - constructor(normal: Vec = [0, 1, 0], w = 0, attribs?: Attribs) { - this.normal = normal; - this.w = w; - this.attribs = attribs; - } - - get type() { - return Type.PLANE; - } - - copy() { - return new Plane(set([], this.normal), this.w, { ...this.attribs }); - } - - toHiccup() { - return ["plane", this.attribs, this.normal, this.w]; - } -} - -export class Points extends APC implements IHiccupShape { - get type() { - return Type.POINTS; - } - - copy() { - return new Points(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["points", this.attribs, this.points]; - } -} - -export class Polygon extends APC implements IHiccupShape { - get type() { - return Type.POLYGON; - } - - copy() { - return new Polygon(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polygon", this.attribs, this.points]; - } -} - -export class Polyline extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return Type.POLYLINE; - } - - copy() { - return new Polyline(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polyline", { ...this.attribs, fill: "none" }, this.points]; - } - - toHiccupPathSegments() { - const res: any[] = []; - for (let pts = this.points, n = pts.length, i = 1; i < n; i++) { - res.push(["L", pts[i]]); - } - return res; - } -} - -export class Quad extends APC implements IHiccupShape { - get type() { - return Type.QUAD; - } - - copy() { - return new Quad(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polygon", this.attribs, this.points]; - } -} -export class Quad3 extends APC implements IHiccupShape { - get type() { - return Type.QUAD3; - } - - copy() { - return new Quad3(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polygon", this.attribs, this.points]; - } -} - -export class Quadratic extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return Type.QUADRATIC; - } - - copy() { - return new Quadratic(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.points[0]], ...this.toHiccupPathSegments()] - ]; - } - - toHiccupPathSegments() { - const pts = this.points; - return [["Q", pts[1], pts[2]]]; - } -} - -export class Ray implements IHiccupShape { - pos: Vec; - dir: Vec; - attribs?: Attribs; - - constructor(pos: Vec, dir: Vec, attribs?: Attribs) { - this.pos = pos; - this.dir = dir; - this.attribs = attribs; - } - - get type() { - return Type.RAY; - } - - copy() { - return new Ray(set([], this.pos), set([], this.dir), { - ...this.attribs - }); - } - - toHiccup() { - return [ - "line", - this.attribs, - this.pos, - maddN2([], this.dir, 1e6, this.pos) - ]; - } -} - -export class Rect implements AABBLike, IHiccupShape { - pos: Vec; - size: Vec; - attribs?: Attribs; - - constructor(pos: Vec = [0, 0], size: number | Vec = 1, attribs?: Attribs) { - this.pos = pos; - this.size = isNumber(size) ? [size, size] : size; - this.attribs = attribs; - } - - get type() { - return Type.RECT; - } - - copy() { - return new Rect(set([], this.pos), set([], this.size), { - ...this.attribs - }); - } - - max() { - return add2([], this.pos, this.size); - } - - toHiccup() { - return ["rect", this.attribs, this.pos, this.size[0], this.size[1]]; - } -} - -export class Sphere implements IHiccupShape { - pos: Vec; - r: number; - attribs?: Attribs; - - constructor(pos: Vec = [0, 0, 0], r = 1, attribs?: Attribs) { - this.pos = pos; - this.r = r; - this.attribs = attribs; - } - - get type() { - return Type.SPHERE; - } - - copy() { - return new Sphere(set([], this.pos), this.r, { ...this.attribs }); - } - - toHiccup() { - return ["sphere", this.attribs, this.pos, this.r]; - } -} - -export class Triangle extends APC implements IHiccupShape { - get type() { - return Type.TRIANGLE; - } - - copy() { - return new Triangle(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polygon", this.attribs, this.points]; - } -} diff --git a/packages/geom/src/api/aabb.ts b/packages/geom/src/api/aabb.ts new file mode 100644 index 0000000000..ed5d0435b5 --- /dev/null +++ b/packages/geom/src/api/aabb.ts @@ -0,0 +1,36 @@ +import { isNumber } from "@thi.ng/checks"; +import { AABBLike, Attribs, Type } from "@thi.ng/geom-api"; +import { add3, set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class AABB implements AABBLike { + pos: Vec; + size: Vec; + attribs?: Attribs; + + constructor( + pos: Vec = [0, 0, 0], + size: number | Vec = 1, + attribs?: Attribs + ) { + this.pos = pos; + this.size = isNumber(size) ? [size, size, size] : size; + this.attribs = attribs; + } + + get type() { + return Type.AABB; + } + + copy(): AABB { + return new AABB( + set([], this.pos), + set([], this.size), + copyAttribs(this) + ); + } + + max() { + return add3([], this.pos, this.size); + } +} diff --git a/packages/geom/src/api/apc.ts b/packages/geom/src/api/apc.ts new file mode 100644 index 0000000000..1d6f35c044 --- /dev/null +++ b/packages/geom/src/api/apc.ts @@ -0,0 +1,19 @@ +import { Attribs, IShape, PCLike } from "@thi.ng/geom-api"; +import { Vec } from "@thi.ng/vectors"; + +export abstract class APC implements PCLike { + points: Vec[]; + attribs?: Attribs; + + constructor(points?: Vec[], attribs?: Attribs) { + this.points = points || []; + this.attribs = attribs; + } + + abstract get type(): number | string; + abstract copy(): IShape; + + *[Symbol.iterator]() { + yield* this.points; + } +} diff --git a/packages/geom/src/api/arc.ts b/packages/geom/src/api/arc.ts new file mode 100644 index 0000000000..59400b7e53 --- /dev/null +++ b/packages/geom/src/api/arc.ts @@ -0,0 +1,110 @@ +import { equiv } from "@thi.ng/equiv"; +import { + Attribs, + IHiccupPathSegment, + IHiccupShape, + Type +} from "@thi.ng/geom-api"; +import { pointAt as arcPointAt, pointAtTheta as arcPointAtTheta } from "@thi.ng/geom-arc"; +import { set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Arc implements IHiccupShape, IHiccupPathSegment { + pos: Vec; + r: Vec; + start: number; + end: number; + axis: number; + xl: boolean; + cw: boolean; + attribs?: Attribs; + + constructor( + pos: Vec, + r: Vec, + axis: number, + start: number, + end: number, + xl = false, + cw = false, + attribs?: Attribs + ) { + this.pos = pos; + this.r = r; + this.axis = axis; + this.start = start; + this.end = end; + this.xl = xl; + this.cw = cw; + this.attribs = attribs; + } + + get type() { + return Type.ARC; + } + + copy(): Arc { + return new Arc( + set([], this.pos), + set([], this.r), + this.axis, + this.start, + this.end, + this.xl, + this.cw, + copyAttribs(this) + ); + } + + equiv(o: any) { + return ( + o instanceof Arc && + equiv(this.pos, o.pos) && + equiv(this.r, o.r) && + this.start === o.start && + this.end === o.end && + this.axis === o.axis && + this.xl === o.xl && + this.cw && + o.cw + ); + } + + pointAt(t: number, out: Vec = []) { + return arcPointAt( + this.pos, + this.r, + this.axis, + this.start, + this.end, + t, + out + ); + } + + pointAtTheta(theta: number, out: Vec = []) { + return arcPointAtTheta(this.pos, this.r, this.axis, theta, out); + } + + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.pointAt(0)], ...this.toHiccupPathSegments()] + ]; + } + + toHiccupPathSegments() { + return [ + [ + "A", + this.r[0], + this.r[1], + this.axis, + this.xl, + this.cw, + this.pointAt(1) + ] + ]; + } +} diff --git a/packages/geom/src/api/circle.ts b/packages/geom/src/api/circle.ts new file mode 100644 index 0000000000..e44d26c927 --- /dev/null +++ b/packages/geom/src/api/circle.ts @@ -0,0 +1,27 @@ +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Circle implements IHiccupShape { + pos: Vec; + r: number; + attribs?: Attribs; + + constructor(pos: Vec = [0, 0], r = 1, attribs?: Attribs) { + this.pos = pos; + this.r = r; + this.attribs = attribs; + } + + get type() { + return Type.CIRCLE; + } + + copy(): Circle { + return new Circle(set([], this.pos), this.r, copyAttribs(this)); + } + + toHiccup() { + return ["circle", this.attribs, this.pos, this.r]; + } +} diff --git a/packages/geom/src/api/cubic.ts b/packages/geom/src/api/cubic.ts new file mode 100644 index 0000000000..58e844f554 --- /dev/null +++ b/packages/geom/src/api/cubic.ts @@ -0,0 +1,26 @@ +import { IHiccupPathSegment, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Cubic extends APC implements IHiccupPathSegment { + get type() { + return Type.CUBIC; + } + + copy(): Cubic { + return copyShape(Cubic, this); + } + + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.points[0]], ...this.toHiccupPathSegments()] + ]; + } + + toHiccupPathSegments() { + const pts = this.points; + return [["C", pts[1], pts[2], pts[3]]]; + } +} diff --git a/packages/geom/src/api/ellipse.ts b/packages/geom/src/api/ellipse.ts new file mode 100644 index 0000000000..3825050391 --- /dev/null +++ b/packages/geom/src/api/ellipse.ts @@ -0,0 +1,36 @@ +import { isNumber } from "@thi.ng/checks"; +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Ellipse implements IHiccupShape { + pos: Vec; + r: Vec; + attribs?: Attribs; + + constructor( + pos: Vec = [0, 0], + r: number | Vec = [1, 1], + attribs?: Attribs + ) { + this.pos = pos; + this.r = isNumber(r) ? [r, r] : r; + this.attribs = attribs; + } + + get type() { + return Type.ELLIPSE; + } + + copy(): Ellipse { + return new Ellipse( + set([], this.pos), + set([], this.r), + copyAttribs(this) + ); + } + + toHiccup() { + return ["ellipse", this.attribs, this.pos, this.r]; + } +} diff --git a/packages/geom/src/api/group.ts b/packages/geom/src/api/group.ts new file mode 100644 index 0000000000..a60221eb75 --- /dev/null +++ b/packages/geom/src/api/group.ts @@ -0,0 +1,35 @@ +import { equiv } from "@thi.ng/equiv"; +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Group implements IHiccupShape { + children: IHiccupShape[]; + attribs: Attribs; + + constructor(attribs: Attribs, children?: IHiccupShape[]) { + this.attribs = attribs; + this.children = children || []; + } + + get type() { + return Type.GROUP; + } + + *[Symbol.iterator]() { + yield* this.children; + } + + copy(): Group { + return new Group(copyAttribs(this), ( + this.children.map((c) => c.copy()) + )); + } + + equiv(o: any) { + return o instanceof Group && equiv(this.children, o.children); + } + + toHiccup() { + return ["g", this.attribs, ...this.children.map((x) => x.toHiccup())]; + } +} diff --git a/packages/geom/src/api/line.ts b/packages/geom/src/api/line.ts new file mode 100644 index 0000000000..25d6fb6f49 --- /dev/null +++ b/packages/geom/src/api/line.ts @@ -0,0 +1,24 @@ +import { IHiccupPathSegment, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Line extends APC implements IHiccupShape, IHiccupPathSegment { + get type() { + return Type.LINE; + } + + copy(): Line { + return copyShape(Line, this); + } + + toHiccup() { + return ["line", this.attribs, this.points[0], this.points[1]]; + } + + toHiccupPathSegments() { + const [a, b] = this.points; + return [ + a[0] === b[0] ? ["V", b[1]] : a[1] === b[1] ? ["H", b[0]] : ["L", b] + ]; + } +} diff --git a/packages/geom/src/api/path.ts b/packages/geom/src/api/path.ts new file mode 100644 index 0000000000..5e5c8354d7 --- /dev/null +++ b/packages/geom/src/api/path.ts @@ -0,0 +1,60 @@ +import { equiv } from "@thi.ng/equiv"; +import { illegalState } from "@thi.ng/errors"; +import { + Attribs, + IHiccupShape, + PathSegment, + Type +} from "@thi.ng/geom-api"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Path implements IHiccupShape { + segments: PathSegment[]; + closed: boolean; + attribs?: Attribs; + + constructor(segments?: PathSegment[], attribs?: Attribs) { + this.segments = segments || []; + this.attribs = attribs; + this.closed = false; + } + + get type() { + return Type.PATH; + } + + *[Symbol.iterator]() { + yield* this.segments; + } + + copy(): Path { + const p = new Path([...this.segments], copyAttribs(this)); + p.closed = this.closed; + return p; + } + + equiv(o: any) { + return o instanceof Path && equiv(this.segments, o.segments); + } + + add(s: PathSegment) { + if (this.closed) illegalState("path already closed"); + this.segments.push(s); + } + + toHiccup() { + let dest: any[] = []; + const segments = this.segments; + const n = segments.length; + if (n > 1) { + dest.push(["M", segments[0].point]); + for (let i = 1; i < n; i++) { + dest = dest.concat(segments[i].geo!.toHiccupPathSegments()); + } + if (this.closed) { + dest.push(["Z"]); + } + } + return ["path", this.attribs || {}, dest]; + } +} diff --git a/packages/geom/src/api/plane.ts b/packages/geom/src/api/plane.ts new file mode 100644 index 0000000000..84a5865a29 --- /dev/null +++ b/packages/geom/src/api/plane.ts @@ -0,0 +1,27 @@ +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Plane implements IHiccupShape { + normal: Vec; + w: number; + attribs?: Attribs; + + constructor(normal: Vec = [0, 1, 0], w = 0, attribs?: Attribs) { + this.normal = normal; + this.w = w; + this.attribs = attribs; + } + + get type() { + return Type.PLANE; + } + + copy(): Plane { + return new Plane(set([], this.normal), this.w, copyAttribs(this)); + } + + toHiccup() { + return ["plane", this.attribs, this.normal, this.w]; + } +} diff --git a/packages/geom/src/api/points.ts b/packages/geom/src/api/points.ts new file mode 100644 index 0000000000..ba713c78f9 --- /dev/null +++ b/packages/geom/src/api/points.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Points extends APC implements IHiccupShape { + get type() { + return Type.POINTS; + } + + copy(): Points { + return copyShape(Points, this); + } + + toHiccup() { + return ["points", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/api/polygon.ts b/packages/geom/src/api/polygon.ts new file mode 100644 index 0000000000..bb91585e70 --- /dev/null +++ b/packages/geom/src/api/polygon.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Polygon extends APC implements IHiccupShape { + get type() { + return Type.POLYGON; + } + + copy(): Polygon { + return copyShape(Polygon, this); + } + + toHiccup() { + return ["polygon", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/api/polyline.ts b/packages/geom/src/api/polyline.ts new file mode 100644 index 0000000000..3a46211222 --- /dev/null +++ b/packages/geom/src/api/polyline.ts @@ -0,0 +1,25 @@ +import { IHiccupPathSegment, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Polyline extends APC implements IHiccupShape, IHiccupPathSegment { + get type() { + return Type.POLYLINE; + } + + copy(): Polyline { + return copyShape(Polyline, this); + } + + toHiccup() { + return ["polyline", { ...this.attribs, fill: "none" }, this.points]; + } + + toHiccupPathSegments() { + const res: any[] = []; + for (let pts = this.points, n = pts.length, i = 1; i < n; i++) { + res.push(["L", pts[i]]); + } + return res; + } +} diff --git a/packages/geom/src/api/quad.ts b/packages/geom/src/api/quad.ts new file mode 100644 index 0000000000..d5718ec713 --- /dev/null +++ b/packages/geom/src/api/quad.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Quad extends APC implements IHiccupShape { + get type() { + return Type.QUAD; + } + + copy(): Quad { + return copyShape(Quad, this); + } + + toHiccup() { + return ["polygon", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/api/quad3.ts b/packages/geom/src/api/quad3.ts new file mode 100644 index 0000000000..704bf86522 --- /dev/null +++ b/packages/geom/src/api/quad3.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Quad3 extends APC implements IHiccupShape { + get type() { + return Type.QUAD3; + } + + copy(): Quad3 { + return copyShape(Quad3, this); + } + + toHiccup() { + return ["polygon", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/api/quadratic.ts b/packages/geom/src/api/quadratic.ts new file mode 100644 index 0000000000..b5ef14c09e --- /dev/null +++ b/packages/geom/src/api/quadratic.ts @@ -0,0 +1,26 @@ +import { IHiccupPathSegment, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Quadratic extends APC implements IHiccupShape, IHiccupPathSegment { + get type() { + return Type.QUADRATIC; + } + + copy(): Quadratic { + return copyShape(Quadratic, this); + } + + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.points[0]], ...this.toHiccupPathSegments()] + ]; + } + + toHiccupPathSegments() { + const pts = this.points; + return [["Q", pts[1], pts[2]]]; + } +} diff --git a/packages/geom/src/api/ray.ts b/packages/geom/src/api/ray.ts new file mode 100644 index 0000000000..70e2380ea4 --- /dev/null +++ b/packages/geom/src/api/ray.ts @@ -0,0 +1,32 @@ +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { maddN2, set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Ray implements IHiccupShape { + pos: Vec; + dir: Vec; + attribs?: Attribs; + + constructor(pos: Vec, dir: Vec, attribs?: Attribs) { + this.pos = pos; + this.dir = dir; + this.attribs = attribs; + } + + get type() { + return Type.RAY; + } + + copy(): Ray { + return new Ray(set([], this.pos), set([], this.dir), copyAttribs(this)); + } + + toHiccup() { + return [ + "line", + this.attribs, + this.pos, + maddN2([], this.dir, 1e6, this.pos) + ]; + } +} diff --git a/packages/geom/src/api/rect.ts b/packages/geom/src/api/rect.ts new file mode 100644 index 0000000000..1f4985c1a5 --- /dev/null +++ b/packages/geom/src/api/rect.ts @@ -0,0 +1,41 @@ +import { isNumber } from "@thi.ng/checks"; +import { + AABBLike, + Attribs, + IHiccupShape, + Type +} from "@thi.ng/geom-api"; +import { add2, set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Rect implements AABBLike, IHiccupShape { + pos: Vec; + size: Vec; + attribs?: Attribs; + + constructor(pos: Vec = [0, 0], size: number | Vec = 1, attribs?: Attribs) { + this.pos = pos; + this.size = isNumber(size) ? [size, size] : size; + this.attribs = attribs; + } + + get type() { + return Type.RECT; + } + + copy(): Rect { + return new Rect( + set([], this.pos), + set([], this.size), + copyAttribs(this) + ); + } + + max() { + return add2([], this.pos, this.size); + } + + toHiccup() { + return ["rect", this.attribs, this.pos, this.size[0], this.size[1]]; + } +} diff --git a/packages/geom/src/api/sphere.ts b/packages/geom/src/api/sphere.ts new file mode 100644 index 0000000000..95f5f5aaf7 --- /dev/null +++ b/packages/geom/src/api/sphere.ts @@ -0,0 +1,27 @@ +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { set3, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Sphere implements IHiccupShape { + pos: Vec; + r: number; + attribs?: Attribs; + + constructor(pos: Vec = [0, 0, 0], r = 1, attribs?: Attribs) { + this.pos = pos; + this.r = r; + this.attribs = attribs; + } + + get type() { + return Type.SPHERE; + } + + copy(): Sphere { + return new Sphere(set3([], this.pos), this.r, copyAttribs(this)); + } + + toHiccup() { + return ["sphere", this.attribs, this.pos, this.r]; + } +} diff --git a/packages/geom/src/api/triangle.ts b/packages/geom/src/api/triangle.ts new file mode 100644 index 0000000000..ce078c0dec --- /dev/null +++ b/packages/geom/src/api/triangle.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Triangle extends APC implements IHiccupShape { + get type() { + return Type.TRIANGLE; + } + + copy(): Triangle { + return copyShape(Triangle, this); + } + + toHiccup() { + return ["polygon", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/ctors/aabb.ts b/packages/geom/src/ctors/aabb.ts index f141834790..92333c4a49 100644 --- a/packages/geom/src/ctors/aabb.ts +++ b/packages/geom/src/ctors/aabb.ts @@ -6,7 +6,8 @@ import { subN3, Vec } from "@thi.ng/vectors"; -import { AABB, Sphere } from "../api"; +import { AABB } from "../api/aabb"; +import { Sphere } from "../api/sphere"; import { argsVV } from "../internal/args"; export function aabb(pos: Vec, size: number | Vec, attribs?: Attribs): AABB; diff --git a/packages/geom/src/ctors/arc.ts b/packages/geom/src/ctors/arc.ts index 145418b32a..076a38b60e 100644 --- a/packages/geom/src/ctors/arc.ts +++ b/packages/geom/src/ctors/arc.ts @@ -2,7 +2,7 @@ import { isNumber } from "@thi.ng/checks"; import { Attribs } from "@thi.ng/geom-api"; import { fromEndPoints } from "@thi.ng/geom-arc"; import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Arc } from "../api"; +import { Arc } from "../api/arc"; export const arc = ( pos: Vec, diff --git a/packages/geom/src/ctors/circle.ts b/packages/geom/src/ctors/circle.ts index dc6ff4aba6..ee9fca21d6 100644 --- a/packages/geom/src/ctors/circle.ts +++ b/packages/geom/src/ctors/circle.ts @@ -1,7 +1,12 @@ import { Attribs } from "@thi.ng/geom-api"; import { circumCenter2 } from "@thi.ng/geom-poly-utils"; -import { dist, mixN2, ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Circle } from "../api"; +import { + dist, + mixN2, + ReadonlyVec, + Vec +} from "@thi.ng/vectors"; +import { Circle } from "../api/circle"; import { argsVN } from "../internal/args"; export function circle(pos: Vec, r: number, attribs?: Attribs): Circle; diff --git a/packages/geom/src/ctors/cubic.ts b/packages/geom/src/ctors/cubic.ts index 18b542a601..177dcd807b 100644 --- a/packages/geom/src/ctors/cubic.ts +++ b/packages/geom/src/ctors/cubic.ts @@ -1,23 +1,20 @@ import { Attribs } from "@thi.ng/geom-api"; -import { - cubicFromArc as _arc, - cubicFromLine as _line, - cubicFromQuadratic as _quad -} from "@thi.ng/geom-splines"; +import { cubicFromArc as _arc, cubicFromLine as _line, cubicFromQuadratic as _quad } from "@thi.ng/geom-splines"; import { Vec } from "@thi.ng/vectors"; -import { Arc, Cubic } from "../api"; -import { argAttribs } from "../internal/args"; +import { Arc } from "../api/arc"; +import { Cubic } from "../api/cubic"; +import { copyAttribs } from "../internal/copy-attribs"; +import { pclike } from "../internal/pclike"; export function cubic(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Cubic; export function cubic(pts: Vec[], attribs?: Attribs): Cubic; export function cubic(...args: any[]) { - const attr = argAttribs(args); - return new Cubic(args.length === 1 ? args[0] : args, attr); + return pclike(Cubic, args); } export const cubicFromArc = (arc: Arc) => _arc(arc.pos, arc.r, arc.axis, arc.start, arc.end).map( - (c) => new Cubic(c, { ...arc.attribs }) + (c) => new Cubic(c, copyAttribs(arc)) ); export const cubicFromLine = (a: Vec, b: Vec, attribs?: Attribs) => diff --git a/packages/geom/src/ctors/ellipse.ts b/packages/geom/src/ctors/ellipse.ts index a0b7775136..756a638ea0 100644 --- a/packages/geom/src/ctors/ellipse.ts +++ b/packages/geom/src/ctors/ellipse.ts @@ -1,6 +1,6 @@ import { Attribs } from "@thi.ng/geom-api"; import { Vec } from "@thi.ng/vectors"; -import { Ellipse } from "../api"; +import { Ellipse } from "../api/ellipse"; import { argsVV } from "../internal/args"; export function ellipse(pos: Vec, r: number | Vec, attribs?: Attribs): Ellipse; diff --git a/packages/geom/src/ctors/group.ts b/packages/geom/src/ctors/group.ts index 74af0412c6..5a84ab27c4 100644 --- a/packages/geom/src/ctors/group.ts +++ b/packages/geom/src/ctors/group.ts @@ -1,5 +1,5 @@ import { Attribs, IHiccupShape } from "@thi.ng/geom-api"; -import { Group } from "../api"; +import { Group } from "../api/group"; export const group = (attribs: Attribs = {}, children?: IHiccupShape[]) => new Group(attribs, children); diff --git a/packages/geom/src/ctors/line.ts b/packages/geom/src/ctors/line.ts index 500b352d9b..b175452e61 100644 --- a/packages/geom/src/ctors/line.ts +++ b/packages/geom/src/ctors/line.ts @@ -1,14 +1,14 @@ import { Attribs } from "@thi.ng/geom-api"; import { liangBarsky2 } from "@thi.ng/geom-clip"; import { Vec, VecPair } from "@thi.ng/vectors"; -import { Line, Rect } from "../api"; -import { argAttribs } from "../internal/args"; +import { Line } from "../api/line"; +import { Rect } from "../api/rect"; +import { pclike } from "../internal/pclike"; export function line(a: Vec, b: Vec, attribs?: Attribs): Line; export function line(pts: Vec[], attribs?: Attribs): Line; export function line(...args: any[]) { - const attr = argAttribs(args); - return new Line(args.length === 1 ? args[0] : args, attr); + return pclike(Line, args); } export const clippedLine = (l: Line, bounds: VecPair | Rect) => { diff --git a/packages/geom/src/ctors/path-builder.ts b/packages/geom/src/ctors/path-builder.ts new file mode 100644 index 0000000000..11aa295d33 --- /dev/null +++ b/packages/geom/src/ctors/path-builder.ts @@ -0,0 +1,195 @@ +import { peek } from "@thi.ng/arrays"; +import { Attribs, SegmentType } from "@thi.ng/geom-api"; +import { eqDelta } from "@thi.ng/math"; +import { + add2, + copy, + mulN2, + set2, + sub2, + Vec, + zeroes +} from "@thi.ng/vectors"; +import { Cubic } from "../api/cubic"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Quadratic } from "../api/quadratic"; +import { arcFrom2Points } from "./arc"; + +export class PathBuilder { + paths: Path[]; + attribs?: Attribs; + protected curr!: Path; + protected currP!: Vec; + protected bezierP!: Vec; + protected startP!: Vec; + + constructor(attribs?: Attribs) { + this.paths = []; + this.attribs = attribs; + this.newPath(); + } + + *[Symbol.iterator]() { + yield* this.paths; + } + + current() { + return this.curr; + } + + newPath() { + this.curr = new Path([], this.attribs); + this.paths.push(this.curr); + this.currP = zeroes(2); + this.bezierP = zeroes(2); + this.startP = zeroes(2); + } + + moveTo(p: Vec, relative = false): PathBuilder { + if (this.curr.segments.length > 0) { + this.curr = new Path(); + this.paths.push(this.curr); + } + p = this.updateCurrent(p, relative); + set2(this.startP, p); + set2(this.bezierP, p); + this.curr.add({ + point: p, + type: SegmentType.MOVE + }); + return this; + } + + lineTo(p: Vec, relative = false): PathBuilder { + this.curr.add({ + geo: new Line([copy(this.currP), this.updateCurrent(p, relative)]), + type: SegmentType.LINE + }); + set2(this.bezierP, this.currP); + return this; + } + + hlineTo(x: number, relative = false): PathBuilder { + this.addHVLine(x, 0, relative); + return this; + } + + vlineTo(y: number, relative = false): PathBuilder { + this.addHVLine(y, 1, relative); + return this; + } + + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve + cubicTo(cp1: Vec, cp2: Vec, p: Vec, relative = false) { + this.addCubic(this.absPoint(cp1, relative), cp2, p, relative); + return this; + } + + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Quadratic_B%C3%A9zier_Curve + quadraticTo(cp: Vec, p: Vec, relative = false) { + this.addQuadratic(this.absPoint(cp, relative), p, relative); + return this; + } + + cubicChainTo(cp2: Vec, p: Vec, relative = false) { + const prevMode = peek(this.curr.segments).type; + const c1 = copy(this.currP); + prevMode === SegmentType.CUBIC && + add2(null, sub2([], c1, this.bezierP), c1); + this.addCubic(c1, cp2, p, relative); + return this; + } + + quadraticChainTo(p: Vec, relative = false) { + const prevMode = peek(this.curr.segments).type; + const c1 = copy(this.currP); + prevMode === SegmentType.QUADRATIC && + sub2(null, mulN2(null, c1, 2), this.bezierP); + this.addQuadratic(c1, p, relative); + return this; + } + + arcTo( + p: Vec, + r: Vec, + xaxis: number, + xl: boolean, + clockwise: boolean, + relative = false + ) { + if (eqDelta(r[0], 0) || eqDelta(r[1], 0)) { + return this.lineTo(p, relative); + } + const prev = copy(this.currP); + this.curr.add({ + geo: arcFrom2Points( + prev, + this.updateCurrent(p, relative), + r, + xaxis, + xl, + clockwise + ), + type: SegmentType.ARC + }); + set2(this.bezierP, this.currP); + return this; + } + + closePath() { + this.curr.add({ + geo: new Line([copy(this.currP), copy(this.startP)]), + type: SegmentType.LINE + }); + this.curr.closed = true; + return this; + } + + protected updateCurrent(p: Vec, relative: boolean) { + p = copy(relative ? add2(null, this.currP, p) : set2(this.currP, p)); + return p; + } + + protected absPoint(p: Vec, relative: boolean) { + return relative ? add2(null, p, this.currP) : p; + } + + protected addHVLine(p: number, i: number, relative: boolean) { + const prev = copy(this.currP); + this.currP[i] = relative ? this.currP[i] + p : p; + set2(this.bezierP, this.currP); + this.curr.add({ + geo: new Line([prev, copy(this.currP)]), + type: SegmentType.LINE + }); + } + + protected addCubic(cp1: Vec, cp2: Vec, p: Vec, relative: boolean) { + cp2 = this.absPoint(cp2, relative); + set2(this.bezierP, cp2); + this.curr.add({ + geo: new Cubic([ + copy(this.currP), + cp1, + cp2, + this.updateCurrent(p, relative) + ]), + type: SegmentType.CUBIC + }); + } + + protected addQuadratic(cp: Vec, p: Vec, relative: boolean) { + set2(this.bezierP, cp); + this.curr.add({ + geo: new Quadratic([ + copy(this.currP), + cp, + this.updateCurrent(p, relative) + ]), + type: SegmentType.QUADRATIC + }); + } +} + +export const pathBuilder = (attribs?: Attribs) => new PathBuilder(attribs); diff --git a/packages/geom/src/ctors/path-from-svg.ts b/packages/geom/src/ctors/path-from-svg.ts new file mode 100644 index 0000000000..4937b6f3c0 --- /dev/null +++ b/packages/geom/src/ctors/path-from-svg.ts @@ -0,0 +1,160 @@ +import { illegalState } from "@thi.ng/errors"; +import { rad } from "@thi.ng/math"; +import { Vec } from "@thi.ng/vectors"; +import { PathBuilder } from "./path-builder"; + +const CMD_RE = /[achlmqstvz]/i; + +export const pathFromSvg = (svg: string) => { + const b = new PathBuilder(); + try { + let cmd = ""; + for (let n = svg.length, i = 0; i < n; ) { + i = skipWS(svg, i); + const c = svg.charAt(i); + if (CMD_RE.test(c)) { + cmd = c; + i++; + } + let p, pa, pb, t1, t2, t3; + switch (cmd.toLowerCase()) { + case "m": + [p, i] = readPoint(svg, i); + b.moveTo(p, cmd === "m"); + break; + case "l": + [p, i] = readPoint(svg, i); + b.lineTo(p, cmd === "l"); + break; + case "h": + [p, i] = readFloat(svg, i); + b.hlineTo(p, cmd === "h"); + break; + case "v": + [p, i] = readFloat(svg, i); + b.vlineTo(p, cmd === "v"); + break; + case "q": + [pa, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + // console.log("quadratic", pa.toString(), p.toString()); + b.quadraticTo(pa, p, cmd === "q"); + break; + case "c": + [pa, i] = readPoint(svg, i); + [pb, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + // console.log("cubic", pa.toString(), pb.toString(), p.toString()); + b.cubicTo(pa, pb, p, cmd === "c"); + break; + case "s": + [pa, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + // console.log("cubicChain", pa.toString(), p.toString()); + b.cubicChainTo(pa, p, cmd === "s"); + break; + case "t": + [p, i] = readPoint(svg, i); + // console.log("quadraticChain", p.toString()); + b.quadraticChainTo(p, cmd === "t"); + break; + case "a": { + [pa, i] = readPoint(svg, i); + [t1, i] = readFloat(svg, i); + [t2, i] = readFlag(svg, i); + [t3, i] = readFlag(svg, i); + [pb, i] = readPoint(svg, i); + // console.log("arc", pa.toString(), rad(t1), t2, t3, pb.toString()); + b.arcTo(pb, pa, rad(t1), t2, t3, cmd === "a"); + break; + } + case "z": + b.closePath(); + break; + default: + throw new Error( + `unsupported segment type: ${c} @ pos ${i}` + ); + } + } + return b.paths; + } catch (e) { + throw e instanceof Error + ? e + : new Error(`illegal char '${svg.charAt(e)}' @ ${e}`); + } +}; + +const isWS = (c: string) => c === " " || c === "\n" || c === "\r" || c === "\t"; + +const skipWS = (src: string, i: number) => { + const n = src.length; + while (i < n && isWS(src.charAt(i))) i++; + return i; +}; + +const readPoint = (src: string, index: number): [Vec, number] => { + let x, y; + [x, index] = readFloat(src, index); + index = skipWS(src, index); + [y, index] = readFloat(src, index); + return [[x, y], index]; +}; + +const readFlag = (src: string, i: number): [boolean, number] => { + i = skipWS(src, i); + const c = src.charAt(i); + return [ + c === "0" + ? false + : c === "1" + ? true + : illegalState(`expected '0' or '1' @ pos: ${i}`), + i + 1 + ]; +}; + +const readFloat = (src: string, index: number) => { + index = skipWS(src, index); + let signOk = true; + let dotOk = true; + let expOk = false; + let commaOk = false; + let i = index; + for (let n = src.length; i < n; i++) { + const c = src.charAt(i); + // console.log("float", src.substring(index, i + 1)); + if ("0" <= c && c <= "9") { + expOk = true; + commaOk = true; + signOk = false; + continue; + } + if (c === "-" || c === "+") { + if (!signOk) break; + signOk = false; + continue; + } + if (c === ".") { + if (!dotOk) break; + dotOk = false; + continue; + } + if (c === "e") { + if (!expOk) throw i; + expOk = false; + dotOk = false; + signOk = true; + continue; + } + if (c === ",") { + if (!commaOk) throw i; + i++; + } + break; + } + if (i === index) { + illegalState(`expected coordinate @ pos: ${i}`); + } + return [parseFloat(src.substring(index, i)), i]; +}; diff --git a/packages/geom/src/ctors/path.ts b/packages/geom/src/ctors/path.ts index 8e13c3df76..0ddf79c8a9 100644 --- a/packages/geom/src/ctors/path.ts +++ b/packages/geom/src/ctors/path.ts @@ -1,27 +1,11 @@ -import { peek } from "@thi.ng/arrays"; import { isNumber } from "@thi.ng/checks"; -import { illegalState } from "@thi.ng/errors"; import { Attribs, PathSegment, SegmentType } from "@thi.ng/geom-api"; -import { eqDelta, rad } from "@thi.ng/math"; import { map, mapcat } from "@thi.ng/transducers"; -import { - add2, - copy, - maddN2, - mulN2, - set2, - sub2, - Vec, - zeroes -} from "@thi.ng/vectors"; -import { - Cubic, - Line, - Path, - Quadratic -} from "../api"; +import { maddN2, Vec } from "@thi.ng/vectors"; +import { Cubic } from "../api/cubic"; +import { Path } from "../api/path"; import { asCubic } from "../ops/as-cubic"; -import { arcFrom2Points } from "./arc"; +import { PathBuilder } from "./path-builder"; export const path = (segments: PathSegment[], attribs?: Attribs) => new Path(segments, attribs); @@ -72,352 +56,3 @@ export const roundedRect = ( .arcTo([r[0], -r[1]], r, 0, false, true, true) .current(); }; - -export class PathBuilder { - paths: Path[]; - attribs?: Attribs; - protected curr!: Path; - protected currP!: Vec; - protected bezierP!: Vec; - protected startP!: Vec; - - constructor(attribs?: Attribs) { - this.paths = []; - this.attribs = attribs; - this.newPath(); - } - - *[Symbol.iterator]() { - yield* this.paths; - } - - current() { - return this.curr; - } - - newPath() { - this.curr = new Path([], this.attribs); - this.paths.push(this.curr); - this.currP = zeroes(2); - this.bezierP = zeroes(2); - this.startP = zeroes(2); - } - - moveTo(p: Vec, relative = false): PathBuilder { - if (this.curr.segments.length > 0) { - this.curr = new Path(); - this.paths.push(this.curr); - } - p = this.updateCurrent(p, relative); - set2(this.startP, p); - set2(this.bezierP, p); - this.curr.add({ - point: p, - type: SegmentType.MOVE - }); - return this; - } - - lineTo(p: Vec, relative = false): PathBuilder { - this.curr.add({ - geo: new Line([copy(this.currP), this.updateCurrent(p, relative)]), - type: SegmentType.LINE - }); - set2(this.bezierP, this.currP); - return this; - } - - hlineTo(x: number, relative = false): PathBuilder { - const prev = copy(this.currP); - this.currP[0] = relative ? this.currP[0] + x : x; - set2(this.bezierP, this.currP); - this.curr.add({ - geo: new Line([prev, copy(this.currP)]), - type: SegmentType.LINE - }); - return this; - } - - vlineTo(y: number, relative = false): PathBuilder { - const prev = copy(this.currP); - this.currP[1] = relative ? this.currP[1] + y : y; - set2(this.bezierP, this.currP); - this.curr.add({ - geo: new Line([prev, copy(this.currP)]), - type: SegmentType.LINE - }); - return this; - } - - // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve - cubicTo(cp1: Vec, cp2: Vec, p: Vec, relative = false) { - const c2 = this.absPoint(cp2, relative); - set2(this.bezierP, c2); - this.curr.add({ - geo: new Cubic([ - copy(this.currP), - this.absPoint(cp1, relative), - c2, - this.updateCurrent(p, relative) - ]), - type: SegmentType.CUBIC - }); - return this; - } - - // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Quadratic_B%C3%A9zier_Curve - quadraticTo(cp: Vec, p: Vec, relative = false) { - const c1 = this.absPoint(cp, relative); - set2(this.bezierP, c1); - this.curr.add({ - geo: new Quadratic([ - copy(this.currP), - c1, - this.updateCurrent(p, relative) - ]), - type: SegmentType.QUADRATIC - }); - return this; - } - - cubicChainTo(cp2: Vec, p: Vec, relative = false) { - const prevMode = peek(this.curr.segments).type; - const c1 = copy(this.currP); - if (prevMode === SegmentType.CUBIC) { - add2(null, sub2([], c1, this.bezierP), c1); - } - const c2 = this.absPoint(cp2, relative); - set2(this.bezierP, c2); - this.curr.add({ - geo: new Cubic([ - copy(this.currP), - c1, - c2, - this.updateCurrent(p, relative) - ]), - type: SegmentType.CUBIC - }); - return this; - } - - quadraticChainTo(p: Vec, relative = false) { - const prevMode = peek(this.curr.segments).type; - const c1 = copy(this.currP); - if (prevMode === SegmentType.QUADRATIC) { - sub2(null, mulN2(null, c1, 2), this.bezierP); - } - set2(this.bezierP, c1); - this.curr.add({ - geo: new Quadratic([ - copy(this.currP), - c1, - this.updateCurrent(p, relative) - ]), - type: SegmentType.CUBIC - }); - return this; - } - - arcTo( - p: Vec, - r: Vec, - xaxis: number, - xl: boolean, - clockwise: boolean, - relative = false - ) { - if (eqDelta(r[0], 0) || eqDelta(r[1], 0)) { - return this.lineTo(p, relative); - } - const prev = copy(this.currP); - this.curr.add({ - geo: arcFrom2Points( - prev, - this.updateCurrent(p, relative), - r, - xaxis, - xl, - clockwise - ), - type: SegmentType.ARC - }); - set2(this.bezierP, this.currP); - return this; - } - - closePath() { - this.curr.add({ - geo: new Line([copy(this.currP), copy(this.startP)]), - type: SegmentType.LINE - }); - this.curr.closed = true; - return this; - } - - protected updateCurrent(p: Vec, relative: boolean) { - p = copy(relative ? add2(null, this.currP, p) : set2(this.currP, p)); - return p; - } - - protected absPoint(p: Vec, relative: boolean) { - return relative ? add2(null, p, this.currP) : p; - } -} - -export const pathBuilder = (attribs?: Attribs) => new PathBuilder(attribs); - -const CMD_RE = /[achlmqstvz]/i; - -export const pathFromSvg = (svg: string) => { - const b = new PathBuilder(); - try { - let cmd = ""; - for (let n = svg.length, i = 0; i < n; ) { - i = skipWS(svg, i); - const c = svg.charAt(i); - if (CMD_RE.test(c)) { - cmd = c; - i++; - } - let p, pa, pb, t1, t2, t3; - switch (cmd.toLowerCase()) { - case "m": - [p, i] = readPoint(svg, i); - b.moveTo(p, cmd === "m"); - break; - case "l": - [p, i] = readPoint(svg, i); - b.lineTo(p, cmd === "l"); - break; - case "h": - [p, i] = readFloat(svg, i); - b.hlineTo(p, cmd === "h"); - break; - case "v": - [p, i] = readFloat(svg, i); - b.vlineTo(p, cmd === "v"); - break; - case "q": - [pa, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - // console.log("quadratic", pa.toString(), p.toString()); - b.quadraticTo(pa, p, cmd === "q"); - break; - case "c": - [pa, i] = readPoint(svg, i); - [pb, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - // console.log("cubic", pa.toString(), pb.toString(), p.toString()); - b.cubicTo(pa, pb, p, cmd === "c"); - break; - case "s": - [pa, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - // console.log("cubicChain", pa.toString(), p.toString()); - b.cubicChainTo(pa, p, cmd === "s"); - break; - case "t": - [p, i] = readPoint(svg, i); - // console.log("quadraticChain", p.toString()); - b.quadraticChainTo(p, cmd === "t"); - break; - case "a": { - [pa, i] = readPoint(svg, i); - [t1, i] = readFloat(svg, i); - [t2, i] = readFlag(svg, i); - [t3, i] = readFlag(svg, i); - [pb, i] = readPoint(svg, i); - // console.log("arc", pa.toString(), rad(t1), t2, t3, pb.toString()); - b.arcTo(pb, pa, rad(t1), t2, t3, cmd === "a"); - break; - } - case "z": - b.closePath(); - break; - default: - throw new Error( - `unsupported segment type: ${c} @ pos ${i}` - ); - } - } - return b.paths; - } catch (e) { - throw e instanceof Error - ? e - : new Error(`illegal char '${svg.charAt(e)}' @ ${e}`); - } -}; - -const isWS = (c: string) => c === " " || c === "\n" || c === "\r" || c === "\t"; - -const skipWS = (src: string, i: number) => { - const n = src.length; - while (i < n && isWS(src.charAt(i))) i++; - return i; -}; - -const readPoint = (src: string, index: number): [Vec, number] => { - let x, y; - [x, index] = readFloat(src, index); - index = skipWS(src, index); - [y, index] = readFloat(src, index); - return [[x, y], index]; -}; - -const readFlag = (src: string, i: number): [boolean, number] => { - i = skipWS(src, i); - const c = src.charAt(i); - return [ - c === "0" - ? false - : c === "1" - ? true - : illegalState(`expected '0' or '1' @ pos: ${i}`), - i + 1 - ]; -}; - -const readFloat = (src: string, index: number) => { - index = skipWS(src, index); - let signOk = true; - let dotOk = true; - let expOk = false; - let commaOk = false; - let i = index; - for (let n = src.length; i < n; i++) { - const c = src.charAt(i); - // console.log("float", src.substring(index, i + 1)); - if ("0" <= c && c <= "9") { - expOk = true; - commaOk = true; - signOk = false; - continue; - } - if (c === "-" || c === "+") { - if (!signOk) break; - signOk = false; - continue; - } - if (c === ".") { - if (!dotOk) break; - dotOk = false; - continue; - } - if (c === "e") { - if (!expOk) throw i; - expOk = false; - dotOk = false; - signOk = true; - continue; - } - if (c === ",") { - if (!commaOk) throw i; - i++; - } - break; - } - if (i === index) { - illegalState(`expected coordinate @ pos: ${i}`); - } - return [parseFloat(src.substring(index, i)), i]; -}; diff --git a/packages/geom/src/ctors/plane.ts b/packages/geom/src/ctors/plane.ts index 3c9797afae..5f208ca27d 100644 --- a/packages/geom/src/ctors/plane.ts +++ b/packages/geom/src/ctors/plane.ts @@ -6,7 +6,7 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Plane } from "../api"; +import { Plane } from "../api/plane"; export const plane = (normal: Vec, w: number, attribs?: Attribs) => new Plane(normalize(null, normal), w, attribs); diff --git a/packages/geom/src/ctors/points.ts b/packages/geom/src/ctors/points.ts index 576ca2c876..5f696aee71 100644 --- a/packages/geom/src/ctors/points.ts +++ b/packages/geom/src/ctors/points.ts @@ -1,6 +1,6 @@ import { Attribs } from "@thi.ng/geom-api"; import { Vec } from "@thi.ng/vectors"; -import { Points } from "../api"; +import { Points } from "../api/points"; export const points = (pts?: Vec[], attribs?: Attribs) => new Points(pts, attribs); diff --git a/packages/geom/src/ctors/polygon.ts b/packages/geom/src/ctors/polygon.ts index c4695aa2bb..ec2765e6b0 100644 --- a/packages/geom/src/ctors/polygon.ts +++ b/packages/geom/src/ctors/polygon.ts @@ -9,7 +9,7 @@ import { zip } from "@thi.ng/transducers"; import { cartesian2, Vec } from "@thi.ng/vectors"; -import { Polygon } from "../api"; +import { Polygon } from "../api/polygon"; export const polygon = (pts: Vec[], attribs?: Attribs) => new Polygon(pts, attribs); diff --git a/packages/geom/src/ctors/polyline.ts b/packages/geom/src/ctors/polyline.ts index e6cb9e2864..9d157095bb 100644 --- a/packages/geom/src/ctors/polyline.ts +++ b/packages/geom/src/ctors/polyline.ts @@ -1,6 +1,6 @@ import { Attribs } from "@thi.ng/geom-api"; import { Vec } from "@thi.ng/vectors"; -import { Polyline } from "../api"; +import { Polyline } from "../api/polyline"; export const polyline = (pts: Vec[], attribs?: Attribs) => new Polyline(pts, attribs); diff --git a/packages/geom/src/ctors/quad.ts b/packages/geom/src/ctors/quad.ts index b1da3db85b..6113630d01 100644 --- a/packages/geom/src/ctors/quad.ts +++ b/packages/geom/src/ctors/quad.ts @@ -8,14 +8,16 @@ import { Vec, Z3 } from "@thi.ng/vectors"; -import { Plane, Quad, Quad3 } from "../api"; +import { Plane } from "../api/plane"; +import { Quad } from "../api/quad"; +import { Quad3 } from "../api/quad3"; import { argAttribs } from "../internal/args"; +import { pclike } from "../internal/pclike"; export function quad(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Quad; export function quad(pts: Vec[], attribs?: Attribs): Quad; export function quad(...args: any[]) { - const attr = argAttribs(args); - return new Quad(args.length === 1 ? args[0] : args, attr); + return pclike(Quad, args); } export function quad3(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Quad; diff --git a/packages/geom/src/ctors/quadratic.ts b/packages/geom/src/ctors/quadratic.ts index 300522db80..4b5086c7c6 100644 --- a/packages/geom/src/ctors/quadratic.ts +++ b/packages/geom/src/ctors/quadratic.ts @@ -1,14 +1,13 @@ import { Attribs } from "@thi.ng/geom-api"; import { quadraticFromLine as _line } from "@thi.ng/geom-splines"; import { Vec } from "@thi.ng/vectors"; -import { Quadratic } from "../api"; -import { argAttribs } from "../internal/args"; +import { Quadratic } from "../api/quadratic"; +import { pclike } from "../internal/pclike"; export function quadratic(a: Vec, b: Vec, c: Vec, attribs?: Attribs): Quadratic; export function quadratic(pts: Vec[], attribs?: Attribs): Quadratic; export function quadratic(...args: any[]) { - const attr = argAttribs(args); - return new Quadratic(args.length === 1 ? args[0] : args, attr); + return pclike(Quadratic, args); } export const quadraticFromLine = (a: Vec, b: Vec, attribs?: Attribs) => diff --git a/packages/geom/src/ctors/ray.ts b/packages/geom/src/ctors/ray.ts index b96669d5d6..c98cc9ded7 100644 --- a/packages/geom/src/ctors/ray.ts +++ b/packages/geom/src/ctors/ray.ts @@ -1,6 +1,6 @@ import { Attribs } from "@thi.ng/geom-api"; import { normalize as _norm, Vec } from "@thi.ng/vectors"; -import { Ray } from "../api"; +import { Ray } from "../api/ray"; export const ray = (pos: Vec, dir: Vec, attribs?: Attribs, normalize = true) => new Ray(pos, normalize ? _norm(null, dir) : dir, attribs); diff --git a/packages/geom/src/ctors/rect.ts b/packages/geom/src/ctors/rect.ts index a34e345b83..da813eb2e1 100644 --- a/packages/geom/src/ctors/rect.ts +++ b/packages/geom/src/ctors/rect.ts @@ -8,7 +8,9 @@ import { subN2, Vec } from "@thi.ng/vectors"; -import { Circle, Polygon, Rect } from "../api"; +import { Circle } from "../api/circle"; +import { Polygon } from "../api/polygon"; +import { Rect } from "../api/rect"; import { argsVV } from "../internal/args"; export function rect(pos: Vec, size: number | Vec, attribs?: Attribs): Rect; diff --git a/packages/geom/src/ctors/sphere.ts b/packages/geom/src/ctors/sphere.ts index b855a98067..c22c30d4ec 100644 --- a/packages/geom/src/ctors/sphere.ts +++ b/packages/geom/src/ctors/sphere.ts @@ -5,7 +5,7 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Sphere } from "../api"; +import { Sphere } from "../api/sphere"; import { argsVN } from "../internal/args"; export function sphere(pos: Vec, r: number, attribs?: Attribs): Sphere; diff --git a/packages/geom/src/ctors/triangle.ts b/packages/geom/src/ctors/triangle.ts index 85daefc683..bdfed12bee 100644 --- a/packages/geom/src/ctors/triangle.ts +++ b/packages/geom/src/ctors/triangle.ts @@ -1,14 +1,13 @@ import { Attribs } from "@thi.ng/geom-api"; import { equilateralTriangle2 } from "@thi.ng/geom-poly-utils"; import { Vec } from "@thi.ng/vectors"; -import { Triangle } from "../api"; -import { argAttribs } from "../internal/args"; +import { Triangle } from "../api/triangle"; +import { pclike } from "../internal/pclike"; export function triangle(a: Vec, b: Vec, c: Vec, attribs?: Attribs): Triangle; export function triangle(pts: Vec[], attribs?: Attribs): Triangle; export function triangle(...args: any[]) { - const attr = argAttribs(args); - return new Triangle(args.length === 1 ? args[0] : args, attr); + return pclike(Triangle, args); } export const equilateralTriangle = (a: Vec, b: Vec, attribs?: Attribs) => diff --git a/packages/geom/src/index.ts b/packages/geom/src/index.ts index 7e9536b6ed..d36ccba5a6 100644 --- a/packages/geom/src/index.ts +++ b/packages/geom/src/index.ts @@ -1,4 +1,23 @@ -export * from "./api"; +export * from "./api/aabb"; +export * from "./api/apc"; +export * from "./api/arc"; +export * from "./api/circle"; +export * from "./api/cubic"; +export * from "./api/ellipse"; +export * from "./api/group"; +export * from "./api/line"; +export * from "./api/path"; +export * from "./api/plane"; +export * from "./api/points"; +export * from "./api/polygon"; +export * from "./api/polyline"; +export * from "./api/quad"; +export * from "./api/quad3"; +export * from "./api/quadratic"; +export * from "./api/ray"; +export * from "./api/rect"; +export * from "./api/sphere"; +export * from "./api/triangle"; export * from "./ctors/aabb"; export * from "./ctors/arc"; @@ -8,6 +27,8 @@ export * from "./ctors/ellipse"; export * from "./ctors/group"; export * from "./ctors/line"; export * from "./ctors/path"; +export * from "./ctors/path-builder"; +export * from "./ctors/path-from-svg"; export * from "./ctors/plane"; export * from "./ctors/points"; export * from "./ctors/polygon"; @@ -57,7 +78,11 @@ export * from "./ops/warp-points"; export * from "./ops/with-attribs"; export * from "./internal/coll-bounds"; +export * from "./internal/copy-attribs"; +export * from "./internal/copy-shape"; export * from "./internal/edges"; +export * from "./internal/pclike"; +export * from "./internal/points-as-shape"; export * from "./internal/split"; export * from "./internal/transform-points"; export * from "./internal/translate-points"; diff --git a/packages/geom/src/internal/copy-attribs.ts b/packages/geom/src/internal/copy-attribs.ts new file mode 100644 index 0000000000..8ebdd1df15 --- /dev/null +++ b/packages/geom/src/internal/copy-attribs.ts @@ -0,0 +1,3 @@ +import { Attribs, IShape } from "@thi.ng/geom-api"; + +export const copyAttribs = ($: IShape) => { ...$.attribs }; diff --git a/packages/geom/src/internal/copy-shape.ts b/packages/geom/src/internal/copy-shape.ts new file mode 100644 index 0000000000..c0f47a7416 --- /dev/null +++ b/packages/geom/src/internal/copy-shape.ts @@ -0,0 +1,6 @@ +import { PCLike, PCLikeConstructor } from "@thi.ng/geom-api"; +import { copyVectors } from "@thi.ng/vectors"; +import { copyAttribs } from "./copy-attribs"; + +export const copyShape = (ctor: PCLikeConstructor, inst: PCLike) => + new ctor(copyVectors(inst.points), copyAttribs(inst)); diff --git a/packages/geom/src/internal/pclike.ts b/packages/geom/src/internal/pclike.ts new file mode 100644 index 0000000000..352ca06449 --- /dev/null +++ b/packages/geom/src/internal/pclike.ts @@ -0,0 +1,7 @@ +import { PCLikeConstructor } from "@thi.ng/geom-api"; +import { argAttribs } from "./args"; + +export const pclike = (ctor: PCLikeConstructor, args: any[]) => { + const attr = argAttribs(args); + return new ctor(args.length === 1 ? args[0] : args, attr); +}; diff --git a/packages/geom/src/internal/points-as-shape.ts b/packages/geom/src/internal/points-as-shape.ts new file mode 100644 index 0000000000..789f2d902e --- /dev/null +++ b/packages/geom/src/internal/points-as-shape.ts @@ -0,0 +1,12 @@ +import { Attribs, PCLikeConstructor } from "@thi.ng/geom-api"; +import { map } from "@thi.ng/transducers"; +import { copyVectors, Vec } from "@thi.ng/vectors"; + +export const pointArraysAsShapes = ( + ctor: PCLikeConstructor, + src?: Iterable, + attribs?: Attribs +) => + src + ? [...map((pts) => new ctor(copyVectors(pts), { ...attribs }), src)] + : undefined; diff --git a/packages/geom/src/internal/transform-points.ts b/packages/geom/src/internal/transform-points.ts index c11c93b361..58c2376a3e 100644 --- a/packages/geom/src/internal/transform-points.ts +++ b/packages/geom/src/internal/transform-points.ts @@ -1,5 +1,7 @@ +import { PCLike, PCLikeConstructor } from "@thi.ng/geom-api"; import { mulV, ReadonlyMat } from "@thi.ng/matrices"; import { ReadonlyVec } from "@thi.ng/vectors"; +import { copyAttribs } from "./copy-attribs"; export const transformPoints = (pts: ReadonlyVec[], mat: ReadonlyMat) => ( pts.forEach((p) => mulV(null, mat, p)), pts @@ -7,3 +9,8 @@ export const transformPoints = (pts: ReadonlyVec[], mat: ReadonlyMat) => ( export const transformedPoints = (pts: ReadonlyVec[], mat: ReadonlyMat) => pts.map((p) => mulV([], mat, p)); + +export const transformedShape = (ctor: PCLikeConstructor) => ( + $: PCLike, + mat: ReadonlyMat +) => new ctor(transformedPoints($.points, mat), copyAttribs($)); diff --git a/packages/geom/src/internal/translate-points.ts b/packages/geom/src/internal/translate-points.ts index db46bcd8fe..d7e1d58179 100644 --- a/packages/geom/src/internal/translate-points.ts +++ b/packages/geom/src/internal/translate-points.ts @@ -1,4 +1,11 @@ +import { PCLike, PCLikeConstructor } from "@thi.ng/geom-api"; import { add, ReadonlyVec } from "@thi.ng/vectors"; +import { copyAttribs } from "./copy-attribs"; export const translatedPoints = (pts: ReadonlyVec[], delta: ReadonlyVec) => pts.map((x) => add([], x, delta)); + +export const translatedShape = (ctor: PCLikeConstructor) => ( + $: PCLike, + mat: ReadonlyVec +) => new ctor(translatedPoints($.points, mat), copyAttribs($)); diff --git a/packages/geom/src/ops/arc-length.ts b/packages/geom/src/ops/arc-length.ts index 9457b5d44e..842cde242c 100644 --- a/packages/geom/src/ops/arc-length.ts +++ b/packages/geom/src/ops/arc-length.ts @@ -4,15 +4,13 @@ import { IShape, Type } from "@thi.ng/geom-api"; import { perimeter } from "@thi.ng/geom-poly-utils"; import { PI, TAU } from "@thi.ng/math"; import { dist } from "@thi.ng/vectors"; -import { - Circle, - Ellipse, - Group, - Line, - Polygon, - Rect, - Triangle -} from "../api"; +import { Circle } from "../api/circle"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Polygon } from "../api/polygon"; +import { Rect } from "../api/rect"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; /** diff --git a/packages/geom/src/ops/area.ts b/packages/geom/src/ops/area.ts index 0f01b6057c..83b1ed3e82 100644 --- a/packages/geom/src/ops/area.ts +++ b/packages/geom/src/ops/area.ts @@ -4,17 +4,15 @@ import { IShape, Type } from "@thi.ng/geom-api"; import { polyArea2 } from "@thi.ng/geom-poly-utils"; import { PI } from "@thi.ng/math"; import { signedArea2, Vec } from "@thi.ng/vectors"; -import { - AABB, - Arc, - Circle, - Ellipse, - Group, - Polygon, - Rect, - Sphere, - Triangle -} from "../api"; +import { AABB } from "../api/aabb"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Polygon } from "../api/polygon"; +import { Rect } from "../api/rect"; +import { Sphere } from "../api/sphere"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; /** diff --git a/packages/geom/src/ops/as-cubic.ts b/packages/geom/src/ops/as-cubic.ts index 3628d913db..b34fbbb6a1 100644 --- a/packages/geom/src/ops/as-cubic.ts +++ b/packages/geom/src/ops/as-cubic.ts @@ -1,22 +1,20 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation1, MultiFn1O } from "@thi.ng/defmulti"; import { CubicOpts, IShape, Type } from "@thi.ng/geom-api"; -import { closedCubicFromBreakPoints, closedCubicFromControlPoints, cubicFromArc } from "@thi.ng/geom-splines"; +import { closedCubicFromBreakPoints, closedCubicFromControlPoints } from "@thi.ng/geom-splines"; import { TAU } from "@thi.ng/math"; import { mapcat } from "@thi.ng/transducers"; -import { - Arc, - Circle, - Cubic, - Group, - Line, - Path, - Polygon, - Quadratic, - Rect -} from "../api"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Polygon } from "../api/polygon"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; import { arc } from "../ctors/arc"; -import { cubicFromLine, cubicFromQuadratic } from "../ctors/cubic"; +import { cubicFromArc, cubicFromLine, cubicFromQuadratic } from "../ctors/cubic"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { asPolygon } from "./as-polygon"; @@ -25,10 +23,7 @@ export const asCubic: MultiFn1O, Cubic[]> = defmulti( ); asCubic.addAll(>>{ - [Type.ARC]: ($: Arc) => - cubicFromArc($.pos, $.r, $.axis, $.start, $.end).map( - (c) => new Cubic(c, $.attribs) - ), + [Type.ARC]: cubicFromArc, [Type.CIRCLE]: ($: Circle) => asCubic(arc($.pos, $.r, 0, 0, TAU, true, true)), @@ -50,7 +45,7 @@ asCubic.addAll(>>{ return (opts.breakPoints ? closedCubicFromBreakPoints($.points, opts.scale, opts.uniform) : closedCubicFromControlPoints($.points, opts.scale, opts.uniform) - ).map((pts) => new Cubic(pts, { ...$.attribs })); + ).map((pts) => new Cubic(pts, copyAttribs($))); }, [Type.QUADRATIC]: ({ attribs, points }: Quadratic) => [ diff --git a/packages/geom/src/ops/as-path.ts b/packages/geom/src/ops/as-path.ts index 57a2db7105..66cfaec1b1 100644 --- a/packages/geom/src/ops/as-path.ts +++ b/packages/geom/src/ops/as-path.ts @@ -1,6 +1,7 @@ import { Attribs, IShape } from "@thi.ng/geom-api"; import { pathFromCubics } from "../ctors/path"; +import { copyAttribs } from "../internal/copy-attribs"; import { asCubic } from "./as-cubic"; export const asPath = (src: IShape, attribs?: Attribs) => - pathFromCubics(asCubic(src), attribs); + pathFromCubics(asCubic(src), attribs || copyAttribs(src)); diff --git a/packages/geom/src/ops/as-polygon.ts b/packages/geom/src/ops/as-polygon.ts index f96e89c1d0..8fb8d66a35 100644 --- a/packages/geom/src/ops/as-polygon.ts +++ b/packages/geom/src/ops/as-polygon.ts @@ -1,6 +1,7 @@ import { defmulti, MultiFn1O } from "@thi.ng/defmulti"; import { IShape, SamplingOpts, Type } from "@thi.ng/geom-api"; -import { Polygon } from "../api"; +import { Polygon } from "../api/polygon"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; @@ -11,7 +12,7 @@ export const asPolygon: MultiFn1O< > = defmulti(dispatch); asPolygon.addAll({ - [Type.POINTS]: ($, opts) => new Polygon(vertices($, opts), { ...$.attribs }) + [Type.POINTS]: ($, opts) => new Polygon(vertices($, opts), copyAttribs($)) }); asPolygon.isa(Type.CIRCLE, Type.POINTS); diff --git a/packages/geom/src/ops/as-polyline.ts b/packages/geom/src/ops/as-polyline.ts index d899688242..4926ae2d5f 100644 --- a/packages/geom/src/ops/as-polyline.ts +++ b/packages/geom/src/ops/as-polyline.ts @@ -1,7 +1,9 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation1O, MultiFn1O } from "@thi.ng/defmulti"; import { IShape, SamplingOpts, Type } from "@thi.ng/geom-api"; -import { Path, Polyline } from "../api"; +import { Path } from "../api/path"; +import { Polyline } from "../api/polyline"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; @@ -17,18 +19,19 @@ asPolyline.addAll(< > >{ [Type.POINTS]: ($: IShape, opts) => - new Polyline(vertices($, opts), { ...$.attribs }), + new Polyline(vertices($, opts), copyAttribs($)), [Type.PATH]: ($: Path, opts) => { const pts = vertices($, opts); - return new Polyline($.closed ? pts.concat([pts[0]]) : pts, { - ...$.attribs - }); + return new Polyline( + $.closed ? pts.concat([pts[0]]) : pts, + copyAttribs($) + ); }, [Type.POLYGON]: ($: IShape, opts) => { const pts = vertices($, opts); - return new Polyline(pts.concat([pts[0]]), { ...$.attribs }); + return new Polyline(pts.concat([pts[0]]), copyAttribs($)); } }); diff --git a/packages/geom/src/ops/bounds.ts b/packages/geom/src/ops/bounds.ts index 8bc06e8653..d14d29b576 100644 --- a/packages/geom/src/ops/bounds.ts +++ b/packages/geom/src/ops/bounds.ts @@ -27,17 +27,15 @@ import { sub2, subN2 } from "@thi.ng/vectors"; -import { - Arc, - Circle, - Cubic, - Ellipse, - Group, - Line, - Path, - Quadratic, - Rect -} from "../api"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; import { rectFromMinMax } from "../ctors/rect"; import { collBounds } from "../internal/coll-bounds"; import { dispatch } from "../internal/dispatch"; diff --git a/packages/geom/src/ops/center.ts b/packages/geom/src/ops/center.ts index 5323b62c21..8711f8a59b 100644 --- a/packages/geom/src/ops/center.ts +++ b/packages/geom/src/ops/center.ts @@ -14,12 +14,11 @@ import { ZERO2, ZERO3 } from "@thi.ng/vectors"; -import { - Arc, - Circle, - Ellipse, - Sphere -} from "../api"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Ellipse } from "../api/ellipse"; +import { Sphere } from "../api/sphere"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { centroid } from "./centroid"; import { translate } from "./translate"; @@ -47,15 +46,15 @@ center.addAll(< $.end, $.xl, $.cw, - { ...$.attribs } + copyAttribs($) ), [Type.CIRCLE]: ($: Circle, origin = ZERO2) => - new Circle(set2([], origin), $.r, { ...$.attribs }), + new Circle(set2([], origin), $.r, copyAttribs($)), [Type.ELLIPSE]: ($: Ellipse, origin = ZERO2) => - new Ellipse(set2([], origin), set2([], $.r), { ...$.attribs }), + new Ellipse(set2([], origin), set2([], $.r), copyAttribs($)), [Type.SPHERE]: ($: Sphere, origin = ZERO3) => - new Sphere(set3([], origin), $.r, { ...$.attribs }) + new Sphere(set3([], origin), $.r, copyAttribs($)) }); diff --git a/packages/geom/src/ops/centroid.ts b/packages/geom/src/ops/centroid.ts index 3461c8c286..d07a5eb776 100644 --- a/packages/geom/src/ops/centroid.ts +++ b/packages/geom/src/ops/centroid.ts @@ -16,14 +16,12 @@ import { set, Vec } from "@thi.ng/vectors"; -import { - Circle, - Group, - Line, - Plane, - Polygon, - Triangle -} from "../api"; +import { Circle } from "../api/circle"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Plane } from "../api/plane"; +import { Polygon } from "../api/polygon"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; import { bounds } from "./bounds"; diff --git a/packages/geom/src/ops/classify-point.ts b/packages/geom/src/ops/classify-point.ts index 6feabcb8de..1e38b23af7 100644 --- a/packages/geom/src/ops/classify-point.ts +++ b/packages/geom/src/ops/classify-point.ts @@ -4,7 +4,9 @@ import { IShape, Type } from "@thi.ng/geom-api"; import { classifyPointInCircle, classifyPointInTriangle2 } from "@thi.ng/geom-isec"; import { EPS, sign } from "@thi.ng/math"; import { dot, ReadonlyVec } from "@thi.ng/vectors"; -import { Circle, Plane, Triangle } from "../api"; +import { Circle } from "../api/circle"; +import { Plane } from "../api/plane"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; export const classifyPoint: MultiFn2O< diff --git a/packages/geom/src/ops/clip-convex.ts b/packages/geom/src/ops/clip-convex.ts index 598418c593..310fcfbb18 100644 --- a/packages/geom/src/ops/clip-convex.ts +++ b/packages/geom/src/ops/clip-convex.ts @@ -2,7 +2,8 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation2 } from "@thi.ng/defmulti"; import { IShape, Type } from "@thi.ng/geom-api"; import { sutherlandHodgeman } from "@thi.ng/geom-clip"; -import { Polygon } from "../api"; +import { Polygon } from "../api/polygon"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { centroid } from "./centroid"; import { vertices } from "./vertices"; @@ -17,7 +18,7 @@ clipConvex.addAll(>>{ vertices(boundary), centroid(boundary) ), - { ...$.attribs } + copyAttribs($) ), [Type.RECT]: ($: IShape, boundary: IShape) => @@ -27,7 +28,7 @@ clipConvex.addAll(>>{ vertices(boundary), centroid(boundary) ), - { ...$.attribs } + copyAttribs($) ) }); diff --git a/packages/geom/src/ops/closest-point.ts b/packages/geom/src/ops/closest-point.ts index 8ea2e755b5..5f70be2dbb 100644 --- a/packages/geom/src/ops/closest-point.ts +++ b/packages/geom/src/ops/closest-point.ts @@ -18,16 +18,14 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { - AABB, - Arc, - Circle, - Cubic, - Line, - Plane, - Quadratic, - Rect -} from "../api"; +import { AABB } from "../api/aabb"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Line } from "../api/line"; +import { Plane } from "../api/plane"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; export const closestPoint: MultiFn2O< diff --git a/packages/geom/src/ops/convex-hull.ts b/packages/geom/src/ops/convex-hull.ts index 033876be71..39c76add56 100644 --- a/packages/geom/src/ops/convex-hull.ts +++ b/packages/geom/src/ops/convex-hull.ts @@ -2,17 +2,18 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation1 } from "@thi.ng/defmulti"; import { IShape, PCLike, Type } from "@thi.ng/geom-api"; import { grahamScan2 } from "@thi.ng/geom-hull"; -import { Polygon } from "../api"; +import { Polygon } from "../api/polygon"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; export const convexHull = defmulti(dispatch); convexHull.addAll(>>{ - [Type.GROUP]: ($: IShape) => new Polygon(vertices($), { ...$.attribs }), + [Type.GROUP]: ($: IShape) => new Polygon(vertices($), copyAttribs($)), [Type.POINTS]: ($: PCLike) => - new Polygon(grahamScan2($.points), { ...$.attribs }), + new Polygon(grahamScan2($.points), copyAttribs($)), [Type.TRIANGLE]: ($: IShape) => $.copy() }); diff --git a/packages/geom/src/ops/edges.ts b/packages/geom/src/ops/edges.ts index 1de8805af5..d097bc2f72 100644 --- a/packages/geom/src/ops/edges.ts +++ b/packages/geom/src/ops/edges.ts @@ -2,7 +2,9 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation1O, MultiFn1O } from "@thi.ng/defmulti"; import { IShape, SamplingOpts, Type } from "@thi.ng/geom-api"; import { VecPair } from "@thi.ng/vectors"; -import { Polygon, Polyline, Rect } from "../api"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; import { edgeIterator } from "../internal/edges"; import { vertices } from "./vertices"; diff --git a/packages/geom/src/ops/fit-into-bounds.ts b/packages/geom/src/ops/fit-into-bounds.ts index a332107176..c910e63ce6 100644 --- a/packages/geom/src/ops/fit-into-bounds.ts +++ b/packages/geom/src/ops/fit-into-bounds.ts @@ -11,7 +11,7 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Rect } from "../api"; +import { Rect } from "../api/rect"; import { collBounds } from "../internal/coll-bounds"; import { bounds } from "./bounds"; import { center } from "./center"; diff --git a/packages/geom/src/ops/flip.ts b/packages/geom/src/ops/flip.ts index e2647baf58..86285478ac 100644 --- a/packages/geom/src/ops/flip.ts +++ b/packages/geom/src/ops/flip.ts @@ -2,12 +2,10 @@ import { IObjectOf } from "@thi.ng/api"; import { DEFAULT, defmulti, Implementation1 } from "@thi.ng/defmulti"; import { IShape, PCLike, Type } from "@thi.ng/geom-api"; import { neg } from "@thi.ng/vectors"; -import { - Arc, - Group, - Path, - Ray -} from "../api"; +import { Arc } from "../api/arc"; +import { Group } from "../api/group"; +import { Path } from "../api/path"; +import { Ray } from "../api/ray"; import { dispatch } from "../internal/dispatch"; export const flip = defmulti(dispatch); diff --git a/packages/geom/src/ops/intersects.ts b/packages/geom/src/ops/intersects.ts index 0503002c9c..b8575e64cd 100644 --- a/packages/geom/src/ops/intersects.ts +++ b/packages/geom/src/ops/intersects.ts @@ -19,15 +19,13 @@ import { testRectCircle, testRectRect } from "@thi.ng/geom-isec"; -import { - AABB, - Circle, - Line, - Plane, - Ray, - Rect, - Sphere -} from "../api"; +import { AABB } from "../api/aabb"; +import { Circle } from "../api/circle"; +import { Line } from "../api/line"; +import { Plane } from "../api/plane"; +import { Ray } from "../api/ray"; +import { Rect } from "../api/rect"; +import { Sphere } from "../api/sphere"; import { dispatch2 } from "../internal/dispatch"; export const intersects: MultiFn2O< diff --git a/packages/geom/src/ops/map-point.ts b/packages/geom/src/ops/map-point.ts index 3142fe47bb..34e78cfa29 100644 --- a/packages/geom/src/ops/map-point.ts +++ b/packages/geom/src/ops/map-point.ts @@ -7,7 +7,7 @@ import { sub, Vec } from "@thi.ng/vectors"; -import { Rect } from "../api"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; export const mapPoint: MultiFn2O = defmulti(< diff --git a/packages/geom/src/ops/point-at.ts b/packages/geom/src/ops/point-at.ts index f5266937a0..6af2982479 100644 --- a/packages/geom/src/ops/point-at.ts +++ b/packages/geom/src/ops/point-at.ts @@ -12,17 +12,15 @@ import { mixQuadratic, Vec } from "@thi.ng/vectors"; -import { - Arc, - Circle, - Cubic, - Ellipse, - Line, - Polygon, - Quadratic, - Ray, - Rect -} from "../api"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Ellipse } from "../api/ellipse"; +import { Line } from "../api/line"; +import { Polygon } from "../api/polygon"; +import { Quadratic } from "../api/quadratic"; +import { Ray } from "../api/ray"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; diff --git a/packages/geom/src/ops/point-inside.ts b/packages/geom/src/ops/point-inside.ts index bbe7fc230c..35e24bd6e6 100644 --- a/packages/geom/src/ops/point-inside.ts +++ b/packages/geom/src/ops/point-inside.ts @@ -10,15 +10,13 @@ import { pointInTriangle2 } from "@thi.ng/geom-isec"; import { isInArray, ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { - AABB, - Circle, - Line, - Points, - Polygon, - Rect, - Triangle -} from "../api"; +import { AABB } from "../api/aabb"; +import { Circle } from "../api/circle"; +import { Line } from "../api/line"; +import { Points } from "../api/points"; +import { Polygon } from "../api/polygon"; +import { Rect } from "../api/rect"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; export const pointInside = defmulti(dispatch); diff --git a/packages/geom/src/ops/resample.ts b/packages/geom/src/ops/resample.ts index aedb27815c..2be7f2d068 100644 --- a/packages/geom/src/ops/resample.ts +++ b/packages/geom/src/ops/resample.ts @@ -7,7 +7,9 @@ import { Type } from "@thi.ng/geom-api"; import { resample as _resample } from "@thi.ng/geom-resample"; -import { Polygon, Polyline } from "../api"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { asPolygon } from "./as-polygon"; @@ -23,10 +25,10 @@ resample.addAll(< [Type.CIRCLE]: ($: IShape, opts) => asPolygon($, opts), [Type.POLYGON]: ($: PCLike, opts) => - new Polygon(_resample($.points, opts, true, true), { ...$.attribs }), + new Polygon(_resample($.points, opts, true, true), copyAttribs($)), [Type.POLYLINE]: ($: PCLike, opts) => - new Polyline(_resample($.points, opts, false, true), { ...$.attribs }) + new Polyline(_resample($.points, opts, false, true), copyAttribs($)) }); resample.isa(Type.ELLIPSE, Type.CIRCLE); diff --git a/packages/geom/src/ops/simplify.ts b/packages/geom/src/ops/simplify.ts index aff8d10b85..4434eaa28a 100644 --- a/packages/geom/src/ops/simplify.ts +++ b/packages/geom/src/ops/simplify.ts @@ -9,16 +9,19 @@ import { } from "@thi.ng/geom-api"; import { simplify as _simplify } from "@thi.ng/geom-resample"; import { Vec } from "@thi.ng/vectors"; -import { Path, Polygon, Polyline } from "../api"; +import { Path } from "../api/path"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; export const simplify = defmulti(dispatch); simplify.addAll(>>{ - [Type.PATH]: (path: Path, eps = 0.1) => { + [Type.PATH]: ($: Path, eps = 0.1) => { const res: PathSegment[] = []; - const orig = path.segments; + const orig = $.segments; const n = orig.length; let points!: Vec[] | null; let lastP!: Vec; @@ -50,12 +53,12 @@ simplify.addAll(>>{ type: SegmentType.POLYLINE }); } - return new Path(res, { ...path.attribs }); + return new Path(res, copyAttribs($)); }, - [Type.POLYGON]: (poly: Polygon, eps = 0.1) => - new Polygon(_simplify(poly.points, eps, true), { ...poly.attribs }), + [Type.POLYGON]: ($: Polygon, eps = 0.1) => + new Polygon(_simplify($.points, eps, true), copyAttribs($)), - [Type.POLYLINE]: (poly: Polyline, eps = 0.1) => - new Polyline(_simplify(poly.points, eps), { ...poly.attribs }) + [Type.POLYLINE]: ($: Polyline, eps = 0.1) => + new Polyline(_simplify($.points, eps), copyAttribs($)) }); diff --git a/packages/geom/src/ops/split-at.ts b/packages/geom/src/ops/split-at.ts index 801fe4efc4..d416e5ec01 100644 --- a/packages/geom/src/ops/split-at.ts +++ b/packages/geom/src/ops/split-at.ts @@ -4,15 +4,15 @@ import { IShape, Type } from "@thi.ng/geom-api"; import { Sampler } from "@thi.ng/geom-resample"; import { cubicSplitAt, quadraticSplitAt } from "@thi.ng/geom-splines"; import { fit01 } from "@thi.ng/math"; -import { copyVectors, set } from "@thi.ng/vectors"; -import { - Arc, - Cubic, - Line, - Polyline, - Quadratic -} from "../api"; +import { set } from "@thi.ng/vectors"; +import { Arc } from "../api/arc"; +import { Cubic } from "../api/cubic"; +import { Line } from "../api/line"; +import { Polyline } from "../api/polyline"; +import { Quadratic } from "../api/quadratic"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; +import { pointArraysAsShapes } from "../internal/points-as-shape"; import { splitLine } from "../internal/split"; export const splitAt = defmulti(dispatch); @@ -29,7 +29,7 @@ splitAt.addAll(>>{ theta, $.xl, $.cw, - { ...$.attribs } + copyAttribs($) ), new Arc( set([], $.pos), @@ -39,7 +39,7 @@ splitAt.addAll(>>{ $.end, $.xl, $.cw, - { ...$.attribs } + copyAttribs($) ) ]; }, @@ -54,12 +54,12 @@ splitAt.addAll(>>{ (pts) => new Line(pts, { ...attribs }) ), - [Type.POLYLINE]: ($: Polyline, t) => { - const res = new Sampler($.points).splitAt(t); - return res - ? res.map((pts) => new Polyline(copyVectors(pts), { ...$.attribs })) - : undefined; - }, + [Type.POLYLINE]: ($: Polyline, t) => + pointArraysAsShapes( + Polyline, + new Sampler($.points).splitAt(t), + $.attribs + ), [Type.QUADRATIC]: ({ attribs, points }: Quadratic, t: number) => quadraticSplitAt(points[0], points[1], points[2], t).map( diff --git a/packages/geom/src/ops/split-near.ts b/packages/geom/src/ops/split-near.ts index b6a903d1e0..3693593d66 100644 --- a/packages/geom/src/ops/split-near.ts +++ b/packages/geom/src/ops/split-near.ts @@ -5,14 +5,14 @@ import { closestT } from "@thi.ng/geom-closest-point"; import { Sampler } from "@thi.ng/geom-resample"; import { quadraticSplitNearPoint, splitCubicNearPoint } from "@thi.ng/geom-splines"; import { clamp01 } from "@thi.ng/math"; -import { copyVectors, ReadonlyVec } from "@thi.ng/vectors"; -import { - Cubic, - Line, - Polyline, - Quadratic -} from "../api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { Cubic } from "../api/cubic"; +import { Line } from "../api/line"; +import { Polyline } from "../api/polyline"; +import { Quadratic } from "../api/quadratic"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; +import { pointArraysAsShapes } from "../internal/points-as-shape"; import { splitLine } from "../internal/split"; /** @@ -49,16 +49,16 @@ splitNearPoint.addAll(< [Type.LINE]: ($: Line, p) => { const t = closestT(p, $.points[0], $.points[1]) || 0; return splitLine($.points[0], $.points[1], clamp01(t)).map( - (pts) => new Line(pts, { ...$.attribs }) + (pts) => new Line(pts, copyAttribs($)) ); }, - [Type.POLYLINE]: ($: Polyline, p) => { - const res = new Sampler($.points).splitNear(p); - return res - ? res.map((pts) => new Polyline(copyVectors(pts), { ...$.attribs })) - : undefined; - }, + [Type.POLYLINE]: ($: Polyline, p) => + pointArraysAsShapes( + Polyline, + new Sampler($.points).splitNear(p), + $.attribs + ), [Type.QUADRATIC]: ({ points, attribs }: Quadratic, p) => quadraticSplitNearPoint(p, points[0], points[1], points[2]).map( diff --git a/packages/geom/src/ops/subdiv-curve.ts b/packages/geom/src/ops/subdiv-curve.ts index 1c860e6013..c1a78fd6c2 100644 --- a/packages/geom/src/ops/subdiv-curve.ts +++ b/packages/geom/src/ops/subdiv-curve.ts @@ -2,7 +2,9 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation2O, MultiFn2O } from "@thi.ng/defmulti"; import { IShape, SubdivKernel, Type } from "@thi.ng/geom-api"; import { subdivide } from "@thi.ng/geom-subdiv-curve"; -import { Polygon, Polyline } from "../api"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; export const subdivCurve: MultiFn2O< @@ -15,9 +17,9 @@ export const subdivCurve: MultiFn2O< subdivCurve.addAll(< IObjectOf> >{ - [Type.POLYGON]: (poly: Polygon, kernel, iter = 1) => - new Polygon(subdivide(poly.points, kernel, iter), { ...poly.attribs }), + [Type.POLYGON]: ($: Polygon, kernel, iter = 1) => + new Polygon(subdivide($.points, kernel, iter), copyAttribs($)), - [Type.POLYLINE]: (line: Polyline, kernel, iter = 1) => - new Polyline(subdivide(line.points, kernel, iter), { ...line.attribs }) + [Type.POLYLINE]: ($: Polyline, kernel, iter = 1) => + new Polyline(subdivide($.points, kernel, iter), copyAttribs($)) }); diff --git a/packages/geom/src/ops/tangent-at.ts b/packages/geom/src/ops/tangent-at.ts index 0e0b87a8cf..91c43d041c 100644 --- a/packages/geom/src/ops/tangent-at.ts +++ b/packages/geom/src/ops/tangent-at.ts @@ -4,7 +4,8 @@ import { IShape, PCLike, Type } from "@thi.ng/geom-api"; import { Sampler } from "@thi.ng/geom-resample"; import { cossin, HALF_PI, TAU } from "@thi.ng/math"; import { direction, Vec } from "@thi.ng/vectors"; -import { Line, Rect } from "../api"; +import { Line } from "../api/line"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; diff --git a/packages/geom/src/ops/transform.ts b/packages/geom/src/ops/transform.ts index 894cc632c0..81228326d4 100644 --- a/packages/geom/src/ops/transform.ts +++ b/packages/geom/src/ops/transform.ts @@ -4,34 +4,28 @@ import { IHiccupShape, IShape, PathSegment, - PCLike, - PCLikeConstructor, SegmentType, Type } from "@thi.ng/geom-api"; import { mulV, ReadonlyMat } from "@thi.ng/matrices"; import { map } from "@thi.ng/transducers"; -import { - Cubic, - Group, - Line, - Path, - Points, - Polygon, - Polyline, - Quad, - Quadratic, - Rect, - Triangle -} from "../api"; +import { Cubic } from "../api/cubic"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Points } from "../api/points"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { Quad } from "../api/quad"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; +import { Triangle } from "../api/triangle"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; -import { transformedPoints } from "../internal/transform-points"; +import { transformedShape as tx } from "../internal/transform-points"; import { asPath } from "./as-path"; import { asPolygon } from "./as-polygon"; -const tx = (ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyMat) => - new ctor(transformedPoints($.points, mat), { ...$.attribs }); - /** * Transforms given shape with provided matrix. Some shape types will be * automatically converted to other types prior to transformation because they @@ -52,7 +46,7 @@ transform.addAll(>>{ [Type.GROUP]: ($: Group, mat) => new Group( - { ...$.attribs }, + copyAttribs($), $.children.map((x) => transform(x, mat)) ), @@ -75,7 +69,7 @@ transform.addAll(>>{ $.segments ) ], - $.attribs + copyAttribs($) ), [Type.POINTS]: tx(Points), diff --git a/packages/geom/src/ops/translate.ts b/packages/geom/src/ops/translate.ts index bdbafbc60e..97cf8854d0 100644 --- a/packages/geom/src/ops/translate.ts +++ b/packages/geom/src/ops/translate.ts @@ -1,12 +1,6 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation2 } from "@thi.ng/defmulti"; -import { - IHiccupShape, - IShape, - PCLike, - PCLikeConstructor, - Type -} from "@thi.ng/geom-api"; +import { IHiccupShape, IShape, Type } from "@thi.ng/geom-api"; import { add2, add3, @@ -14,34 +8,30 @@ import { set2, set3 } from "@thi.ng/vectors"; -import { - AABB, - Arc, - Circle, - Ellipse, - Group, - Line, - Path, - Points, - Polygon, - Polyline, - Quad, - Ray, - Rect, - Sphere, - Triangle -} from "../api"; +import { AABB } from "../api/aabb"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Points } from "../api/points"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { Quad } from "../api/quad"; +import { Ray } from "../api/ray"; +import { Rect } from "../api/rect"; +import { Sphere } from "../api/sphere"; +import { Triangle } from "../api/triangle"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; -import { translatedPoints } from "../internal/translate-points"; - -const tx = (ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyVec) => - new ctor(translatedPoints($.points, mat), { ...$.attribs }); +import { translatedShape as tx } from "../internal/translate-points"; export const translate = defmulti(dispatch); translate.addAll(>>{ [Type.AABB]: ($: AABB, delta) => - new AABB(add3([], $.pos, delta), set3([], $.size), { ...$.attribs }), + new AABB(add3([], $.pos, delta), set3([], $.size), copyAttribs($)), [Type.ARC]: ($: Arc, delta) => { const a = $.copy(); @@ -50,14 +40,14 @@ translate.addAll(>>{ }, [Type.CIRCLE]: ($: Circle, delta) => - new Circle(add2([], $.pos, delta), $.r, { ...$.attribs }), + new Circle(add2([], $.pos, delta), $.r, copyAttribs($)), [Type.ELLIPSE]: ($: Ellipse, delta) => - new Ellipse(add2([], $.pos, delta), set2([], $.r), { ...$.attribs }), + new Ellipse(add2([], $.pos, delta), set2([], $.r), copyAttribs($)), [Type.GROUP]: ($: Group, delta) => new Group( - { ...$.attribs }, + copyAttribs($), $.children.map((s) => translate(s, delta)) ), @@ -76,7 +66,7 @@ translate.addAll(>>{ point: add2([], s.point!, delta) } ), - { ...$.attribs } + copyAttribs($) ), [Type.POINTS]: tx(Points), @@ -88,13 +78,13 @@ translate.addAll(>>{ [Type.QUAD]: tx(Quad), [Type.RAY]: ($: Ray, delta) => - new Ray(add2([], $.pos, delta), $.dir, { ...$.attribs }), + new Ray(add2([], $.pos, delta), $.dir, copyAttribs($)), [Type.RECT]: ($: Rect, delta) => - new Rect(add2([], $.pos, delta), set2([], $.size), { ...$.attribs }), + new Rect(add2([], $.pos, delta), set2([], $.size), copyAttribs($)), [Type.SPHERE]: ($: Sphere, delta) => - new Sphere(add3([], $.pos, delta), $.r, { ...$.attribs }), + new Sphere(add3([], $.pos, delta), $.r, copyAttribs($)), [Type.TRIANGLE]: tx(Triangle) }); diff --git a/packages/geom/src/ops/union.ts b/packages/geom/src/ops/union.ts index ef4888efa4..95ae9d033e 100644 --- a/packages/geom/src/ops/union.ts +++ b/packages/geom/src/ops/union.ts @@ -1,7 +1,8 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation2 } from "@thi.ng/defmulti"; import { IShape, Type } from "@thi.ng/geom-api"; -import { AABB, Rect } from "../api"; +import { AABB } from "../api/aabb"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; import { unionBounds } from "../internal/union-bounds"; diff --git a/packages/geom/src/ops/unmap-point.ts b/packages/geom/src/ops/unmap-point.ts index 2024e9501f..cd5202b060 100644 --- a/packages/geom/src/ops/unmap-point.ts +++ b/packages/geom/src/ops/unmap-point.ts @@ -7,7 +7,8 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Quad, Rect } from "../api"; +import { Quad } from "../api/quad"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; /** diff --git a/packages/geom/src/ops/vertices.ts b/packages/geom/src/ops/vertices.ts index 07d578ee27..a363cd8e2f 100644 --- a/packages/geom/src/ops/vertices.ts +++ b/packages/geom/src/ops/vertices.ts @@ -19,20 +19,18 @@ import { set2, Vec } from "@thi.ng/vectors"; -import { - AABB, - Arc, - Circle, - Cubic, - Ellipse, - Group, - Path, - Points, - Polygon, - Polyline, - Quadratic, - Rect -} from "../api"; +import { AABB } from "../api/aabb"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Path } from "../api/path"; +import { Points } from "../api/points"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; export const vertices: MultiFn1O< diff --git a/packages/geom/src/ops/volume.ts b/packages/geom/src/ops/volume.ts index 84eb3a64a5..8e01ff5826 100644 --- a/packages/geom/src/ops/volume.ts +++ b/packages/geom/src/ops/volume.ts @@ -2,7 +2,8 @@ import { IObjectOf } from "@thi.ng/api"; import { DEFAULT, defmulti, Implementation1 } from "@thi.ng/defmulti"; import { IShape, Type } from "@thi.ng/geom-api"; import { PI } from "@thi.ng/math"; -import { AABB, Sphere } from "../api"; +import { AABB } from "../api/aabb"; +import { Sphere } from "../api/sphere"; import { dispatch } from "../internal/dispatch"; /** diff --git a/packages/hdom-canvas/CHANGELOG.md b/packages/hdom-canvas/CHANGELOG.md index 7c19877629..aff957e4cf 100644 --- a/packages/hdom-canvas/CHANGELOG.md +++ b/packages/hdom-canvas/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.2.3...@thi.ng/hdom-canvas@2.2.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hdom-canvas + + + + + +## [2.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.2.2...@thi.ng/hdom-canvas@2.2.3) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/hdom-canvas + + + + + ## [2.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.2.1...@thi.ng/hdom-canvas@2.2.2) (2019-08-16) diff --git a/packages/hdom-canvas/package.json b/packages/hdom-canvas/package.json index f96d4a3130..4b27f97a22 100644 --- a/packages/hdom-canvas/package.json +++ b/packages/hdom-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-canvas", - "version": "2.2.2", + "version": "2.2.4", "description": "Declarative canvas scenegraph & visualization for @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib draw", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -33,11 +33,13 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/color": "^1.0.2", - "@thi.ng/diff": "^3.2.2", - "@thi.ng/hdom": "^8.0.3" + "@thi.ng/color": "^1.1.0", + "@thi.ng/diff": "^3.2.3", + "@thi.ng/hdom": "^8.0.4", + "@thi.ng/math": "^1.4.2", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "ES6", diff --git a/packages/hdom-canvas/src/api.ts b/packages/hdom-canvas/src/api.ts new file mode 100644 index 0000000000..387f268f31 --- /dev/null +++ b/packages/hdom-canvas/src/api.ts @@ -0,0 +1,8 @@ +import { IObjectOf } from "@thi.ng/api"; + +export interface DrawState { + attribs: IObjectOf; + grads?: IObjectOf; + edits: string[]; + restore?: boolean; +} diff --git a/packages/hdom-canvas/src/draw/arc.ts b/packages/hdom-canvas/src/draw/arc.ts new file mode 100644 index 0000000000..b9484d6833 --- /dev/null +++ b/packages/hdom-canvas/src/draw/arc.ts @@ -0,0 +1,33 @@ +import { IObjectOf } from "@thi.ng/api"; +import { TAU } from "@thi.ng/math"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const circularArc = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + r: number, + start = 0, + end = TAU, + antiCCW = false +) => { + ctx.beginPath(); + ctx.arc(pos[0], pos[1], r, start, end, antiCCW); + endShape(ctx, attribs); +}; + +export const ellipticArc = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + r: ReadonlyVec, + axis = 0, + start = 0, + end = TAU, + ccw = false +) => { + ctx.beginPath(); + ctx.ellipse(pos[0], pos[1], r[0], r[1], axis, start, end, ccw); + endShape(ctx, attribs); +}; diff --git a/packages/hdom-canvas/src/draw/color.ts b/packages/hdom-canvas/src/draw/color.ts new file mode 100644 index 0000000000..e5b6916866 --- /dev/null +++ b/packages/hdom-canvas/src/draw/color.ts @@ -0,0 +1,36 @@ +import { isString } from "@thi.ng/checks"; +import { resolveAsCSS } from "@thi.ng/color"; +import { DrawState } from "../api"; + +export const resolveColor = (v: any) => (isString(v) ? v : resolveAsCSS(v)); + +export const resolveGradientOrColor = (state: DrawState, v: any) => + isString(v) + ? v[0] === "$" + ? state.grads![v.substr(1)] + : v + : resolveAsCSS(v); + +export const defLinearGradient = ( + ctx: CanvasRenderingContext2D, + { from, to }: any, + stops: any[][] +) => { + const g = ctx.createLinearGradient(from[0], from[1], to[0], to[1]); + for (let s of stops) { + g.addColorStop(s[0], resolveColor(s[1])); + } + return g; +}; + +export const defRadialGradient = ( + ctx: CanvasRenderingContext2D, + { from, to, r1, r2 }: any, + stops: any[][] +) => { + const g = ctx.createRadialGradient(from[0], from[1], r1, to[0], to[1], r2); + for (let s of stops) { + g.addColorStop(s[0], resolveColor(s[1])); + } + return g; +}; diff --git a/packages/hdom-canvas/src/draw/end-shape.ts b/packages/hdom-canvas/src/draw/end-shape.ts new file mode 100644 index 0000000000..40677e4f7e --- /dev/null +++ b/packages/hdom-canvas/src/draw/end-shape.ts @@ -0,0 +1,14 @@ +import { IObjectOf } from "@thi.ng/api"; + +export const endShape = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + ctx.fill(); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.stroke(); + } +}; diff --git a/packages/hdom-canvas/src/draw/image.ts b/packages/hdom-canvas/src/draw/image.ts new file mode 100644 index 0000000000..11f69f0136 --- /dev/null +++ b/packages/hdom-canvas/src/draw/image.ts @@ -0,0 +1,28 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export const image = ( + ctx: CanvasRenderingContext2D, + _: IObjectOf, + { width, height }: IObjectOf, + img: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, + dpos: ReadonlyVec, + spos?: ReadonlyVec, + ssize?: ReadonlyVec +) => { + width = width || img.width; + height = height || img.height; + spos + ? ctx.drawImage( + img, + spos[0], + spos[1], + ssize ? ssize[0] : width, + ssize ? ssize[1] : height, + dpos[0], + dpos[1], + width, + height + ) + : ctx.drawImage(img, dpos[0], dpos[1], width, height); +}; diff --git a/packages/hdom-canvas/src/draw/line.ts b/packages/hdom-canvas/src/draw/line.ts new file mode 100644 index 0000000000..43d641f30d --- /dev/null +++ b/packages/hdom-canvas/src/draw/line.ts @@ -0,0 +1,15 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export const line = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + a: ReadonlyVec, + b: ReadonlyVec +) => { + if (attribs.stroke === "none") return; + ctx.beginPath(); + ctx.moveTo(a[0], a[1]); + ctx.lineTo(b[0], b[1]); + ctx.stroke(); +}; diff --git a/packages/hdom-canvas/src/draw/path.ts b/packages/hdom-canvas/src/draw/path.ts new file mode 100644 index 0000000000..1c6b44ec84 --- /dev/null +++ b/packages/hdom-canvas/src/draw/path.ts @@ -0,0 +1,113 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const path = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + segments: any[] +) => { + ctx.beginPath(); + let a: ReadonlyVec = [0, 0]; + for (let i = 0, n = segments.length; i < n; i++) { + const s = segments[i]; + let b = s[1], + c, + d; + switch (s[0]) { + // move to + case "m": + b = [a[0] + b[0], a[1] + b[1]]; + case "M": + ctx.moveTo(b[0], b[1]); + a = b; + break; + // line to + case "l": + b = [a[0] + b[0], a[1] + b[1]]; + case "L": + ctx.lineTo(b[0], b[1]); + a = b; + break; + // horizontal line rel + case "h": + b = [a[0] + b, a[1]]; + ctx.lineTo(b[0], b[1]); + a = b; + break; + // horizontal line abs + case "H": + b = [b, a[1]]; + ctx.lineTo(b[0], b[1]); + a = b; + break; + // vertical line rel + case "v": + b = [a[0], a[1] + b]; + ctx.lineTo(b[0], b[1]); + a = b; + break; + // vertical line abs + case "V": + b = [a[0], b]; + ctx.lineTo(b[0], b[1]); + a = b; + break; + // cubic curve rel + case "c": + c = s[2]; + d = s[3]; + d = [a[0] + d[0], a[1] + d[1]]; + ctx.bezierCurveTo( + a[0] + b[0], + a[1] + b[1], + a[0] + c[0], + a[1] + c[1], + d[0], + d[1] + ); + a = d; + break; + // cubic curve abs + case "C": + c = s[2]; + d = s[3]; + ctx.bezierCurveTo(b[0], b[1], c[0], c[1], d[0], d[1]); + a = d; + break; + // quadratic curve rel + case "q": + c = s[2]; + c = [a[0] + c[0], a[1] + c[1]]; + ctx.quadraticCurveTo(a[0] + b[0], a[1] + b[1], c[0], c[1]); + a = c; + break; + // quadratic curve abs + case "Q": + c = s[2]; + ctx.quadraticCurveTo(b[0], b[1], c[0], c[1]); + a = c; + break; + // circular arc rel + // Note: NOT compatible w/ SVG arc segments + case "a": + c = s[2]; + c = [a[0] + c[0], a[1] + c[1]]; + ctx.arcTo(a[0] + b[0], a[1] + b[1], c[0], c[1], s[3]); + a = c; + break; + // circular arc abs + // Note: NOT compatible w/ SVG arc segments + case "A": + c = s[2]; + ctx.arcTo(b[0], b[1], c[0], c[1], s[3]); + a = c; + break; + // close path + case "z": + case "Z": + ctx.closePath(); + } + } + endShape(ctx, attribs); +}; diff --git a/packages/hdom-canvas/src/draw/points.ts b/packages/hdom-canvas/src/draw/points.ts new file mode 100644 index 0000000000..cf4e26288c --- /dev/null +++ b/packages/hdom-canvas/src/draw/points.ts @@ -0,0 +1,40 @@ +import { IObjectOf } from "@thi.ng/api"; +import { TAU } from "@thi.ng/math"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export const points = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + opts: IObjectOf, + pts: Iterable +) => { + const s: number = (opts && opts.size) || 1; + let v: any; + if ((v = attribs.fill) && v !== "none") { + __drawPoints(ctx, opts, pts, s, "fill", "fillRect"); + } + if ((v = attribs.stroke) && v !== "none") { + __drawPoints(ctx, opts, pts, s, "stroke", "strokeRect"); + } +}; + +const __drawPoints = ( + ctx: CanvasRenderingContext2D, + opts: IObjectOf, + pts: Iterable, + s: number, + cmd: "fill" | "stroke", + cmdR: "fillRect" | "strokeRect" +) => { + if (opts.shape === "circle") { + for (let p of pts) { + ctx.beginPath(); + ctx.arc(p[0], p[1], s, 0, TAU); + ctx[cmd](); + } + } else { + for (let p of pts) { + ctx[cmdR](p[0], p[1], s, s); + } + } +}; diff --git a/packages/hdom-canvas/src/draw/polygon.ts b/packages/hdom-canvas/src/draw/polygon.ts new file mode 100644 index 0000000000..759e80a9db --- /dev/null +++ b/packages/hdom-canvas/src/draw/polygon.ts @@ -0,0 +1,33 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const polygon = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pts: ReadonlyVec[] +) => { + if (pts.length < 2) return; + __drawPoly(ctx, pts); + ctx.closePath(); + endShape(ctx, attribs); +}; + +/** + * Shared internal helper for polygon & polyline fns. + * + * @param ctx + * @param pts + */ +export const __drawPoly = ( + ctx: CanvasRenderingContext2D, + pts: ReadonlyVec[] +) => { + let p: ReadonlyVec = pts[0]; + ctx.beginPath(); + ctx.moveTo(p[0], p[1]); + for (let i = 1, n = pts.length; i < n; i++) { + p = pts[i]; + ctx.lineTo(p[0], p[1]); + } +}; diff --git a/packages/hdom-canvas/src/draw/polyline.ts b/packages/hdom-canvas/src/draw/polyline.ts new file mode 100644 index 0000000000..0e05acf281 --- /dev/null +++ b/packages/hdom-canvas/src/draw/polyline.ts @@ -0,0 +1,13 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { __drawPoly } from "./polygon"; + +export const polyline = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pts: ReadonlyVec[] +) => { + if (pts.length < 2 || attribs.stroke == "none") return; + __drawPoly(ctx, pts); + ctx.stroke(); +}; diff --git a/packages/hdom-canvas/src/draw/rect.ts b/packages/hdom-canvas/src/draw/rect.ts new file mode 100644 index 0000000000..6a783e0888 --- /dev/null +++ b/packages/hdom-canvas/src/draw/rect.ts @@ -0,0 +1,36 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { path } from "./path"; + +export const rect = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + w: number, + h: number, + r = 0 +) => { + let v: any; + if (r > 0) { + r = Math.min(Math.min(w, h) / 2, r); + w -= 2 * r; + h -= 2 * r; + return path(ctx, attribs, [ + ["M", [pos[0] + r, pos[1]]], + ["h", w], + ["a", [r, 0], [r, r], r], + ["v", h], + ["a", [0, r], [-r, r], r], + ["h", -w], + ["a", [-r, 0], [-r, -r], r], + ["v", -h], + ["a", [0, -r], [r, -r], r] + ]); + } + if ((v = attribs.fill) && v !== "none") { + ctx.fillRect(pos[0], pos[1], w, h); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.strokeRect(pos[0], pos[1], w, h); + } +}; diff --git a/packages/hdom-canvas/src/draw/text.ts b/packages/hdom-canvas/src/draw/text.ts new file mode 100644 index 0000000000..e959435b0c --- /dev/null +++ b/packages/hdom-canvas/src/draw/text.ts @@ -0,0 +1,18 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export const text = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + body: any, + maxWidth?: number +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + ctx.fillText(body.toString(), pos[0], pos[1], maxWidth); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.strokeText(body.toString(), pos[0], pos[1], maxWidth); + } +}; diff --git a/packages/hdom-canvas/src/impl.ts b/packages/hdom-canvas/src/impl.ts new file mode 100644 index 0000000000..45dde7330b --- /dev/null +++ b/packages/hdom-canvas/src/impl.ts @@ -0,0 +1,183 @@ +import { assert, NO_OP } from "@thi.ng/api"; +import { isArray, isNotStringAndIterable } from "@thi.ng/checks"; +import { diffArray, DiffMode } from "@thi.ng/diff"; +import { + equiv, + HDOMImplementation, + HDOMOpts, + releaseTree +} from "@thi.ng/hdom"; +import { walk } from "./walk"; + +const FN = "function"; +const STR = "string"; + +/** + * Special HTML5 canvas component which injects a branch-local hdom + * implementation for virtual SVG-like shape components / elements. + * These elements are then translated into canvas draw commands during + * the hdom update process. + * + * The canvas component automatically adjusts its size for HDPI displays + * by adding CSS `width` & `height` properties and pre-scaling the + * drawing context accordingly before shapes are processed. + * + * Shape components are expressed in standard hiccup syntax, however + * with the following restrictions: + * + * - Shape component objects with life cycle methods are only partially + * supported, i.e. only the `render` & `release` methods are used + * (Note, for performance reasons `release` methods are ignored by + * default. If your shape tree contains stateful components which use + * the `release` life cycle method, you'll need to explicitly enable + * the canvas component's `__release` attribute by setting it to + * `true`). + * - Currently no event listeners can be assigned to shapes (ignored), + * though this is planned for a future version. The canvas element + * itself can of course have event handlers as usual. + * + * All embedded component functions receive the user context object just + * like normal hdom components. + * + * For best performance, it's recommended to ensure all resulting shapes + * elements are provided in already normalized hiccup format (i.e. + * `[tag, {attribs}, ...]`). That way the `__normalize: false` control + * attribute can be added either to the canvas component itself (or to + * individual shapes / groups), and if present, will skip normalization + * of all children. + * + * @param _ hdom user context (ignored) + * @param attribs canvas attribs + * @param shapes shape components + */ +export const canvas = { + render(_: any, attribs: any, ...body: any[]) { + const cattribs = { ...attribs }; + delete cattribs.__diff; + delete cattribs.__normalize; + const dpr = window.devicePixelRatio || 1; + if (dpr !== 1) { + !cattribs.style && (cattribs.style = {}); + cattribs.style.width = `${cattribs.width}px`; + cattribs.style.height = `${cattribs.height}px`; + cattribs.width *= dpr; + cattribs.height *= dpr; + } + return [ + "canvas", + cattribs, + [ + "g", + { + __impl: IMPL, + __diff: attribs.__diff !== false, + __normalize: attribs.__normalize !== false, + __release: attribs.__release === true, + __serialize: false, + __clear: attribs.__clear, + scale: dpr !== 1 ? dpr : null + }, + ...body + ] + ]; + } +}; + +export const createTree = ( + _: Partial, + canvas: HTMLCanvasElement, + tree: any +) => { + // console.log(Date.now(), "draw"); + const ctx = canvas.getContext("2d"); + assert(!!ctx, "canvas ctx unavailable"); + const attribs = tree[1]; + if (attribs) { + if (attribs.__skip) return; + if (attribs.__clear !== false) { + ctx!.clearRect(0, 0, canvas.width, canvas.height); + } + } + walk(ctx!, tree, { attribs: {}, edits: [] }); +}; + +export const normalizeTree = (opts: Partial, tree: any): any => { + if (tree == null) { + return tree; + } + if (isArray(tree)) { + const tag = tree[0]; + if (typeof tag === FN) { + return normalizeTree( + opts, + tag.apply(null, [opts.ctx, ...tree.slice(1)]) + ); + } + if (typeof tag === STR) { + const attribs = tree[1]; + if (attribs && attribs.__normalize === false) { + return tree; + } + const res = [tree[0], attribs]; + for (let i = 2, n = tree.length; i < n; i++) { + const n = normalizeTree(opts, tree[i]); + n != null && res.push(n); + } + return res; + } + } else if (typeof tree === FN) { + return normalizeTree(opts, tree(opts.ctx)); + } else if (typeof tree.toHiccup === FN) { + return normalizeTree(opts, tree.toHiccup(opts.ctx)); + } else if (typeof tree.deref === FN) { + return normalizeTree(opts, tree.deref()); + } else if (isNotStringAndIterable(tree)) { + const res = []; + for (let t of tree) { + const n = normalizeTree(opts, t); + n != null && res.push(n); + } + return res; + } + return tree; +}; + +export const diffTree = ( + opts: Partial, + parent: HTMLCanvasElement, + prev: any[], + curr: any[], + child: number +) => { + const attribs = curr[1]; + if (attribs.__skip) return; + if (attribs.__diff === false) { + releaseTree(prev); + return createTree(opts, parent, curr); + } + // delegate to branch-local implementation + let impl: HDOMImplementation = attribs.__impl; + if (impl && impl !== IMPL) { + return impl.diffTree(opts, parent, prev, curr, child); + } + const delta = diffArray(prev, curr, DiffMode.ONLY_DISTANCE, equiv); + if (delta.distance > 0) { + return createTree(opts, parent, curr); + } +}; + +export const IMPL: HDOMImplementation = { + createTree, + normalizeTree, + diffTree, + hydrateTree: NO_OP, + getElementById: NO_OP, + createElement: NO_OP, + createTextElement: NO_OP, + replaceChild: NO_OP, + getChild: NO_OP, + removeAttribs: NO_OP, + removeChild: NO_OP, + setAttrib: NO_OP, + setContent: NO_OP +}; diff --git a/packages/hdom-canvas/src/index.ts b/packages/hdom-canvas/src/index.ts index 139eea13b5..a6f3630901 100644 --- a/packages/hdom-canvas/src/index.ts +++ b/packages/hdom-canvas/src/index.ts @@ -1,799 +1,16 @@ -import { assert, IObjectOf, NO_OP } from "@thi.ng/api"; -import { - isArray, - isArrayLike, - isNotStringAndIterable, - isNumber, - isString -} from "@thi.ng/checks"; -import { asCSS, ColorMode, ReadonlyColor } from "@thi.ng/color"; -import { diffArray, DiffMode } from "@thi.ng/diff"; -import { - equiv, - HDOMImplementation, - HDOMOpts, - releaseTree -} from "@thi.ng/hdom"; - -interface DrawState { - attribs: IObjectOf; - grads?: IObjectOf; - edits?: string[]; - restore?: boolean; -} - -type ReadonlyVec = ArrayLike & Iterable; - -const TAU = Math.PI * 2; - -const FN = "function"; -const STR = "string"; - -const DEFAULTS: any = { - align: "left", - alpha: 1, - baseline: "alphabetic", - compose: "source-over", - dash: [], - dashOffset: 0, - direction: "inherit", - fill: "#000", - filter: "none", - font: "10px sans-serif", - lineCap: "butt", - lineJoin: "miter", - miterLimit: 10, - shadowBlur: 0, - shadowColor: "rgba(0,0,0,0)", - shadowX: 0, - shadowY: 0, - smooth: true, - stroke: "#000", - weight: 1 -}; - -const CTX_ATTRIBS: IObjectOf = { - align: "textAlign", - alpha: "globalAlpha", - baseline: "textBaseline", - clip: "clip", - compose: "globalCompositeOperation", - dash: "setLineDash", - dashOffset: "lineDashOffset", - direction: "direction", - fill: "fillStyle", - filter: "filter", - font: "font", - lineCap: "lineCap", - lineJoin: "lineJoin", - miterLimit: "miterLimit", - shadowBlur: "shadowBlur", - shadowColor: "shadowColor", - shadowX: "shadowOffsetX", - shadowY: "shadowOffsetY", - smooth: "imageSmoothingEnabled", - stroke: "strokeStyle", - weight: "lineWidth" -}; - -/** - * Special HTML5 canvas component which injects a branch-local hdom - * implementation for virtual SVG-like shape components / elements. - * These elements are then translated into canvas draw commands during - * the hdom update process. - * - * The canvas component automatically adjusts its size for HDPI displays - * by adding CSS `width` & `height` properties and pre-scaling the - * drawing context accordingly before shapes are processed. - * - * Shape components are expressed in standard hiccup syntax, however - * with the following restrictions: - * - * - Shape component objects with life cycle methods are only partially - * supported, i.e. only the `render` & `release` methods are used - * (Note, for performance reasons `release` methods are ignored by - * default. If your shape tree contains stateful components which use - * the `release` life cycle method, you'll need to explicitly enable - * the canvas component's `__release` attribute by setting it to - * `true`). - * - Currently no event listeners can be assigned to shapes (ignored), - * though this is planned for a future version. The canvas element - * itself can of course have event handlers as usual. - * - * All embedded component functions receive the user context object just - * like normal hdom components. - * - * For best performance, it's recommended to ensure all resulting shapes - * elements are provided in already normalized hiccup format (i.e. - * `[tag, {attribs}, ...]`). That way the `__normalize: false` control - * attribute can be added either to the canvas component itself (or to - * individual shapes / groups), and if present, will skip normalization - * of all children. - * - * @param _ hdom user context (ignored) - * @param attribs canvas attribs - * @param shapes shape components - */ -export const canvas = { - render(_: any, attribs: any, ...body: any[]) { - const cattribs = { ...attribs }; - delete cattribs.__diff; - delete cattribs.__normalize; - const dpr = window.devicePixelRatio || 1; - if (dpr !== 1) { - !cattribs.style && (cattribs.style = {}); - cattribs.style.width = `${cattribs.width}px`; - cattribs.style.height = `${cattribs.height}px`; - cattribs.width *= dpr; - cattribs.height *= dpr; - } - return [ - "canvas", - cattribs, - [ - "g", - { - __impl: IMPL, - __diff: attribs.__diff !== false, - __normalize: attribs.__normalize !== false, - __release: attribs.__release === true, - __serialize: false, - __clear: attribs.__clear, - scale: dpr !== 1 ? dpr : null - }, - ...body - ] - ]; - } -}; - -export const createTree = ( - _: Partial, - canvas: HTMLCanvasElement, - tree: any -) => { - // console.log(Date.now(), "draw"); - const ctx = canvas.getContext("2d"); - assert(!!ctx, "canvas ctx unavailable"); - const attribs = tree[1]; - if (attribs) { - if (attribs.__skip) return; - if (attribs.__clear !== false) { - ctx!.clearRect(0, 0, canvas.width, canvas.height); - } - } - walk(ctx!, tree, { attribs: {} }); -}; - -export const normalizeTree = (opts: Partial, tree: any): any => { - if (tree == null) { - return tree; - } - if (isArray(tree)) { - const tag = tree[0]; - if (typeof tag === FN) { - return normalizeTree( - opts, - tag.apply(null, [opts.ctx, ...tree.slice(1)]) - ); - } - if (typeof tag === STR) { - const attribs = tree[1]; - if (attribs && attribs.__normalize === false) { - return tree; - } - const res = [tree[0], attribs]; - for (let i = 2, n = tree.length; i < n; i++) { - const n = normalizeTree(opts, tree[i]); - n != null && res.push(n); - } - return res; - } - } else if (typeof tree === FN) { - return normalizeTree(opts, tree(opts.ctx)); - } else if (typeof tree.toHiccup === FN) { - return normalizeTree(opts, tree.toHiccup(opts.ctx)); - } else if (typeof tree.deref === FN) { - return normalizeTree(opts, tree.deref()); - } else if (isNotStringAndIterable(tree)) { - const res = []; - for (let t of tree) { - const n = normalizeTree(opts, t); - n != null && res.push(n); - } - return res; - } - return tree; -}; - -export const diffTree = ( - opts: Partial, - parent: HTMLCanvasElement, - prev: any[], - curr: any[], - child: number -) => { - const attribs = curr[1]; - if (attribs.__skip) return; - if (attribs.__diff === false) { - releaseTree(prev); - return createTree(opts, parent, curr); - } - // delegate to branch-local implementation - let impl: HDOMImplementation = attribs.__impl; - if (impl && impl !== IMPL) { - return impl.diffTree(opts, parent, prev, curr, child); - } - const delta = diffArray(prev, curr, DiffMode.ONLY_DISTANCE, equiv); - if (delta.distance > 0) { - return createTree(opts, parent, curr); - } -}; - -export const IMPL: HDOMImplementation = { - createTree, - normalizeTree, - diffTree, - hydrateTree: NO_OP, - getElementById: NO_OP, - createElement: NO_OP, - createTextElement: NO_OP, - replaceChild: NO_OP, - getChild: NO_OP, - removeAttribs: NO_OP, - removeChild: NO_OP, - setAttrib: NO_OP, - setContent: NO_OP -}; - -const walk = ( - ctx: CanvasRenderingContext2D, - shape: any[], - pstate: DrawState -) => { - if (!shape) return; - if (isArray(shape[0])) { - for (let s of shape) { - walk(ctx, s, pstate); - } - return; - } - const state = mergeState(ctx, pstate, shape[1]); - const attribs = state ? state.attribs : pstate.attribs; - if (attribs.__skip) return; - switch (shape[0]) { - case "g": - case "defs": - for ( - let i = 2, - n = shape.length, - __state = shape[0] === "g" ? state || pstate : pstate; - i < n; - i++ - ) { - walk(ctx, shape[i], __state); - } - break; - case "linearGradient": - defLinearGradient(ctx, pstate, shape[1], shape[2]); - break; - case "radialGradient": - defRadialGradient(ctx, pstate, shape[1], shape[2]); - break; - case "points": - points(ctx, attribs, shape[1], shape[2]); - break; - case "line": - line(ctx, attribs, shape[2], shape[3]); - break; - case "hline": - line(ctx, attribs, [-1e6, shape[2]], [1e6, shape[2]]); - break; - case "vline": - line(ctx, attribs, [shape[2], -1e6], [shape[2], 1e6]); - break; - case "polyline": - polyline(ctx, attribs, shape[2]); - break; - case "polygon": - polygon(ctx, attribs, shape[2]); - break; - case "path": - path(ctx, attribs, shape[2]); - break; - case "rect": - rect(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); - break; - case "circle": - circularArc(ctx, attribs, shape[2], shape[3]); - break; - case "ellipse": - ellipticArc( - ctx, - attribs, - shape[2], - shape[3], - shape[4], - shape[5], - shape[6] - ); - break; - case "arc": - circularArc(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); - break; - case "text": - text(ctx, attribs, shape[2], shape[3], shape[4]); - break; - case "img": - image( - ctx, - attribs, - shape[1], - shape[2], - shape[3], - shape[4], - shape[5] - ); - default: - } - state && restoreState(ctx, pstate, state); -}; - -const mergeState = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - attribs: IObjectOf -) => { - let res: DrawState | undefined; - if (!attribs) return; - if (applyTransform(ctx, attribs)) { - res = { - attribs: { ...state.attribs }, - grads: { ...state.grads }, - edits: [], - restore: true - }; - } - for (let id in attribs) { - const k = CTX_ATTRIBS[id]; - if (k) { - const v = attribs[id]; - if (v != null && state.attribs[id] !== v) { - if (!res) { - res = { - attribs: { ...state.attribs }, - grads: { ...state.grads }, - edits: [] - }; - } - res!.attribs[id] = v; - res!.edits!.push(id); - setAttrib(ctx, state, id, k, v); - } - } - } - return res; -}; - -const restoreState = ( - ctx: CanvasRenderingContext2D, - prev: DrawState, - curr: DrawState -) => { - if (curr.restore) { - ctx.restore(); - return; - } - const edits = curr.edits; - if (edits) { - for (let attribs = prev.attribs, i = edits.length; --i >= 0; ) { - const id = edits[i]; - const v = attribs[id]; - setAttrib( - ctx, - prev, - id, - CTX_ATTRIBS[id], - v != null ? v : DEFAULTS[id] - ); - } - } -}; - -const setAttrib = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - id: string, - k: string, - val: any -) => { - switch (id) { - case "fill": - case "stroke": - case "shadowColor": - (ctx)[k] = resolveColor(state, val); - break; - case "dash": - (ctx)[k].call(ctx, val); - break; - case "clip": - break; - default: - (ctx)[k] = val; - } -}; - -const resolveColor = (state: DrawState, v: any) => - isString(v) - ? v[0] === "$" - ? state.grads![v.substr(1)] - : v - : isArrayLike(v) - ? isNumber((v).mode) - ? asCSS(v) - : asCSS(v, ColorMode.RGBA) - : isNumber(v) - ? asCSS(v, ColorMode.INT32) - : v; - -const applyTransform = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf -) => { - let v: any; - if ( - (v = attribs.transform) || - attribs.setTransform || - attribs.translate || - attribs.scale || - attribs.rotate - ) { - ctx.save(); - if (v) { - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - } else if ((v = attribs.setTransform)) { - ctx.setTransform(v[0], v[1], v[2], v[3], v[4], v[5]); - } else { - (v = attribs.translate) && ctx.translate(v[0], v[1]); - (v = attribs.rotate) && ctx.rotate(v); - (v = attribs.scale) && - (isArrayLike(v) ? ctx.scale(v[0], v[1]) : ctx.scale(v, v)); - } - return true; - } - return false; -}; - -const endShape = (ctx: CanvasRenderingContext2D, attribs: IObjectOf) => { - let v: any; - if ((v = attribs.fill) && v !== "none") { - ctx.fill(); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.stroke(); - } -}; - -const defLinearGradient = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - { id, from, to }: any, - stops: any[][] -) => { - const g = ctx.createLinearGradient(from[0], from[1], to[0], to[1]); - for (let s of stops) { - g.addColorStop(s[0], resolveColor(state, s[1])); - } - !state.grads && (state.grads = {}); - state.grads[id] = g; -}; - -const defRadialGradient = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - { id, from, to, r1, r2 }: any, - stops: any[][] -) => { - const g = ctx.createRadialGradient(from[0], from[1], r1, to[0], to[1], r2); - for (let s of stops) { - g.addColorStop(s[0], resolveColor(state, s[1])); - } - !state.grads && (state.grads = {}); - state.grads[id] = g; -}; - -const line = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - a: ReadonlyVec, - b: ReadonlyVec -) => { - if (attribs.stroke === "none") return; - ctx.beginPath(); - ctx.moveTo(a[0], a[1]); - ctx.lineTo(b[0], b[1]); - ctx.stroke(); -}; - -const polyline = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pts: ReadonlyVec[] -) => { - if (pts.length < 2 || attribs.stroke == "none") return; - let p: ReadonlyVec = pts[0]; - ctx.beginPath(); - ctx.moveTo(p[0], p[1]); - for (let i = 1, n = pts.length; i < n; i++) { - p = pts[i]; - ctx.lineTo(p[0], p[1]); - } - ctx.stroke(); -}; - -const polygon = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pts: ReadonlyVec[] -) => { - if (pts.length < 2) return; - let p: ReadonlyVec = pts[0]; - ctx.beginPath(); - ctx.moveTo(p[0], p[1]); - for (let i = 1, n = pts.length; i < n; i++) { - p = pts[i]; - ctx.lineTo(p[0], p[1]); - } - ctx.closePath(); - endShape(ctx, attribs); -}; - -const path = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - segments: any[] -) => { - ctx.beginPath(); - let a: ReadonlyVec = [0, 0]; - for (let i = 0, n = segments.length; i < n; i++) { - const s = segments[i]; - let b = s[1], - c, - d; - switch (s[0]) { - // move to - case "m": - b = [a[0] + b[0], a[1] + b[1]]; - case "M": - ctx.moveTo(b[0], b[1]); - a = b; - break; - // line to - case "l": - b = [a[0] + b[0], a[1] + b[1]]; - case "L": - ctx.lineTo(b[0], b[1]); - a = b; - break; - // horizontal line rel - case "h": - b = [a[0] + b, a[1]]; - ctx.lineTo(b[0], b[1]); - a = b; - break; - // horizontal line abs - case "H": - b = [b, a[1]]; - ctx.lineTo(b[0], b[1]); - a = b; - break; - // vertical line rel - case "v": - b = [a[0], a[1] + b]; - ctx.lineTo(b[0], b[1]); - a = b; - break; - // vertical line abs - case "V": - b = [a[0], b]; - ctx.lineTo(b[0], b[1]); - a = b; - break; - // cubic curve rel - case "c": - c = s[2]; - d = s[3]; - d = [a[0] + d[0], a[1] + d[1]]; - ctx.bezierCurveTo( - a[0] + b[0], - a[1] + b[1], - a[0] + c[0], - a[1] + c[1], - d[0], - d[1] - ); - a = d; - break; - // cubic curve abs - case "C": - c = s[2]; - d = s[3]; - ctx.bezierCurveTo(b[0], b[1], c[0], c[1], d[0], d[1]); - a = d; - break; - // quadratic curve rel - case "q": - c = s[2]; - c = [a[0] + c[0], a[1] + c[1]]; - ctx.quadraticCurveTo(a[0] + b[0], a[1] + b[1], c[0], c[1]); - a = c; - break; - // quadratic curve abs - case "Q": - c = s[2]; - ctx.quadraticCurveTo(b[0], b[1], c[0], c[1]); - a = c; - break; - // circular arc rel - // Note: NOT compatible w/ SVG arc segments - case "a": - c = s[2]; - c = [a[0] + c[0], a[1] + c[1]]; - ctx.arcTo(a[0] + b[0], a[1] + b[1], c[0], c[1], s[3]); - a = c; - break; - // circular arc abs - // Note: NOT compatible w/ SVG arc segments - case "A": - c = s[2]; - ctx.arcTo(b[0], b[1], c[0], c[1], s[3]); - a = c; - break; - // close path - case "z": - case "Z": - ctx.closePath(); - } - } - endShape(ctx, attribs); -}; - -const circularArc = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - r: number, - start = 0, - end = TAU, - antiCCW = false -) => { - ctx.beginPath(); - ctx.arc(pos[0], pos[1], r, start, end, antiCCW); - endShape(ctx, attribs); -}; - -const ellipticArc = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - r: ReadonlyVec, - axis = 0, - start = 0, - end = TAU, - ccw = false -) => { - ctx.beginPath(); - ctx.ellipse(pos[0], pos[1], r[0], r[1], axis, start, end, ccw); - endShape(ctx, attribs); -}; - -const rect = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - w: number, - h: number, - r = 0 -) => { - let v: any; - if (r > 0) { - r = Math.min(Math.min(w, h) / 2, r); - w -= 2 * r; - h -= 2 * r; - return path(ctx, attribs, [ - ["M", [pos[0] + r, pos[1]]], - ["h", w], - ["a", [r, 0], [r, r], r], - ["v", h], - ["a", [0, r], [-r, r], r], - ["h", -w], - ["a", [-r, 0], [-r, -r], r], - ["v", -h], - ["a", [0, -r], [r, -r], r] - ]); - } - if ((v = attribs.fill) && v !== "none") { - ctx.fillRect(pos[0], pos[1], w, h); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.strokeRect(pos[0], pos[1], w, h); - } -}; - -const points = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - opts: IObjectOf, - pts: Iterable -) => { - const s = (opts && opts.size) || 1; - let v: any; - if ((v = attribs.fill) && v !== "none") { - if (opts.shape === "circle") { - for (let p of pts) { - ctx.beginPath(); - ctx.arc(p[0], p[1], s, 0, TAU); - ctx.fill(); - } - } else { - for (let p of pts) { - ctx.fillRect(p[0], p[1], s, s); - } - } - } - if ((v = attribs.stroke) && v !== "none") { - if (opts.shape === "circle") { - for (let p of pts) { - ctx.beginPath(); - ctx.arc(p[0], p[1], s, 0, TAU); - ctx.stroke(); - } - } else { - for (let p of pts) { - ctx.strokeRect(p[0], p[1], s, s); - } - } - } -}; - -const text = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - body: any, - maxWidth?: number -) => { - let v: any; - if ((v = attribs.fill) && v !== "none") { - ctx.fillText(body.toString(), pos[0], pos[1], maxWidth); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.strokeText(body.toString(), pos[0], pos[1], maxWidth); - } -}; - -const image = ( - ctx: CanvasRenderingContext2D, - _: IObjectOf, - { width, height }: IObjectOf, - img: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, - dpos: ReadonlyVec, - spos?: ReadonlyVec, - ssize?: ReadonlyVec -) => { - width = width || img.width; - height = height || img.height; - spos - ? ctx.drawImage( - img, - spos[0], - spos[1], - ssize ? ssize[0] : width, - ssize ? ssize[1] : height, - dpos[0], - dpos[1], - width, - height - ) - : ctx.drawImage(img, dpos[0], dpos[1], width, height); -}; +export * from "./api"; +export * from "./impl"; +export * from "./state"; +export * from "./walk"; + +export * from "./draw/arc"; +export * from "./draw/color"; +export * from "./draw/end-shape"; +export * from "./draw/image"; +export * from "./draw/line"; +export * from "./draw/path"; +export * from "./draw/points"; +export * from "./draw/polygon"; +export * from "./draw/polyline"; +export * from "./draw/rect"; +export * from "./draw/text"; diff --git a/packages/hdom-canvas/src/state.ts b/packages/hdom-canvas/src/state.ts new file mode 100644 index 0000000000..91f5f0f183 --- /dev/null +++ b/packages/hdom-canvas/src/state.ts @@ -0,0 +1,161 @@ +import { IObjectOf } from "@thi.ng/api"; +import { isArrayLike } from "@thi.ng/checks"; +import { DrawState } from "./api"; +import { resolveGradientOrColor } from "./draw/color"; + +const DEFAULTS: any = { + align: "left", + alpha: 1, + baseline: "alphabetic", + compose: "source-over", + dash: [], + dashOffset: 0, + direction: "inherit", + fill: "#000", + filter: "none", + font: "10px sans-serif", + lineCap: "butt", + lineJoin: "miter", + miterLimit: 10, + shadowBlur: 0, + shadowColor: "rgba(0,0,0,0)", + shadowX: 0, + shadowY: 0, + smooth: true, + stroke: "#000", + weight: 1 +}; + +const CTX_ATTRIBS: IObjectOf = { + align: "textAlign", + alpha: "globalAlpha", + baseline: "textBaseline", + clip: "clip", + compose: "globalCompositeOperation", + dash: "setLineDash", + dashOffset: "lineDashOffset", + direction: "direction", + fill: "fillStyle", + filter: "filter", + font: "font", + lineCap: "lineCap", + lineJoin: "lineJoin", + miterLimit: "miterLimit", + shadowBlur: "shadowBlur", + shadowColor: "shadowColor", + shadowX: "shadowOffsetX", + shadowY: "shadowOffsetY", + smooth: "imageSmoothingEnabled", + stroke: "strokeStyle", + weight: "lineWidth" +}; + +const newState = (state: DrawState, restore = false) => ({ + attribs: { ...state.attribs }, + grads: { ...state.grads }, + edits: [], + restore +}); + +export const mergeState = ( + ctx: CanvasRenderingContext2D, + state: DrawState, + attribs: IObjectOf +) => { + let res: DrawState | undefined; + if (!attribs) return; + if (applyTransform(ctx, attribs)) { + res = newState(state, true); + } + for (let id in attribs) { + const k = CTX_ATTRIBS[id]; + if (k) { + const v = attribs[id]; + if (v != null && state.attribs[id] !== v) { + !res && (res = newState(state)); + res.attribs[id] = v; + res.edits!.push(id); + setAttrib(ctx, state, id, k, v); + } + } + } + return res; +}; + +export const restoreState = ( + ctx: CanvasRenderingContext2D, + prev: DrawState, + curr: DrawState +) => { + if (curr.restore) { + ctx.restore(); + return; + } + const edits = curr.edits; + const attribs = prev.attribs; + for (let i = edits.length; --i >= 0; ) { + const id = edits[i]; + const v = attribs[id]; + setAttrib(ctx, prev, id, CTX_ATTRIBS[id], v != null ? v : DEFAULTS[id]); + } +}; + +export const registerGradient = ( + state: DrawState, + id: string, + g: CanvasGradient +) => { + !state.grads && (state.grads = {}); + state.grads[id] = g; +}; + +const setAttrib = ( + ctx: CanvasRenderingContext2D, + state: DrawState, + id: string, + k: string, + val: any +) => { + switch (id) { + case "fill": + case "stroke": + case "shadowColor": + (ctx)[k] = resolveGradientOrColor(state, val); + break; + case "dash": + (ctx)[k].call(ctx, val); + break; + case "clip": + break; + default: + (ctx)[k] = val; + } +}; + +const applyTransform = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf +) => { + let v: any; + if ( + (v = attribs.transform) || + attribs.setTransform || + attribs.translate || + attribs.scale || + attribs.rotate + ) { + ctx.save(); + if (v) { + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + } else if ((v = attribs.setTransform)) { + ctx.setTransform(v[0], v[1], v[2], v[3], v[4], v[5]); + } else { + (v = attribs.translate) && ctx.translate(v[0], v[1]); + (v = attribs.rotate) && ctx.rotate(v); + (v = attribs.scale) && + (isArrayLike(v) ? ctx.scale(v[0], v[1]) : ctx.scale(v, v)); + } + return true; + } + return false; +}; diff --git a/packages/hdom-canvas/src/walk.ts b/packages/hdom-canvas/src/walk.ts new file mode 100644 index 0000000000..e4a6931030 --- /dev/null +++ b/packages/hdom-canvas/src/walk.ts @@ -0,0 +1,119 @@ +import { isArray } from "@thi.ng/checks"; +import { DrawState } from "./api"; +import { circularArc, ellipticArc } from "./draw/arc"; +import { defLinearGradient, defRadialGradient } from "./draw/color"; +import { image } from "./draw/image"; +import { line } from "./draw/line"; +import { path } from "./draw/path"; +import { points } from "./draw/points"; +import { polygon } from "./draw/polygon"; +import { polyline } from "./draw/polyline"; +import { rect } from "./draw/rect"; +import { text } from "./draw/text"; +import { mergeState, registerGradient, restoreState } from "./state"; + +export const walk = ( + ctx: CanvasRenderingContext2D, + shape: any[], + pstate: DrawState +) => { + if (!shape) return; + if (isArray(shape[0])) { + for (let s of shape) { + walk(ctx, s, pstate); + } + return; + } + const state = mergeState(ctx, pstate, shape[1]); + const attribs = state ? state.attribs : pstate.attribs; + if (attribs.__skip) return; + switch (shape[0]) { + case "g": + case "defs": + defs(ctx, state, pstate, shape); + break; + case "linearGradient": + registerGradient( + pstate, + shape[1].id, + defLinearGradient(ctx, shape[1], shape[2]) + ); + break; + case "radialGradient": + registerGradient( + pstate, + shape[1].id, + defRadialGradient(ctx, shape[1], shape[2]) + ); + break; + case "points": + points(ctx, attribs, shape[1], shape[2]); + break; + case "line": + line(ctx, attribs, shape[2], shape[3]); + break; + case "hline": + line(ctx, attribs, [-1e6, shape[2]], [1e6, shape[2]]); + break; + case "vline": + line(ctx, attribs, [shape[2], -1e6], [shape[2], 1e6]); + break; + case "polyline": + polyline(ctx, attribs, shape[2]); + break; + case "polygon": + polygon(ctx, attribs, shape[2]); + break; + case "path": + path(ctx, attribs, shape[2]); + break; + case "rect": + rect(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); + break; + case "circle": + circularArc(ctx, attribs, shape[2], shape[3]); + break; + case "ellipse": + ellipticArc( + ctx, + attribs, + shape[2], + shape[3], + shape[4], + shape[5], + shape[6] + ); + break; + case "arc": + circularArc(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); + break; + case "text": + text(ctx, attribs, shape[2], shape[3], shape[4]); + break; + case "img": + image( + ctx, + attribs, + shape[1], + shape[2], + shape[3], + shape[4], + shape[5] + ); + default: + } + state && restoreState(ctx, pstate, state); +}; + +const defs = ( + ctx: CanvasRenderingContext2D, + state: DrawState | undefined, + pstate: DrawState, + shape: any[] +) => { + const n = shape.length; + const __state = shape[0] === "g" ? state || pstate : pstate; + for (let i = 2; i < n; i++) { + walk(ctx, shape[i], __state); + } +}; diff --git a/packages/hdom-components/CHANGELOG.md b/packages/hdom-components/CHANGELOG.md index d79d26bb94..d1530812f7 100644 --- a/packages/hdom-components/CHANGELOG.md +++ b/packages/hdom-components/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.1.3...@thi.ng/hdom-components@3.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hdom-components + + + + + ## [3.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.1.2...@thi.ng/hdom-components@3.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/hdom-components diff --git a/packages/hdom-components/package.json b/packages/hdom-components/package.json index be1bccb181..e364d08e8b 100644 --- a/packages/hdom-components/package.json +++ b/packages/hdom-components/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-components", - "version": "3.1.3", + "version": "3.1.4", "description": "Raw, skinnable UI & SVG components for @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", "@thi.ng/math": "^1.4.2", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/transducers-stats": "^1.1.3", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/transducers-stats": "^1.1.4", "@types/webgl2": "^0.0.5" }, "keywords": [ diff --git a/packages/hdom-mock/CHANGELOG.md b/packages/hdom-mock/CHANGELOG.md index b5b407b936..08d4d5775e 100644 --- a/packages/hdom-mock/CHANGELOG.md +++ b/packages/hdom-mock/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-mock@1.1.3...@thi.ng/hdom-mock@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hdom-mock + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-mock@1.1.2...@thi.ng/hdom-mock@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/hdom-mock diff --git a/packages/hdom-mock/package.json b/packages/hdom-mock/package.json index f4e1dd648d..d6e45bf74f 100644 --- a/packages/hdom-mock/package.json +++ b/packages/hdom-mock/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-mock", - "version": "1.1.3", + "version": "1.1.4", "description": "Mock base implementation for @thi.ng/hdom API", "module": "./index.js", "main": "./lib/index.js", @@ -33,9 +33,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/hdom": "^8.0.3" + "@thi.ng/hdom": "^8.0.4" }, "keywords": [ "ES6", diff --git a/packages/hdom/CHANGELOG.md b/packages/hdom/CHANGELOG.md index 1ab82e2cf0..36e68fe302 100644 --- a/packages/hdom/CHANGELOG.md +++ b/packages/hdom/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@8.0.3...@thi.ng/hdom@8.0.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hdom + + + + + ## [8.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@8.0.2...@thi.ng/hdom@8.0.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/hdom diff --git a/packages/hdom/package.json b/packages/hdom/package.json index d6df03369d..b0ce64d01b 100644 --- a/packages/hdom/package.json +++ b/packages/hdom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom", - "version": "8.0.3", + "version": "8.0.4", "description": "Lightweight vanilla ES6 UI component trees with customizable branch-local behaviors", "module": "./index.js", "main": "./lib/index.js", @@ -25,7 +25,7 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/atom": "^3.0.3", + "@thi.ng/atom": "^3.0.4", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", @@ -34,12 +34,12 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/diff": "^3.2.2", + "@thi.ng/diff": "^3.2.3", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/hiccup": "^3.2.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/hiccup": "^3.2.4" }, "keywords": [ "browser", diff --git a/packages/hdom/src/diff.ts b/packages/hdom/src/diff.ts index f8d9dbfc56..853ec23b77 100644 --- a/packages/hdom/src/diff.ts +++ b/packages/hdom/src/diff.ts @@ -12,6 +12,11 @@ import { HDOMImplementation, HDOMOpts } from "./api"; const isArray = Array.isArray; const max = Math.max; +const OBJP = Object.getPrototypeOf({}); + +const FN = "function"; +const STR = "string"; + // child index tracking template buffer const INDEX = (() => { const res = new Array(2048); @@ -73,10 +78,6 @@ export const diffTree = ( const el = impl.getChild(parent, child); let i: number; let ii: number; - let j: number; - let idx: number; - let k: any; - let eq: any[]; let status: number; let val: any; if (edits[0] !== 0 || prev[1].key !== attribs.key) { @@ -103,43 +104,29 @@ export const diffTree = ( status = edits[ii]; if (!status) continue; if (status === -1) { - // element removed / edited? - val = edits[ii + 2]; - if (isArray(val)) { - k = val[1].key; - if (k !== undefined && equivKeys[k][2] !== undefined) { - eq = equivKeys[k]; - k = eq[0]; - // LOGGER.fine(`diff equiv key @ ${k}:`, prev[k], curr[eq[2]]); - diffTree(opts, impl, el, prev[k], curr[eq[2]], offsets[k]); - } else { - idx = edits[ii + 1]; - // LOGGER.fine("remove @", offsets[idx], val); - releaseTree(val); - impl.removeChild(el, offsets[idx]); - for (j = prevLength; j > idx; j--) { - offsets[j] = max(offsets[j] - 1, 0); - } - } - } else if (typeof val === "string") { - impl.setContent(el, ""); - } + diffDeleted( + opts, + impl, + el, + prev, + curr, + edits, + ii, + equivKeys, + offsets, + prevLength + ); } else { - // element added/inserted? - val = edits[ii + 2]; - if (typeof val === "string") { - impl.setContent(el, val); - } else if (isArray(val)) { - k = val[1].key; - if (k === undefined || equivKeys[k][0] === undefined) { - idx = edits[ii + 1]; - // LOGGER.fine("insert @", offsets[idx], val); - impl.createTree(opts, el, val, offsets[idx]); - for (j = prevLength; j >= idx; j--) { - offsets[j]++; - } - } - } + diffAdded( + opts, + impl, + el, + edits, + ii, + equivKeys, + offsets, + prevLength + ); } } // call __init after all children have been added/updated @@ -148,6 +135,74 @@ export const diffTree = ( } }; +const diffDeleted = ( + opts: Partial, + impl: HDOMImplementation, + el: T, + prev: any[], + curr: any[], + edits: any[], + ii: number, + equivKeys: IObjectOf, + offsets: any[], + prevLength: number +) => { + const val = edits[ii + 2]; + if (isArray(val)) { + let k = val[1].key; + if (k !== undefined && equivKeys[k][2] !== undefined) { + const eq = equivKeys[k]; + k = eq[0]; + // LOGGER.fine(`diff equiv key @ ${k}:`, prev[k], curr[eq[2]]); + diffTree(opts, impl, el, prev[k], curr[eq[2]], offsets[k]); + } else { + const idx = edits[ii + 1]; + // LOGGER.fine("remove @", offsets[idx], val); + releaseTree(val); + impl.removeChild(el, offsets[idx]); + incOffsets(offsets, prevLength, idx); + } + } else if (typeof val === STR) { + impl.setContent(el, ""); + } +}; + +const diffAdded = ( + opts: Partial, + impl: HDOMImplementation, + el: T, + edits: any[], + ii: number, + equivKeys: IObjectOf, + offsets: any[], + prevLength: number +) => { + const val = edits[ii + 2]; + if (typeof val === STR) { + impl.setContent(el, val); + } else if (isArray(val)) { + const k = val[1].key; + if (k === undefined || equivKeys[k][0] === undefined) { + const idx = edits[ii + 1]; + // LOGGER.fine("insert @", offsets[idx], val); + impl.createTree(opts, el, val, offsets[idx]); + decOffsets(offsets, prevLength, idx); + } + } +}; + +const incOffsets = (offsets: any[], j: number, idx: number) => { + for (; j > idx; j--) { + offsets[j] = max(offsets[j] - 1, 0); + } +}; + +const decOffsets = (offsets: any[], j: number, idx: number) => { + for (; j >= idx; j--) { + offsets[j]++; + } +}; + /** * Helper function for `diffTree()` to compute & apply the difference * between a node's `prev` and `curr` attributes. @@ -169,26 +224,16 @@ export const diffAttributes = ( let i: number, e, edits; for (edits = delta.edits!, i = edits.length; (i -= 2) >= 0; ) { e = edits[i]; - if (e.indexOf("on") === 0) { - impl.removeAttribs(el, [e], prev); - } - if (e !== "value") { - impl.setAttrib(el, e, edits[i + 1], curr); - } else { - val = edits[i + 1]; - } + e.indexOf("on") === 0 && impl.removeAttribs(el, [e], prev); + e !== "value" + ? impl.setAttrib(el, e, edits[i + 1], curr) + : (val = edits[i + 1]); } for (edits = delta.adds!, i = edits.length; --i >= 0; ) { e = edits[i]; - if (e !== "value") { - impl.setAttrib(el, e, curr[e], curr); - } else { - val = curr[e]; - } - } - if (val !== SEMAPHORE) { - impl.setAttrib(el, "value", val, curr); + e !== "value" ? impl.setAttrib(el, e, curr[e], curr) : (val = curr[e]); } + val !== SEMAPHORE && impl.setAttrib(el, "value", val, curr); }; /** @@ -233,11 +278,6 @@ const extractEquivElements = (edits: any[]) => { return equiv; }; -const OBJP = Object.getPrototypeOf({}); - -const FN = "function"; -const STR = "string"; - /** * Customized version @thi.ng/equiv which takes `__diff` attributes into * account (at any nesting level). If an hdom element's attribute object diff --git a/packages/hdom/src/dom.ts b/packages/hdom/src/dom.ts index 281c058e85..a4922f2cd5 100644 --- a/packages/hdom/src/dom.ts +++ b/packages/hdom/src/dom.ts @@ -6,6 +6,9 @@ import { HDOMImplementation, HDOMOpts } from "./api"; const isArray = isa; const isNotStringAndIterable = isi; +const maybeInitElement = (el: T, tree: any) => + tree.__init && tree.__init.apply(tree.__this, [el, ...tree.__args]); + /** * See `HDOMImplementation` interface for further details. * @@ -48,12 +51,7 @@ export const createTree = ( createTree(opts, impl, el, tree[i]); } } - if ((tree).__init) { - (tree).__init.apply((tree).__this, [ - el, - ...(tree).__args - ]); - } + maybeInitElement(el, tree); return el; } if (isNotStringAndIterable(tree)) { @@ -104,16 +102,9 @@ export const hydrateTree = ( index ); } - if ((tree).__init) { - (tree).__init.apply((tree).__this, [ - el, - ...(tree).__args - ]); - } + maybeInitElement(el, tree); for (let a in attribs) { - if (a.indexOf("on") === 0) { - impl.setAttrib(el, a, attribs[a]); - } + a.indexOf("on") === 0 && impl.setAttrib(el, a, attribs[a]); } for (let n = tree.length, i = 2; i < n; i++) { hydrateTree(opts, impl, el, tree[i], i - 2); @@ -149,34 +140,22 @@ export const createElement = ( const el = SVG_TAGS[tag] ? document.createElementNS(SVG_NS, tag) : document.createElement(tag); - if (parent) { - if (insert == null) { - parent.appendChild(el); - } else { - parent.insertBefore(el, parent.children[insert]); - } - } - if (attribs) { - setAttribs(el, attribs); - } - return el; + attribs && setAttribs(el, attribs); + return addChild(parent, el, insert); }; export const createTextElement = ( parent: Element, content: string, insert?: number -) => { - const el = document.createTextNode(content); - if (parent) { - if (insert === undefined) { - parent.appendChild(el); - } else { - parent.insertBefore(el, parent.children[insert]); - } - } - return el; -}; +) => addChild(parent, document.createTextNode(content), insert); + +export const addChild = (parent: Element, child: Node, insert?: number) => + parent + ? insert === undefined + ? parent.appendChild(child) + : parent.insertBefore(child, parent.children[insert]) + : child; export const getChild = (parent: Element, child: number) => parent.children[child]; @@ -250,11 +229,9 @@ export const setAttrib = (el: Element, id: string, val: any, attribs?: any) => { (el)[id] = val; break; default: - if (isListener) { - setListener(el, id.substr(2), val); - } else { - el.setAttribute(id, val); - } + isListener + ? setListener(el, id.substr(2), val) + : el.setAttribute(id, val); } } else { (el)[id] != null ? ((el)[id] = null) : el.removeAttribute(id); @@ -322,13 +299,10 @@ export const setListener = ( el: Element, id: string, listener: EventListener | [EventListener, boolean | AddEventListenerOptions] -) => { - if (isArray(listener)) { - el.addEventListener(id, ...listener); - } else { - el.addEventListener(id, listener); - } -}; +) => + isArray(listener) + ? el.addEventListener(id, ...listener) + : el.addEventListener(id, listener); /** * Removes event listener (possibly with options). @@ -341,13 +315,10 @@ export const removeListener = ( el: Element, id: string, listener: EventListener | [EventListener, boolean | AddEventListenerOptions] -) => { - if (isArray(listener)) { - el.removeEventListener(id, ...listener); - } else { - el.removeEventListener(id, listener); - } -}; +) => + isArray(listener) + ? el.removeEventListener(id, ...listener) + : el.removeEventListener(id, listener); export const clearDOM = (el: Element) => (el.innerHTML = ""); diff --git a/packages/hdom/src/normalize.ts b/packages/hdom/src/normalize.ts index 6e9e655135..1c8ab52e7c 100644 --- a/packages/hdom/src/normalize.ts +++ b/packages/hdom/src/normalize.ts @@ -1,6 +1,6 @@ import { isArray as isa, isNotStringAndIterable as isi, isPlainObject as iso } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; -import { NO_SPANS, TAG_REGEXP } from "@thi.ng/hiccup"; +import { NO_SPANS, RE_TAG } from "@thi.ng/hiccup"; import { HDOMOpts } from "./api"; const isArray = isa; @@ -42,7 +42,7 @@ export const normalizeElement = (spec: any[], keys: boolean) => { let id: string; let clazz: string; let attribs; - if (typeof tag !== "string" || !(match = TAG_REGEXP.exec(tag))) { + if (typeof tag !== "string" || !(match = RE_TAG.exec(tag))) { illegalArgs(`${tag} is not a valid tag name`); } mtag = match![1]; diff --git a/packages/heaps/CHANGELOG.md b/packages/heaps/CHANGELOG.md index 5bf96be582..d27cc3b74c 100644 --- a/packages/heaps/CHANGELOG.md +++ b/packages/heaps/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@1.1.2...@thi.ng/heaps@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/heaps + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@1.1.1...@thi.ng/heaps@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/heaps diff --git a/packages/heaps/package.json b/packages/heaps/package.json index 87c9c0b4b4..25b0a6c699 100644 --- a/packages/heaps/package.json +++ b/packages/heaps/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/heaps", - "version": "1.1.2", + "version": "1.1.3", "description": "Generic binary heap & d-ary heap implementations with customizable ordering", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/compare": "^1.0.9" }, "keywords": [ diff --git a/packages/hiccup-carbon-icons/CHANGELOG.md b/packages/hiccup-carbon-icons/CHANGELOG.md index ec9f27785d..3bcbc36507 100644 --- a/packages/hiccup-carbon-icons/CHANGELOG.md +++ b/packages/hiccup-carbon-icons/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-carbon-icons@1.0.18...@thi.ng/hiccup-carbon-icons@1.0.19) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hiccup-carbon-icons + + + + + ## [1.0.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-carbon-icons@1.0.17...@thi.ng/hiccup-carbon-icons@1.0.18) (2019-08-16) **Note:** Version bump only for package @thi.ng/hiccup-carbon-icons diff --git a/packages/hiccup-carbon-icons/package.json b/packages/hiccup-carbon-icons/package.json index 22d980a4b5..4589cf6247 100644 --- a/packages/hiccup-carbon-icons/package.json +++ b/packages/hiccup-carbon-icons/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-carbon-icons", - "version": "1.0.18", + "version": "1.0.19", "description": "Full set of IBM's Carbon icons in hiccup format", "module": "./index.js", "main": "./lib/index.js", @@ -25,7 +25,7 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/hiccup": "^3.2.3", + "@thi.ng/hiccup": "^3.2.4", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", diff --git a/packages/hiccup-css/CHANGELOG.md b/packages/hiccup-css/CHANGELOG.md index 4c5adba5d1..61bf5c0fe5 100644 --- a/packages/hiccup-css/CHANGELOG.md +++ b/packages/hiccup-css/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@1.1.3...@thi.ng/hiccup-css@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hiccup-css + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@1.1.2...@thi.ng/hiccup-css@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/hiccup-css diff --git a/packages/hiccup-css/package.json b/packages/hiccup-css/package.json index 4d6beb66e8..a36c91f9d4 100644 --- a/packages/hiccup-css/package.json +++ b/packages/hiccup-css/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-css", - "version": "1.1.3", + "version": "1.1.4", "description": "CSS from nested JS data structures", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "clojure", diff --git a/packages/hiccup-markdown/CHANGELOG.md b/packages/hiccup-markdown/CHANGELOG.md index 84b9d68f4c..86d20ca439 100644 --- a/packages/hiccup-markdown/CHANGELOG.md +++ b/packages/hiccup-markdown/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-markdown@1.1.3...@thi.ng/hiccup-markdown@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hiccup-markdown + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-markdown@1.1.2...@thi.ng/hiccup-markdown@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/hiccup-markdown diff --git a/packages/hiccup-markdown/package.json b/packages/hiccup-markdown/package.json index 650e81cb47..e733793d18 100644 --- a/packages/hiccup-markdown/package.json +++ b/packages/hiccup-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-markdown", - "version": "1.1.3", + "version": "1.1.4", "description": "Markdown serialization of hiccup DOM trees", "module": "./index.js", "main": "./lib/index.js", @@ -33,14 +33,14 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/arrays": "^0.2.3", + "@thi.ng/arrays": "^0.2.4", "@thi.ng/checks": "^2.3.0", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/fsm": "^2.2.3", - "@thi.ng/hiccup": "^3.2.3", - "@thi.ng/strings": "^1.2.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/defmulti": "^1.1.3", + "@thi.ng/errors": "^1.2.0", + "@thi.ng/fsm": "^2.2.4", + "@thi.ng/hiccup": "^3.2.4", + "@thi.ng/strings": "^1.2.3", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "ES6", diff --git a/packages/hiccup-markdown/src/serialize.ts b/packages/hiccup-markdown/src/serialize.ts index 3aa4a79d38..dc286ecf53 100644 --- a/packages/hiccup-markdown/src/serialize.ts +++ b/packages/hiccup-markdown/src/serialize.ts @@ -9,10 +9,17 @@ import { illegalArgs } from "@thi.ng/errors"; import { normalize } from "@thi.ng/hiccup"; import { repeat, wrap } from "@thi.ng/strings"; +interface SerializeState { + indent: number; + sep: string; + id?: number; + pre?: boolean; +} + export const serialize = (tree: any, ctx: any) => _serialize(tree, ctx, { indent: 0, sep: "" }); -const _serialize = (tree: any, ctx: any, state: any): string => { +const _serialize = (tree: any, ctx: any, state: SerializeState): string => { if (tree == null) return ""; if (Array.isArray(tree)) { if (!tree.length) { @@ -62,7 +69,11 @@ const _serialize = (tree: any, ctx: any, state: any): string => { return tree.toString(); }; -const serializeIter = (iter: Iterable, ctx: any, state: any) => { +const serializeIter = ( + iter: Iterable, + ctx: any, + state: SerializeState +) => { if (!iter) return ""; const res = []; for (let i of iter) { @@ -71,86 +82,79 @@ const serializeIter = (iter: Iterable, ctx: any, state: any) => { return res.join(state.sep); }; -const header = (level: number) => (el: any[], ctx: any, state: any) => - repeat("#", level) + " " + body(el, ctx, state) + "\n\n"; +const header = (level: number) => ( + el: any[], + ctx: any, + state: SerializeState +) => repeat("#", level) + " " + body(el, ctx, state) + "\n\n"; -const body = (el: any[], ctx: any, state: any) => +const body = (el: any[], ctx: any, state: SerializeState) => serializeIter(el[2], ctx, state); -export const serializeElement: MultiFn3 = defmulti( - (el) => el[0] -); +export const serializeElement: MultiFn3< + any, + any, + SerializeState, + string +> = defmulti((el) => el[0]); serializeElement.add(DEFAULT, body); -serializeElement.add("h1", header(1)); -serializeElement.add("h2", header(2)); -serializeElement.add("h3", header(3)); -serializeElement.add("h4", header(4)); -serializeElement.add("h5", header(5)); -serializeElement.add("h6", header(6)); -serializeElement.add("p", (el, ctx, state) => `\n${body(el, ctx, state)}\n`); +serializeElement.addAll({ + h1: header(1), + h2: header(2), + h3: header(3), + h4: header(4), + h5: header(5), + h6: header(6), + + p: (el, ctx, state) => `\n${body(el, ctx, state)}\n`, -serializeElement.add("img", (el) => `![${el[1].alt || ""}](${el[1].src})`); + img: (el) => `![${el[1].alt || ""}](${el[1].src})`, -serializeElement.add( - "a", - (el, ctx, state) => `[${body(el, ctx, state)}](${el[1].href})` -); + a: (el, ctx, state) => `[${body(el, ctx, state)}](${el[1].href})`, -serializeElement.add("em", (el, ctx, state) => `_${body(el, ctx, state)}_`); + em: (el, ctx, state) => `_${body(el, ctx, state)}_`, -serializeElement.add( - "strong", - (el, ctx, state) => `**${body(el, ctx, state)}**` -); + strong: (el, ctx, state) => `**${body(el, ctx, state)}**`, -serializeElement.add( - "pre", - (el, ctx, state) => + pre: (el, ctx, state) => `\n\`\`\`${el[1].lang || ""}\n${body(el, ctx, { ...state, pre: true, sep: "\n" - })}\n\`\`\`\n` -); - -serializeElement.add("code", (el, ctx, state) => - state.pre ? el[2][0] : `\`${body(el, ctx, state)}\`` -); - -serializeElement.add("ul", (el, ctx, state) => { - const cstate = { - ...state, - indent: state.indent + 4, - sep: "\n" - }; - return wrap(state.indent === 0 ? "\n" : "")(body(el, ctx, cstate)); -}); + })}\n\`\`\`\n`, -serializeElement.add("ol", (el, ctx, state) => { - const cstate = { - ...state, - indent: state.indent + 4, - id: 0, - sep: "\n" - }; - return wrap(state.indent === 0 ? "\n" : "")(body(el, ctx, cstate)); -}); + code: (el, ctx, state) => + state.pre ? el[2][0] : `\`${body(el, ctx, state)}\``, -serializeElement.add( - "li", - (el, ctx, state) => + ul: (el, ctx, state) => { + const cstate: SerializeState = { + ...state, + indent: state.indent + 4, + sep: "\n" + }; + return wrap(state.indent === 0 ? "\n" : "")(body(el, ctx, cstate)); + }, + + ol: (el, ctx, state) => { + const cstate: SerializeState = { + ...state, + indent: state.indent + 4, + id: 0, + sep: "\n" + }; + return wrap(state.indent === 0 ? "\n" : "")(body(el, ctx, cstate)); + }, + + li: (el, ctx, state) => repeat(" ", state.indent - 4) + (state.id != null ? ++state.id + "." : "-") + " " + - body(el, ctx, { ...state, sep: "" }) -); + body(el, ctx, { ...state, sep: "" }), -serializeElement.add( - "blockquote", - (el, ctx, state) => `\n> ${body(el, ctx, state)}\n` -); + blockquote: (el, ctx, state) => `\n> ${body(el, ctx, state)}\n`, -serializeElement.add("br", () => "\\\n"); + br: () => "\\\n", -serializeElement.add("hr", () => "\n---\n"); + hr: () => "\n---\n" +}); diff --git a/packages/hiccup-svg/CHANGELOG.md b/packages/hiccup-svg/CHANGELOG.md index dc63a0cda3..f46bc0a75b 100644 --- a/packages/hiccup-svg/CHANGELOG.md +++ b/packages/hiccup-svg/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.2.6...@thi.ng/hiccup-svg@3.3.0) (2019-08-21) + + +### Bug Fixes + +* **hiccup-svg:** convertAttrib() arg order ([8b48a27](https://github.com/thi-ng/umbrella/commit/8b48a27)) + + +### Features + +* **hiccup-svg:** update polyline(), add fill: none default ([cff9e30](https://github.com/thi-ng/umbrella/commit/cff9e30)) + + + + + +## [3.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.2.5...@thi.ng/hiccup-svg@3.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/hiccup-svg + + + + + ## [3.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.2.4...@thi.ng/hiccup-svg@3.2.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/hiccup-svg diff --git a/packages/hiccup-svg/package.json b/packages/hiccup-svg/package.json index 15269b4dcd..e7cc77cca6 100644 --- a/packages/hiccup-svg/package.json +++ b/packages/hiccup-svg/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-svg", - "version": "3.2.5", + "version": "3.3.0", "description": "SVG element functions for @thi.ng/hiccup & @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -34,8 +34,8 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/color": "^1.0.2", - "@thi.ng/hiccup": "^3.2.3" + "@thi.ng/color": "^1.1.0", + "@thi.ng/hiccup": "^3.2.4" }, "keywords": [ "components", diff --git a/packages/hiccup-svg/src/convert.ts b/packages/hiccup-svg/src/convert.ts index dc316dc20a..d8d09e3693 100644 --- a/packages/hiccup-svg/src/convert.ts +++ b/packages/hiccup-svg/src/convert.ts @@ -121,31 +121,36 @@ const convertAttribs = (attribs: any) => { // convertTransforms(res, attribs); for (let id in attribs) { const v = attribs[id]; - if (ATTRIB_ALIASES[id]) { - res[ATTRIB_ALIASES[id]] = v; + const aid = ATTRIB_ALIASES[id]; + if (aid) { + res[aid] = v; } else { - switch (id) { - case "font": { - const i = v.indexOf(" "); - res["font-size"] = v.substr(0, i); - res["font-family"] = v.substr(i + 1); - break; - } - case "align": - res["text-anchor"] = TEXT_ALIGN[v]; - break; - case "baseline": - // no SVG support? - break; - case "filter": - // TODO needs to be translated into def first - // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter - // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter - break; - default: - res[id] = v; - } + convertAttrib(res, id, v); } } return res; }; + +const convertAttrib = (res: any, id: string, v: any) => { + switch (id) { + case "font": { + const i = v.indexOf(" "); + res["font-size"] = v.substr(0, i); + res["font-family"] = v.substr(i + 1); + break; + } + case "align": + res["text-anchor"] = TEXT_ALIGN[v]; + break; + case "baseline": + // no SVG support? + break; + case "filter": + // TODO needs to be translated into def first + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter + // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter + break; + default: + res[id] = v; + } +}; diff --git a/packages/hiccup-svg/src/format.ts b/packages/hiccup-svg/src/format.ts index 998132b7c0..a1f2c12a9c 100644 --- a/packages/hiccup-svg/src/format.ts +++ b/packages/hiccup-svg/src/format.ts @@ -1,5 +1,5 @@ -import { isArrayLike, isNumber, isString } from "@thi.ng/checks"; -import { asCSS, ColorMode, ReadonlyColor } from "@thi.ng/color"; +import { isArrayLike, isString } from "@thi.ng/checks"; +import { resolveAsCSS } from "@thi.ng/color"; import { Vec2Like } from "./api"; let PRECISION = 2; @@ -30,11 +30,11 @@ export const fpoints = (pts: Vec2Like[], sep = " ") => * therefore need to be complete, e.g. `{ rotate: "rotate(60)" }` * * For color related attribs (`fill`, `stroke`), if given value is - * array-like or a number, it will be converted into a CSS color string - * using thi.ng/color/asCSS. + * array-like, a number or an `IColor` instance, it will be converted + * into a CSS color string using thi.ng/color's `asCSS()`. * * String color attribs prefixed with `$` are replaced with `url(#...)` - * refs (used for gradients). + * refs (used for referencing gradients). * * Returns updated attribs or `undefined` if `attribs` itself is * null-ish. @@ -73,31 +73,36 @@ const ftransforms = (attribs: any) => { delete attribs.rotate; delete attribs.scale; } else { - const tx: string[] = []; - if ((v = attribs.translate)) { - tx.push(isString(v) ? v : `translate(${ff(v[0])} ${ff(v[1])})`); - delete attribs.translate; - } - if ((v = attribs.rotate)) { - tx.push(isString(v) ? v : `rotate(${ff((v * 180) / Math.PI)})`); - delete attribs.rotate; - } - if ((v = attribs.scale)) { - tx.push( - isString(v) - ? v - : isArrayLike(v) - ? `scale(${ff(v[0])} ${ff(v[1])})` - : `scale(${ff(v)})` - ); - delete attribs.scale; - } - attribs.transform = tx.join(" "); + attribs.transform = buildTransform(attribs); } } return attribs; }; +const buildTransform = (attribs: any) => { + const tx: string[] = []; + let v: any; + if ((v = attribs.translate)) { + tx.push(isString(v) ? v : `translate(${ff(v[0])} ${ff(v[1])})`); + delete attribs.translate; + } + if ((v = attribs.rotate)) { + tx.push(isString(v) ? v : `rotate(${ff((v * 180) / Math.PI)})`); + delete attribs.rotate; + } + if ((v = attribs.scale)) { + tx.push( + isString(v) + ? v + : isArrayLike(v) + ? `scale(${ff(v[0])} ${ff(v[1])})` + : `scale(${ff(v)})` + ); + delete attribs.scale; + } + return tx.join(" "); +}; + /** * Attempts to convert a single color attrib value. * @@ -110,10 +115,4 @@ export const fcolor = (col: any) => ? col[0] === "$" ? `url(#${col.substr(1)})` : col - : isArrayLike(col) - ? isNumber((col).mode) - ? asCSS(col) - : asCSS(col, ColorMode.RGBA) - : isNumber(col) - ? asCSS(col, ColorMode.INT32) - : col; + : resolveAsCSS(col); diff --git a/packages/hiccup-svg/src/points.ts b/packages/hiccup-svg/src/points.ts index 318185a7bc..3adda59c44 100644 --- a/packages/hiccup-svg/src/points.ts +++ b/packages/hiccup-svg/src/points.ts @@ -21,18 +21,8 @@ export const points = ( const group = ["g", fattribs({ ...attribs })]; let href: string; if (!shape || shape[0] !== "#") { - const r = ff(size); href = "_" + ((Math.random() * 1e6) | 0).toString(36); - group.push([ - "g", - { opacity: 0 }, - shape === "circle" - ? ["circle", { id: href, cx: 0, cy: 0, r: r }] - : [ - "rect", - { id: href, x: -r / 2, y: -r / 2, width: r, height: r } - ] - ]); + group.push(["g", { opacity: 0 }, buildShape(shape, href, size)]); href = "#" + href; } else { href = shape; @@ -43,3 +33,12 @@ export const points = ( } return group; }; + +const buildShape = (shape: string, id: string, r: number) => { + const rf = ff(r); + if (shape === "circle") { + return ["circle", { id, cx: 0, cy: 0, r: rf }]; + } + const rf2 = ff(-r / 2); + return ["rect", { id, x: rf2, y: rf2, width: rf, height: rf }]; +}; diff --git a/packages/hiccup-svg/src/polyline.ts b/packages/hiccup-svg/src/polyline.ts index 2dcf187b9b..76b5932519 100644 --- a/packages/hiccup-svg/src/polyline.ts +++ b/packages/hiccup-svg/src/polyline.ts @@ -4,7 +4,8 @@ import { fattribs, fpoints } from "./format"; export const polyline = (pts: Vec2Like[], attribs?: any): any[] => [ "polyline", fattribs({ - ...attribs, - points: fpoints(pts) + fill: "none", + points: fpoints(pts), + ...attribs }) ]; diff --git a/packages/hiccup/CHANGELOG.md b/packages/hiccup/CHANGELOG.md index bb66cda763..7eb41e5ca5 100644 --- a/packages/hiccup/CHANGELOG.md +++ b/packages/hiccup/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@3.2.3...@thi.ng/hiccup@3.2.4) (2019-08-21) + + +### Bug Fixes + +* **hiccup:** update/rename regexes & tag maps ([6dba80d](https://github.com/thi-ng/umbrella/commit/6dba80d)) + + + + + ## [3.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@3.2.2...@thi.ng/hiccup@3.2.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/hiccup diff --git a/packages/hiccup/package.json b/packages/hiccup/package.json index 329c42af89..53d0b5b4e4 100644 --- a/packages/hiccup/package.json +++ b/packages/hiccup/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup", - "version": "3.2.3", + "version": "3.2.4", "description": "HTML/SVG/XML serialization of nested data structures, iterables & closures", "module": "./index.js", "main": "./lib/index.js", @@ -25,7 +25,7 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/atom": "^3.0.3", + "@thi.ng/atom": "^3.0.4", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", @@ -35,7 +35,7 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "clojure", diff --git a/packages/hiccup/src/api.ts b/packages/hiccup/src/api.ts index 0a5d533d30..e4fb67bc3f 100644 --- a/packages/hiccup/src/api.ts +++ b/packages/hiccup/src/api.ts @@ -2,8 +2,6 @@ export const SVG_NS = "http://www.w3.org/2000/svg"; export const XLINK_NS = "http://www.w3.org/1999/xlink"; export const XHTML_NS = "http://www.w3.org/1999/xhtml"; -export const TAG_REGEXP = /^([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?$/; - export const PROC_TAGS: { [id: string]: string } = { "?xml": "?>\n", "!DOCTYPE": ">\n", @@ -12,20 +10,6 @@ export const PROC_TAGS: { [id: string]: string } = { "!ATTLIST": ">\n" }; -// tslint:disable-next-line -export const SVG_TAGS: { - [id: string]: number; -} = "animate animateColor animateMotion animateTransform circle clipPath color-profile defs desc discard ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop style svg switch symbol text textPath title tref tspan use view" - .split(" ") - .reduce((acc: any, x) => ((acc[x] = 1), acc), {}); - -// tslint:disable-next-line -export const VOID_TAGS: { - [id: string]: number; -} = "area base br circle col command ellipse embed hr img input keygen line link meta param path polygon polyline rect source stop track use wbr ?xml" - .split(" ") - .reduce((acc: any, x) => ((acc[x] = 1), acc), {}); - export const ENTITIES: { [id: string]: string } = { "&": "&", "<": "<", @@ -34,6 +18,9 @@ export const ENTITIES: { [id: string]: string } = { "'": "'" }; +export const RE_TAG = /^([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?$/; +export const RE_ENTITY = new RegExp(`[${Object.keys(ENTITIES).join("")}]`, "g"); + export const COMMENT = "__COMMENT__"; export const NO_SPANS: { @@ -45,4 +32,18 @@ export const NO_SPANS: { textarea: 1 }; -export const ENTITY_RE = new RegExp(`[${Object.keys(ENTITIES)}]`, "g"); +const tagMap = ( + tags: string +): { + [id: string]: boolean; +} => tags.split(" ").reduce((acc: any, x) => ((acc[x] = true), acc), {}); + +// tslint:disable-next-line +export const SVG_TAGS = tagMap( + "animate animateColor animateMotion animateTransform circle clipPath color-profile defs desc discard ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop style svg switch symbol text textPath title tref tspan use view" +); + +// tslint:disable-next-line +export const VOID_TAGS = tagMap( + "area base br circle col command ellipse embed hr img input keygen line link meta param path polygon polyline rect source stop track use wbr ?xml" +); diff --git a/packages/hiccup/src/escape.ts b/packages/hiccup/src/escape.ts index 954527dbf2..41a6784e28 100644 --- a/packages/hiccup/src/escape.ts +++ b/packages/hiccup/src/escape.ts @@ -1,3 +1,3 @@ -import { ENTITIES, ENTITY_RE } from "./api"; +import { ENTITIES, RE_ENTITY } from "./api"; -export const escape = (x: string) => x.replace(ENTITY_RE, (y) => ENTITIES[y]); +export const escape = (x: string) => x.replace(RE_ENTITY, (y) => ENTITIES[y]); diff --git a/packages/hiccup/src/index.ts b/packages/hiccup/src/index.ts index 15898b3dd8..834c6705ee 100644 --- a/packages/hiccup/src/index.ts +++ b/packages/hiccup/src/index.ts @@ -2,4 +2,5 @@ export * from "./api"; export * from "./css"; export * from "./deref"; export * from "./escape"; +export * from "./normalize"; export * from "./serialize"; diff --git a/packages/hiccup/src/normalize.ts b/packages/hiccup/src/normalize.ts new file mode 100644 index 0000000000..b0d3d03b17 --- /dev/null +++ b/packages/hiccup/src/normalize.ts @@ -0,0 +1,40 @@ +import { isPlainObject, isString } from "@thi.ng/checks"; +import { illegalArgs } from "@thi.ng/errors"; +import { RE_TAG } from "./api"; +import { css } from "./css"; + +export const normalize = (tag: any[]) => { + let el = tag[0]; + let match: RegExpExecArray | null; + let id: string; + let clazz: string; + const hasAttribs = isPlainObject(tag[1]); + const attribs: any = hasAttribs ? { ...tag[1] } : {}; + if (!isString(el) || !(match = RE_TAG.exec(el))) { + illegalArgs(`"${el}" is not a valid tag name`); + } + el = match![1]; + id = match![2]; + clazz = match![3]; + if (id) { + attribs.id = id; + } + if (clazz) { + clazz = clazz.replace(/\./g, " "); + if (attribs.class) { + attribs.class += " " + clazz; + } else { + attribs.class = clazz; + } + } + if (tag.length > 1) { + if (isPlainObject(attribs.style)) { + attribs.style = css(attribs.style); + } + tag = tag.slice(hasAttribs ? 2 : 1).filter((x) => x != null); + if (tag.length > 0) { + return [el, attribs, tag]; + } + } + return [el, attribs]; +}; diff --git a/packages/hiccup/src/serialize.ts b/packages/hiccup/src/serialize.ts index bd5af353df..a2ce486203 100644 --- a/packages/hiccup/src/serialize.ts +++ b/packages/hiccup/src/serialize.ts @@ -2,7 +2,6 @@ import { implementsFunction, isFunction, isNotStringAndIterable, - isPlainObject, isString } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; @@ -10,11 +9,10 @@ import { COMMENT, NO_SPANS, PROC_TAGS, - TAG_REGEXP, VOID_TAGS } from "./api"; -import { css } from "./css"; import { escape } from "./escape"; +import { normalize } from "./normalize"; /** * Recursively normalizes and serializes given tree as HTML/SVG/XML @@ -164,94 +162,7 @@ const _serialize = ( return ""; } if (Array.isArray(tree)) { - if (!tree.length) { - return ""; - } - let tag = tree[0]; - if (isFunction(tag)) { - return _serialize( - tag.apply(null, [ctx, ...tree.slice(1)]), - ctx, - esc, - span, - keys, - path - ); - } - if (implementsFunction(tag, "render")) { - return _serialize( - tag.render.apply(null, [ctx, ...tree.slice(1)]), - ctx, - esc, - span, - keys, - path - ); - } - if (isString(tag)) { - if (tag === COMMENT) { - return tree.length > 2 - ? `\n\n` - : `\n\n`; - } - tree = normalize(tree); - tag = tree[0]; - const attribs = tree[1]; - if (attribs.__skip || attribs.__serialize === false) { - return ""; - } - let body = tree[2]; - let res = `<${tag}`; - if (keys && attribs.key === undefined) { - attribs.key = path.join("-"); - } - for (let a in attribs) { - if (a.startsWith("__")) continue; - if (attribs.hasOwnProperty(a)) { - let v = attribs[a]; - if (v != null) { - if (isFunction(v)) { - if (/^on\w+/.test(a) || (v = v(attribs)) == null) { - continue; - } - } - if (v === true) { - res += " " + a; - } else if (v !== false) { - v = v.toString(); - if (v.length) { - res += ` ${a}="${esc ? escape(v) : v}"`; - } - } - } - } - } - if (body) { - if (VOID_TAGS[tag]) { - illegalArgs(`No body allowed in tag: ${tag}`); - } - const proc = PROC_TAGS[tag]; - res += proc ? " " : ">"; - span = span && !proc && !NO_SPANS[tag]; - for (let i = 0, n = body.length; i < n; i++) { - res += _serialize(body[i], ctx, esc, span, keys, [ - ...path, - i - ]); - } - return (res += proc || ``); - } else if (!VOID_TAGS[tag]) { - return (res += `>`); - } - return (res += PROC_TAGS[tag] || "/>"); - } - if (isNotStringAndIterable(tree)) { - return _serializeIter(tree, ctx, esc, span, keys, path); - } - illegalArgs(`invalid tree node: ${tree}`); + return serializeElement(tree, ctx, esc, span, keys, path); } if (isFunction(tree)) { return _serialize(tree(ctx), ctx, esc, span, keys, path); @@ -263,7 +174,7 @@ const _serialize = ( return _serialize(tree.deref(), ctx, esc, span, keys, path); } if (isNotStringAndIterable(tree)) { - return _serializeIter(tree, ctx, esc, span, keys, path); + return serializeIter(tree, ctx, esc, span, keys, path); } tree = esc ? escape(tree.toString()) : tree; return span @@ -271,7 +182,113 @@ const _serialize = ( : tree; }; -const _serializeIter = ( +const serializeElement = ( + tree: any[], + ctx: any, + esc: boolean, + span: boolean, + keys: boolean, + path: any[] +) => { + if (!tree.length) { + return ""; + } + let tag = tree[0]; + if (isFunction(tag)) { + return _serialize( + tag.apply(null, [ctx, ...tree.slice(1)]), + ctx, + esc, + span, + keys, + path + ); + } + if (implementsFunction(tag, "render")) { + return _serialize( + tag.render.apply(null, [ctx, ...tree.slice(1)]), + ctx, + esc, + span, + keys, + path + ); + } + if (tag === COMMENT) { + return serializeComment(tree); + } + if (isString(tag)) { + tree = normalize(tree); + tag = tree[0]; + const attribs = tree[1]; + if (attribs.__skip || attribs.__serialize === false) { + return ""; + } + let body = tree[2]; + let res = `<${tag}`; + keys && attribs.key === undefined && (attribs.key = path.join("-")); + res += serializeAttribs(attribs, esc); + res += body + ? serializeBody(tag, body, ctx, esc, span, keys, path) + : !VOID_TAGS[tag] + ? `>` + : PROC_TAGS[tag] || "/>"; + return res; + } + if (isNotStringAndIterable(tree)) { + return serializeIter(tree, ctx, esc, span, keys, path); + } + return illegalArgs(`invalid tree node: ${tree}`); +}; + +const serializeAttribs = (attribs: any, esc: boolean) => { + let res = ""; + for (let a in attribs) { + if (a.startsWith("__")) continue; + let v = attribs[a]; + if (v == null) continue; + if (isFunction(v) && (/^on\w+/.test(a) || (v = v(attribs)) == null)) + continue; + if (v === true) { + res += " " + a; + } else if (v !== false) { + v = v.toString(); + v.length && (res += ` ${a}="${esc ? escape(v) : v}"`); + } + } + return res; +}; + +const serializeBody = ( + tag: string, + body: any[], + ctx: any, + esc: boolean, + span: boolean, + keys: boolean, + path: any[] +) => { + if (VOID_TAGS[tag]) { + illegalArgs(`No body allowed in tag: ${tag}`); + } + const proc = PROC_TAGS[tag]; + let res = proc ? " " : ">"; + span = span && !proc && !NO_SPANS[tag]; + for (let i = 0, n = body.length; i < n; i++) { + res += _serialize(body[i], ctx, esc, span, keys, [...path, i]); + } + return res + (proc || ``); +}; + +const serializeComment = (tree: any[]) => + tree.length > 2 + ? `\n\n` + : `\n\n`; + +const serializeIter = ( iter: Iterable, ctx: any, esc: boolean, @@ -287,39 +304,3 @@ const _serializeIter = ( } return res.join(""); }; - -export const normalize = (tag: any[]) => { - let el = tag[0]; - let match: RegExpExecArray | null; - let id: string; - let clazz: string; - const hasAttribs = isPlainObject(tag[1]); - const attribs: any = hasAttribs ? { ...tag[1] } : {}; - if (!isString(el) || !(match = TAG_REGEXP.exec(el))) { - illegalArgs(`"${el}" is not a valid tag name`); - } - el = match![1]; - id = match![2]; - clazz = match![3]; - if (id) { - attribs.id = id; - } - if (clazz) { - clazz = clazz.replace(/\./g, " "); - if (attribs.class) { - attribs.class += " " + clazz; - } else { - attribs.class = clazz; - } - } - if (tag.length > 1) { - if (isPlainObject(attribs.style)) { - attribs.style = css(attribs.style); - } - tag = tag.slice(hasAttribs ? 2 : 1).filter((x) => x != null); - if (tag.length > 0) { - return [el, attribs, tag]; - } - } - return [el, attribs]; -}; diff --git a/packages/iges/CHANGELOG.md b/packages/iges/CHANGELOG.md index 956358319a..9f522e9db1 100644 --- a/packages/iges/CHANGELOG.md +++ b/packages/iges/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/iges@1.1.8...@thi.ng/iges@1.1.9) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/iges + + + + + ## [1.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/iges@1.1.7...@thi.ng/iges@1.1.8) (2019-08-16) **Note:** Version bump only for package @thi.ng/iges diff --git a/packages/iges/package.json b/packages/iges/package.json index d9f0b063ca..c66aab57de 100644 --- a/packages/iges/package.json +++ b/packages/iges/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/iges", - "version": "1.1.8", + "version": "1.1.9", "description": "IGES 5.3 serializer for (currently only) polygonal geometry, both open & closed", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/strings": "^1.2.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/defmulti": "^1.1.3", + "@thi.ng/strings": "^1.2.3", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "CAD", diff --git a/packages/imgui/CHANGELOG.md b/packages/imgui/CHANGELOG.md index d7536b0126..1f54ebb010 100644 --- a/packages/imgui/CHANGELOG.md +++ b/packages/imgui/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/imgui@0.1.1...@thi.ng/imgui@0.1.2) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/imgui + + + + + +## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/imgui@0.1.0...@thi.ng/imgui@0.1.1) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/imgui + + + + + # 0.1.0 (2019-08-16) diff --git a/packages/imgui/README.md b/packages/imgui/README.md index 42f65d905a..d37e23551a 100644 --- a/packages/imgui/README.md +++ b/packages/imgui/README.md @@ -27,7 +27,7 @@ This project is part of the ## About -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/feature/imgui/assets/screenshots/imgui-all.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/imgui-all.png) Currently still somewhat bare-bones, but already usable & customizable [immediate mode GUI](https://github.com/ocornut/imgui#references) implementation, @@ -136,7 +136,7 @@ The `GridLayout` class supports infinite nesting and column/row-based space allocation, based on an initial configuration and supporting multiple column/row spans. -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/feature/imgui/assets/imgui-layout.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/imgui-layout.png) The code producing this structure: diff --git a/packages/imgui/package.json b/packages/imgui/package.json index 4cce848378..0f6ff4f0ec 100644 --- a/packages/imgui/package.json +++ b/packages/imgui/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/imgui", - "version": "0.1.0", + "version": "0.1.2", "description": "Immediate mode GUI with flexible state handling & data only shape output", "module": "./index.js", "main": "./lib/index.js", @@ -33,15 +33,15 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/geom": "^1.7.3", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/geom-isec": "^0.3.5", - "@thi.ng/geom-tessellate": "^0.2.5", + "@thi.ng/geom": "^1.7.5", + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/geom-isec": "^0.3.7", + "@thi.ng/geom-tessellate": "^0.2.7", "@thi.ng/math": "^1.4.2", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "canvas", diff --git a/packages/interceptors/CHANGELOG.md b/packages/interceptors/CHANGELOG.md index dc6ac018dc..3755b2c77c 100644 --- a/packages/interceptors/CHANGELOG.md +++ b/packages/interceptors/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@2.1.3...@thi.ng/interceptors@2.2.0) (2019-08-21) + + +### Features + +* **interceptors:** add module logger, setLogger() ([17f050d](https://github.com/thi-ng/umbrella/commit/17f050d)) + + + + + ## [2.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@2.1.2...@thi.ng/interceptors@2.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/interceptors diff --git a/packages/interceptors/package.json b/packages/interceptors/package.json index 0eb1deda3e..069dc0402d 100644 --- a/packages/interceptors/package.json +++ b/packages/interceptors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/interceptors", - "version": "2.1.3", + "version": "2.2.0", "description": "Interceptor based event bus, side effect & immutable state handling", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/atom": "^3.0.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/atom": "^3.0.4", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/paths": "^2.1.4" }, "keywords": [ "ES6", diff --git a/packages/interceptors/src/api.ts b/packages/interceptors/src/api.ts index f133e69bdd..7fa274869b 100644 --- a/packages/interceptors/src/api.ts +++ b/packages/interceptors/src/api.ts @@ -1,3 +1,4 @@ +import { ILogger, NULL_LOGGER } from "@thi.ng/api"; import { ReadonlyAtom } from "@thi.ng/atom"; export type InterceptorFn = ( @@ -81,3 +82,7 @@ export interface InterceptorContext { [FX_DISPATCH_ASYNC]?: AsyncEffectDef | AsyncEffectDef[]; [id: string]: any; } + +export let LOGGER = NULL_LOGGER; + +export const setLogger = (logger: ILogger) => (LOGGER = logger); diff --git a/packages/interceptors/src/event-bus.ts b/packages/interceptors/src/event-bus.ts index 1dcc6bf35d..32c644cd8b 100644 --- a/packages/interceptors/src/event-bus.ts +++ b/packages/interceptors/src/event-bus.ts @@ -29,6 +29,7 @@ import { Interceptor, InterceptorContext, InterceptorFn, + LOGGER, SideEffect } from "./api"; @@ -195,10 +196,10 @@ export class StatelessEventBus implements IDispatch { this.dispatch([success, res]) ).catch((e) => this.dispatch([err, e])); } else { - console.warn("async effect did not return Promise"); + LOGGER.warn("async effect did not return Promise"); } } else { - console.warn(`skipping invalid async effect: ${id}`); + LOGGER.warn(`skipping invalid async effect: ${id}`); } }, -999 @@ -224,15 +225,11 @@ export class StatelessEventBus implements IDispatch { } addHandler(id: string, spec: EventDef) { - const iceps = isArray(spec) - ? (spec).map(asInterceptor) - : isFunction(spec) - ? [{ pre: spec }] - : [spec]; + const iceps = this.interceptorsFromSpec(spec); if (iceps.length > 0) { if (this.handlers[id]) { this.removeHandler(id); - console.warn(`overriding handler for ID: ${id}`); + LOGGER.warn(`overriding handler for ID: ${id}`); } this.handlers[id] = iceps; } else { @@ -249,7 +246,7 @@ export class StatelessEventBus implements IDispatch { addEffect(id: string, fx: SideEffect, priority = 1) { if (this.effects[id]) { this.removeEffect(id); - console.warn(`overriding effect for ID: ${id}`); + LOGGER.warn(`overriding effect for ID: ${id}`); } this.effects[id] = fx; const p: EffectPriority = [id, priority]; @@ -425,22 +422,37 @@ export class StatelessEventBus implements IDispatch { protected processEvent(ctx: InterceptorContext, e: Event) { const iceps = this.handlers[e[0]]; if (!iceps) { - console.warn(`missing handler for event type: ${e[0].toString()}`); + LOGGER.warn(`missing handler for event type: ${e[0].toString()}`); return; } - const n = iceps.length - 1; + if (!this.processForward(ctx, iceps, e)) { + return; + } + this.processReverse(ctx, iceps, e); + } + + protected processForward( + ctx: InterceptorContext, + iceps: Interceptor[], + e: Event + ) { let hasPost = false; - for (let i = 0; i <= n && !ctx[FX_CANCEL]; i++) { + for (let i = 0, n = iceps.length; i < n && !ctx[FX_CANCEL]; i++) { const icep = iceps[i]; if (icep.pre) { this.mergeEffects(ctx, icep.pre(ctx[FX_STATE], e, this, ctx)); } hasPost = hasPost || !!icep.post; } - if (!hasPost) { - return; - } - for (let i = n; i >= 0 && !ctx[FX_CANCEL]; i--) { + return hasPost; + } + + protected processReverse( + ctx: InterceptorContext, + iceps: Interceptor[], + e: Event + ) { + for (let i = iceps.length; --i >= 0 && !ctx[FX_CANCEL]; ) { const icep = iceps[i]; if (icep.post) { this.mergeEffects(ctx, icep.post(ctx[FX_STATE], e, this, ctx)); @@ -459,16 +471,23 @@ export class StatelessEventBus implements IDispatch { for (let p of this.priorities) { const id = p[0]; const val = ctx[id]; - if (val !== undefined) { - const fn = effects[id]; - if (id !== FX_STATE) { - for (let v of val) { - fn(v, this, ctx); - } - } else { - fn(val, this, ctx); - } + val !== undefined && this.processEffect(ctx, effects, id, val); + } + } + + protected processEffect( + ctx: InterceptorContext, + effects: IObjectOf, + id: string, + val: any + ) { + const fn = effects[id]; + if (id !== FX_STATE) { + for (let v of val) { + fn(v, this, ctx); } + } else { + fn(val, this, ctx); } } @@ -543,6 +562,14 @@ export class StatelessEventBus implements IDispatch { } } } + + protected interceptorsFromSpec(spec: EventDef) { + return isArray(spec) + ? (spec).map(asInterceptor) + : isFunction(spec) + ? [{ pre: spec }] + : [spec]; + } } /** @@ -726,7 +753,7 @@ const undoHandler = (action: string): InterceptorFn => ( bus, ctx ) => { - let id = ev ? ev[0] : "history"; + const id = ev ? ev[0] : "history"; if (implementsFunction(ctx[id], action)) { const ok = ctx[id][action](); return { @@ -738,6 +765,6 @@ const undoHandler = (action: string): InterceptorFn => ( : undefined }; } else { - console.warn("no history in context"); + LOGGER.warn("no history in context"); } }; diff --git a/packages/interceptors/src/interceptors.ts b/packages/interceptors/src/interceptors.ts index 098c31a47c..a2d56ef3ca 100644 --- a/packages/interceptors/src/interceptors.ts +++ b/packages/interceptors/src/interceptors.ts @@ -135,6 +135,12 @@ export const ensurePred = ( } : undefined; +const eventPathState = ( + state: any, + path: Fn | undefined, + e: Event +) => getIn(state, path ? path(e) : e[1]); + /** * Specialization of `ensurePred()` to ensure a state value is less than * given max at the time when the event is being processed. The optional @@ -158,7 +164,7 @@ export const ensureStateLessThan = ( max: number, path?: Fn, err?: InterceptorFn -) => ensurePred((state, e) => getIn(state, path ? path(e) : e[1]) < max, err); +) => ensurePred((state, e) => eventPathState(state, path, e) < max, err); /** * Specialization of `ensurePred()` to ensure a state value is greater @@ -172,7 +178,7 @@ export const ensureStateGreaterThan = ( min: number, path?: Fn, err?: InterceptorFn -) => ensurePred((state, e) => getIn(state, path ? path(e) : e[1]) > min, err); +) => ensurePred((state, e) => eventPathState(state, path, e) > min, err); /** * Specialization of `ensurePred()` to ensure a state value is within @@ -191,7 +197,7 @@ export const ensureStateRange = ( err?: InterceptorFn ) => ensurePred((state, e) => { - const x = getIn(state, path ? path(e) : e[1]); + const x = eventPathState(state, path, e); return x >= min && x <= max; }, err); diff --git a/packages/intervals/CHANGELOG.md b/packages/intervals/CHANGELOG.md index 3ae4f891bc..f588cce06c 100644 --- a/packages/intervals/CHANGELOG.md +++ b/packages/intervals/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/intervals@1.0.12...@thi.ng/intervals@1.0.13) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/intervals + + + + + ## [1.0.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/intervals@1.0.11...@thi.ng/intervals@1.0.12) (2019-07-31) **Note:** Version bump only for package @thi.ng/intervals diff --git a/packages/intervals/package.json b/packages/intervals/package.json index d77d871d54..6b6c387c02 100644 --- a/packages/intervals/package.json +++ b/packages/intervals/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/intervals", - "version": "1.0.12", + "version": "1.0.13", "description": "Closed/open/semi-open interval data type, queries & operations", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.3.3", + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "ES6", diff --git a/packages/intervals/src/index.ts b/packages/intervals/src/index.ts index 25632f54c8..d95b929ac6 100644 --- a/packages/intervals/src/index.ts +++ b/packages/intervals/src/index.ts @@ -1,4 +1,10 @@ -import { Fn, ICompare, IContains, ICopy, IEquiv } from "@thi.ng/api"; +import { + Fn, + ICompare, + IContains, + ICopy, + IEquiv +} from "@thi.ng/api"; import { illegalArgs } from "@thi.ng/errors"; export const enum Classifier { @@ -30,8 +36,8 @@ export class Interval * * Openness / closedness symbols: * - * - LHS: `]` or `(` (open), `[` (closed) - * - RHS: `[` or `)` (open), `]` (closed) + * - LHS: open: `]` / `(`, closed: `[` + * - RHS: open: `[` / `)`, closed: `]` * * ``` * // semi-open interval between -∞ and +1 @@ -70,8 +76,8 @@ export class Interval (x === "" && i === 0) || (inf && inf[1] === "-") ? -Infinity : (x === "" && i > 0) || (inf && inf[1] !== "-") - ? Infinity - : parseFloat(x); + ? Infinity + : parseFloat(x); if (isNaN(n)) { illegalArgs(`term: '${x}'`); } @@ -147,12 +153,12 @@ export class Interval return this.l < i.l ? -1 : this.l > i.l - ? 1 - : this.r < i.r - ? -1 - : this.r > i.r - ? 1 - : 0; + ? 1 + : this.r < i.r + ? -1 + : this.r > i.r + ? 1 + : 0; } equiv(i: any) { @@ -211,8 +217,8 @@ export class Interval return this.isBefore(x) ? new Interval(x, this.r, false, this.ropen) : this.isAfter(x) - ? new Interval(this.l, x, this.lopen, false) - : this; + ? new Interval(this.l, x, this.lopen, false) + : this; } /** @@ -225,8 +231,8 @@ export class Interval return this.overlaps(i) ? 0 : this.l < i.l - ? i.l - this.r - : this.l - i.r; + ? i.l - this.r + : this.l - i.r; } /** @@ -240,14 +246,14 @@ export class Interval return this.isBefore(i.r) ? Classifier.DISJOINT_RIGHT : this.isAfter(i.l) - ? Classifier.DISJOINT_LEFT - : this.contains(i.l) - ? this.contains(i.r) - ? Classifier.SUPERSET - : Classifier.OVERLAP_RIGHT - : this.contains(i.r) - ? Classifier.OVERLAP_LEFT - : Classifier.SUBSET; + ? Classifier.DISJOINT_LEFT + : this.contains(i.l) + ? this.contains(i.r) + ? Classifier.SUPERSET + : Classifier.OVERLAP_RIGHT + : this.contains(i.r) + ? Classifier.OVERLAP_LEFT + : Classifier.SUBSET; } overlaps(i: Readonly) { @@ -306,7 +312,7 @@ export class Interval ao: boolean, bo: boolean ): [number, boolean] { - return a < b ? [a, ao] : a === b ? [a, ao || bo] : [b, bo]; + return minmax(a < b, a, b, ao, bo); } protected $max( @@ -315,6 +321,14 @@ export class Interval ao: boolean, bo: boolean ): [number, boolean] { - return a > b ? [a, ao] : a === b ? [a, ao || bo] : [b, bo]; + return minmax(a > b, a, b, ao, bo); } } + +const minmax = ( + test: boolean, + a: number, + b: number, + ao: boolean, + bo: boolean +): [number, boolean] => (test ? [a, ao] : a === b ? [a, ao || bo] : [b, bo]); diff --git a/packages/iterators/CHANGELOG.md b/packages/iterators/CHANGELOG.md index 7a4cad8f19..23ae4928ab 100644 --- a/packages/iterators/CHANGELOG.md +++ b/packages/iterators/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@5.1.3...@thi.ng/iterators@5.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/iterators + + + + + ## [5.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@5.1.2...@thi.ng/iterators@5.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/iterators diff --git a/packages/iterators/package.json b/packages/iterators/package.json index eaee8ca9e5..2907adcd99 100644 --- a/packages/iterators/package.json +++ b/packages/iterators/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/iterators", - "version": "5.1.3", + "version": "5.1.4", "description": "clojure.core inspired, composable ES6 iterators & generators", "module": "./index.js", "main": "./lib/index.js", @@ -33,9 +33,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/dcons": "^2.1.3", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.3.3", + "@thi.ng/dcons": "^2.1.4", + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "clojure", diff --git a/packages/leb128/CHANGELOG.md b/packages/leb128/CHANGELOG.md index 2dc0fbe60b..81d16c708a 100644 --- a/packages/leb128/CHANGELOG.md +++ b/packages/leb128/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/leb128@0.1.3...@thi.ng/leb128@0.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/leb128 + + + + + ## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/leb128@0.1.2...@thi.ng/leb128@0.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/leb128 diff --git a/packages/leb128/package.json b/packages/leb128/package.json index 374b7d6d17..08697e9396 100644 --- a/packages/leb128/package.json +++ b/packages/leb128/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/leb128", - "version": "0.1.3", + "version": "0.1.4", "description": "WASM based LEB128 encoder / decoder (signed & unsigned)", "module": "./index.js", "main": "./lib/index.js", @@ -34,8 +34,8 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers-binary": "^0.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers-binary": "^0.4.4" }, "keywords": [ "LEB128", diff --git a/packages/lsys/CHANGELOG.md b/packages/lsys/CHANGELOG.md index 6c8a677cae..5ec3d47f21 100644 --- a/packages/lsys/CHANGELOG.md +++ b/packages/lsys/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.21...@thi.ng/lsys@0.2.22) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/lsys + + + + + +## [0.2.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.20...@thi.ng/lsys@0.2.21) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/lsys + + + + + ## [0.2.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.19...@thi.ng/lsys@0.2.20) (2019-08-16) **Note:** Version bump only for package @thi.ng/lsys diff --git a/packages/lsys/package.json b/packages/lsys/package.json index 9ce796b0ce..250e76de90 100644 --- a/packages/lsys/package.json +++ b/packages/lsys/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/lsys", - "version": "0.2.20", + "version": "0.2.22", "description": "Functional, extensible L-System architecture w/ support for probabilistic rules", "module": "./index.js", "main": "./lib/index.js", @@ -33,13 +33,13 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/compose": "^1.3.2", - "@thi.ng/errors": "^1.1.2", + "@thi.ng/api": "^6.3.3", + "@thi.ng/compose": "^1.3.3", + "@thi.ng/errors": "^1.2.0", "@thi.ng/math": "^1.4.2", - "@thi.ng/random": "^1.1.10", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/random": "^1.1.11", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "axiom", diff --git a/packages/malloc/CHANGELOG.md b/packages/malloc/CHANGELOG.md index c15cecfafb..f5cff736ca 100644 --- a/packages/malloc/CHANGELOG.md +++ b/packages/malloc/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [4.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/malloc@4.0.3...@thi.ng/malloc@4.0.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/malloc + + + + + ## [4.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/malloc@4.0.2...@thi.ng/malloc@4.0.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/malloc diff --git a/packages/malloc/package.json b/packages/malloc/package.json index 20a1bf21d7..2cb0ccff80 100644 --- a/packages/malloc/package.json +++ b/packages/malloc/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/malloc", - "version": "4.0.3", + "version": "4.0.4", "description": "ArrayBuffer based malloc() impl for hybrid JS/WASM use cases, based on thi.ng/tinyalloc", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/binary": "^1.1.0", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "ES6", diff --git a/packages/matrices/CHANGELOG.md b/packages/matrices/CHANGELOG.md index 238fba94d8..1d515989f5 100644 --- a/packages/matrices/CHANGELOG.md +++ b/packages/matrices/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.5.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.6...@thi.ng/matrices@0.5.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/matrices + + + + + +## [0.5.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.5...@thi.ng/matrices@0.5.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/matrices + + + + + ## [0.5.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.4...@thi.ng/matrices@0.5.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/matrices diff --git a/packages/matrices/package.json b/packages/matrices/package.json index 4446a22307..6271001252 100644 --- a/packages/matrices/package.json +++ b/packages/matrices/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/matrices", - "version": "0.5.5", + "version": "0.5.7", "description": "Matrix & quaternion operations for 2D/3D geometry processing", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2D", diff --git a/packages/memoize/CHANGELOG.md b/packages/memoize/CHANGELOG.md index 1dbf8e92a6..59b7f19a06 100644 --- a/packages/memoize/CHANGELOG.md +++ b/packages/memoize/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/memoize@1.1.2...@thi.ng/memoize@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/memoize + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/memoize@1.1.1...@thi.ng/memoize@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/memoize diff --git a/packages/memoize/package.json b/packages/memoize/package.json index aaf7f77fe4..74092a09ad 100644 --- a/packages/memoize/package.json +++ b/packages/memoize/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/memoize", - "version": "1.1.2", + "version": "1.1.3", "description": "Function memoization with configurable caches", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2" + "@thi.ng/api": "^6.3.3" }, "keywords": [ "cache", diff --git a/packages/morton/CHANGELOG.md b/packages/morton/CHANGELOG.md index c0ccaa0480..28d3c47c0d 100644 --- a/packages/morton/CHANGELOG.md +++ b/packages/morton/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/morton@1.1.2...@thi.ng/morton@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/morton + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/morton@1.1.1...@thi.ng/morton@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/morton diff --git a/packages/morton/package.json b/packages/morton/package.json index 660298826f..5facffee6e 100644 --- a/packages/morton/package.json +++ b/packages/morton/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/morton", - "version": "1.1.2", + "version": "1.1.3", "description": "Z-order-curve / Morton encoding & decoding for 1D, 2D, 3D", "module": "./index.js", "main": "./lib/index.js", @@ -34,7 +34,7 @@ }, "dependencies": { "@thi.ng/binary": "^1.1.0", - "@thi.ng/errors": "^1.1.2", + "@thi.ng/errors": "^1.2.0", "@thi.ng/math": "^1.4.2" }, "keywords": [ diff --git a/packages/paths/CHANGELOG.md b/packages/paths/CHANGELOG.md index fe5410bf22..102296f610 100644 --- a/packages/paths/CHANGELOG.md +++ b/packages/paths/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@2.1.3...@thi.ng/paths@2.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/paths + + + + + ## [2.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@2.1.2...@thi.ng/paths@2.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/paths diff --git a/packages/paths/package.json b/packages/paths/package.json index 2edb4bbdd2..00c7f1856a 100644 --- a/packages/paths/package.json +++ b/packages/paths/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/paths", - "version": "2.1.3", + "version": "2.1.4", "description": "immutable, optimized path-based object property / array accessors", "module": "./index.js", "main": "./lib/index.js", @@ -34,7 +34,7 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "accessors", diff --git a/packages/pixel/CHANGELOG.md b/packages/pixel/CHANGELOG.md index 13c892ed36..560aba8c43 100644 --- a/packages/pixel/CHANGELOG.md +++ b/packages/pixel/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.1.2...@thi.ng/pixel@0.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/pixel + + + + + ## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.1.1...@thi.ng/pixel@0.1.2) (2019-08-16) **Note:** Version bump only for package @thi.ng/pixel diff --git a/packages/pixel/package.json b/packages/pixel/package.json index d01ed6f011..99e14280e9 100644 --- a/packages/pixel/package.json +++ b/packages/pixel/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pixel", - "version": "0.1.2", + "version": "0.1.3", "description": "TODO", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", "@thi.ng/math": "^1.4.2", - "@thi.ng/porter-duff": "^0.1.1" + "@thi.ng/porter-duff": "^0.1.2" }, "keywords": [ "ES6", diff --git a/packages/pixel/src/packed.ts b/packages/pixel/src/packed.ts index 56172ba0ce..e921312b4a 100644 --- a/packages/pixel/src/packed.ts +++ b/packages/pixel/src/packed.ts @@ -20,7 +20,11 @@ import { clampRegion, ensureChannel, ensureSize, - prepRegions + prepRegions, + setChannelConvert, + setChannelSame, + setChannelUni, + transformABGR } from "./utils"; interface UIntArrayConstructor { @@ -235,26 +239,21 @@ export class PackedBuffer { const dbuf = this.pixels; const set = chan.setInt; if (isNumber(src)) { - for (let i = dbuf.length; --i >= 0; ) { - dbuf[i] = set(dbuf[i], src); - } + setChannelUni(dbuf, src, set); } else { const sbuf = src.pixels; const schan = src.format.channels[0]; ensureSize(sbuf, this.width, this.height); if (chan.size === schan.size) { - const get = schan.int; - for (let i = dbuf.length; --i >= 0; ) { - dbuf[i] = set(dbuf[i], get(sbuf[i])); - } + setChannelSame(dbuf, sbuf, schan.int, set); } else { - const sto = src.format.toABGR; - const from = this.format.fromABGR; - const mask = chan.maskA; - const invMask = ~mask; - for (let i = dbuf.length; --i >= 0; ) { - dbuf[i] = (dbuf[i] & invMask) | (from(sto(sbuf[i])) & mask); - } + setChannelConvert( + dbuf, + sbuf, + this.format.fromABGR, + src.format.toABGR, + chan.maskA + ); } } return this; @@ -271,22 +270,12 @@ export class PackedBuffer { } premultiply() { - const pix = this.pixels; - const from = this.format.fromABGR; - const to = this.format.toABGR; - for (let i = pix.length; --i >= 0; ) { - pix[i] = from(premultiplyInt(to(pix[i]))); - } + transformABGR(this.pixels, this.format, premultiplyInt); return this; } postmultiply() { - const pix = this.pixels; - const from = this.format.fromABGR; - const to = this.format.toABGR; - for (let i = pix.length; --i >= 0; ) { - pix[i] = from(postmultiplyInt(to(pix[i]))); - } + transformABGR(this.pixels, this.format, postmultiplyInt); return this; } diff --git a/packages/pixel/src/utils.ts b/packages/pixel/src/utils.ts index 6807fb801c..c986460e40 100644 --- a/packages/pixel/src/utils.ts +++ b/packages/pixel/src/utils.ts @@ -1,4 +1,10 @@ -import { assert, TypedArray } from "@thi.ng/api"; +import { + assert, + Fn, + Fn2, + TypedArray, + UIntArray +} from "@thi.ng/api"; import { clamp } from "@thi.ng/math"; import { BlitOpts, PackedFormat } from "./api"; @@ -64,3 +70,49 @@ export const prepRegions = ( ); return { sx, sy, dx, dy, rw, rh }; }; + +export const setChannelUni = ( + dbuf: UIntArray, + src: number, + set: Fn2 +) => { + for (let i = dbuf.length; --i >= 0; ) { + dbuf[i] = set(dbuf[i], src); + } +}; + +export const setChannelSame = ( + dbuf: UIntArray, + sbuf: UIntArray, + get: Fn, + set: Fn2 +) => { + for (let i = dbuf.length; --i >= 0; ) { + dbuf[i] = set(dbuf[i], get(sbuf[i])); + } +}; + +export const setChannelConvert = ( + dbuf: UIntArray, + sbuf: UIntArray, + from: Fn, + sto: Fn, + mask: number +) => { + const invMask = ~mask; + for (let i = dbuf.length; --i >= 0; ) { + dbuf[i] = (dbuf[i] & invMask) | (from(sto(sbuf[i])) & mask); + } +}; + +export const transformABGR = ( + pix: UIntArray, + format: PackedFormat, + fn: Fn +) => { + const from = format.fromABGR; + const to = format.toABGR; + for (let i = pix.length; --i >= 0; ) { + pix[i] = from(fn(to(pix[i]))); + } +}; diff --git a/packages/pointfree-lang/CHANGELOG.md b/packages/pointfree-lang/CHANGELOG.md index f398f90ccc..764c0d32e5 100644 --- a/packages/pointfree-lang/CHANGELOG.md +++ b/packages/pointfree-lang/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@1.1.3...@thi.ng/pointfree-lang@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/pointfree-lang + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@1.1.2...@thi.ng/pointfree-lang@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/pointfree-lang diff --git a/packages/pointfree-lang/package.json b/packages/pointfree-lang/package.json index c4d39f8292..1cb80b0fc0 100644 --- a/packages/pointfree-lang/package.json +++ b/packages/pointfree-lang/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pointfree-lang", - "version": "1.1.3", + "version": "1.1.4", "description": "Forth style syntax layer/compiler for the @thi.ng/pointfree DSL", "module": "./index.js", "main": "./lib/index.js", @@ -35,9 +35,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/pointfree": "^1.1.3" + "@thi.ng/api": "^6.3.3", + "@thi.ng/errors": "^1.2.0", + "@thi.ng/pointfree": "^1.2.0" }, "keywords": [ "concatenative", diff --git a/packages/pointfree/CHANGELOG.md b/packages/pointfree/CHANGELOG.md index cb21718110..3fa997677e 100644 --- a/packages/pointfree/CHANGELOG.md +++ b/packages/pointfree/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@1.1.3...@thi.ng/pointfree@1.2.0) (2019-08-21) + + +### Features + +* **pointfree:** add new r-stack words, refactor ([dbad162](https://github.com/thi-ng/umbrella/commit/dbad162)) + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@1.1.2...@thi.ng/pointfree@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/pointfree diff --git a/packages/pointfree/README.md b/packages/pointfree/README.md index 78fdacc6cd..f39306451b 100644 --- a/packages/pointfree/README.md +++ b/packages/pointfree/README.md @@ -57,7 +57,7 @@ Current features: - words implemented as tiny vanilla JS functions (easily extensible) - optimized pre-composition/compilation of custom user defined words (see - [comp.ts](https://github.com/thi-ng/umbrella/tree/master/packages/pointfree/src/comp.ts)) + [word.ts](https://github.com/thi-ng/umbrella/tree/master/packages/pointfree/src/word.ts)) - dual stack (main & stash/scratch space) - nested execution environments (scopes) - arbitrary stack values @@ -204,7 +204,7 @@ The latter are replaced by calls to `push` which pushes the given value on the stack as is. Therefore, a stack program like: `[1, 2, pf.add]` compiles to: -``` +```ts pf.add(pf.push(2)(pf.push(1)())) ``` @@ -213,7 +213,7 @@ pf.add(pf.push(2)(pf.push(1)())) Most concatenative languages use stack effect comments as the standard approach to document the effect a word has on the stack structure. -``` +```forth ( x y -- x ) ``` @@ -361,7 +361,7 @@ Since quoatations are just arrays, we can treat them as data, i.e. **the functional composition of two quotations is the same as concatenating two arrays**: -``` +```js const add10 = [10, pf.add]; const mul10 = [10, pf.mul]; @@ -786,157 +786,157 @@ at word construction time and return a pre-configured stack function. ### D-Stack modification -| Word | Stack effect | Description | -| --- | --- | --- | -| `drop` | `( x -- )` | remove TOS | -| `drop2` | `( x y -- )` | remove top 2 vals | -| `dropif` | `( x -- ? )` | remove only if TOS truthy | -| `dsp` | `( -- stack.length )` | push d-stack depth | -| `dup` | `( x -- x x )` | duplicate TOS | -| `dup2` | `( x y -- x y x y )` | duplicate top 2 vals | -| `dup3` | `( x y z -- x y z x y z )` | duplicate top 3 vals | -| `dupif` | `( x -- x x? )` | dup only if TOS truthy | -| `maptos(fn)` | `( x -- f(x) )` | transform TOS w/ `f` | -| `map2(fn)` | `( x y -- f(y, x) )` | reduce top 2 vals with `f`, single result | -| `nip` | `( x y -- y )` | remove `x` from stack | -| `over` | `( x y -- x y x )` | push dup of `x` | -| `pick` | `( n -- stack[n] )` | dup deeper stack value | -| `push(...args)` | `( -- ...args )` | push `args` on stack | -| `rot` | `( x y z -- y z x )` | rotate top 3 vals down/left | -| `invrot` | `( x y z -- z x y )` | rotate top 3 vals up/right | -| `swap` | `( x y -- y x )` | swap top 2 vals | -| `swap2` | `( a b c d -- c d a b )` | swap top 2 pairs | -| `tuck` | `( x y -- y x y )` | insert dup of TOS | +| Word | Stack effect | Description | +|-----------------|----------------------------|-------------------------------------------| +| `drop` | `( x -- )` | remove TOS | +| `drop2` | `( x y -- )` | remove top 2 vals | +| `dropif` | `( x -- ? )` | remove only if TOS truthy | +| `dsp` | `( -- stack.length )` | push d-stack depth | +| `dup` | `( x -- x x )` | duplicate TOS | +| `dup2` | `( x y -- x y x y )` | duplicate top 2 vals | +| `dup3` | `( x y z -- x y z x y z )` | duplicate top 3 vals | +| `dupif` | `( x -- x x? )` | dup only if TOS truthy | +| `maptos(fn)` | `( x -- f(x) )` | transform TOS w/ `f` | +| `map2(fn)` | `( x y -- f(y, x) )` | reduce top 2 vals with `f`, single result | +| `nip` | `( x y -- y )` | remove `x` from stack | +| `over` | `( x y -- x y x )` | push dup of `x` | +| `pick` | `( n -- stack[n] )` | dup deeper stack value | +| `push(...args)` | `( -- ...args )` | push `args` on stack | +| `rot` | `( x y z -- y z x )` | rotate top 3 vals down/left | +| `invrot` | `( x y z -- z x y )` | rotate top 3 vals up/right | +| `swap` | `( x y -- y x )` | swap top 2 vals | +| `swap2` | `( a b c d -- c d a b )` | swap top 2 pairs | +| `tuck` | `( x y -- y x y )` | insert dup of TOS | ### R-Stack modification -| Word | Stack effect | Description | -| --- | --- | --- | -| `rdrop` | `( x -- )` | drop TOS from r-stack | -| `rdrop2` | `( x y -- )` | remove top 2 vals from r-stack | -| `rswap` | `( x y -- y x )` | swap top 2 vals on r-stack | -| `rswap2` | `( a b c d -- c d a b )` | swap top 2 pairs on r-stack | -| `rsp` | `( -- stack.length )` | push r-stack depth on d-stack | -| `movdr` | `( x -- )` (d-stack effect) | push d-stack TOS on r-stack | -| `movrd` | `( -- x )` (d-stack effect) | push r-stack TOS on d-stack | -| `cpdr` | `( x -- x )` (d-stack effect) | copy d-stack TOS on r-stack | -| `cprd` | `( -- x )` (d-stack effect) | copy r-stack TOS on d-stack | +| Word | Stack effect | Description | +|----------|-------------------------------|--------------------------------| +| `rdrop` | `( x -- )` | drop TOS from r-stack | +| `rdrop2` | `( x y -- )` | remove top 2 vals from r-stack | +| `rswap` | `( x y -- y x )` | swap top 2 vals on r-stack | +| `rswap2` | `( a b c d -- c d a b )` | swap top 2 pairs on r-stack | +| `rsp` | `( -- stack.length )` | push r-stack depth on d-stack | +| `movdr` | `( x -- )` (d-stack effect) | push d-stack TOS on r-stack | +| `movrd` | `( -- x )` (d-stack effect) | push r-stack TOS on d-stack | +| `cpdr` | `( x -- x )` (d-stack effect) | copy d-stack TOS on r-stack | +| `cprd` | `( -- x )` (d-stack effect) | copy r-stack TOS on d-stack | ### Word & quotation execution / combinators -| Word | Stack effect | Description | -| --- | --- | --- | -| `exec` | `( w -- ? )` | call TOS as (compiled) word w/ curr ctx | -| `dip` | `( x q -- .. x )` | | -| `dip2` | `( x y q -- .. x y )` | | -| `dip3` | `( x y z q -- .. x y z )` | | -| `dip4` | `( x y z w q -- .. x y z w )` | | -| `keep` | `( x q -- .. x )` | | -| `keep2` | `( x y q -- .. x y )` | | -| `keep3` | `( x y z q -- .. x y z )` | | -| `bi` | `( x p q -- pres qres )` | | -| `bi2` | `( x y p q -- pres qres )` | | -| `bi3` | `( x y z p q -- pres qres )` | | +| Word | Stack effect | Description | +|---------|-------------------------------|-----------------------------------------| +| `exec` | `( w -- ? )` | call TOS as (compiled) word w/ curr ctx | +| `dip` | `( x q -- .. x )` | | +| `dip2` | `( x y q -- .. x y )` | | +| `dip3` | `( x y z q -- .. x y z )` | | +| `dip4` | `( x y z w q -- .. x y z w )` | | +| `keep` | `( x q -- .. x )` | | +| `keep2` | `( x y q -- .. x y )` | | +| `keep3` | `( x y z q -- .. x y z )` | | +| `bi` | `( x p q -- pres qres )` | | +| `bi2` | `( x y p q -- pres qres )` | | +| `bi3` | `( x y z p q -- pres qres )` | | ### Primitive math -| Word | Stack effect | Description | -| --- | --- | --- | -| `add` | `( x y -- x+y )` | -| `sub` | `( x y -- x-y )` | -| `mul` | `( x y -- x*y )` | -| `div` | `( x y -- x/y )` | -| `mod` | `( x y -- x%y )` | -| `inc` | `( x -- x+1 )` | -| `dec` | `( x -- x-1 )` | -| `neg` | `( x -- -x )` | -| `even` | `( x -- bool )` | true, if `x` is even | -| `odd` | `( x -- bool )` | true, if `x` is odd | -| `min` | `( x y -- min(x, y) )` | -| `max` | `( x y -- max(x, y) )` | -| `log` | `( x -- log(x) )` | -| `pow` | `( x y -- pow(x, y) )` | -| `rand` | `( -- Math.random() )` | -| `sqrt` | `( x -- sqrt(x) )` | -| `sin` | `( x -- sin(x) )` | -| `cos` | `( x -- cos(x) )` | -| `atan2` | `( x y -- atan2(y, x) )` | -| `lsl` | `( x y -- x<>y )` | -| `lsru` | `( x y -- x>>>y )` | -| `bitand` | `( x y -- x&y )` | -| `bitor` | `( x y -- x\|y )` | -| `bitxor` | `( x y -- x^y )` | -| `bitnot` | `( x -- ~x )` | +| Word | Stack effect | Description | +|----------|--------------------------|----------------------| +| `add` | `( x y -- x+y )` | | +| `sub` | `( x y -- x-y )` | | +| `mul` | `( x y -- x*y )` | | +| `div` | `( x y -- x/y )` | | +| `mod` | `( x y -- x%y )` | | +| `inc` | `( x -- x+1 )` | | +| `dec` | `( x -- x-1 )` | | +| `neg` | `( x -- -x )` | | +| `even` | `( x -- bool )` | true, if `x` is even | +| `odd` | `( x -- bool )` | true, if `x` is odd | +| `min` | `( x y -- min(x, y) )` | | +| `max` | `( x y -- max(x, y) )` | | +| `log` | `( x -- log(x) )` | | +| `pow` | `( x y -- pow(x, y) )` | | +| `rand` | `( -- Math.random() )` | | +| `sqrt` | `( x -- sqrt(x) )` | | +| `sin` | `( x -- sin(x) )` | | +| `cos` | `( x -- cos(x) )` | | +| `atan2` | `( x y -- atan2(y, x) )` | | +| `lsl` | `( x y -- x<>y )` | | +| `lsru` | `( x y -- x>>>y )` | | +| `bitand` | `( x y -- x&y )` | | +| `bitor` | `( x y -- x\|y )` | | +| `bitxor` | `( x y -- x^y )` | | +| `bitnot` | `( x -- ~x )` | | ### Logic -| Word | Stack effect | -| --- | --- | -| `eq` | `( x y -- x===y )` | -| `equiv` | `( x y -- equiv(x,y) )` | -| `neq` | `( x y -- x!==y )` | -| `and` | `( x y -- x&&y )` | -| `or` | `( x y -- x\|\|y )` | -| `not` | `( x -- !x )` | -| `lt` | `( x y -- xy )` | -| `lteq` | `( x y -- x<=y )` | -| `gteq` | `( x y -- x>=y )` | -| `iszero` | `( x -- x===0 )` | -| `ispos` | `( x -- x>0 )` | -| `isneg` | `( x -- x<0 )` | -| `isnull` | `( x -- x==null )` | +| Word | Stack effect | +|----------|-------------------------| +| `eq` | `( x y -- x===y )` | +| `equiv` | `( x y -- equiv(x,y) )` | +| `neq` | `( x y -- x!==y )` | +| `and` | `( x y -- x&&y )` | +| `or` | `( x y -- x\|\|y )` | +| `not` | `( x -- !x )` | +| `lt` | `( x y -- xy )` | +| `lteq` | `( x y -- x<=y )` | +| `gteq` | `( x y -- x>=y )` | +| `iszero` | `( x -- x===0 )` | +| `ispos` | `( x -- x>0 )` | +| `isneg` | `( x -- x<0 )` | +| `isnull` | `( x -- x==null )` | ### Environment -| Word | Stack effect | Description | -| --- | --- | --- | -| `load` | `( k -- env[k] )` | pushes `env[k]` on d-stack | -| `store` | `( x k -- )` | stores TOS as `env[k]` | -| `loadkey(k)` | `( -- env[k] )` | like `load` w/ predefined key | -| `storekey(k)` | `( x -- )` | like `store` w/ predefined key | -| `pushenv` | `( -- env )` | pushes curr env on d-stack | +| Word | Stack effect | Description | +|---------------|-------------------|--------------------------------| +| `load` | `( k -- env[k] )` | pushes `env[k]` on d-stack | +| `store` | `( x k -- )` | stores TOS as `env[k]` | +| `loadkey(k)` | `( -- env[k] )` | like `load` w/ predefined key | +| `storekey(k)` | `( x -- )` | like `store` w/ predefined key | +| `pushenv` | `( -- env )` | pushes curr env on d-stack | ### Arrays, objects, strings -| Word | Stack effect | Description | -| --- | --- | --- | -| `at` | `( obj k -- obj[k] )` | `obj` can be array/obj/string | -| `bindkeys` | `(v1 v2 .. [k1 k2 ..] obj -- obj )` | bind key/value pairs in `obj` | -| `collect` | `( ... n -- [...] )` | tuple of top `n` vals | -| `foldl` | `( arr q init -- x )` | like `mapl`, but w/ `init` val for reduction | -| `length` | `( x -- x.length )` | length of arraylike | -| `list` | `( -- [] )` | create new empty array | -| `mapl` | `( arr q -- ? )` | transform array w/ quotation (no explicit result array) | -| `mapll` | `( arr q -- ? )` | transform array w/ quotation | -| `obj` | `( -- {} )` | create new empty object | -| `pushl` | `( x arr -- arr )` | push `x` on LHS of array | -| `pushr` | `( arr x -- arr )` | push `x` on RHS of array | -| `popr` | `( arr -- arr arr[-1] )` | extract RHS of array as new TOS | -| `pull` | `( arr -- x arr )` | short for: `[popr, swap]` | -| `pull2` | `( arr -- x y arr )` | short for: `[pull, pull]` | -| `pull3` | `( arr -- x y z arr )` | short for: `[pull2, pull]` | -| `pull4` | `( arr -- a b c d arr )` | short for: `[pull2, pull2]` | -| `split` | `( arr x -- [...] [...] )` | split array at index `x` | -| `setat` | `( val obj k -- obj )` | `obj` can be array/obj | -| `tuple(n)` | `( ... -- [...] )` | HOF, like `collect`, but w/ predefined size | -| `vec2` | `( x y -- [x, y] )` | same as `tuple(2)` | -| `vec3` | `( x y z -- [x, y, z] )` | same as `tuple(3)` | -| `vec4` | `( x y z w -- [x, y, z, w] )` | same as `tuple(4)` | -| `vadd` | `( a b -- c )` | add 2 arrays (or array + scalar) | -| `vsub` | `( a b -- c )` | subtract 2 arrays (or array + scalar) | -| `vmul` | `( a b -- c )` | multiply 2 arrays (or array + scalar) | -| `vdiv` | `( a b -- c )` | divide 2 arrays (or array + scalar) | -| `op2v(f)` | `( a b -- c )` | HOF word gen, e.g. `vadd` is based on | +| Word | Stack effect | Description | +|------------|-------------------------------------|---------------------------------------------------------| +| `at` | `( obj k -- obj[k] )` | `obj` can be array/obj/string | +| `bindkeys` | `(v1 v2 .. [k1 k2 ..] obj -- obj )` | bind key/value pairs in `obj` | +| `collect` | `( ... n -- [...] )` | tuple of top `n` vals | +| `foldl` | `( arr q init -- x )` | like `mapl`, but w/ `init` val for reduction | +| `length` | `( x -- x.length )` | length of arraylike | +| `list` | `( -- [] )` | create new empty array | +| `mapl` | `( arr q -- ? )` | transform array w/ quotation (no explicit result array) | +| `mapll` | `( arr q -- ? )` | transform array w/ quotation | +| `obj` | `( -- {} )` | create new empty object | +| `pushl` | `( x arr -- arr )` | push `x` on LHS of array | +| `pushr` | `( arr x -- arr )` | push `x` on RHS of array | +| `popr` | `( arr -- arr arr[-1] )` | extract RHS of array as new TOS | +| `pull` | `( arr -- x arr )` | short for: `[popr, swap]` | +| `pull2` | `( arr -- x y arr )` | short for: `[pull, pull]` | +| `pull3` | `( arr -- x y z arr )` | short for: `[pull2, pull]` | +| `pull4` | `( arr -- a b c d arr )` | short for: `[pull2, pull2]` | +| `split` | `( arr x -- [...] [...] )` | split array at index `x` | +| `setat` | `( val obj k -- obj )` | `obj` can be array/obj | +| `tuple(n)` | `( ... -- [...] )` | HOF, like `collect`, but w/ predefined size | +| `vec2` | `( x y -- [x, y] )` | same as `tuple(2)` | +| `vec3` | `( x y z -- [x, y, z] )` | same as `tuple(3)` | +| `vec4` | `( x y z w -- [x, y, z, w] )` | same as `tuple(4)` | +| `vadd` | `( a b -- c )` | add 2 arrays (or array + scalar) | +| `vsub` | `( a b -- c )` | subtract 2 arrays (or array + scalar) | +| `vmul` | `( a b -- c )` | multiply 2 arrays (or array + scalar) | +| `vdiv` | `( a b -- c )` | divide 2 arrays (or array + scalar) | +| `op2v(f)` | `( a b -- c )` | HOF word gen, e.g. `vadd` is based on | ### I/O -| Word | Stack effect | Description | -| --- | --- | --- | -| `print` | `( x -- )` | `console.log(x)` | -| `printds` | `( -- )` | print out D-stack | -| `printrs` | `( -- )` | print out R-stack | +| Word | Stack effect | Description | +|-----------|--------------|-------------------| +| `print` | `( x -- )` | `console.log(x)` | +| `printds` | `( -- )` | print out D-stack | +| `printrs` | `( -- )` | print out R-stack | ### Control flow @@ -955,7 +955,7 @@ Non-HOF version of `cond`, expects `test` result and both branches on d-stack. Executes `thenq` word/quotation if `test` is truthy, else runs `elseq`. -``` +```forth ( test thenq elseq -- ? ) ``` @@ -980,13 +980,13 @@ executes body. Repeats while test is truthy. Non-HOF version of `loop`. Expects test result and body quotation/word on d-stack. -``` +```forth ( testq bodyq -- ? ) ``` #### `dotimes` -``` +```forth ( n body -- ? ) ``` diff --git a/packages/pointfree/package.json b/packages/pointfree/package.json index 86ffd42879..94f8630531 100644 --- a/packages/pointfree/package.json +++ b/packages/pointfree/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pointfree", - "version": "1.1.3", + "version": "1.2.0", "description": "Pointfree functional composition / Forth style stack execution engine", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/compose": "^1.3.2", + "@thi.ng/compose": "^1.3.3", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "composition", diff --git a/packages/pointfree/src/array.ts b/packages/pointfree/src/array.ts new file mode 100644 index 0000000000..d176c1d01c --- /dev/null +++ b/packages/pointfree/src/array.ts @@ -0,0 +1,382 @@ +import { isArray, isPlainObject } from "@thi.ng/checks"; +import { illegalArgs, illegalState } from "@thi.ng/errors"; +import { StackContext, StackFn } from "./api"; +import { op1, op2, op2v } from "./ops"; +import { $, $n } from "./safe"; +import { invrot, swap } from "./stack"; +import { $stackFn, word } from "./word"; + +//////////////////// Array / list ops //////////////////// + +/** + * Pushes a new empty array on the d-stack. While it's easily possible to + * use `[]` as part of a stack program, the `list` word is intended to + * be used as part of re-usuable `word()` definitions to ensure a new + * array is being created for every single invocation of the word (else + * only a single instance is created due to the mutable nature of JS + * arrays). + * + * Compare: + * + * ``` + * // using array literal within word definition + * foo = pf.word([ [], 1, pf.pushl ]) + * pf.runU(foo) + * // [ 1 ] + * pf.runU(foo) + * // [ 1, 1 ] // wrong! + * + * // using `list` instead + * bar = pf.word([ pf.list, 1, pf.pushl ]) + * pf.runU(bar) + * // [ 1 ] + * pf.runU(bar) + * // [ 1 ] // correct! + * ``` + * + * ( -- [] ) + * + * @param ctx + */ +export const list = (ctx: StackContext) => (ctx[0].push([]), ctx); + +/** + * Pushes new empty JS object on d-stack. + * Same reasoning as for `list`. + * + * ( -- {} ) + * + * @param ctx + */ +export const obj = (ctx: StackContext) => (ctx[0].push({}), ctx); + +/** + * Pushes `val` on the LHS of array. + * + * ( val arr -- arr ) + * + * @param ctx + */ +export const pushl = (ctx: StackContext) => { + $(ctx[0], 2); + const stack = ctx[0]; + const a = stack.pop(); + a.unshift(stack.pop()); + stack.push(a); + return ctx; +}; + +/** + * Pushes `val` on the RHS of array. + * + * ( arr val -- arr ) + * + * @param ctx + */ +export const pushr = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + stack[n].push(stack[n + 1]); + stack.length--; + return ctx; +}; + +/** + * Removes RHS from array as new TOS on d-stack. + * Throws error is `arr` is empty. + * + * ( arr -- arr arr[-1] ) + * + * @param ctx + */ +export const popr = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 1; + $n(n, 0); + const a = stack[n]; + !a.length && illegalState("can't pop empty array"); + stack.push(a.pop()); + return ctx; +}; + +export const pull = word([popr, swap]); +export const pull2 = word([pull, pull]); +export const pull3 = word([pull2, pull]); +export const pull4 = word([pull2, pull2]); + +export const vadd = op2v((b, a) => a + b); +export const vsub = op2v((b, a) => a - b); +export const vmul = op2v((b, a) => a * b); +export const vdiv = op2v((b, a) => a / b); + +/** + * Splits vector / array at given index `x`. + * + * ( arr x -- [...] [...] ) + * + * @param ctx + */ +export const split = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + const a = stack[n]; + const b = stack[n + 1]; + stack[n + 1] = a.splice(b, a.length - b); + return ctx; +}; + +/** + * Concatenates two arrays on d-stack: + * + * ( arr1 arr2 -- arr ) + * + * @param ctx + */ +export const cat = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + stack[n] = stack[n].concat(stack.pop()); + return ctx; +}; + +/** + * Generic array transformer. + * + * ( arr q -- ? ) + * + * Pops both args from d-stack, then executes quotation for each array + * item (each pushed on d-stack prior to calling quotation). Can produce + * any number of results and therefore also be used as filter, mapcat, + * reduce... + * + * ``` + * // each item times 10 + * run([[1, 2, 3, 4], [10, mul], mapl]) + * // [ [ 10, 20, 30, 40 ], [], {} ] + * ``` + * + * Use for filtering: + * + * ``` + * // drop even numbers, duplicate odd ones + * run([[1, 2, 3, 4], [dup, even, cond(drop, dup)], mapl]) + * // [ [ 1, 1, 3, 3 ], [], {} ] + * ``` + * + * Reduction: + * + * ``` + * // the `0` is the initial reduction result + * runU([0, [1, 2, 3, 4], [add], mapl]) + * // 10 + * ``` + * + * **Important**: `mapl` does not produce a result array. However, + * there're several options to collect results as array, e.g. + * + * Use `mapll()` to transform: + * + * ``` + * runU([[1, 2, 3, 4], [10, mul], mapll]) + * // [ 10, 20, 30, 40] + * ``` + * + * Collecting results as array is a form of reduction, so we can use + * `list` to produce an initial new array and `pushr` to push each new + * interim value into the result: + * + * ``` + * runU([list, [1, 2, 3, 4], [10, mul, pushr], mapl]) + * // [ 10, 20, 30, 40 ] + * ``` + * + * If the array size is known & not changed by transformation: + * + * ``` + * runU([[1, 2, 3, 4], [10, mul], mapl, 4, collect]) + * // [ 10, 20, 30, 40 ] + * ``` + * + * @param ctx + */ +export const mapl = (ctx: StackContext) => { + $(ctx[0], 2); + const stack = ctx[0]; + const w = $stackFn(stack.pop()); + const list = stack.pop(); + const n = list.length; + for (let i = 0; i < n; i++) { + ctx[0].push(list[i]); + ctx = w(ctx); + } + return ctx; +}; + +/** + * Similar to `mapl()`, but produces new array of transformed values. + * + * ( arr q -- arr ) + * + * ``` + * runU([[1, 2, 3, 4], [10, mul], mapll]) + * // [ 10, 20, 30, 40] + * ``` + * + * Filter / mapcat: + * + * ``` + * // drop even numbers, duplicate odd ones + * run([[1, 2, 3, 4], [dup, even, cond(drop, dup)], mapll]) + * // [ [ [ 1, 1, 3, 3 ] ], [], {} ] + * ``` + * + * @param ctx + */ +export const mapll = (ctx: StackContext) => { + $(ctx[0], 2); + let stack = ctx[0]; + const w = $stackFn(stack.pop()); + const list = stack.pop(); + const n = list.length; + let r = 0; + for (let i = 0; i < n; i++) { + let m = stack.length; + stack.push(list[i]); + ctx = w(ctx); + stack = ctx[0]; + r += stack.length - m; + } + stack.push(stack.splice(stack.length - r, r)); + return ctx; +}; + +/** + * Convenience wrapper for `mapl` to provide an alternative stack layout + * for reduction purposes: + * + * ( arr q init -- reduction ) + */ +export const foldl = word([invrot, mapl]); + +/** + * Pops TOS (a number) and then forms a tuple of the top `n` remaining + * values and pushes it as new TOS. The original collected stack values + * are removed from d-stack. + * + * ( ... n --- ... [...] ) + * + * @param ctx + */ +export const collect = (ctx: StackContext) => { + const stack = ctx[0]; + let n = stack.length - 1, + m; + $n(n, 0); + $n((n -= m = stack.pop()), 0); + stack.push(stack.splice(n, m)); + return ctx; +}; + +/** + * Higher order helper word to `collect()` tuples of pre-defined size + * `n`. The size can be given as number or a stack function producing a + * number. + * + * ( ... -- [...]) + * + * @param n + */ +export const tuple = (n: number | StackFn) => word([n, collect]); + +export const vec2 = tuple(2); +export const vec3 = tuple(3); +export const vec4 = tuple(4); + +/** + * Higher order helper word to convert a TOS tuple/array into a string + * using `Array.join()` with given `sep`arator. + * + * @param sep + */ +export const join = (sep = "") => op1((x) => x.join(sep)); + +/** + * Pushes length of TOS on d-stack. + * + * ( x -- x.length ) + * + * @param ctx + */ +export const length = op1((x) => x.length); + +/** + * Replaces TOS with its shallow copy. MUST be an array or plain object. + * + * ( x -- copy ) + */ +export const copy = op1((x) => + isArray(x) + ? x.slice() + : isPlainObject(x) + ? { ...x } + : illegalArgs(`can't copy type ${typeof x}`) +); + +/** + * Reads key/index from object/array. + * + * ( obj k -- obj[k] ) + * + * @param ctx + */ +export const at = op2((b, a) => a[b]); + +/** + * Writes `val` at key/index in object/array. + * + * ( val obj k -- obj ) + * + * @param ctx + */ +export const setat = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 3; + $n(n, 0); + stack[n + 1][stack[n + 2]] = stack[n]; + stack[n] = stack[n + 1]; + stack.length -= 2; + return ctx; +}; + +//////////////////// Objects //////////////////// + +/** + * Takes an array of keys and target object, then pops & binds deeper + * stack values to respective keys in object. Pushes result object back + * on stack at the end. Throws error if there're less stack values than + * keys in given array. + * + * ``` + * runU([1,2,3, ["a","b","c"], {}, bindkeys]) + * // { c: 3, b: 2, a: 1 } + * ``` + * + * (v1 v2 .. [k1 k2 ..] obj -- obj ) + * + * @param ctx + */ +export const bindkeys = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 2); + const obj = stack.pop(); + const keys = stack.pop(); + $(stack, keys.length); + for (let i = keys.length - 1; i >= 0; i--) { + obj[keys[i]] = stack.pop(); + } + stack.push(obj); + return ctx; +}; diff --git a/packages/pointfree/src/binary.ts b/packages/pointfree/src/binary.ts new file mode 100644 index 0000000000..00b27c3a4c --- /dev/null +++ b/packages/pointfree/src/binary.ts @@ -0,0 +1,52 @@ +import { op1, op2 } from "./ops"; + +//////////////////// Binary ops //////////////////// + +/** + * ( x y -- x&y ) + * + * @param ctx + */ +export const bitand = op2((b, a) => a & b); + +/** + * ( x y -- x|y ) + * + * @param ctx + */ +export const bitor = op2((b, a) => a | b); + +/** + * ( x y -- x^y ) + * + * @param ctx + */ +export const bitxor = op2((b, a) => a ^ b); + +/** + * ( x -- ~x ) + * + * @param ctx + */ +export const bitnot = op1((x) => ~x); + +/** + * ( x y -- x< a << b); + +/** + * ( x y -- x>>y ) + * + * @param ctx + */ +export const lsr = op2((b, a) => a >> b); + +/** + * ( x y -- x>>>y ) + * + * @param ctx + */ +export const lsru = op2((b, a) => a >>> b); diff --git a/packages/pointfree/src/cond.ts b/packages/pointfree/src/cond.ts new file mode 100644 index 0000000000..bf6d5102b2 --- /dev/null +++ b/packages/pointfree/src/cond.ts @@ -0,0 +1,76 @@ +import { IObjectOf } from "@thi.ng/api"; +import { illegalState } from "@thi.ng/errors"; +import { StackContext, StackProc } from "./api"; +import { $ } from "./safe"; +import { nop } from "./stack"; +import { $stackFn } from "./word"; + +//////////////////// Conditionals //////////////////// + +/** + * Higher order word. Takes two stack programs: truthy and falsey + * branches, respectively. When executed, pops TOS and runs only one of + * the branches depending if TOS was truthy or not. + * + * Note: Unlike JS `if() {...} else {...}` constructs, the actual + * conditional is NOT part of this word. + * + * ( bool -- ? ) + * + * @param _then + * @param _else + */ +export const cond = (_then: StackProc, _else: StackProc = nop) => ( + ctx: StackContext +) => ($(ctx[0], 1), $stackFn(ctx[0].pop() ? _then : _else)(ctx)); + +/** + * Non-HOF version of `cond`, expects `test` result and both branches on + * d-stack. Executes `thenq` word/quotation if `test` is truthy, else + * runs `elseq`. + * + * ( test thenq elseq -- ? ) + * + * @param ctx + */ +export const condq = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 3); + const _else = stack.pop(); + const _then = stack.pop(); + return $stackFn(stack.pop() ? _then : _else)(ctx); +}; + +/** + * Higher order word. Takes an object of stack programs with keys in the + * object being used to check for equality with TOS. If a match is + * found, executes corresponding stack program. If a `default` key is + * specified and no other cases matched, run `default` program. In all + * other cases throws an error. + * + * Important: The default case has the original TOS re-added to the + * d-stack before execution. + * + * @param cases + */ +export const cases = (cases: IObjectOf) => (ctx: StackContext) => { + $(ctx[0], 1); + const stack = ctx[0]; + const tos = stack.pop(); + const cas = cases[tos]; + if (cas !== undefined) { + return $stackFn(cas)(ctx); + } + if (cases.default) { + stack.push(tos); + return $stackFn(cases.default)(ctx); + } + illegalState(`no matching case for: ${tos}`); + return ctx; +}; + +export const casesq = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 2); + return cases(stack.pop())(ctx); +}; diff --git a/packages/pointfree/src/context.ts b/packages/pointfree/src/context.ts new file mode 100644 index 0000000000..82c2dd6095 --- /dev/null +++ b/packages/pointfree/src/context.ts @@ -0,0 +1,14 @@ +import { Stack, StackContext, StackEnv } from "./api"; + +/** + * Creates a new StackContext tuple from given d-stack and/or + * environment only (the r-stack is always initialized empty). + * + * @param stack initial d-stack + * @param env initial environment + */ +export const ctx = (stack: Stack = [], env: StackEnv = {}): StackContext => [ + stack, + [], + env +]; diff --git a/packages/pointfree/src/dataflow.ts b/packages/pointfree/src/dataflow.ts new file mode 100644 index 0000000000..ce1ad4078b --- /dev/null +++ b/packages/pointfree/src/dataflow.ts @@ -0,0 +1,206 @@ +import { StackContext } from "./api"; +import { and, or } from "./logic"; +import { $ } from "./safe"; +import { + dup, + dup2, + dup3, + over, + swap +} from "./stack"; +import { $stackFn, exec, word } from "./word"; + +//////////////////// Dataflow combinators //////////////////// + +// these combinators have been ported from Factor: +// http://docs.factorcode.org:8080/content/article-dataflow-combinators.html + +/** + * Removes `x` from d-stack, calls `q` and restores `x` to the top of + * the d-stack after quotation is finished. + * + * ( x q -- x ) + * + * Same behavior as: `[swap, movdr, exec, movrd]`, only the current + * implementation doesn't use r-stack and stashes `x` off stack. + * + * @param ctx + */ +export const dip = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 2); + const q = stack.pop(); + const x = stack.pop(); + ctx = $stackFn(q)(ctx); + ctx[0].push(x); + return ctx; +}; + +/** + * Removes `x y` from d-stack, calls `q` and restores removed vals + * to the top of the d-stack after quotation is finished. + * + * ( x y q -- x y ) + */ +export const dip2 = word([swap, [dip], dip]); + +/** + * Removes `x y z` from d-stack, calls `q` and restores removed + * vals to the top of the d-stack after quotation is finished. + * + * ( x y z q -- x y z ) + */ +export const dip3 = word([swap, [dip2], dip]); + +/** + * Removes `x y z w` from d-stack, calls `q` and restores removed + * vals to the top of the d-stack after quotation is finished. + * + * ( x y z w q -- x y z w ) + */ +export const dip4 = word([swap, [dip3], dip]); + +/** + * Calls a quotation with a value on the d-stack, restoring the value + * after quotation finished. + * + * ( x q -- .. x ) + */ +export const keep = word([over, [exec], dip]); + +/** + * Call a quotation with two values on the stack, restoring the values + * after quotation finished. + * + * ( x y q -- .. x y ) + */ +export const keep2 = word([[dup2], dip, dip2]); + +/** + * Call a quotation with three values on the stack, restoring the values + * after quotation finished. + * + * ( x y z q -- .. x y z ) + */ +export const keep3 = word([[dup3], dip, dip3]); + +/** + * First applies `p` to the value `x`, then applies `q` to the same + * value. + * + * ( x p q -- px qx ) + */ +export const bi = word([[keep], dip, exec]); + +/** + * First applies `p` to the two input values `x y`, then applies `q` to + * the same values. + * + * ( x y p q -- pxy qxy ) + */ +export const bi2 = word([[keep2], dip, exec]); + +/** + * First applies `p` to the three input values `x y z`, then applies `q` + * to the same values. + * + * ( x y z p q -- pxyz qxyz ) + */ +export const bi3 = word([[keep3], dip, exec]); + +/** + * Applies `p` to `x`, then `q` to `x`, and finally `r` to `x` + * + * ( x p q r -- px qx rx ) + */ +export const tri = word([[[keep], dip, keep], dip, exec]); + +/** + * Applies `p` to the two input values `x y`, then same with `q`, and + * finally with `r`. + * + * ( x y p q r -- pxy qxy rxy ) + */ +export const tri2 = word([[[keep2], dip, keep2], dip, exec]); + +/** + * Applies `p` to the three input values `x y z`, then same with `q`, + * and finally with `r`. + * + * ( x y z p q r -- pxyz qxyz rxyz ) + */ +export const tri3 = word([[[keep3], dip, keep3], dip, exec]); + +/** + * Applies `p` to `x`, then applies `q` to `y`. + * + * ( x y p q -- px qy ) + */ +export const bis = word([[dip], dip, exec]); + +/** + * Applies `p` to `a b`, then applies `q` to `c d`. + * + * ( a b c d p q -- pab qcd ) + */ +export const bis2 = word([[dip2], dip, exec]); + +/** + * Applies `p` to `x`, then `q` to `y`, and finally `r` to `z`. + * + * ( x y z p q r -- ) + */ +export const tris = word([[[dip2], dip, dip], dip, exec]); + +/** + * Applies `p` to `u v`, then `q` to `w x`, and finally `r` to `y z`. + * + * ( u v w x y z p q r -- puv qwx ryz ) + */ +export const tris2 = word([[dip4], dip2, bis2]); + +/** + * Applies the quotation `q` to `x`, then to `y`. + * + * ( x y q -- qx qy ) + */ +export const bia = word([dup, bis]); + +/** + * Applies the quotation `q` to `x y`, then to `z w`. + * + * ( x y z w q -- qxy qzw ) + */ +export const bia2 = word([dup, bis2]); + +/** + * Applies the `q` to `x`, then to `y`, and finally to `z`. + * + * ( x y z q -- qx qy qz ) + */ +export const tria = word([dup, dup, tris]); + +/** + * Applies the quotation to `u v`, then to `w x`, and then to `y z`. + * + * ( u v w x y z q -- quv qwx qyz ) + */ +export const tria2 = word([dup, dup, tris2]); + +/** + * Applies `q` individually to both input vals `x y` and combines + * results with `and`. The final result will be true if both interim + * results were truthy. + * + * ( x y q -- qx && qy ) + */ +export const both = word([bia, and]); + +/** + * Applies `q` individually to both input vals `x y` and combines results with `or`. + * The final result will be true if at least one of the interim results + * was truthy. + * + * ( x y q -- qx || qy ) + */ +export const either = word([bia, or]); diff --git a/packages/pointfree/src/env.ts b/packages/pointfree/src/env.ts new file mode 100644 index 0000000000..a4b9862cb3 --- /dev/null +++ b/packages/pointfree/src/env.ts @@ -0,0 +1,75 @@ +import { illegalArgs } from "@thi.ng/errors"; +import { StackContext } from "./api"; +import { $ } from "./safe"; + +//////////////////// Environment //////////////////// + +/** + * Pushes current env onto d-stack. + * + * ( -- env ) + * + * @param ctx + * @param env + */ +export const pushenv = (ctx: StackContext) => (ctx[0].push(ctx[2]), ctx); + +/** + * Loads value for `key` from current env and pushes it on d-stack. + * Throws error if var doesn't exist. + * + * ( key -- env[key] ) + * + * @param ctx + * @param env + */ +export const load = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 1); + const id = stack.pop(); + !ctx[2].hasOwnProperty(id) && illegalArgs(`unknown var: ${id}`); + stack.push(ctx[2][id]); + return ctx; +}; + +/** + * Stores `val` under `key` in env. + * + * ( val key -- ) + * + * @param ctx + * @param env + */ +export const store = (ctx: StackContext) => ( + $(ctx[0], 2), (ctx[2][ctx[0].pop()] = ctx[0].pop()), ctx +); + +/** + * Higher order word. Similar to `load`, but always uses given + * preconfigured `key` instead of reading it from d-stack at runtime + * (also slightly faster). Throws error if var doesn't exist. + * + * ( -- env[key] ) + * @param ctx + * @param env + */ +export const loadkey = (key: PropertyKey) => (ctx: StackContext) => { + !ctx[2].hasOwnProperty(key) && + illegalArgs(`unknown var: ${key.toString()}`); + ctx[0].push(ctx[2][key]); + return ctx; +}; + +/** + * Higher order word. Similar to `store`, but always uses given + * preconfigure `key` instead of reading it from d-stack at runtime + * (also slightly faster). + * + * ( val -- ) + * + * @param ctx + * @param env + */ +export const storekey = (key: PropertyKey) => (ctx: StackContext) => ( + $(ctx[0], 1), (ctx[2][key] = ctx[0].pop()), ctx +); diff --git a/packages/pointfree/src/index.ts b/packages/pointfree/src/index.ts index c85b6e7b67..83fdc48d02 100644 --- a/packages/pointfree/src/index.ts +++ b/packages/pointfree/src/index.ts @@ -1,1814 +1,19 @@ -import { - Fn, - Fn2, - IObjectOf, - NO_OP -} from "@thi.ng/api"; -import { isArray, isFunction, isPlainObject } from "@thi.ng/checks"; -import { compL } from "@thi.ng/compose"; import { equiv as _equiv } from "@thi.ng/equiv"; -import { illegalArgs, illegalState } from "@thi.ng/errors"; -import { - Stack, - StackContext, - StackEnv, - StackFn, - StackProc, - StackProgram -} from "./api"; - -let $: (stack: Stack, n: number) => void; -let $n: (m: number, n: number) => void; - -export const safeMode = (state: boolean) => { - if (state) { - $n = (m: number, n: number) => m < n && illegalState(`stack underflow`); - $ = (stack: Stack, n: number) => $n(stack.length, n); - } else { - $ = $n = NO_OP; - } -}; -safeMode(true); - -/** - * Executes program / quotation with given stack context (initial D/R - * stacks and optional environment). Returns updated context. - * - * @param prog - * @param ctx - */ -export const run = ( - prog: StackProc, - ctx: StackContext = [[], [], {}] -): StackContext => { - // !ctx[0] && (ctx[0] = []); - // !ctx[1] && (ctx[1] = []); - // !ctx[2] && (ctx[2] = {}); - if (isFunction(prog)) { - return prog(ctx); - } - for ( - let p = isArray(prog) ? prog : [prog], n = p.length, i = 0, w; - i < n; - i++ - ) { - if (isFunction((w = p[i]))) { - ctx = w(ctx); - } else { - ctx[0].push(w); - } - } - return ctx; -}; - -/** - * Like `run()`, but returns unwrapped result. Syntax sugar for: - * `unwrap(run(...),n)` - * - * @param prog - * @param ctx - * @param n - */ -export const runU = (prog: StackProc, ctx?: StackContext, n = 1) => - unwrap(run(prog, ctx), n); - -/** - * Like `run()`, but returns result environment. Syntax sugar for: - * `run(...)[2]` - * - * @param prog - * @param ctx - * @param n - */ -export const runE = (prog: StackProc, ctx?: StackContext) => run(prog, ctx)[2]; - -/** - * Creates a new StackContext tuple from given d-stack and/or - * environment only (the r-stack is always initialized empty). - * - * @param stack initial d-stack - * @param env initial environment - */ -export const ctx = (stack: Stack = [], env: StackEnv = {}): StackContext => [ - stack, - [], - env -]; - -const $stackFn = (f: StackProc) => (isArray(f) ? word(f) : f); - -const tos = (stack: Stack) => stack[stack.length - 1]; - -const compile = (prog: StackProgram) => - compL.apply(null, ( - prog.map((w) => - !isFunction(w) ? (ctx: StackContext) => (ctx[0].push(w), ctx) : w - ) - )); - -/** - * Takes a result tuple returned by `run()` and unwraps one or more - * items from result stack. If no `n` is given, defaults to single value - * (TOS) and returns it as is. Returns an array for all other `n`. - * - * @param result - * @param n - */ -export const unwrap = ([stack]: StackContext, n = 1) => - n === 1 ? tos(stack) : stack.slice(Math.max(0, stack.length - n)); - -//////////////////// Dynamic words & quotations //////////////////// - -/** - * Higher order word. Takes a StackProgram and returns it as StackFn to - * be used like any word. Unknown stack effect. - * - * If the optional `env` is given, uses a shallow copy of that - * environment (one per invocation) instead of the current one passed by - * `run()` at runtime. If `mergeEnv` is true (default), the user - * provided env will be merged with the current env (also shallow - * copies). This is useful in conjunction with `pushenv()` and `store()` - * or `storekey()` to save results of sub procedures in the main env. - * - * Note: The provided (or merged) env is only active within the - * execution scope of the word. - * - * ( ? -- ? ) - * - * @param prog - * @param env - * @param mergeEnv - */ -export const word = (prog: StackProgram, env?: StackEnv, mergeEnv = true) => { - const w: StackFn = compile(prog); - return env - ? mergeEnv - ? (ctx: StackContext) => ( - w([ctx[0], ctx[1], { ...ctx[2], ...env }]), ctx - ) - : (ctx: StackContext) => (w([ctx[0], ctx[1], { ...env }]), ctx) - : w; -}; - -/** - * Like `word()`, but automatically calls `unwrap()` on result context - * to produced unwrapped value/tuple. - * - * **Importatant:** Words defined with this function CANNOT be used as - * part of a larger stack program, only for standalone use. - * - * @param prog - * @param n - * @param env - * @param mergeEnv - */ -export const wordU = ( - prog: StackProgram, - n = 1, - env?: StackEnv, - mergeEnv = true -) => { - const w: StackFn = compile(prog); - return env - ? mergeEnv - ? (ctx: StackContext) => - unwrap(w([ctx[0], ctx[1], { ...ctx[2], ...env }]), n) - : (ctx: StackContext) => unwrap(w([ctx[0], ctx[1], { ...env }]), n) - : (ctx: StackContext) => unwrap(w(ctx), n); -}; - -/** - * Executes TOS as stack function and places result back on d-stack. TOS - * MUST be a valid word or quotation. - * - * ( x -- x() ) - * - * @param ctx - */ -export const exec = (ctx: StackContext) => ( - $(ctx[0], 1), $stackFn(ctx[0].pop())(ctx) -); - -//////////////////// JS host calls //////////////////// - -/** - * Expects TOS to be a quotation with a vanilla JS function as first - * element. Calls fn with all remaining items in quot as arguments and - * pushes result back on d-stack (even if fn returned `undefined`). - * - * ( [f ...] -- f(...) ) - * - * @param ctx - */ -export const execjs = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 1); - const [fn, ...args] = stack.pop(); - stack.push(fn(...args)); - return ctx; -}; - -//////////////////// Operator generators //////////////////// - -/** - * Higher order word. Replaces TOS of d-stack with result of given op. - * - * ( x -- y ) - * - * @param op - */ -const op1 = (op: Fn) => { - return (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 1; - $n(n, 0); - stack[n] = op(stack[n]); - return ctx; - }; -}; - -/** - * Higher order word. Takes 2 values from d-stack and writes back result - * from given op. The arg order is (TOS, TOS-1) - * - * ( a b -- c ) - * - * @param op - */ -const op2 = (op: Fn2) => (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack[n] = op(stack.pop(), stack[n]); - return ctx; -}; - -export { op1 as maptos, op2 as map2 }; - -/** - * Similar to `op2`, but for array operators. Either `a` or `b` can be a - * non-array value, but not both. Creates new array of result values. - * The result will have the same length as the shortest arg (if `a` and - * `b` have different lengths). - * - * - ( a b -- a ), if `a` is an array - * - ( a b -- b ), if `a` is not an array - * - * @param f - */ -export const op2v = (f: Fn2) => ( - ctx: StackContext -): StackContext => { - $(ctx[0], 2); - const stack = ctx[0]; - const b = stack.pop(); - const n = stack.length - 1; - const a = stack[n]; - const isa = isArray(a); - const isb = isArray(b); - let c: any[]; - if (isa && isb) { - c = new Array(Math.min(a.length, b.length)); - for (let i = c.length - 1; i >= 0; i--) { - c[i] = f(b[i], a[i]); - } - } else { - if (isb && !isa) { - c = new Array(b.length); - for (let i = c.length - 1; i >= 0; i--) { - c[i] = f(b[i], a); - } - } else if (isa && !isb) { - c = new Array(a.length); - for (let i = c.length - 1; i >= 0; i--) { - c[i] = f(b, a[i]); - } - } else { - illegalArgs("at least one arg must be an array"); - } - } - stack[n] = c!; - return ctx; -}; - -//////////////////// Stack manipulation words //////////////////// - -/** - * Utility word w/ no stack nor side effect. - */ -export const nop = (ctx: StackContext) => ctx; - -/** - * Pushes current d-stack size on d-stack. - * - * ( -- n ) - * @param ctx - */ -export const dsp = (ctx: StackContext) => (ctx[0].push(ctx[0].length), ctx); - -/** - * Uses TOS as index to look up a deeper d-stack value, then places it - * as new TOS. Throws error if stack depth is < `x`. - * - * ( ... x -- ... stack[x] ) - * - * @param ctx - */ -export const pick = (ctx: StackContext) => { - const stack = ctx[0]; - let n = stack.length - 1; - $n(n, 0); - $n((n -= stack.pop() + 1), 0); - stack.push(stack[n]); - return ctx; -}; - -/** - * Removes TOS from d-stack. - * - * ( x -- ) - * - * @param ctx - */ -export const drop = (ctx: StackContext) => ($(ctx[0], 1), ctx[0].length--, ctx); - -/** - * Removes top 2 vals from d-stack. - * - * ( x y -- ) - * - * @param ctx - */ -export const drop2 = (ctx: StackContext) => ( - $(ctx[0], 2), (ctx[0].length -= 2), ctx -); - -/** - * If TOS is truthy then drop it: - * - * ( x -- ) - * - * Else, no effect: - * - * ( x -- x ) - */ -export const dropif = (ctx: StackContext) => ( - $(ctx[0], 1), tos(ctx[0]) && ctx[0].length--, ctx -); - -/** - * Higher order word. Pushes given args verbatim on d-stack. - * - * ( -- ...args ) - * - * @param args - */ -export const push = (...args: any[]) => (ctx: StackContext) => ( - ctx[0].push(...args), ctx -); - -/** - * Duplicates TOS on d-stack. - * - * ( x -- x x ) - * - * @param ctx - */ -export const dup = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[0].push(tos(ctx[0])), ctx -); - -/** - * Duplicates top 2 vals on d-stack. - * - * ( x y -- x y x y ) - * - * @param ctx - */ -export const dup2 = (ctx: StackContext) => { - const stack = ctx[0]; - let n = stack.length - 2; - $n(n, 0); - stack.push(stack[n], stack[n + 1]); - return ctx; -}; - -/** - * Duplicates top 3 vals on d-stack. - * - * ( x y -- x y x y ) - * - * @param ctx - */ -export const dup3 = (ctx: StackContext) => { - const stack = ctx[0]; - let n = stack.length - 3; - $n(n, 0); - stack.push(stack[n], stack[n + 1], stack[n + 2]); - return ctx; -}; - -/** - * If TOS is truthy then push copy of it on d-stack: - * - * ( x -- x x ) - * - * Else, no effect: - * - * ( x -- x ) - * - * @param ctx - */ -export const dupif = (ctx: StackContext) => { - $(ctx[0], 1); - const x = tos(ctx[0]); - x && ctx[0].push(x); - return ctx; -}; - -const _swap = (i: number) => (ctx: StackContext) => { - const stack = ctx[i]; - const n = stack.length - 2; - $n(n, 0); - const a = stack[n]; - stack[n] = stack[n + 1]; - stack[n + 1] = a; - return ctx; -}; - -const _swap2 = (i: number) => (ctx: StackContext) => { - const stack = ctx[i]; - let n = stack.length - 1; - $n(n, 3); - let a = stack[n]; - stack[n] = stack[n - 2]; - stack[n - 2] = a; - n--; - a = stack[n]; - stack[n] = stack[n - 2]; - stack[n - 2] = a; - return ctx; -}; - -/** - * Swaps the two topmost d-stack items. - * - * ( x y -- y x ) - * - * @param ctx - */ -export const swap = _swap(0); - -/** - * Swaps the two topmost d-stack pairs. - * - * ( a b c d -- c d a b ) - * - * @param ctx - */ -export const swap2 = _swap2(0); - -/** - * Removes second topmost item from d-stack. - * - * ( x y -- y ) - * - * @param ctx - */ -export const nip = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack[n] = stack.pop(); - return ctx; -}; - -/** - * Inserts copy of TOS @ TOS-2 in d-stack. - * - * ( x y -- y x y ) - * - * @param ctx - */ -export const tuck = (ctx: StackContext) => { - $(ctx[0], 2); - const stack = ctx[0]; - const a = stack.pop(); - stack.push(a, stack.pop(), a); - return ctx; -}; - -/** - * Rotates three topmost d-stack items downwards/to the left. - * - * ( x y z -- y z x ) - * - * @param ctx - */ -export const rot = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 1; - $n(n, 2); - const c = stack[n - 2]; - stack[n - 2] = stack[n - 1]; - stack[n - 1] = stack[n]; - stack[n] = c; - return ctx; -}; - -/** - * Rotates three topmost d-stack items upwards/to the right. - * - * ( x y z -- z x y ) - * - * @param ctx - */ -export const invrot = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 1; - $n(n, 2); - const c = stack[n]; - stack[n] = stack[n - 1]; - stack[n - 1] = stack[n - 2]; - stack[n - 2] = c; - return ctx; -}; - -/** - * Pushes copy of TOS-1 as new TOS on d-stack. - * - * ( x y -- x y x ) - * - * @param ctx - */ -export const over = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack.push(stack[n]); - return ctx; -}; - -//////////////////// R-Stack ops //////////////////// - -/** - * Pushes current r-stack size on d-stack. - * - * ( -- n ) - * - * @param ctx - */ -export const rsp = (ctx: StackContext) => (ctx[0].push(ctx[1].length), ctx); - -/** - * Removes TOS from r-stack. - * - * ( x -- ) - * - * @param ctx - */ -export const rdrop = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[1].length--, ctx -); - -/** - * Removes top 2 vals from r-stack. - * - * ( x y -- ) - * - * @param ctx - */ -export const rdrop2 = (ctx: StackContext) => ( - $(ctx[1], 2), (ctx[1].length -= 2), ctx -); - -export const movdr = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[1].push(ctx[0].pop()), ctx -); - -export const movrd = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[0].push(ctx[1].pop()), ctx -); - -export const cpdr = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[1].push(tos(ctx[0])), ctx -); - -export const cprd = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[0].push(tos(ctx[1])), ctx -); - -const mov2 = (a: number, b: number) => (ctx: StackContext) => { - const src = ctx[a]; - $(src, 2); - const v = src.pop(); - ctx[b].push(src.pop(), v); - return ctx; -}; - -const cp2 = (a: number, b: number) => (ctx: StackContext) => { - const src = ctx[a]; - const n = src.length - 2; - $n(n, 0); - ctx[b].push(src[n], src[n + 1]); - return ctx; -}; - -export const movdr2 = mov2(0, 1); -export const movrd2 = mov2(1, 0); -export const cpdr2 = cp2(0, 1); -export const cprd2 = cp2(1, 0); - -/** - * Swaps the two topmost r-stack items. - * - * ( x y -- y x ) - * - * @param ctx - */ -export const rswap = _swap(1); - -/** - * Swaps the two topmost d-stack pairs. - * - * ( a b c d -- c d a b ) - * - * @param ctx - */ -export const rswap2 = _swap2(1); - -/** - * Like `inc`, but applies to r-stack TOS. - * - * @param ctx - */ -export const rinc = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[1][ctx[1].length - 1]++, ctx -); - -/** - * Like `dec`, but applies to r-stack TOS. - * - * @param ctx - */ -export const rdec = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[1][ctx[1].length - 1]--, ctx -); - -//////////////////// Math ops //////////////////// - -/** - * ( x y -- x+y ) - * - * @param ctx - */ -export const add = op2((b, a) => a + b); - -/** - * ( x y -- x*y ) - * - * @param ctx - */ -export const mul = op2((b, a) => a * b); - -/** - * ( x y -- x-y ) - * - * @param ctx - */ -export const sub = op2((b, a) => a - b); - -/** - * ( x y -- x/y ) - * - * @param ctx - */ -export const div = op2((b, a) => a / b); - -/** - * ( x -- 1/x ) - * - * @param ctx - */ -export const oneover = word([1, swap, div]); - -/** - * ( x y -- x%y ) - * - * @param ctx - */ -export const mod = op2((b, a) => a % b); - -/** - * ( x y -- min(x,y) ) - * - * @param ctx - */ -export const min = op2(Math.min); - -/** - * ( x y -- max(x,y) ) - * - * @param ctx - */ -export const max = op2(Math.max); - -/** - * ( x -- x+1 ) - * - * @param ctx - */ -export const inc = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[0][ctx[0].length - 1]++, ctx -); - -/** - * ( x -- x-1 ) - * - * @param ctx - */ -export const dec = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[0][ctx[0].length - 1]--, ctx -); - -/** - * ( x -- -x ) - * - * @param ctx - */ -export const neg = op1((x) => -x); - -/** - * ( x y -- pow(x,y) ) - * - * @param ctx - */ -export const pow = op2((b, a) => Math.pow(a, b)); - -/** - * ( x -- sqrt(x) ) - * - * @param ctx - */ -export const sqrt = op1(Math.sqrt); - -/** - * ( x -- exp(x) ) - * - * @param ctx - */ -export const exp = op1(Math.exp); - -/** - * ( x -- log(x) ) - * - * @param ctx - */ -export const log = op1(Math.log); - -/** - * ( x -- sin(x) ) - * - * @param ctx - */ -export const sin = op1(Math.sin); - -/** - * ( x -- cos(x) ) - * - * @param ctx - */ -export const cos = op1(Math.cos); - -/** - * ( x -- tan(x) ) - * - * @param ctx - */ -export const tan = op1(Math.tan); - -/** - * ( x -- tanh(x) ) - * - * @param ctx - */ -export const tanh = op1(Math.tanh); - -/** - * ( x -- floor(x) ) - * - * @param ctx - */ -export const floor = op1(Math.floor); - -/** - * ( x -- ceil(x) ) - * - * @param ctx - */ -export const ceil = op1(Math.ceil); - -/** - * ( x y -- sqrt(x*x+y*y) ) - * - * @param ctx - */ -export const hypot = op2(Math.hypot); - -/** - * ( x y -- atan2(y,x) ) - * - * @param ctx - */ -export const atan2 = op2(Math.atan2); - -/** - * ( -- Math.random() ) - * - * @param ctx - */ -export const rand = (ctx: StackContext) => (ctx[0].push(Math.random()), ctx); - -/** - * ( x -- bool ) - * - * @param ctx - */ -export const even = op1((x) => !(x & 1)); - -/** - * ( x -- bool ) - * - * @param ctx - */ -export const odd = op1((x) => !!(x & 1)); - -//////////////////// Binary ops //////////////////// - -/** - * ( x y -- x&y ) - * - * @param ctx - */ -export const bitand = op2((b, a) => a & b); - -/** - * ( x y -- x|y ) - * - * @param ctx - */ -export const bitor = op2((b, a) => a | b); - -/** - * ( x y -- x^y ) - * - * @param ctx - */ -export const bitxor = op2((b, a) => a ^ b); - -/** - * ( x -- ~x ) - * - * @param ctx - */ -export const bitnot = op1((x) => ~x); - -/** - * ( x y -- x< a << b); - -/** - * ( x y -- x>>y ) - * - * @param ctx - */ -export const lsr = op2((b, a) => a >> b); - -/** - * ( x y -- x>>>y ) - * - * @param ctx - */ -export const lsru = op2((b, a) => a >>> b); - -//////////////////// Logic ops //////////////////// - -/** - * ( x y -- x===y ) - * - * @param ctx - */ -export const eq = op2((b, a) => a === b); - -/** - * ( x y -- equiv(x,y) ) - * - * @param ctx - */ -export const equiv = op2(_equiv); - -/** - * ( x y -- x!==y ) - * - * @param ctx - */ -export const neq = op2((b, a) => a !== b); - -/** - * ( x y -- x&&y ) - * - * @param ctx - */ -export const and = op2((b, a) => !!a && !!b); - -/** - * ( x y -- x||y ) - * - * @param ctx - */ -export const or = op2((b, a) => !!a || !!b); - -/** - * ( x -- !x ) - * - * @param ctx - */ -export const not = op1((x) => !x); - -/** - * ( x y -- x a < b); - -/** - * ( x y -- x>y ) - * - * @param ctx - */ -export const gt = op2((b, a) => a > b); - -/** - * ( x y -- x<=y ) - * - * @param ctx - */ -export const lteq = op2((b, a) => a <= b); - -/** - * ( x y -- x>=y ) - * - * @param ctx - */ -export const gteq = op2((b, a) => a >= b); - -/** - * ( x -- x===0 ) - * - * @param ctx - */ -export const iszero = op1((x) => x === 0); - -/** - * ( x -- x>0 ) - * - * @param ctx - */ -export const ispos = op1((x) => x > 0); - -/** - * ( x -- x<0 ) - * - * @param ctx - */ -export const isneg = op1((x) => x < 0); - -/** - * ( x -- x==null ) - * - * @param ctx - */ -export const isnull = op1((x) => x == null); - -//////////////////// Dataflow combinators //////////////////// - -// these combinators have been ported from Factor: -// http://docs.factorcode.org:8080/content/article-dataflow-combinators.html - -/** - * Removes `x` from d-stack, calls `q` and restores `x` to the top of - * the d-stack after quotation is finished. - * - * ( x q -- x ) - * - * Same behavior as: `[swap, movdr, exec, movrd]`, only the current - * implementation doesn't use r-stack and stashes `x` off stack. - * - * @param ctx - */ -export const dip = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 2); - const q = stack.pop(); - const x = stack.pop(); - ctx = $stackFn(q)(ctx); - ctx[0].push(x); - return ctx; -}; - -/** - * Removes `x y` from d-stack, calls `q` and restores removed vals - * to the top of the d-stack after quotation is finished. - * - * ( x y q -- x y ) - */ -export const dip2 = word([swap, [dip], dip]); - -/** - * Removes `x y z` from d-stack, calls `q` and restores removed - * vals to the top of the d-stack after quotation is finished. - * - * ( x y z q -- x y z ) - */ -export const dip3 = word([swap, [dip2], dip]); - -/** - * Removes `x y z w` from d-stack, calls `q` and restores removed - * vals to the top of the d-stack after quotation is finished. - * - * ( x y z w q -- x y z w ) - */ -export const dip4 = word([swap, [dip3], dip]); - -/** - * Calls a quotation with a value on the d-stack, restoring the value - * after quotation finished. - * - * ( x q -- .. x ) - */ -export const keep = word([over, [exec], dip]); - -/** - * Call a quotation with two values on the stack, restoring the values - * after quotation finished. - * - * ( x y q -- .. x y ) - */ -export const keep2 = word([[dup2], dip, dip2]); - -/** - * Call a quotation with three values on the stack, restoring the values - * after quotation finished. - * - * ( x y z q -- .. x y z ) - */ -export const keep3 = word([[dup3], dip, dip3]); - -/** - * First applies `p` to the value `x`, then applies `q` to the same - * value. - * - * ( x p q -- px qx ) - */ -export const bi = word([[keep], dip, exec]); - -/** - * First applies `p` to the two input values `x y`, then applies `q` to - * the same values. - * - * ( x y p q -- pxy qxy ) - */ -export const bi2 = word([[keep2], dip, exec]); - -/** - * First applies `p` to the three input values `x y z`, then applies `q` - * to the same values. - * - * ( x y z p q -- pxyz qxyz ) - */ -export const bi3 = word([[keep3], dip, exec]); - -/** - * Applies `p` to `x`, then `q` to `x`, and finally `r` to `x` - * - * ( x p q r -- px qx rx ) - */ -export const tri = word([[[keep], dip, keep], dip, exec]); - -/** - * Applies `p` to the two input values `x y`, then same with `q`, and - * finally with `r`. - * - * ( x y p q r -- pxy qxy rxy ) - */ -export const tri2 = word([[[keep2], dip, keep2], dip, exec]); - -/** - * Applies `p` to the three input values `x y z`, then same with `q`, - * and finally with `r`. - * - * ( x y z p q r -- pxyz qxyz rxyz ) - */ -export const tri3 = word([[[keep3], dip, keep3], dip, exec]); - -/** - * Applies `p` to `x`, then applies `q` to `y`. - * - * ( x y p q -- px qy ) - */ -export const bis = word([[dip], dip, exec]); - -/** - * Applies `p` to `a b`, then applies `q` to `c d`. - * - * ( a b c d p q -- pab qcd ) - */ -export const bis2 = word([[dip2], dip, exec]); - -/** - * Applies `p` to `x`, then `q` to `y`, and finally `r` to `z`. - * - * ( x y z p q r -- ) - */ -export const tris = word([[[dip2], dip, dip], dip, exec]); - -/** - * Applies `p` to `u v`, then `q` to `w x`, and finally `r` to `y z`. - * - * ( u v w x y z p q r -- puv qwx ryz ) - */ -export const tris2 = word([[dip4], dip2, bis2]); - -/** - * Applies the quotation `q` to `x`, then to `y`. - * - * ( x y q -- qx qy ) - */ -export const bia = word([dup, bis]); - -/** - * Applies the quotation `q` to `x y`, then to `z w`. - * - * ( x y z w q -- qxy qzw ) - */ -export const bia2 = word([dup, bis2]); - -/** - * Applies the `q` to `x`, then to `y`, and finally to `z`. - * - * ( x y z q -- qx qy qz ) - */ -export const tria = word([dup, dup, tris]); - -/** - * Applies the quotation to `u v`, then to `w x`, and then to `y z`. - * - * ( u v w x y z q -- quv qwx qyz ) - */ -export const tria2 = word([dup, dup, tris2]); - -/** - * Applies `q` individually to both input vals `x y` and combines - * results with `and`. The final result will be true if both interim - * results were truthy. - * - * ( x y q -- qx && qy ) - */ -export const both = word([bia, and]); - -/** - * Applies `q` individually to both input vals `x y` and combines results with `or`. - * The final result will be true if at least one of the interim results - * was truthy. - * - * ( x y q -- qx || qy ) - */ -export const either = word([bia, or]); - -//////////////////// Conditionals //////////////////// - -/** - * Higher order word. Takes two stack programs: truthy and falsey - * branches, respectively. When executed, pops TOS and runs only one of - * the branches depending if TOS was truthy or not. - * - * Note: Unlike JS `if() {...} else {...}` constructs, the actual - * conditional is NOT part of this word. - * - * ( bool -- ? ) - * - * @param _then - * @param _else - */ -export const cond = (_then: StackProc, _else: StackProc = nop) => ( - ctx: StackContext -) => ($(ctx[0], 1), $stackFn(ctx[0].pop() ? _then : _else)(ctx)); - -/** - * Non-HOF version of `cond`, expects `test` result and both branches on - * d-stack. Executes `thenq` word/quotation if `test` is truthy, else - * runs `elseq`. - * - * ( test thenq elseq -- ? ) - * - * @param ctx - */ -export const condq = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 3); - const _else = stack.pop(); - const _then = stack.pop(); - return $stackFn(stack.pop() ? _then : _else)(ctx); -}; - -/** - * Higher order word. Takes an object of stack programs with keys in the - * object being used to check for equality with TOS. If a match is - * found, executes corresponding stack program. If a `default` key is - * specified and no other cases matched, run `default` program. In all - * other cases throws an error. - * - * Important: The default case has the original TOS re-added to the - * d-stack before execution. - * - * @param cases - */ -export const cases = (cases: IObjectOf) => (ctx: StackContext) => { - $(ctx[0], 1); - const stack = ctx[0]; - const tos = stack.pop(); - const cas = cases[tos]; - if (cas !== undefined) { - return $stackFn(cas)(ctx); - } - if (cases.default) { - stack.push(tos); - return $stackFn(cases.default)(ctx); - } - illegalState(`no matching case for: ${tos}`); - return ctx; -}; - -export const casesq = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 2); - return cases(stack.pop())(ctx); -}; - -//////////////////// Loop constructs //////////////////// - -/** - * Higher order word. Takes a `test` and `body` stack program. Applies - * test to copy of TOS and executes body. Repeats while test is truthy. - * - * ( -- ? ) - * - * ``` - * run([loop([dup, ispos], [dup, print, dec])], [[3]]) - * // 3 - * // 2 - * // 1 - * // [ true, [ 0 ], undefined ] - * ``` - * @param test - * @param body - */ -export const loop = (test: StackProc, body: StackProc) => { - const _test = $stackFn(test); - const _body = $stackFn(body); - return (ctx: StackContext) => { - while (true) { - ctx = _test(ctx); - $(ctx[0], 1); - if (!ctx[0].pop()) { - return ctx; - } - ctx = _body(ctx); - } - }; -}; - -/** - * Non-HOF version of `loop`. Expects test result and body quotation / - * word on d-stack. - * - * ( testq bodyq -- ? ) - * - * @param ctx - */ -export const loopq = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 2); - const body = stack.pop(); - return loop(stack.pop(), body)(ctx); -}; - -/** - * Executes given `body` word/quotation `n` times. In each iteration - * pushes current counter on d-stack prior to executing body. - * - * ``` - * pf.run([3, ["i=", pf.swap, pf.add, pf.print], pf.dotimes]) - * // i=0 - * // i=1 - * // i=2 - * ``` - * - * With empty body acts as finite range generator 0 .. n: - * - * ``` - * // range gen - * pf.run([3, [], pf.dotimes]) - * [ [ 0, 1, 2 ], [], {} ] - * - * // range gen (collect results as array) - * pf.runU([3, pf.cpdr, [], pf.dotimes, pf.movrd, pf.collect]) - * // [ 0, 1, 2 ] - * ``` - * - * ( n body -- ? ) - * - * @param body - */ -export const dotimes = (ctx: StackContext) => { - let stack = ctx[0]; - $(stack, 2); - const w = $stackFn(stack.pop()); - for (let i = 0, n = stack.pop(); i < n; i++) { - ctx[0].push(i); - ctx = w(ctx); - } - return ctx; -}; - -//////////////////// Array / list ops //////////////////// - -/** - * Pushes a new empty array on the d-stack. While it's easily possible to - * use `[]` as part of a stack program, the `list` word is intended to - * be used as part of re-usuable `word()` definitions to ensure a new - * array is being created for every single invocation of the word (else - * only a single instance is created due to the mutable nature of JS - * arrays). - * - * Compare: - * - * ``` - * // using array literal within word definition - * foo = pf.word([ [], 1, pf.pushl ]) - * pf.runU(foo) - * // [ 1 ] - * pf.runU(foo) - * // [ 1, 1 ] // wrong! - * - * // using `list` instead - * bar = pf.word([ pf.list, 1, pf.pushl ]) - * pf.runU(bar) - * // [ 1 ] - * pf.runU(bar) - * // [ 1 ] // correct! - * ``` - * - * ( -- [] ) - * - * @param ctx - */ -export const list = (ctx: StackContext) => (ctx[0].push([]), ctx); - -/** - * Pushes new empty JS object on d-stack. - * Same reasoning as for `list`. - * - * ( -- {} ) - * - * @param ctx - */ -export const obj = (ctx: StackContext) => (ctx[0].push({}), ctx); - -/** - * Pushes `val` on the LHS of array. - * - * ( val arr -- arr ) - * - * @param ctx - */ -export const pushl = (ctx: StackContext) => { - $(ctx[0], 2); - const stack = ctx[0]; - const a = stack.pop(); - a.unshift(stack.pop()); - stack.push(a); - return ctx; -}; - -/** - * Pushes `val` on the RHS of array. - * - * ( arr val -- arr ) - * - * @param ctx - */ -export const pushr = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack[n].push(stack[n + 1]); - stack.length--; - return ctx; -}; - -/** - * Removes RHS from array as new TOS on d-stack. - * Throws error is `arr` is empty. - * - * ( arr -- arr arr[-1] ) - * - * @param ctx - */ -export const popr = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 1; - $n(n, 0); - const a = stack[n]; - !a.length && illegalState("can't pop empty array"); - stack.push(a.pop()); - return ctx; -}; - -export const pull = word([popr, swap]); -export const pull2 = word([pull, pull]); -export const pull3 = word([pull2, pull]); -export const pull4 = word([pull2, pull2]); - -export const vadd = op2v((b, a) => a + b); -export const vsub = op2v((b, a) => a - b); -export const vmul = op2v((b, a) => a * b); -export const vdiv = op2v((b, a) => a / b); - -/** - * Splits vector / array at given index `x`. - * - * ( arr x -- [...] [...] ) - * - * @param ctx - */ -export const split = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - const a = stack[n]; - const b = stack[n + 1]; - stack[n + 1] = a.splice(b, a.length - b); - return ctx; -}; - -/** - * Concatenates two arrays on d-stack: - * - * ( arr1 arr2 -- arr ) - * - * @param ctx - */ -export const cat = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack[n] = stack[n].concat(stack.pop()); - return ctx; -}; - -/** - * Generic array transformer. - * - * ( arr q -- ? ) - * - * Pops both args from d-stack, then executes quotation for each array - * item (each pushed on d-stack prior to calling quotation). Can produce - * any number of results and therefore also be used as filter, mapcat, - * reduce... - * - * ``` - * // each item times 10 - * run([[1, 2, 3, 4], [10, mul], mapl]) - * // [ [ 10, 20, 30, 40 ], [], {} ] - * ``` - * - * Use for filtering: - * - * ``` - * // drop even numbers, duplicate odd ones - * run([[1, 2, 3, 4], [dup, even, cond(drop, dup)], mapl]) - * // [ [ 1, 1, 3, 3 ], [], {} ] - * ``` - * - * Reduction: - * - * ``` - * // the `0` is the initial reduction result - * runU([0, [1, 2, 3, 4], [add], mapl]) - * // 10 - * ``` - * - * **Important**: `mapl` does not produce a result array. However, - * there're several options to collect results as array, e.g. - * - * Use `mapll()` to transform: - * - * ``` - * runU([[1, 2, 3, 4], [10, mul], mapll]) - * // [ 10, 20, 30, 40] - * ``` - * - * Collecting results as array is a form of reduction, so we can use - * `list` to produce an initial new array and `pushr` to push each new - * interim value into the result: - * - * ``` - * runU([list, [1, 2, 3, 4], [10, mul, pushr], mapl]) - * // [ 10, 20, 30, 40 ] - * ``` - * - * If the array size is known & not changed by transformation: - * - * ``` - * runU([[1, 2, 3, 4], [10, mul], mapl, 4, collect]) - * // [ 10, 20, 30, 40 ] - * ``` - * - * @param ctx - */ -export const mapl = (ctx: StackContext) => { - $(ctx[0], 2); - const stack = ctx[0]; - const w = $stackFn(stack.pop()); - const list = stack.pop(); - const n = list.length; - for (let i = 0; i < n; i++) { - ctx[0].push(list[i]); - ctx = w(ctx); - } - return ctx; -}; - -/** - * Similar to `mapl()`, but produces new array of transformed values. - * - * ( arr q -- arr ) - * - * ``` - * runU([[1, 2, 3, 4], [10, mul], mapll]) - * // [ 10, 20, 30, 40] - * ``` - * - * Filter / mapcat: - * - * ``` - * // drop even numbers, duplicate odd ones - * run([[1, 2, 3, 4], [dup, even, cond(drop, dup)], mapll]) - * // [ [ [ 1, 1, 3, 3 ] ], [], {} ] - * ``` - * - * @param ctx - */ -export const mapll = (ctx: StackContext) => { - $(ctx[0], 2); - let stack = ctx[0]; - const w = $stackFn(stack.pop()); - const list = stack.pop(); - const n = list.length; - let r = 0; - for (let i = 0; i < n; i++) { - let m = stack.length; - stack.push(list[i]); - ctx = w(ctx); - stack = ctx[0]; - r += stack.length - m; - } - stack.push(stack.splice(stack.length - r, r)); - return ctx; -}; - -/** - * Convenience wrapper for `mapl` to provide an alternative stack layout - * for reduction purposes: - * - * ( arr q init -- reduction ) - */ -export const foldl = word([invrot, mapl]); - -/** - * Pops TOS (a number) and then forms a tuple of the top `n` remaining - * values and pushes it as new TOS. The original collected stack values - * are removed from d-stack. - * - * ( ... n --- ... [...] ) - * - * @param ctx - */ -export const collect = (ctx: StackContext) => { - const stack = ctx[0]; - let n = stack.length - 1, - m; - $n(n, 0); - $n((n -= m = stack.pop()), 0); - stack.push(stack.splice(n, m)); - return ctx; -}; - -/** - * Higher order helper word to `collect()` tuples of pre-defined size - * `n`. The size can be given as number or a stack function producing a - * number. - * - * ( ... -- [...]) - * - * @param n - */ -export const tuple = (n: number | StackFn) => word([n, collect]); - -export const vec2 = tuple(2); -export const vec3 = tuple(3); -export const vec4 = tuple(4); - -/** - * Higher order helper word to convert a TOS tuple/array into a string - * using `Array.join()` with given `sep`arator. - * - * @param sep - */ -export const join = (sep = "") => op1((x) => x.join(sep)); - -/** - * Pushes length of TOS on d-stack. - * - * ( x -- x.length ) - * - * @param ctx - */ -export const length = op1((x) => x.length); - -/** - * Replaces TOS with its shallow copy. MUST be an array or plain object. - * - * ( x -- copy ) - */ -export const copy = op1((x) => - isArray(x) - ? [...x] - : isPlainObject(x) - ? { ...x } - : illegalArgs(`can't copy type ${typeof x}`) -); - -/** - * Reads key/index from object/array. - * - * ( obj k -- obj[k] ) - * - * @param ctx - */ -export const at = op2((b, a) => a[b]); - -/** - * Writes `val` at key/index in object/array. - * - * ( val obj k -- obj ) - * - * @param ctx - */ -export const setat = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 3; - $n(n, 0); - stack[n + 1][stack[n + 2]] = stack[n]; - stack[n] = stack[n + 1]; - stack.length -= 2; - return ctx; -}; - -//////////////////// Objects //////////////////// - -/** - * Takes an array of keys and target object, then pops & binds deeper - * stack values to respective keys in object. Pushes result object back - * on stack at the end. Throws error if there're less stack values than - * keys in given array. - * - * ``` - * runU([1,2,3, ["a","b","c"], {}, bindkeys]) - * // { c: 3, b: 2, a: 1 } - * ``` - * - * (v1 v2 .. [k1 k2 ..] obj -- obj ) - * - * @param ctx - */ -export const bindkeys = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 2); - const obj = stack.pop(); - const keys = stack.pop(); - $(stack, keys.length); - for (let i = keys.length - 1; i >= 0; i--) { - obj[keys[i]] = stack.pop(); - } - stack.push(obj); - return ctx; -}; - -//////////////////// Environment //////////////////// - -/** - * Pushes current env onto d-stack. - * - * ( -- env ) - * - * @param ctx - * @param env - */ -export const pushenv = (ctx: StackContext) => (ctx[0].push(ctx[2]), ctx); - -/** - * Loads value for `key` from current env and pushes it on d-stack. - * Throws error if var doesn't exist. - * - * ( key -- env[key] ) - * - * @param ctx - * @param env - */ -export const load = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 1); - const id = stack.pop(); - !ctx[2].hasOwnProperty(id) && illegalArgs(`unknown var: ${id}`); - stack.push(ctx[2][id]); - return ctx; -}; - -/** - * Stores `val` under `key` in env. - * - * ( val key -- ) - * - * @param ctx - * @param env - */ -export const store = (ctx: StackContext) => ( - $(ctx[0], 2), (ctx[2][ctx[0].pop()] = ctx[0].pop()), ctx -); - -/** - * Higher order word. Similar to `load`, but always uses given - * preconfigured `key` instead of reading it from d-stack at runtime - * (also slightly faster). Throws error if var doesn't exist. - * - * ( -- env[key] ) - * @param ctx - * @param env - */ -export const loadkey = (key: PropertyKey) => (ctx: StackContext) => { - !ctx[2].hasOwnProperty(key) && - illegalArgs(`unknown var: ${key.toString()}`); - ctx[0].push(ctx[2][key]); - return ctx; -}; - -/** - * Higher order word. Similar to `store`, but always uses given - * preconfigure `key` instead of reading it from d-stack at runtime - * (also slightly faster). - * - * ( val -- ) - * - * @param ctx - * @param env - */ -export const storekey = (key: PropertyKey) => (ctx: StackContext) => ( - $(ctx[0], 1), (ctx[2][key] = ctx[0].pop()), ctx -); - -//////////////////// I/O //////////////////// - -/** - * Prints TOS to console - * - * ( x -- ) - * - * @param ctx - */ -export const print = (ctx: StackContext) => ( - $(ctx[0], 1), console.log(ctx[0].pop()), ctx -); - -export const printds = (ctx: StackContext) => (console.log(ctx[0]), ctx); - -export const printrs = (ctx: StackContext) => (console.log(ctx[1]), ctx); - export * from "./api"; - -export { $ as ensureStack, $n as ensureStackN }; +export * from "./array"; +export * from "./binary"; +export * from "./cond"; +export * from "./context"; +export * from "./dataflow"; +export * from "./env"; +export * from "./io"; +export * from "./logic"; +export * from "./loop"; +export * from "./math"; +export * from "./run"; +export * from "./safe"; +export * from "./stack"; +export * from "./word"; + +export { $ as ensureStack, $n as ensureStackN } from "./safe"; +export { op1 as maptos, op2 as map2, op2v } from "./ops"; diff --git a/packages/pointfree/src/io.ts b/packages/pointfree/src/io.ts new file mode 100644 index 0000000000..f43486ab1e --- /dev/null +++ b/packages/pointfree/src/io.ts @@ -0,0 +1,19 @@ +import { StackContext } from "./api"; +import { $ } from "./safe"; + +//////////////////// I/O //////////////////// + +/** + * Prints TOS to console + * + * ( x -- ) + * + * @param ctx + */ +export const print = (ctx: StackContext) => ( + $(ctx[0], 1), console.log(ctx[0].pop()), ctx +); + +export const printds = (ctx: StackContext) => (console.log(ctx[0]), ctx); + +export const printrs = (ctx: StackContext) => (console.log(ctx[1]), ctx); diff --git a/packages/pointfree/src/logic.ts b/packages/pointfree/src/logic.ts new file mode 100644 index 0000000000..0aaf4cd403 --- /dev/null +++ b/packages/pointfree/src/logic.ts @@ -0,0 +1,102 @@ +import { equiv as _equiv } from "@thi.ng/equiv"; +import { op1, op2 } from "./ops"; + +//////////////////// Logic ops //////////////////// + +/** + * ( x y -- x===y ) + * + * @param ctx + */ +export const eq = op2((b, a) => a === b); + +/** + * ( x y -- equiv(x,y) ) + * + * @param ctx + */ +export const equiv = op2(_equiv); + +/** + * ( x y -- x!==y ) + * + * @param ctx + */ +export const neq = op2((b, a) => a !== b); + +/** + * ( x y -- x&&y ) + * + * @param ctx + */ +export const and = op2((b, a) => !!a && !!b); + +/** + * ( x y -- x||y ) + * + * @param ctx + */ +export const or = op2((b, a) => !!a || !!b); + +/** + * ( x -- !x ) + * + * @param ctx + */ +export const not = op1((x) => !x); + +/** + * ( x y -- x a < b); + +/** + * ( x y -- x>y ) + * + * @param ctx + */ +export const gt = op2((b, a) => a > b); + +/** + * ( x y -- x<=y ) + * + * @param ctx + */ +export const lteq = op2((b, a) => a <= b); + +/** + * ( x y -- x>=y ) + * + * @param ctx + */ +export const gteq = op2((b, a) => a >= b); + +/** + * ( x -- x===0 ) + * + * @param ctx + */ +export const iszero = op1((x) => x === 0); + +/** + * ( x -- x>0 ) + * + * @param ctx + */ +export const ispos = op1((x) => x > 0); + +/** + * ( x -- x<0 ) + * + * @param ctx + */ +export const isneg = op1((x) => x < 0); + +/** + * ( x -- x==null ) + * + * @param ctx + */ +export const isnull = op1((x) => x == null); diff --git a/packages/pointfree/src/loop.ts b/packages/pointfree/src/loop.ts new file mode 100644 index 0000000000..f650a4cf5a --- /dev/null +++ b/packages/pointfree/src/loop.ts @@ -0,0 +1,89 @@ +import { StackContext, StackProc } from "./api"; +import { $ } from "./safe"; +import { $stackFn } from "./word"; + +//////////////////// Loop constructs //////////////////// + +/** + * Higher order word. Takes a `test` and `body` stack program. Applies + * test to copy of TOS and executes body. Repeats while test is truthy. + * + * ( -- ? ) + * + * ``` + * run([loop([dup, ispos], [dup, print, dec])], [[3]]) + * // 3 + * // 2 + * // 1 + * // [ true, [ 0 ], undefined ] + * ``` + * @param test + * @param body + */ +export const loop = (test: StackProc, body: StackProc) => { + const _test = $stackFn(test); + const _body = $stackFn(body); + return (ctx: StackContext) => { + while (true) { + ctx = _test(ctx); + $(ctx[0], 1); + if (!ctx[0].pop()) { + return ctx; + } + ctx = _body(ctx); + } + }; +}; + +/** + * Non-HOF version of `loop`. Expects test result and body quotation / + * word on d-stack. + * + * ( testq bodyq -- ? ) + * + * @param ctx + */ +export const loopq = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 2); + const body = stack.pop(); + return loop(stack.pop(), body)(ctx); +}; + +/** + * Executes given `body` word/quotation `n` times. In each iteration + * pushes current counter on d-stack prior to executing body. + * + * ``` + * pf.run([3, ["i=", pf.swap, pf.add, pf.print], pf.dotimes]) + * // i=0 + * // i=1 + * // i=2 + * ``` + * + * With empty body acts as finite range generator 0 .. n: + * + * ``` + * // range gen + * pf.run([3, [], pf.dotimes]) + * [ [ 0, 1, 2 ], [], {} ] + * + * // range gen (collect results as array) + * pf.runU([3, pf.cpdr, [], pf.dotimes, pf.movrd, pf.collect]) + * // [ 0, 1, 2 ] + * ``` + * + * ( n body -- ? ) + * + * @param body + */ +export const dotimes = (ctx: StackContext) => { + let stack = ctx[0]; + $(stack, 2); + const w = $stackFn(stack.pop()); + for (let i = 0, n = stack.pop(); i < n; i++) { + ctx[0].push(i); + ctx = w(ctx); + } + return ctx; +}; diff --git a/packages/pointfree/src/math.ts b/packages/pointfree/src/math.ts new file mode 100644 index 0000000000..28ae436b75 --- /dev/null +++ b/packages/pointfree/src/math.ts @@ -0,0 +1,174 @@ +import { StackContext } from "./api"; +import { op1, op2 } from "./ops"; +import { swap } from "./stack"; +import { word } from "./word"; + +//////////////////// Math ops //////////////////// + +/** + * ( x y -- x+y ) + * + * @param ctx + */ +export const add = op2((b, a) => a + b); + +/** + * ( x y -- x*y ) + * + * @param ctx + */ +export const mul = op2((b, a) => a * b); + +/** + * ( x y -- x-y ) + * + * @param ctx + */ +export const sub = op2((b, a) => a - b); + +/** + * ( x y -- x/y ) + * + * @param ctx + */ +export const div = op2((b, a) => a / b); + +/** + * ( x -- 1/x ) + * + * @param ctx + */ +export const oneover = word([1, swap, div]); + +/** + * ( x y -- x%y ) + * + * @param ctx + */ +export const mod = op2((b, a) => a % b); + +/** + * ( x y -- min(x,y) ) + * + * @param ctx + */ +export const min = op2(Math.min); + +/** + * ( x y -- max(x,y) ) + * + * @param ctx + */ +export const max = op2(Math.max); + +/** + * ( x -- -x ) + * + * @param ctx + */ +export const neg = op1((x) => -x); + +/** + * ( x y -- pow(x,y) ) + * + * @param ctx + */ +export const pow = op2((b, a) => Math.pow(a, b)); + +/** + * ( x -- sqrt(x) ) + * + * @param ctx + */ +export const sqrt = op1(Math.sqrt); + +/** + * ( x -- exp(x) ) + * + * @param ctx + */ +export const exp = op1(Math.exp); + +/** + * ( x -- log(x) ) + * + * @param ctx + */ +export const log = op1(Math.log); + +/** + * ( x -- sin(x) ) + * + * @param ctx + */ +export const sin = op1(Math.sin); + +/** + * ( x -- cos(x) ) + * + * @param ctx + */ +export const cos = op1(Math.cos); + +/** + * ( x -- tan(x) ) + * + * @param ctx + */ +export const tan = op1(Math.tan); + +/** + * ( x -- tanh(x) ) + * + * @param ctx + */ +export const tanh = op1(Math.tanh); + +/** + * ( x -- floor(x) ) + * + * @param ctx + */ +export const floor = op1(Math.floor); + +/** + * ( x -- ceil(x) ) + * + * @param ctx + */ +export const ceil = op1(Math.ceil); + +/** + * ( x y -- sqrt(x*x+y*y) ) + * + * @param ctx + */ +export const hypot = op2(Math.hypot); + +/** + * ( x y -- atan2(y,x) ) + * + * @param ctx + */ +export const atan2 = op2(Math.atan2); + +/** + * ( -- Math.random() ) + * + * @param ctx + */ +export const rand = (ctx: StackContext) => (ctx[0].push(Math.random()), ctx); + +/** + * ( x -- bool ) + * + * @param ctx + */ +export const even = op1((x) => !(x & 1)); + +/** + * ( x -- bool ) + * + * @param ctx + */ +export const odd = op1((x) => !!(x & 1)); diff --git a/packages/pointfree/src/ops.ts b/packages/pointfree/src/ops.ts new file mode 100644 index 0000000000..38a2d901e5 --- /dev/null +++ b/packages/pointfree/src/ops.ts @@ -0,0 +1,96 @@ +import { Fn, Fn2 } from "@thi.ng/api"; +import { isArray } from "@thi.ng/checks"; +import { illegalArgs } from "@thi.ng/errors"; +import { StackContext } from "./api"; +import { $, $n } from "./safe"; + +//////////////////// Operator generators //////////////////// + +/** + * Higher order word. Replaces TOS of d-stack with result of given op. + * + * ( x -- y ) + * + * @param op + */ +export const op1 = (op: Fn) => { + return (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 1; + $n(n, 0); + stack[n] = op(stack[n]); + return ctx; + }; +}; + +/** + * Higher order word. Takes 2 values from d-stack and writes back result + * from given op. The arg order is (TOS, TOS-1) + * + * ( a b -- c ) + * + * @param op + */ +export const op2 = (op: Fn2) => (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + stack[n] = op(stack.pop(), stack[n]); + return ctx; +}; + +/** + * Similar to `op2`, but for array operators. Either `a` or `b` can be a + * non-array value, but not both. Creates new array of result values. + * The result will have the same length as the shortest arg (if `a` and + * `b` have different lengths). + * + * - ( a b -- a ), if `a` is an array + * - ( a b -- b ), if `a` is not an array + * + * @param f + */ +export const op2v = (f: Fn2) => ( + ctx: StackContext +): StackContext => { + $(ctx[0], 2); + const stack = ctx[0]; + const b = stack.pop(); + const n = stack.length - 1; + const a = stack[n]; + const isa = isArray(a); + const isb = isArray(b); + stack[n] = + isa && isb + ? op2vAB(f, a, b) + : isb && !isa + ? op2vB(f, a, b) + : isa && !isb + ? op2vA(f, a, b) + : illegalArgs("at least one arg must be an array"); + return ctx; +}; + +const op2vAB = (f: Fn2, a: any, b: any) => { + const res = new Array(Math.min(a.length, b.length)); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = f(b[i], a[i]); + } + return res; +}; + +const op2vA = (f: Fn2, a: any, b: any) => { + const res = new Array(a.length); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = f(b, a[i]); + } + return res; +}; + +const op2vB = (f: Fn2, a: any, b: any) => { + const res = new Array(b.length); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = f(b[i], a); + } + return res; +}; diff --git a/packages/pointfree/src/run.ts b/packages/pointfree/src/run.ts new file mode 100644 index 0000000000..fbad7f3ba3 --- /dev/null +++ b/packages/pointfree/src/run.ts @@ -0,0 +1,52 @@ +import { isArray, isFunction } from "@thi.ng/checks"; +import { StackContext, StackProc } from "./api"; +import { unwrap } from "./word"; + +/** + * Executes program / quotation with given stack context (initial D/R + * stacks and optional environment). Returns updated context. + * + * @param prog + * @param ctx + */ +export const run = ( + prog: StackProc, + ctx: StackContext = [[], [], {}] +): StackContext => { + if (isFunction(prog)) { + return prog(ctx); + } + for ( + let p = isArray(prog) ? prog : [prog], n = p.length, i = 0, w; + i < n; + i++ + ) { + if (isFunction((w = p[i]))) { + ctx = w(ctx); + } else { + ctx[0].push(w); + } + } + return ctx; +}; + +/** + * Like `run()`, but returns unwrapped result. Syntax sugar for: + * `unwrap(run(...),n)` + * + * @param prog + * @param ctx + * @param n + */ +export const runU = (prog: StackProc, ctx?: StackContext, n = 1) => + unwrap(run(prog, ctx), n); + +/** + * Like `run()`, but returns result environment. Syntax sugar for: + * `run(...)[2]` + * + * @param prog + * @param ctx + * @param n + */ +export const runE = (prog: StackProc, ctx?: StackContext) => run(prog, ctx)[2]; diff --git a/packages/pointfree/src/safe.ts b/packages/pointfree/src/safe.ts new file mode 100644 index 0000000000..7ede2ab06c --- /dev/null +++ b/packages/pointfree/src/safe.ts @@ -0,0 +1,18 @@ +import { Fn2, NO_OP } from "@thi.ng/api"; +import { illegalState } from "@thi.ng/errors"; +import { Stack } from "./api"; + +// ensure stack size +export let $: Fn2; +export let $n: Fn2; + +export const safeMode = (state: boolean) => { + if (state) { + $n = (m: number, n: number) => m < n && illegalState(`stack underflow`); + $ = (stack: Stack, n: number) => $n(stack.length, n); + } else { + $ = $n = NO_OP; + } +}; + +safeMode(true); diff --git a/packages/pointfree/src/stack.ts b/packages/pointfree/src/stack.ts new file mode 100644 index 0000000000..d7d6a4c23f --- /dev/null +++ b/packages/pointfree/src/stack.ts @@ -0,0 +1,434 @@ +import { Stack, StackContext } from "./api"; +import { $, $n } from "./safe"; + +const __xsp = (id: 0 | 1) => (ctx: StackContext) => ( + ctx[0].push(ctx[id].length), ctx +); + +const __dup = (id: 0 | 1) => __copy(id, id); + +const __dup2 = (id: 0 | 1) => (ctx: StackContext) => { + const stack = ctx[id]; + let n = stack.length - 2; + $n(n, 0); + stack.push(stack[n], stack[n + 1]); + return ctx; +}; + +const __dup3 = (id: 0 | 1) => (ctx: StackContext) => { + const stack = ctx[id]; + let n = stack.length - 3; + $n(n, 0); + stack.push(stack[n], stack[n + 1], stack[n + 2]); + return ctx; +}; + +const __drop = (id: 0 | 1, n = 1) => (ctx: StackContext) => ( + $(ctx[id], 1), (ctx[id].length -= n), ctx +); + +const __swap = (i: number) => (ctx: StackContext) => { + const stack = ctx[i]; + const n = stack.length - 2; + $n(n, 0); + const a = stack[n]; + stack[n] = stack[n + 1]; + stack[n + 1] = a; + return ctx; +}; + +const __swap2 = (i: number) => (ctx: StackContext) => { + const stack = ctx[i]; + let n = stack.length - 1; + $n(n, 3); + let a = stack[n]; + stack[n] = stack[n - 2]; + stack[n - 2] = a; + n--; + a = stack[n]; + stack[n] = stack[n - 2]; + stack[n - 2] = a; + return ctx; +}; + +const __over = (id: 0 | 1) => (ctx: StackContext) => { + const stack = ctx[id]; + const n = stack.length - 2; + $n(n, 0); + stack.push(stack[n]); + return ctx; +}; + +const __move = (src: 0 | 1, dest: 0 | 1) => (ctx: StackContext) => ( + $(ctx[src], 1), ctx[dest].push(ctx[src].pop()), ctx +); + +const __move2 = (a: 0 | 1, b: 0 | 1) => (ctx: StackContext) => { + const src = ctx[a]; + $(src, 2); + const v = src.pop(); + ctx[b].push(src.pop(), v); + return ctx; +}; + +const __copy = (src: 0 | 1, dest: 0 | 1) => (ctx: StackContext) => ( + $(ctx[src], 1), ctx[dest].push(tos(ctx[src])), ctx +); + +const __copy2 = (a: 0 | 1, b: 0 | 1) => (ctx: StackContext) => { + const src = ctx[a]; + const n = src.length - 2; + $n(n, 0); + ctx[b].push(src[n], src[n + 1]); + return ctx; +}; + +const __incdec = (id: 0 | 1, n: number) => (ctx: StackContext) => ( + $(ctx[id], 1), (ctx[id][ctx[id].length - 1] += n), ctx +); + +//////////////////// Stack manipulation words //////////////////// + +/** + * Returns top of stack value (always unsafe, no underflow checking). + * + * @param stack + */ +export const tos = (stack: Stack) => stack[stack.length - 1]; + +/** + * Utility word w/ no stack nor side effect. + */ +export const nop = (ctx: StackContext) => ctx; + +/** + * Pushes current d-stack size on d-stack. + * + * ( -- n ) + * @param ctx + */ +export const dsp = __xsp(0); + +/** + * Uses TOS as index to look up a deeper d-stack value, then places it + * as new TOS. Throws error if stack depth is < `x`. + * + * ( ... x -- ... stack[x] ) + * + * @param ctx + */ +export const pick = (ctx: StackContext) => { + const stack = ctx[0]; + let n = stack.length - 1; + $n(n, 0); + $n((n -= stack.pop() + 1), 0); + stack.push(stack[n]); + return ctx; +}; + +/** + * Removes TOS from d-stack. + * + * ( x -- ) + * + * @param ctx + */ +export const drop = __drop(0); + +/** + * Removes top 2 vals from d-stack. + * + * ( x y -- ) + * + * @param ctx + */ +export const drop2 = __drop(0, 2); + +/** + * If TOS is truthy then drop it: + * + * ( x -- ) + * + * Else, no effect: + * + * ( x -- x ) + */ +export const dropif = (ctx: StackContext) => ( + $(ctx[0], 1), tos(ctx[0]) && ctx[0].length--, ctx +); + +/** + * Higher order word. Pushes given args verbatim on d-stack. + * + * ( -- ...args ) + * + * @param args + */ +export const push = (...args: any[]) => (ctx: StackContext) => ( + ctx[0].push(...args), ctx +); + +/** + * Duplicates TOS on d-stack. + * + * ( x -- x x ) + * + * @param ctx + */ +export const dup = __dup(0); + +/** + * Duplicates top 2 vals on d-stack. + * + * ( x y -- x y x y ) + * + * @param ctx + */ +export const dup2 = __dup2(0); + +/** + * Duplicates top 3 vals on d-stack. + * + * ( x y -- x y x y ) + * + * @param ctx + */ +export const dup3 = __dup3(0); + +/** + * If TOS is truthy then push copy of it on d-stack: + * + * ( x -- x x ) + * + * Else, no effect: + * + * ( x -- x ) + * + * @param ctx + */ +export const dupif = (ctx: StackContext) => { + $(ctx[0], 1); + const x = tos(ctx[0]); + x && ctx[0].push(x); + return ctx; +}; + +/** + * Swaps the two topmost d-stack items. + * + * ( x y -- y x ) + * + * @param ctx + */ +export const swap = __swap(0); + +/** + * Swaps the two topmost d-stack pairs. + * + * ( a b c d -- c d a b ) + * + * @param ctx + */ +export const swap2 = __swap2(0); + +/** + * Removes second topmost item from d-stack. + * + * ( x y -- y ) + * + * @param ctx + */ +export const nip = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + stack[n] = stack.pop(); + return ctx; +}; + +/** + * Inserts copy of TOS @ TOS-2 in d-stack. + * + * ( x y -- y x y ) + * + * @param ctx + */ +export const tuck = (ctx: StackContext) => { + $(ctx[0], 2); + const stack = ctx[0]; + const a = stack.pop(); + stack.push(a, stack.pop(), a); + return ctx; +}; + +/** + * Rotates three topmost d-stack items downwards/to the left. + * + * ( x y z -- y z x ) + * + * @param ctx + */ +export const rot = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 1; + $n(n, 2); + const c = stack[n - 2]; + stack[n - 2] = stack[n - 1]; + stack[n - 1] = stack[n]; + stack[n] = c; + return ctx; +}; + +/** + * Rotates three topmost d-stack items upwards/to the right. + * + * ( x y z -- z x y ) + * + * @param ctx + */ +export const invrot = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 1; + $n(n, 2); + const c = stack[n]; + stack[n] = stack[n - 1]; + stack[n - 1] = stack[n - 2]; + stack[n - 2] = c; + return ctx; +}; + +/** + * Pushes copy of TOS-1 as new TOS on d-stack. + * + * ( x y -- x y x ) + * + * @param ctx + */ +export const over = __over(0); + +/** + * ( x -- x+1 ) + * + * @param ctx + */ +export const inc = __incdec(0, 1); + +/** + * ( x -- x-1 ) + * + * @param ctx + */ +export const dec = __incdec(0, -1); + +//////////////////// R-Stack ops //////////////////// + +/** + * Pushes current r-stack size on d-stack. + * + * ( -- n ) + * + * @param ctx + */ +export const rsp = __xsp(1); + +/** + * Duplicates TOS on r-stack. + * + * ( x -- x x ) + * + * @param ctx + */ +export const rdup = __dup(1); + +/** + * Duplicates top 2 vals on r-stack. + * + * ( x y -- x y x y ) + * + * @param ctx + */ +export const rdup2 = __dup2(1); + +/** + * Duplicates top 3 vals on r-stack. + * + * ( x y -- x y x y ) + * + * @param ctx + */ +export const rdup3 = __dup3(1); + +/** + * Removes TOS from r-stack. + * + * ( x -- ) + * + * @param ctx + */ +export const rdrop = __drop(1); + +/** + * Removes top 2 vals from r-stack. + * + * ( x y -- ) + * + * @param ctx + */ +export const rdrop2 = __drop(1, 2); + +export const movdr = __move(0, 1); + +export const movrd = __move(1, 0); + +export const cpdr = __copy(0, 1); + +export const cprd = __copy(1, 0); + +export const movdr2 = __move2(0, 1); + +export const movrd2 = __move2(1, 0); + +export const cpdr2 = __copy2(0, 1); + +export const cprd2 = __copy2(1, 0); + +/** + * Swaps the two topmost r-stack items. + * + * ( x y -- y x ) + * + * @param ctx + */ +export const rswap = __swap(1); + +/** + * Swaps the two topmost d-stack pairs. + * + * ( a b c d -- c d a b ) + * + * @param ctx + */ +export const rswap2 = __swap2(1); + +/** + * Pushes copy of TOS-1 as new TOS on r-stack. + * + * ( x y -- x y x ) + * + * @param ctx + */ +export const rover = __over(1); + +/** + * Like `inc`, but applies to r-stack TOS. + * + * @param ctx + */ +export const rinc = __incdec(1, 1); + +/** + * Like `dec`, but applies to r-stack TOS. + * + * @param ctx + */ +export const rdec = __incdec(1, -1); diff --git a/packages/pointfree/src/word.ts b/packages/pointfree/src/word.ts new file mode 100644 index 0000000000..3c3334fc1a --- /dev/null +++ b/packages/pointfree/src/word.ts @@ -0,0 +1,122 @@ +import { isArray, isFunction } from "@thi.ng/checks"; +import { compL } from "@thi.ng/compose"; +import { + StackContext, + StackEnv, + StackFn, + StackProc, + StackProgram +} from "./api"; +import { $ } from "./safe"; +import { tos } from "./stack"; + +export const $stackFn = (f: StackProc) => (isArray(f) ? word(f) : f); + +const compile = (prog: StackProgram) => + compL.apply(null, ( + prog.map((w) => + !isFunction(w) ? (ctx: StackContext) => (ctx[0].push(w), ctx) : w + ) + )); + +/** + * Takes a result tuple returned by `run()` and unwraps one or more + * items from result stack. If no `n` is given, defaults to single value + * (TOS) and returns it as is. Returns an array for all other `n`. + * + * @param result + * @param n + */ +export const unwrap = ([stack]: StackContext, n = 1) => + n === 1 ? tos(stack) : stack.slice(Math.max(0, stack.length - n)); + +//////////////////// Dynamic words & quotations //////////////////// + +/** + * Higher order word. Takes a StackProgram and returns it as StackFn to + * be used like any word. Unknown stack effect. + * + * If the optional `env` is given, uses a shallow copy of that + * environment (one per invocation) instead of the current one passed by + * `run()` at runtime. If `mergeEnv` is true (default), the user + * provided env will be merged with the current env (also shallow + * copies). This is useful in conjunction with `pushenv()` and `store()` + * or `storekey()` to save results of sub procedures in the main env. + * + * Note: The provided (or merged) env is only active within the + * execution scope of the word. + * + * ( ? -- ? ) + * + * @param prog + * @param env + * @param mergeEnv + */ +export const word = (prog: StackProgram, env?: StackEnv, mergeEnv = true) => { + const w: StackFn = compile(prog); + return env + ? mergeEnv + ? (ctx: StackContext) => ( + w([ctx[0], ctx[1], { ...ctx[2], ...env }]), ctx + ) + : (ctx: StackContext) => (w([ctx[0], ctx[1], { ...env }]), ctx) + : w; +}; + +/** + * Like `word()`, but automatically calls `unwrap()` on result context + * to produced unwrapped value/tuple. + * + * **Importatant:** Words defined with this function CANNOT be used as + * part of a larger stack program, only for standalone use. + * + * @param prog + * @param n + * @param env + * @param mergeEnv + */ +export const wordU = ( + prog: StackProgram, + n = 1, + env?: StackEnv, + mergeEnv = true +) => { + const w: StackFn = compile(prog); + return env + ? mergeEnv + ? (ctx: StackContext) => + unwrap(w([ctx[0], ctx[1], { ...ctx[2], ...env }]), n) + : (ctx: StackContext) => unwrap(w([ctx[0], ctx[1], { ...env }]), n) + : (ctx: StackContext) => unwrap(w(ctx), n); +}; + +/** + * Executes TOS as stack function and places result back on d-stack. TOS + * MUST be a valid word or quotation. + * + * ( x -- x() ) + * + * @param ctx + */ +export const exec = (ctx: StackContext) => ( + $(ctx[0], 1), $stackFn(ctx[0].pop())(ctx) +); + +//////////////////// JS host calls //////////////////// + +/** + * Expects TOS to be a quotation with a vanilla JS function as first + * element. Calls fn with all remaining items in quot as arguments and + * pushes result back on d-stack (even if fn returned `undefined`). + * + * ( [f ...] -- f(...) ) + * + * @param ctx + */ +export const execjs = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 1); + const [fn, ...args] = stack.pop(); + stack.push(fn(...args)); + return ctx; +}; diff --git a/packages/poisson/CHANGELOG.md b/packages/poisson/CHANGELOG.md index 178170e489..cb6617be78 100644 --- a/packages/poisson/CHANGELOG.md +++ b/packages/poisson/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.23...@thi.ng/poisson@0.2.24) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/poisson + + + + + +## [0.2.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.22...@thi.ng/poisson@0.2.23) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/poisson + + + + + ## [0.2.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.21...@thi.ng/poisson@0.2.22) (2019-08-16) **Note:** Version bump only for package @thi.ng/poisson diff --git a/packages/poisson/package.json b/packages/poisson/package.json index d35d9622be..afd40fbde5 100644 --- a/packages/poisson/package.json +++ b/packages/poisson/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/poisson", - "version": "0.2.22", + "version": "0.2.24", "description": "nD Poisson-disc sampling w/ support for spatial density functions and custom PRNGs", "module": "./index.js", "main": "./lib/index.js", @@ -34,9 +34,9 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/geom-api": "^0.3.3", - "@thi.ng/random": "^1.1.10", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/geom-api": "^0.3.5", + "@thi.ng/random": "^1.1.11", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "2d", diff --git a/packages/porter-duff/CHANGELOG.md b/packages/porter-duff/CHANGELOG.md index 9696cf450d..890638f75e 100644 --- a/packages/porter-duff/CHANGELOG.md +++ b/packages/porter-duff/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/porter-duff@0.1.1...@thi.ng/porter-duff@0.1.2) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/porter-duff + + + + + ## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/porter-duff@0.1.0...@thi.ng/porter-duff@0.1.1) (2019-07-31) **Note:** Version bump only for package @thi.ng/porter-duff diff --git a/packages/porter-duff/package.json b/packages/porter-duff/package.json index 69ddfa8a28..c917c1f983 100644 --- a/packages/porter-duff/package.json +++ b/packages/porter-duff/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/porter-duff", - "version": "0.1.1", + "version": "0.1.2", "description": "Porter-Duff operators for packed ints & float-array alpha compositing", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/math": "^1.4.2" }, "keywords": [ diff --git a/packages/random/CHANGELOG.md b/packages/random/CHANGELOG.md index 6e9ed59511..8a720de785 100644 --- a/packages/random/CHANGELOG.md +++ b/packages/random/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/random@1.1.10...@thi.ng/random@1.1.11) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/random + + + + + ## [1.1.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/random@1.1.9...@thi.ng/random@1.1.10) (2019-07-31) **Note:** Version bump only for package @thi.ng/random diff --git a/packages/random/package.json b/packages/random/package.json index e49cf211ce..a28b552a91 100644 --- a/packages/random/package.json +++ b/packages/random/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/random", - "version": "1.1.10", + "version": "1.1.11", "description": "Pseudo-random number generators w/ unified API", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2" + "@thi.ng/api": "^6.3.3" }, "keywords": [ "ES6", diff --git a/packages/range-coder/CHANGELOG.md b/packages/range-coder/CHANGELOG.md index 1c2992ce31..1eeb8e72b3 100644 --- a/packages/range-coder/CHANGELOG.md +++ b/packages/range-coder/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/range-coder@1.0.23...@thi.ng/range-coder@1.0.24) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/range-coder + + + + + ## [1.0.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/range-coder@1.0.22...@thi.ng/range-coder@1.0.23) (2019-08-16) **Note:** Version bump only for package @thi.ng/range-coder diff --git a/packages/range-coder/package.json b/packages/range-coder/package.json index c3c6753f45..0421d79725 100644 --- a/packages/range-coder/package.json +++ b/packages/range-coder/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/range-coder", - "version": "1.0.23", + "version": "1.0.24", "description": "Binary data range encoder / decoder", "module": "./index.js", "main": "./lib/index.js", @@ -25,7 +25,7 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/transducers": "^5.4.3", + "@thi.ng/transducers": "^5.4.4", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", @@ -34,7 +34,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/bitstream": "^1.1.2" + "@thi.ng/bitstream": "^1.1.3" }, "keywords": [ "ES6", diff --git a/packages/resolve-map/CHANGELOG.md b/packages/resolve-map/CHANGELOG.md index 73df631ca5..b2c68de9c9 100644 --- a/packages/resolve-map/CHANGELOG.md +++ b/packages/resolve-map/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [4.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@4.1.5...@thi.ng/resolve-map@4.1.6) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/resolve-map + + + + + ## [4.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@4.1.4...@thi.ng/resolve-map@4.1.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/resolve-map diff --git a/packages/resolve-map/package.json b/packages/resolve-map/package.json index 614b4237e2..fe4039f466 100644 --- a/packages/resolve-map/package.json +++ b/packages/resolve-map/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/resolve-map", - "version": "4.1.5", + "version": "4.1.6", "description": "DAG resolution of vanilla objects & arrays with internally linked values", "module": "./index.js", "main": "./lib/index.js", @@ -32,10 +32,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/paths": "^2.1.4" }, "keywords": [ "configuration", diff --git a/packages/rle-pack/CHANGELOG.md b/packages/rle-pack/CHANGELOG.md index b9972196ae..b6d7ae8991 100644 --- a/packages/rle-pack/CHANGELOG.md +++ b/packages/rle-pack/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@2.1.2...@thi.ng/rle-pack@2.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rle-pack + + + + + ## [2.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@2.1.1...@thi.ng/rle-pack@2.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/rle-pack diff --git a/packages/rle-pack/package.json b/packages/rle-pack/package.json index 2065839083..e4f15a3a47 100644 --- a/packages/rle-pack/package.json +++ b/packages/rle-pack/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rle-pack", - "version": "2.1.2", + "version": "2.1.3", "description": "Binary run-length encoding packer w/ flexible repeat bit widths", "module": "./index.js", "main": "./lib/index.js", @@ -34,8 +34,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/bitstream": "^1.1.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/bitstream": "^1.1.3", + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "binary", diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index b8027e769b..bb8809cf01 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@2.0.3...@thi.ng/router@2.0.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/router + + + + + ## [2.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@2.0.2...@thi.ng/router@2.0.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/router diff --git a/packages/router/package.json b/packages/router/package.json index fcbde2f420..a5854025f4 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/router", - "version": "2.0.3", + "version": "2.0.4", "description": "Generic router for browser & non-browser based applications", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "browser", diff --git a/packages/rstream-csp/CHANGELOG.md b/packages/rstream-csp/CHANGELOG.md index de5b75f8d8..213dfa50cf 100644 --- a/packages/rstream-csp/CHANGELOG.md +++ b/packages/rstream-csp/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.31](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@1.0.30...@thi.ng/rstream-csp@1.0.31) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-csp + + + + + ## [1.0.30](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@1.0.29...@thi.ng/rstream-csp@1.0.30) (2019-08-16) **Note:** Version bump only for package @thi.ng/rstream-csp diff --git a/packages/rstream-csp/package.json b/packages/rstream-csp/package.json index 67076fde71..5dbf4a2ce4 100644 --- a/packages/rstream-csp/package.json +++ b/packages/rstream-csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-csp", - "version": "1.0.30", + "version": "1.0.31", "description": "@thi.ng/csp bridge module for @thi.ng/rstream", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/csp": "^1.1.3", - "@thi.ng/rstream": "^2.5.3" + "@thi.ng/csp": "^1.1.4", + "@thi.ng/rstream": "^2.5.4" }, "keywords": [ "bridge", diff --git a/packages/rstream-dot/CHANGELOG.md b/packages/rstream-dot/CHANGELOG.md index afe0cc164c..5d007fb3f1 100644 --- a/packages/rstream-dot/CHANGELOG.md +++ b/packages/rstream-dot/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@1.1.3...@thi.ng/rstream-dot@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-dot + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@1.1.2...@thi.ng/rstream-dot@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/rstream-dot diff --git a/packages/rstream-dot/package.json b/packages/rstream-dot/package.json index af7e0447a4..d72135755b 100644 --- a/packages/rstream-dot/package.json +++ b/packages/rstream-dot/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-dot", - "version": "1.1.3", + "version": "1.1.4", "description": "Graphviz DOT conversion of @thi.ng/rstream dataflow graph topologies", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/rstream": "^2.5.3" + "@thi.ng/rstream": "^2.5.4" }, "keywords": [ "conversion", diff --git a/packages/rstream-gestures/CHANGELOG.md b/packages/rstream-gestures/CHANGELOG.md index 121fe52788..c554033600 100644 --- a/packages/rstream-gestures/CHANGELOG.md +++ b/packages/rstream-gestures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@1.2.3...@thi.ng/rstream-gestures@1.2.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + + + + ## [1.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@1.2.2...@thi.ng/rstream-gestures@1.2.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/rstream-gestures diff --git a/packages/rstream-gestures/package.json b/packages/rstream-gestures/package.json index 8fd0cf2709..c925d690c5 100644 --- a/packages/rstream-gestures/package.json +++ b/packages/rstream-gestures/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-gestures", - "version": "1.2.3", + "version": "1.2.4", "description": "Unified mouse, mouse wheel & single-touch event stream abstraction", "module": "./index.js", "main": "./lib/index.js", @@ -33,9 +33,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/rstream": "^2.5.3", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/api": "^6.3.3", + "@thi.ng/rstream": "^2.5.4", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "dataflow", diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 00de9ba964..70eb00392b 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@3.1.5...@thi.ng/rstream-graph@3.1.6) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-graph + + + + + ## [3.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@3.1.4...@thi.ng/rstream-graph@3.1.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/rstream-graph diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index f04f3b18dd..27c771edf6 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "3.1.5", + "version": "3.1.6", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "module": "./index.js", "main": "./lib/index.js", @@ -33,13 +33,13 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.3", - "@thi.ng/resolve-map": "^4.1.5", - "@thi.ng/rstream": "^2.5.3", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/paths": "^2.1.4", + "@thi.ng/resolve-map": "^4.1.6", + "@thi.ng/rstream": "^2.5.4", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "compute", diff --git a/packages/rstream-log-file/CHANGELOG.md b/packages/rstream-log-file/CHANGELOG.md index 5614c9348f..6d1f0a7f06 100644 --- a/packages/rstream-log-file/CHANGELOG.md +++ b/packages/rstream-log-file/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log-file@0.1.18...@thi.ng/rstream-log-file@0.1.19) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-log-file + + + + + ## [0.1.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log-file@0.1.17...@thi.ng/rstream-log-file@0.1.18) (2019-08-16) **Note:** Version bump only for package @thi.ng/rstream-log-file diff --git a/packages/rstream-log-file/package.json b/packages/rstream-log-file/package.json index b0ba374ec2..0d57e38711 100644 --- a/packages/rstream-log-file/package.json +++ b/packages/rstream-log-file/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-log-file", - "version": "0.1.18", + "version": "0.1.19", "description": "File output handler for structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "module": "./index.js", "main": "./lib/index.js", @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/rstream": "^2.5.3" + "@thi.ng/rstream": "^2.5.4" }, "keywords": [ "append", diff --git a/packages/rstream-log/CHANGELOG.md b/packages/rstream-log/CHANGELOG.md index 8c2ab8b481..3970e09e8e 100644 --- a/packages/rstream-log/CHANGELOG.md +++ b/packages/rstream-log/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@3.1.3...@thi.ng/rstream-log@3.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-log + + + + + ## [3.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@3.1.2...@thi.ng/rstream-log@3.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/rstream-log diff --git a/packages/rstream-log/package.json b/packages/rstream-log/package.json index e3acc5a7de..b76e195bc0 100644 --- a/packages/rstream-log/package.json +++ b/packages/rstream-log/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-log", - "version": "3.1.3", + "version": "3.1.4", "description": "Structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "module": "./index.js", "main": "./lib/index.js", @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/rstream": "^2.5.3", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/rstream": "^2.5.4", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "ES6", diff --git a/packages/rstream-query/CHANGELOG.md b/packages/rstream-query/CHANGELOG.md index 4d63115d5c..7b0a3c1a7c 100644 --- a/packages/rstream-query/CHANGELOG.md +++ b/packages/rstream-query/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@1.1.3...@thi.ng/rstream-query@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-query + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@1.1.2...@thi.ng/rstream-query@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/rstream-query diff --git a/packages/rstream-query/package.json b/packages/rstream-query/package.json index a7232eeced..f440624d97 100644 --- a/packages/rstream-query/package.json +++ b/packages/rstream-query/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-query", - "version": "1.1.3", + "version": "1.1.4", "description": "@thi.ng/rstream based triple store & reactive query engine", "module": "./index.js", "main": "./lib/index.js", @@ -33,14 +33,15 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/associative": "^2.4.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/associative": "^3.0.0", "@thi.ng/checks": "^2.3.0", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/rstream": "^2.5.3", - "@thi.ng/rstream-dot": "^1.1.3", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/math": "^1.4.2", + "@thi.ng/rstream": "^2.5.4", + "@thi.ng/rstream-dot": "^1.1.4", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "dataflow", diff --git a/packages/rstream-query/src/store.ts b/packages/rstream-query/src/store.ts index 0e44fdd1d3..7c9478cfc8 100644 --- a/packages/rstream-query/src/store.ts +++ b/packages/rstream-query/src/store.ts @@ -1,7 +1,8 @@ import { assert, IObjectOf } from "@thi.ng/api"; -import { intersection, join } from "@thi.ng/associative"; +import { join } from "@thi.ng/associative"; import { equiv } from "@thi.ng/equiv"; import { illegalArgs } from "@thi.ng/errors"; +import { min3id } from "@thi.ng/math"; import { ISubscribable, nextID, @@ -18,19 +19,14 @@ import { import { assocObj, comp, - compR, dedupe, - keySelector, map, mapIndexed, - Reducer, transduce, Transducer } from "@thi.ng/transducers"; import { - BindFn, Edit, - LOGGER, PathPattern, PathQuerySpec, Pattern, @@ -45,6 +41,16 @@ import { } from "./api"; import { patternVars, resolvePathPattern } from "./pattern"; import { isQVar, qvarResolver } from "./qvar"; +import { + bindVars, + filterSolutions, + indexSel, + intersect2, + intersect3, + joinSolutions, + limitSolutions, + resultTriples +} from "./xforms"; export class TripleStore implements Iterable, IToDot { NEXT_ID: number; @@ -361,24 +367,17 @@ export class TripleStore implements Iterable, IToDot { for (let q of spec.q) { if (isWhereQuery(q)) { curr = this.addMultiJoin(this.addParamQueries(q.where)); - query && (curr = this.addJoin(query, curr)); } else if (isPathQuery(q)) { curr = this.addPathQuery(q.path); - query && (curr = this.addJoin(query, curr)); } + query && curr && (curr = this.addJoin(query, curr)); query = curr; } assert(!!query, "illegal query spec"); let xforms: Transducer[] = []; - if (spec.limit) { - xforms.push(limitSolutions(spec.limit)); - } - if (spec.bind) { - xforms.push(bindVars(spec.bind)); - } - if (spec.select) { - xforms.push(filterSolutions(spec.select)); - } + spec.limit && xforms.push(limitSolutions(spec.limit)); + spec.bind && xforms.push(bindVars(spec.bind)); + spec.select && xforms.push(filterSolutions(spec.select)); if (xforms.length) { query = >( query!.subscribe(comp.apply(null, xforms)) @@ -398,10 +397,7 @@ export class TripleStore implements Iterable, IToDot { } protected nextID() { - if (this.freeIDs.length) { - return this.freeIDs.pop()!; - } - return this.NEXT_ID++; + return this.freeIDs.length ? this.freeIDs.pop()! : this.NEXT_ID++; } private broadcastTriple( @@ -424,18 +420,7 @@ export class TripleStore implements Iterable, IToDot { ) { if (s && p && o) { const triples = this.triples; - const index = - s.size < p.size - ? s.size < o.size - ? s - : p.size < o.size - ? p - : o - : p.size < o.size - ? p - : s.size < o.size - ? s - : o; + const index = [s, p, o][min3id(s.size, p.size, o.size)]; for (let id of index) { if (equiv(triples[id], f)) { return id; @@ -482,83 +467,6 @@ const submit = ( } }; -const intersect2: Transducer, TripleIds> = comp( - map(({ a, b }) => intersection(a, b)), - dedupe(equiv) -); - -const intersect3: Transducer, TripleIds> = comp( - map(({ s, p, o }) => intersection(intersection(s, p), o)), - dedupe(equiv) -); - -const indexSel = (key: any): Transducer => ( - rfn: Reducer -) => { - const r = rfn[2]; - return compR(rfn, (acc, e) => { - LOGGER.fine("index sel", e.key, key); - if (equiv(e.key, key)) { - return r(acc, e.index); - } - return acc; - }); -}; - -const resultTriples = (graph: TripleStore) => - map>((ids) => { - const res = new Set(); - for (let id of ids) res.add(graph.triples[id]); - return res; - }); - -const joinSolutions = (n: number) => - map, Solutions>((src) => { - let res: Solutions = src[0]; - for (let i = 1; i < n && res.size; i++) { - res = join(res, src[i]); - } - return res; - }); - -const filterSolutions = (qvars: Iterable) => { - const filterVars = keySelector([...qvars]); - return map((sol: Solutions) => { - const res: Solutions = new Set(); - for (let s of sol) { - res.add(filterVars(s)); - } - return res; - }); -}; - -const limitSolutions = (n: number) => - map((sol: Solutions) => { - if (sol.size <= n) { - return sol; - } - const res: Solutions = new Set(); - let m = n; - for (let s of sol) { - res.add(s); - if (--m <= 0) break; - } - return res; - }); - -const bindVars = (bindings: IObjectOf) => - map((sol: Solutions) => { - const res: Solutions = new Set(); - for (let s of sol) { - s = { ...s }; - res.add(s); - for (let b in bindings) { - s[b] = bindings[b](s); - } - } - return res; - }); - const isWhereQuery = (q: SubQuerySpec): q is WhereQuerySpec => !!(q).where; const isPathQuery = (q: SubQuerySpec): q is PathQuerySpec => !!(q).path; diff --git a/packages/rstream-query/src/xforms.ts b/packages/rstream-query/src/xforms.ts new file mode 100644 index 0000000000..f66fce3cf7 --- /dev/null +++ b/packages/rstream-query/src/xforms.ts @@ -0,0 +1,98 @@ +import { IObjectOf } from "@thi.ng/api"; +import { intersection, join } from "@thi.ng/associative"; +import { equiv } from "@thi.ng/equiv"; +import { LOGGER } from "@thi.ng/rstream"; +import { + comp, + compR, + dedupe, + keySelector, + map, + Reducer, + Transducer +} from "@thi.ng/transducers"; +import { + BindFn, + Edit, + Solutions, + Triple, + TripleIds +} from "./api"; +import { TripleStore } from "./store"; + +export const intersect2: Transducer, TripleIds> = comp( + map(({ a, b }) => intersection(a, b)), + dedupe(equiv) +); + +export const intersect3: Transducer, TripleIds> = comp( + map(({ s, p, o }) => intersection(intersection(s, p), o)), + dedupe(equiv) +); + +export const indexSel = (key: any): Transducer => ( + rfn: Reducer +) => { + const r = rfn[2]; + return compR(rfn, (acc, e) => { + LOGGER.fine("index sel", e.key, key); + if (equiv(e.key, key)) { + return r(acc, e.index); + } + return acc; + }); +}; + +export const resultTriples = (graph: TripleStore) => + map>((ids) => { + const res = new Set(); + for (let id of ids) res.add(graph.triples[id]); + return res; + }); + +export const joinSolutions = (n: number) => + map, Solutions>((src) => { + let res: Solutions = src[0]; + for (let i = 1; i < n && res.size; i++) { + res = join(res, src[i]); + } + return res; + }); + +export const filterSolutions = (qvars: Iterable) => { + const filterVars = keySelector([...qvars]); + return map((sol: Solutions) => { + const res: Solutions = new Set(); + for (let s of sol) { + res.add(filterVars(s)); + } + return res; + }); +}; + +export const limitSolutions = (n: number) => + map((sol: Solutions) => { + if (sol.size <= n) { + return sol; + } + const res: Solutions = new Set(); + let m = n; + for (let s of sol) { + res.add(s); + if (--m <= 0) break; + } + return res; + }); + +export const bindVars = (bindings: IObjectOf) => + map((sol: Solutions) => { + const res: Solutions = new Set(); + for (let s of sol) { + s = { ...s }; + res.add(s); + for (let b in bindings) { + s[b] = bindings[b](s); + } + } + return res; + }); diff --git a/packages/rstream/CHANGELOG.md b/packages/rstream/CHANGELOG.md index 7c83c3154d..0a6e96fa47 100644 --- a/packages/rstream/CHANGELOG.md +++ b/packages/rstream/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.5.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@2.5.3...@thi.ng/rstream@2.5.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream + + + + + ## [2.5.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@2.5.2...@thi.ng/rstream@2.5.3) (2019-08-16) diff --git a/packages/rstream/package.json b/packages/rstream/package.json index 51f121edad..16d159b5dd 100644 --- a/packages/rstream/package.json +++ b/packages/rstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream", - "version": "2.5.3", + "version": "2.5.4", "description": "Reactive multi-tap streams, dataflow & transformation pipeline constructs", "module": "./index.js", "main": "./lib/index.js", @@ -33,13 +33,13 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/associative": "^2.4.3", - "@thi.ng/atom": "^3.0.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/associative": "^3.0.0", + "@thi.ng/atom": "^3.0.4", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.3", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/paths": "^2.1.4", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "datastructure", diff --git a/packages/sax/CHANGELOG.md b/packages/sax/CHANGELOG.md index e13c1f1d4c..3642b26cd6 100644 --- a/packages/sax/CHANGELOG.md +++ b/packages/sax/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@1.1.3...@thi.ng/sax@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/sax + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@1.1.2...@thi.ng/sax@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/sax diff --git a/packages/sax/package.json b/packages/sax/package.json index cb9cb5da6b..3e1cdfd08f 100644 --- a/packages/sax/package.json +++ b/packages/sax/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/sax", - "version": "1.1.3", + "version": "1.1.4", "description": "Transducer-based, SAX-like, non-validating, speedy & tiny XML parser", "module": "./index.js", "main": "./lib/index.js", @@ -33,9 +33,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/transducers-fsm": "^1.1.3" + "@thi.ng/api": "^6.3.3", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/transducers-fsm": "^1.1.4" }, "keywords": [ "ES6", diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts index d16b02bdd1..c7b061a8aa 100644 --- a/packages/sax/src/index.ts +++ b/packages/sax/src/index.ts @@ -269,20 +269,7 @@ const PARSER: FSMStateMap = { } else if (isWS(ch)) { state.state = State.MAYBE_ATTRIB; } else if (ch === ">") { - state.state = State.ELEM_BODY; - state.scope.push({ - tag: state.tag, - attribs: state.attribs, - children: [] - }); - state.body = ""; - return [ - { - type: Type.ELEM_START, - tag: state.tag, - attribs: state.attribs - } - ]; + return beginElementBody(state); } else if (ch === "/") { state.state = State.ELEM_SINGLE; } else { diff --git a/packages/shader-ast-glsl/CHANGELOG.md b/packages/shader-ast-glsl/CHANGELOG.md index 847903411e..19808b1292 100644 --- a/packages/shader-ast-glsl/CHANGELOG.md +++ b/packages/shader-ast-glsl/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.5...@thi.ng/shader-ast-glsl@0.1.6) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/shader-ast-glsl + + + + + +## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.4...@thi.ng/shader-ast-glsl@0.1.5) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/shader-ast-glsl + + + + + ## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.3...@thi.ng/shader-ast-glsl@0.1.4) (2019-08-16) **Note:** Version bump only for package @thi.ng/shader-ast-glsl diff --git a/packages/shader-ast-glsl/package.json b/packages/shader-ast-glsl/package.json index ecd5de98a8..bd03bc486b 100644 --- a/packages/shader-ast-glsl/package.json +++ b/packages/shader-ast-glsl/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/shader-ast-glsl", - "version": "0.1.4", + "version": "0.1.6", "description": "Customizable GLSL code generator for @thi.ng/shader-ast", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/shader-ast": "^0.2.2" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/shader-ast": "^0.3.0" }, "keywords": [ "AST", diff --git a/packages/shader-ast-glsl/src/api.ts b/packages/shader-ast-glsl/src/api.ts new file mode 100644 index 0000000000..128318abe3 --- /dev/null +++ b/packages/shader-ast-glsl/src/api.ts @@ -0,0 +1,30 @@ +import { Fn } from "@thi.ng/api"; +import { + FloatSym, + Sym, + Term, + Vec2Sym, + Vec4Sym +} from "@thi.ng/shader-ast"; + +export const enum GLSLVersion { + GLES_100 = "100", + GLES_300 = "300 es" +} + +export interface GLSLOpts { + type: "vs" | "fs"; + version: GLSLVersion; + versionPragma: boolean; + prelude: string; +} + +export interface GLSLTarget extends Fn, string> { + gl_FragColor: Vec4Sym; + gl_FragCoord: Vec4Sym; + gl_FragData: Sym<"vec4[]">; + gl_FrontFacing: Sym<"bool">; + gl_PointCoord: Vec2Sym; + gl_PointSize: FloatSym; + gl_Position: Vec4Sym; +} diff --git a/packages/shader-ast-glsl/src/index.ts b/packages/shader-ast-glsl/src/index.ts index 47f352eb09..2cf8efe442 100644 --- a/packages/shader-ast-glsl/src/index.ts +++ b/packages/shader-ast-glsl/src/index.ts @@ -1,229 +1,2 @@ -import { Fn } from "@thi.ng/api"; -import { isBoolean, isNumber } from "@thi.ng/checks"; -import { unsupported } from "@thi.ng/errors"; -import { - defTarget, - FloatSym, - FnCall, - FuncArg, - isMat, - isVec, - itemType, - Sym, - sym, - Term, - Type, - Vec2Sym, - Vec4Sym -} from "@thi.ng/shader-ast"; - -export const enum GLSLVersion { - GLES_100 = "100", - GLES_300 = "300 es" -} - -export interface GLSLOpts { - type: "vs" | "fs"; - version: GLSLVersion; - versionPragma: boolean; - prelude: string; -} - -export interface GLSLTarget extends Fn, string> { - gl_FragColor: Vec4Sym; - gl_FragCoord: Vec4Sym; - gl_FragData: Sym<"vec4[]">; - gl_FrontFacing: Sym<"bool">; - gl_PointCoord: Vec2Sym; - gl_PointSize: FloatSym; - gl_Position: Vec4Sym; -} - -const RE_SEMI = /[};]$/; - -/** - * GLSL code gen, targets GLSL ES 3.00 (WebGL2) by default. - * - * Use options object to configure shader type and GLSL version: `100` - * for WebGL, 300 for WebGL2. Currently, the only differences in terms - * of code generation, not correctness, are: - * - * - attribute, varying, uniform declarations - * - texture lookup function naming - * - * Unsupported features in GLSL 100: - * - * - Fragment shader output vars - * - * @param opts - */ -export const targetGLSL = (opts?: Partial) => { - const _opts: GLSLOpts = { - type: "fs", - version: GLSLVersion.GLES_300, - versionPragma: true, - prelude: "", - ...opts - }; - const isVS = _opts.type === "vs"; - - // TODO update once we have struct support - const $type = (t: Type) => t; - - const $list = (body: Term[], sep = ", ") => body.map(emit).join(sep); - - const $fn = (t: FnCall) => `${t.id}(${$list(t.args)})`; - - const $decl = (sym: Sym | FuncArg, arg = false) => { - const { id, type, opts, init } = >sym; - const res: string[] = []; - if (opts.type) { - let type: string; - if (_opts.version < GLSLVersion.GLES_300) { - if (isVS) { - type = ({ - in: "attribute", - out: "varying", - uni: "uniform" - })[opts.type]; - } else { - type = ({ - in: "varying", - out: null, - uni: "uniform" - })[opts.type]; - !type && - unsupported( - "GLSL 100 doesn't support fragment shader output variables" - ); - } - } else { - opts.loc != null && res.push(`layout(location=${opts.loc}) `); - type = opts.type === "uni" ? "uniform" : opts.type; - } - res.push(type + " "); - } else { - opts.const && res.push("const "); - arg && opts.q && res.push(opts.q + " "); - } - opts.prec && res.push(opts.prec + " "); - res.push($type(itemType(type)), " ", id); - opts.num && res.push(`[${opts.num}]`); - init && res.push(" = ", emit(init)); - return res.join(""); - }; - - const emit: Fn, string> = defTarget({ - arg: (t) => $decl(t, true), - - array_init: (t) => - _opts.version >= GLSLVersion.GLES_300 - ? `${t.type}(${$list(t.init)})` - : unsupported( - `array initializers not available in GLSL ${ - _opts.version - }` - ), - - assign: (t) => emit(t.l) + " = " + emit(t.r), - - ctrl: (t) => t.id, - - call: $fn, - - call_i: (t) => - t.id === "texture" && _opts.version < GLSLVersion.GLES_300 - ? `${t.id}${(>t.args[0]).type.substr(7)}(${$list( - t.args - )})` - : $fn(t), - - decl: (t) => $decl(t.id), - - fn: (t) => - `${$type(t.type)} ${t.id}(${$list(t.args)}) ${emit(t.scope)}`, - - for: (t) => - `for(${t.init ? emit(t.init) : ""}; ${emit(t.test)}; ${ - t.iter ? emit(t.iter) : "" - }) ${emit(t.scope)}`, - - idx: (t) => `${emit(t.val)}[${emit(t.id)}]`, - - if: (t) => { - const res = `if (${emit(t.test)}) ` + emit(t.t); - return t.f ? res + " else " + emit(t.f) : res; - }, - - lit: (t) => { - const v = t.val; - switch (t.type) { - case "bool": - return isBoolean(v) ? String(v) : `bool(${emit(v)})`; - case "float": - return isNumber(v) - ? v === Math.trunc(v) - ? v + ".0" - : String(v) - : `float(${emit(v)})`; - case "int": - case "uint": - return isNumber(v) ? String(v) : `${t.type}(${emit(v)})`; - default: { - if (isVec(t) || isMat(t)) { - return `${t.type}(${$list(v)})`; - } - return unsupported(`unknown type: ${t.type}`); - } - } - }, - - op1: (t) => - t.post ? `(${emit(t.val)}${t.op})` : `(${t.op}${emit(t.val)})`, - - op2: (t) => `(${emit(t.l)} ${t.op} ${emit(t.r)})`, - - ret: (t) => "return" + (t.val ? " " + emit(t.val) : ""), - - scope: (t) => { - let res = t.body - .map(emit) - .reduce( - (acc, x) => (acc.push(RE_SEMI.test(x) ? x : x + ";"), acc), - [] - ) - .join("\n"); - res += t.body.length && !RE_SEMI.test(res) ? ";" : ""; - if (!t.global) { - return `{\n${res}\n}`; - } - if (_opts.prelude) { - res = _opts.prelude + "\n" + res; - } - if (_opts.versionPragma) { - res = `#version ${_opts.version}\n` + res; - } - return res; - }, - - swizzle: (t) => `${emit(t.val)}.${t.id}`, - - sym: (t) => t.id, - - ternary: (t) => `(${emit(t.test)} ? ${emit(t.t)} : ${emit(t.f)})`, - - while: (t) => `while (${emit(t.test)}) ${emit(t.scope)}` - }); - - Object.assign(emit, { - gl_FragColor: sym("vec4", "gl_FragColor"), - gl_FragCoord: sym("vec4", "gl_FragCoord", { const: true }), - gl_FragData: sym("vec4[]", "gl_FragData", { num: 1 }), - gl_FrontFacing: sym("bool", "gl_FrontFacing", { const: true }), - gl_PointCoord: sym("vec2", "gl_PointCoord", { const: true }), - gl_PointSize: sym("float", "gl_PointSize"), - gl_Position: sym("vec4", "gl_Position") - }); - - return emit; -}; +export * from "./api"; +export * from "./target"; diff --git a/packages/shader-ast-glsl/src/target.ts b/packages/shader-ast-glsl/src/target.ts new file mode 100644 index 0000000000..00c93fb8d8 --- /dev/null +++ b/packages/shader-ast-glsl/src/target.ts @@ -0,0 +1,205 @@ +import { Fn } from "@thi.ng/api"; +import { isBoolean, isNumber } from "@thi.ng/checks"; +import { unsupported } from "@thi.ng/errors"; +import { + defTarget, + FnCall, + FuncArg, + isMat, + isVec, + itemType, + Sym, + sym, + Term, + Type +} from "@thi.ng/shader-ast"; +import { GLSLOpts, GLSLTarget, GLSLVersion } from "./api"; + +const RE_SEMI = /[};]$/; + +/** + * GLSL code gen, targets GLSL ES 3.00 (WebGL2) by default. + * + * Use options object to configure shader type and GLSL version: `100` + * for WebGL, 300 for WebGL2. Currently, the only differences in terms + * of code generation, not correctness, are: + * + * - attribute, varying, uniform declarations + * - texture lookup function naming + * + * Unsupported features in GLSL 100: + * + * - Fragment shader output vars + * + * @param opts + */ +export const targetGLSL = (opts?: Partial) => { + const _opts: GLSLOpts = { + type: "fs", + version: GLSLVersion.GLES_300, + versionPragma: true, + prelude: "", + ...opts + }; + const isVS = _opts.type === "vs"; + + // TODO update once we have struct support + const $type = (t: Type) => t; + + const $list = (body: Term[], sep = ", ") => body.map(emit).join(sep); + + const $fn = (t: FnCall) => `${t.id}(${$list(t.args)})`; + + const $decl = (sym: Sym | FuncArg, arg = false) => { + const { id, type, opts, init } = >sym; + const res: string[] = []; + if (opts.type) { + let type: string; + if (_opts.version < GLSLVersion.GLES_300) { + if (isVS) { + type = ({ + in: "attribute", + out: "varying", + uni: "uniform" + })[opts.type]; + } else { + type = ({ + in: "varying", + out: null, + uni: "uniform" + })[opts.type]; + !type && + unsupported( + "GLSL 100 doesn't support fragment shader output variables" + ); + } + } else { + opts.loc != null && res.push(`layout(location=${opts.loc}) `); + type = opts.type === "uni" ? "uniform" : opts.type; + } + res.push(type + " "); + } else { + opts.const && res.push("const "); + arg && opts.q && res.push(opts.q + " "); + } + opts.prec && res.push(opts.prec + " "); + res.push($type(itemType(type)), " ", id); + opts.num && res.push(`[${opts.num}]`); + init && res.push(" = ", emit(init)); + return res.join(""); + }; + + const emit: Fn, string> = defTarget({ + arg: (t) => $decl(t, true), + + array_init: (t) => + _opts.version >= GLSLVersion.GLES_300 + ? `${t.type}(${$list(t.init)})` + : unsupported( + `array initializers not available in GLSL ${ + _opts.version + }` + ), + + assign: (t) => emit(t.l) + " = " + emit(t.r), + + ctrl: (t) => t.id, + + call: $fn, + + call_i: (t) => + t.id === "texture" && _opts.version < GLSLVersion.GLES_300 + ? `${t.id}${(>t.args[0]).type.substr(7)}(${$list( + t.args + )})` + : $fn(t), + + decl: (t) => $decl(t.id), + + fn: (t) => + `${$type(t.type)} ${t.id}(${$list(t.args)}) ${emit(t.scope)}`, + + for: (t) => + `for(${t.init ? emit(t.init) : ""}; ${emit(t.test)}; ${ + t.iter ? emit(t.iter) : "" + }) ${emit(t.scope)}`, + + idx: (t) => `${emit(t.val)}[${emit(t.id)}]`, + + if: (t) => { + const res = `if (${emit(t.test)}) ` + emit(t.t); + return t.f ? res + " else " + emit(t.f) : res; + }, + + lit: (t) => { + const v = t.val; + switch (t.type) { + case "bool": + return isBoolean(v) ? String(v) : `bool(${emit(v)})`; + case "float": + return isNumber(v) + ? v === Math.trunc(v) + ? v + ".0" + : String(v) + : `float(${emit(v)})`; + case "int": + case "uint": + return isNumber(v) ? String(v) : `${t.type}(${emit(v)})`; + default: { + if (isVec(t) || isMat(t)) { + return `${t.type}(${$list(v)})`; + } + return unsupported(`unknown type: ${t.type}`); + } + } + }, + + op1: (t) => + t.post ? `(${emit(t.val)}${t.op})` : `(${t.op}${emit(t.val)})`, + + op2: (t) => `(${emit(t.l)} ${t.op} ${emit(t.r)})`, + + ret: (t) => "return" + (t.val ? " " + emit(t.val) : ""), + + scope: (t) => { + let res = t.body + .map(emit) + .reduce( + (acc, x) => (acc.push(RE_SEMI.test(x) ? x : x + ";"), acc), + [] + ) + .join("\n"); + res += t.body.length && !RE_SEMI.test(res) ? ";" : ""; + if (!t.global) { + return `{\n${res}\n}`; + } + if (_opts.prelude) { + res = _opts.prelude + "\n" + res; + } + if (_opts.versionPragma) { + res = `#version ${_opts.version}\n` + res; + } + return res; + }, + + swizzle: (t) => `${emit(t.val)}.${t.id}`, + + sym: (t) => t.id, + + ternary: (t) => `(${emit(t.test)} ? ${emit(t.t)} : ${emit(t.f)})`, + + while: (t) => `while (${emit(t.test)}) ${emit(t.scope)}` + }); + + Object.assign(emit, { + gl_FragColor: sym("vec4", "gl_FragColor"), + gl_FragCoord: sym("vec4", "gl_FragCoord", { const: true }), + gl_FragData: sym("vec4[]", "gl_FragData", { num: 1 }), + gl_FrontFacing: sym("bool", "gl_FrontFacing", { const: true }), + gl_PointCoord: sym("vec2", "gl_PointCoord", { const: true }), + gl_PointSize: sym("float", "gl_PointSize"), + gl_Position: sym("vec4", "gl_Position") + }); + + return emit; +}; diff --git a/packages/shader-ast-js/CHANGELOG.md b/packages/shader-ast-js/CHANGELOG.md index 7dcd251428..cec827a06c 100644 --- a/packages/shader-ast-js/CHANGELOG.md +++ b/packages/shader-ast-js/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.3.0...@thi.ng/shader-ast-js@0.3.1) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/shader-ast-js + + + + + +# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.2.3...@thi.ng/shader-ast-js@0.3.0) (2019-08-17) + + +### Features + +* **shader-ast-js:** add support for 2-arg atan(), move types to api.ts ([a760085](https://github.com/thi-ng/umbrella/commit/a760085)) + + + + + ## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.2.2...@thi.ng/shader-ast-js@0.2.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/shader-ast-js diff --git a/packages/shader-ast-js/package.json b/packages/shader-ast-js/package.json index 2ab3517231..306c8ff836 100644 --- a/packages/shader-ast-js/package.json +++ b/packages/shader-ast-js/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/shader-ast-js", - "version": "0.2.3", + "version": "0.3.1", "description": "Customizable JS code generator, compiler & runtime for @thi.ng/shader-ast", "module": "./index.js", "main": "./lib/index.js", @@ -33,13 +33,13 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/errors": "^1.1.2", + "@thi.ng/errors": "^1.2.0", "@thi.ng/math": "^1.4.2", - "@thi.ng/matrices": "^0.5.5", - "@thi.ng/shader-ast": "^0.2.2", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/matrices": "^0.5.7", + "@thi.ng/shader-ast": "^0.3.0", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "AST", diff --git a/packages/shader-ast-js/src/api.ts b/packages/shader-ast-js/src/api.ts new file mode 100644 index 0000000000..ca9dd0d8e0 --- /dev/null +++ b/packages/shader-ast-js/src/api.ts @@ -0,0 +1,212 @@ +import { + Fn, + Fn2, + Fn3, + Fn4, + Fn5, + Fn6 +} from "@thi.ng/api"; +import { Mat } from "@thi.ng/matrices"; +import { Term } from "@thi.ng/shader-ast"; +import { Vec } from "@thi.ng/vectors"; + +export interface JSTarget extends Fn, string> { + /** + * Compiles given AST to JavaScript, using optional `env` as backend + * for various operators / builtins. If `env` is not given the + * bundled `JS_DEFAULT_ENV` is used (based on thi.ng/vectors and + * thi.ng/matrices packages). + * + * Any functions defined in the given AST will be exported using + * their defined name via the returned object. + * + * ``` + * const js = targetJS(); + * const module = js.compile( + * defn("float", "foo", [["float"]], (x)=> [ret(mul(x, float(10)))]) + * ); + * + * module.foo(42) + * // 420 + * + * module.foo.toString() + * // function foo(_sym0) { + * // return (_sym0 * 10); + * // } + * ``` + * + * @param tree + * @param env + */ + compile(tree: Term, env?: JSEnv): any; +} + +export interface JSBuiltinsCommon { + abs: Fn; + clamp: Fn3; + max: Fn2; + min: Fn2; + sign: Fn; +} + +export interface JSBuiltinsMath { + sub1: Fn; + add: Fn2; + sub: Fn2; + mul: Fn2; + div: Fn2; + inc: Fn; + dec: Fn; +} + +export interface JSBuiltinsBinary { + bitand: Fn2; + lshift: Fn2; + bitnot1: Fn2; + bitor: Fn2; + rshift: Fn2; + bitxor: Fn2; +} + +export interface JSBuiltinsFloat extends JSBuiltinsCommon { + acos: Fn; + asin: Fn; + atan: Fn; + atannn: Fn2; + ceil: Fn; + cos: Fn; + degrees: Fn; + dFdx: Fn; + dFdy: Fn; + exp: Fn; + exp2: Fn; + floor: Fn; + fract: Fn; + fwidth: Fn; + inversesqrt: Fn; + log: Fn; + log2: Fn; + mix: Fn3; + mixn: Fn3; + mod: Fn2; + modn: Fn2; + pow: Fn2; + radians: Fn; + sin: Fn; + smoothstep: Fn3; + sqrt: Fn; + step: Fn2; + tan: Fn; +} + +export interface JSBuiltinsInt + extends JSBuiltinsCommon, + JSBuiltinsMath, + JSBuiltinsBinary { + modi: Fn2; +} + +export interface JSBuiltinsVecScalar { + addvn: Fn2; + subvn: Fn2; + mulvn: Fn2; + divvn: Fn2; + addnv: Fn2; + subnv: Fn2; + mulnv: Fn2; + divnv: Fn2; +} + +export interface JSBuiltinsVec + extends JSBuiltinsFloat, + JSBuiltinsMath, + JSBuiltinsVecScalar { + distance: Fn2; + dot: Fn2; + faceForward: Fn3; + length: Fn; + normalize: Fn; + reflect: Fn2; + refract: Fn3; +} + +export interface JSBuiltinsVec3 extends JSBuiltinsVec { + cross: Fn2; +} + +export interface JSBuiltinsIntVec + extends JSBuiltinsInt, + JSBuiltinsVecScalar, + JSBuiltinsBinary { + modivn: Fn2; + modinv: Fn2; +} + +export interface JSBuiltinsMat + extends JSBuiltinsMath, + JSBuiltinsVecScalar { + mulm: Fn2; + mulvm: Fn2; + mulmv: Fn2; +} + +export interface JSBuiltinsSampler { + texelFetch: Fn3; + texelFetchOffset: Fn4; + texture: Fn3; + texturen: Fn3; + textureGrad: Fn4; + textureGradn: Fn4; + textureLod: Fn3; + textureLodn: Fn3; + textureOffset: Fn4; + textureOffsetn: Fn4; + textureProj: Fn3; + textureProjn: Fn3; + textureSize: Fn2; +} + +export interface JSEnv { + vec2n: Fn; + vec3n: Fn; + vec3vn: Fn2; + vec4n: Fn; + vec4vn: Fn2; + vec4vnn: Fn3; + vec4vv: Fn2; + mat2n: Fn; + mat2vv: Fn2; + mat3n: Fn; + mat3vvv: Fn3; + mat4n: Fn; + mat4vvvv: Fn4; + // swizzle1: Fn2; + swizzle2: Fn3; + swizzle3: Fn4; + swizzle4: Fn5; + // set_swizzle1: Fn3; + set_swizzle2: Fn4; + set_swizzle3: Fn5; + set_swizzle4: Fn6; + float: JSBuiltinsFloat; + int: JSBuiltinsInt; + uint: JSBuiltinsInt; + vec2: JSBuiltinsVec; + vec3: JSBuiltinsVec3; + vec4: JSBuiltinsVec; + ivec2: JSBuiltinsIntVec; + ivec3: JSBuiltinsIntVec; + ivec4: JSBuiltinsIntVec; + uvec2: JSBuiltinsIntVec; + uvec3: JSBuiltinsIntVec; + uvec4: JSBuiltinsIntVec; + mat2: JSBuiltinsMat; + mat3: JSBuiltinsMat; + mat4: JSBuiltinsMat; + sampler1D: JSBuiltinsSampler; + sampler2D: JSBuiltinsSampler; + sampler3D: JSBuiltinsSampler; + samplerCube: JSBuiltinsSampler; + sampler2DShadow: JSBuiltinsSampler; + samplerCubeShadow: JSBuiltinsSampler; +} diff --git a/packages/shader-ast-js/src/env.ts b/packages/shader-ast-js/src/env.ts new file mode 100644 index 0000000000..26658f1985 --- /dev/null +++ b/packages/shader-ast-js/src/env.ts @@ -0,0 +1,98 @@ +import { + mat22n, + mat22v, + mat33n, + mat33v, + mat44n, + mat44v +} from "@thi.ng/matrices"; +import { + setSwizzle2, + setSwizzle3, + setSwizzle4, + setVN3, + setVN4, + setVV4, + swizzle2, + swizzle3, + swizzle4, + ZERO3, + ZERO4 +} from "@thi.ng/vectors"; +import { JSBuiltinsSampler, JSEnv } from "./api"; +import { FLOAT } from "./env/float"; +import { INT } from "./env/int"; +import { IVEC2 } from "./env/ivec2"; +import { IVEC3 } from "./env/ivec3"; +import { IVEC4 } from "./env/ivec4"; +import { MAT2 } from "./env/mat2"; +import { MAT3 } from "./env/mat3"; +import { MAT4 } from "./env/mat4"; +import { UINT } from "./env/uint"; +import { UVEC2 } from "./env/uvec2"; +import { UVEC3 } from "./env/uvec3"; +import { UVEC4 } from "./env/uvec4"; +import { VEC2 } from "./env/vec2"; +import { VEC3 } from "./env/vec3"; +import { VEC4 } from "./env/vec4"; + +// TODO texture lookups +// all texture fns currently return [0,0,0,0] or 0 +const SAMPLER_TODO: JSBuiltinsSampler = { + texelFetch: () => ZERO4, + texelFetchOffset: () => ZERO4, + texture: () => ZERO4, + texturen: () => 0, + textureGrad: () => ZERO4, + textureGradn: () => 0, + textureLod: () => ZERO4, + textureLodn: () => 0, + textureOffset: () => ZERO4, + textureOffsetn: () => 0, + textureProj: () => ZERO4, + textureProjn: () => 0, + textureSize: () => ZERO3 +}; + +export const JS_DEFAULT_ENV: JSEnv = { + vec2n: (n) => [n, n], + vec3n: (n) => [n, n, n], + vec4n: (n) => [n, n, n, n], + vec3vn: (a, n) => setVN3([], a, n), + vec4vn: (a, n) => setVN4([], a, n), + vec4vnn: (a, z, w) => setVV4([], a, [z, w]), + vec4vv: (a, b) => setVV4([], a, b), + mat2n: (n) => mat22n([], n), + mat2vv: (a, b) => mat22v([], a, b), + mat3n: (n) => mat33n([], n), + mat3vvv: (a, b, c) => mat33v([], a, b, c), + mat4n: (n) => mat44n([], n), + mat4vvvv: (a, b, c, d) => mat44v([], a, b, c, d), + swizzle2: (a, b, c) => swizzle2([], a, b, c), + swizzle3: (a, b, c, d) => swizzle3([], a, b, c, d), + swizzle4: (a, b, c, d, e) => swizzle4([], a, b, c, d, e), + set_swizzle2: setSwizzle2, + set_swizzle3: setSwizzle3, + set_swizzle4: setSwizzle4, + float: FLOAT, + int: INT, + uint: UINT, + vec2: VEC2, + vec3: VEC3, + vec4: VEC4, + ivec2: IVEC2, + ivec3: IVEC3, + ivec4: IVEC4, + uvec2: UVEC2, + uvec3: UVEC3, + uvec4: UVEC4, + mat2: MAT2, + mat3: MAT3, + mat4: MAT4, + sampler1D: SAMPLER_TODO, + sampler2D: SAMPLER_TODO, + sampler3D: SAMPLER_TODO, + samplerCube: SAMPLER_TODO, + sampler2DShadow: SAMPLER_TODO, + samplerCubeShadow: SAMPLER_TODO +}; diff --git a/packages/shader-ast-js/src/env/float.ts b/packages/shader-ast-js/src/env/float.ts new file mode 100644 index 0000000000..76874a76f7 --- /dev/null +++ b/packages/shader-ast-js/src/env/float.ts @@ -0,0 +1,47 @@ +import { + clamp, + deg, + fmod, + fract, + mix, + rad, + smoothStep, + step +} from "@thi.ng/math"; +import { JSBuiltinsFloat } from "../api"; + +export const FLOAT: JSBuiltinsFloat = { + abs: Math.abs, + acos: Math.acos, + asin: Math.asin, + atan: Math.atan, + atannn: Math.atan2, + ceil: Math.ceil, + clamp, + cos: Math.cos, + degrees: deg, + dFdx: () => 0, + dFdy: () => 0, + exp: Math.exp, + exp2: (x) => Math.pow(2, x), + floor: Math.floor, + fract, + fwidth: () => 0, + inversesqrt: (x) => 1 / Math.sqrt(x), + log: Math.log, + log2: Math.log2, + max: Math.max, + min: Math.min, + mix, + mixn: mix, + mod: fmod, + modn: fmod, + pow: Math.pow, + radians: rad, + sign: Math.sign, + sin: Math.sin, + smoothstep: smoothStep, + sqrt: Math.sqrt, + step, + tan: Math.tan +}; diff --git a/packages/shader-ast-js/src/env/int.ts b/packages/shader-ast-js/src/env/int.ts new file mode 100644 index 0000000000..301c26e3d5 --- /dev/null +++ b/packages/shader-ast-js/src/env/int.ts @@ -0,0 +1,24 @@ +import { clamp } from "@thi.ng/math"; +import { JSBuiltinsInt } from "../api"; + +export const INT: JSBuiltinsInt = { + abs: Math.abs, + add: (a, b) => (a + b) | 0, + bitand: (a, b) => a & b, + bitnot1: (a) => ~a, + bitor: (a, b) => a | b, + bitxor: (a, b) => a ^ b, + clamp, + dec: (a) => (a - 1) | 0, + div: (a, b) => (a / b) | 0, + inc: (a) => (a + 1) | 0, + lshift: (a, b) => a << b, + max: Math.max, + min: Math.min, + modi: (a, b) => a % b, + mul: (a, b) => (a * b) | 0, + rshift: (a, b) => a >> b, + sign: Math.sign, + sub: (a, b) => (a - b) | 0, + sub1: (a) => -a | 0 +}; diff --git a/packages/shader-ast-js/src/env/ivec2.ts b/packages/shader-ast-js/src/env/ivec2.ts new file mode 100644 index 0000000000..b27de1e141 --- /dev/null +++ b/packages/shader-ast-js/src/env/ivec2.ts @@ -0,0 +1,45 @@ +import { + addI2, + addNI2, + bitAndI2, + bitNotI2, + bitOrI2, + bitXorI2, + divI2, + divNI2, + lshiftI2, + mod2, + modN2, + mulI2, + mulNI2, + rshiftI2, + subI2, + subNI2 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC2 } from "./vec2"; + +export const IVEC2: JSBuiltinsIntVec = { + ...VEC2, + add: (a, b) => addI2([], a, b), + addvn: (a, b) => addNI2([], a, b), + addnv: (a, b) => addNI2([], b, a), + div: (a, b) => divI2([], a, b), + divvn: (a, b) => divNI2([], a, b), + divnv: (a, b) => mulNI2([], b, 1 / a), + modi: (a, b) => mod2([], a, b), + modivn: (a, b) => modN2([], a, b), + modinv: (a, b) => mod2([], [a, a], b), + mul: (a, b) => mulI2([], a, b), + mulvn: (a, b) => mulNI2([], a, b), + mulnv: (a, b) => mulNI2([], b, a), + sub: (a, b) => subI2([], a, b), + subvn: (a, b) => subNI2([], a, b), + subnv: (a, b) => subI2([], [a, a], b), + bitand: (a, b) => bitAndI2([], a, b), + lshift: (a, b) => lshiftI2([], a, b), + bitnot1: (a) => bitNotI2([], a), + bitor: (a, b) => bitOrI2([], a, b), + rshift: (a, b) => rshiftI2([], a, b), + bitxor: (a, b) => bitXorI2([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/ivec3.ts b/packages/shader-ast-js/src/env/ivec3.ts new file mode 100644 index 0000000000..d9c31a9fd2 --- /dev/null +++ b/packages/shader-ast-js/src/env/ivec3.ts @@ -0,0 +1,45 @@ +import { + addI3, + addNI3, + bitAndI3, + bitNotI3, + bitOrI3, + bitXorI3, + divI3, + divNI3, + lshiftI3, + mod3, + modN3, + mulI3, + mulNI3, + rshiftI3, + subI3, + subNI3 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC3 } from "./vec3"; + +export const IVEC3: JSBuiltinsIntVec = { + ...VEC3, + add: (a, b) => addI3([], a, b), + addvn: (a, b) => addNI3([], a, b), + addnv: (a, b) => addNI3([], b, a), + div: (a, b) => divI3([], a, b), + divvn: (a, b) => divNI3([], a, b), + divnv: (a, b) => mulNI3([], b, 1 / a), + modi: (a, b) => mod3([], a, b), + modivn: (a, b) => modN3([], a, b), + modinv: (a, b) => mod3([], [a, a, a], b), + mul: (a, b) => mulI3([], a, b), + mulvn: (a, b) => mulNI3([], a, b), + mulnv: (a, b) => mulNI3([], b, a), + sub: (a, b) => subI3([], a, b), + subvn: (a, b) => subNI3([], a, b), + subnv: (a, b) => subI3([], [a, a, a], b), + bitand: (a, b) => bitAndI3([], a, b), + lshift: (a, b) => lshiftI3([], a, b), + bitnot1: (a) => bitNotI3([], a), + bitor: (a, b) => bitOrI3([], a, b), + rshift: (a, b) => rshiftI3([], a, b), + bitxor: (a, b) => bitXorI3([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/ivec4.ts b/packages/shader-ast-js/src/env/ivec4.ts new file mode 100644 index 0000000000..d40f917712 --- /dev/null +++ b/packages/shader-ast-js/src/env/ivec4.ts @@ -0,0 +1,45 @@ +import { + addI4, + addNI4, + bitAndI4, + bitNotI4, + bitOrI4, + bitXorI4, + divI4, + divNI4, + lshiftI4, + mod4, + modN4, + mulI4, + mulNI4, + rshiftI4, + subI4, + subNI4 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC4 } from "./vec4"; + +export const IVEC4: JSBuiltinsIntVec = { + ...VEC4, + add: (a, b) => addI4([], a, b), + addvn: (a, b) => addNI4([], a, b), + addnv: (a, b) => addNI4([], b, a), + div: (a, b) => divI4([], a, b), + divvn: (a, b) => divNI4([], a, b), + divnv: (a, b) => mulNI4([], b, 1 / a), + modi: (a, b) => mod4([], a, b), + modivn: (a, b) => modN4([], a, b), + modinv: (a, b) => mod4([], [a, a, a, a], b), + mul: (a, b) => mulI4([], a, b), + mulvn: (a, b) => mulNI4([], a, b), + mulnv: (a, b) => mulNI4([], b, a), + sub: (a, b) => subI4([], a, b), + subvn: (a, b) => subNI4([], a, b), + subnv: (a, b) => subI4([], [a, a, a, a], b), + bitand: (a, b) => bitAndI4([], a, b), + lshift: (a, b) => lshiftI4([], a, b), + bitnot1: (a) => bitNotI4([], a), + bitor: (a, b) => bitOrI4([], a, b), + rshift: (a, b) => rshiftI4([], a, b), + bitxor: (a, b) => bitXorI4([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/mat2.ts b/packages/shader-ast-js/src/env/mat2.ts new file mode 100644 index 0000000000..500f2d893e --- /dev/null +++ b/packages/shader-ast-js/src/env/mat2.ts @@ -0,0 +1,36 @@ +import { + add22, + addN22, + div22, + divN22, + mul22, + mulM22, + mulN22, + mulV22, + mulVM22, + sub22, + subN22 +} from "@thi.ng/matrices"; +import { neg } from "@thi.ng/vectors"; +import { JSBuiltinsMat } from "../api"; + +export const MAT2: JSBuiltinsMat = { + add: (a, b) => add22([], a, b), + addnv: (a, b) => addN22([], b, a), + addvn: (a, b) => addN22([], a, b), + dec: (a) => subN22([], a, 1), + div: (a, b) => div22([], a, b), + divnv: (a, b) => mulN22([], b, 1 / a), + divvn: (a, b) => divN22([], a, b), + inc: (a) => addN22([], a, 1), + mul: (a, b) => mul22([], a, b), + mulm: (a, b) => mulM22([], a, b), + mulmv: (a, b) => mulV22([], a, b), + mulnv: (a, b) => mulN22([], b, a), + mulvm: (a, b) => mulVM22([], a, b), + mulvn: (a, b) => mulN22([], a, b), + sub: (a, b) => sub22([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub22(null, [a, a, a, a], b), + subvn: (a, b) => subN22([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/mat3.ts b/packages/shader-ast-js/src/env/mat3.ts new file mode 100644 index 0000000000..6e8fe9cfec --- /dev/null +++ b/packages/shader-ast-js/src/env/mat3.ts @@ -0,0 +1,36 @@ +import { + add33, + addN33, + div33, + divN33, + mul33, + mulM33, + mulN33, + mulV33, + mulVM33, + sub33, + subN33 +} from "@thi.ng/matrices"; +import { neg, vecOf } from "@thi.ng/vectors"; +import { JSBuiltinsMat } from "../api"; + +export const MAT3: JSBuiltinsMat = { + add: (a, b) => add33([], a, b), + addnv: (a, b) => addN33([], b, a), + addvn: (a, b) => addN33([], a, b), + dec: (a) => subN33([], a, 1), + div: (a, b) => div33([], a, b), + divnv: (a, b) => mulN33([], b, 1 / a), + divvn: (a, b) => divN33([], a, b), + inc: (a) => addN33([], a, 1), + mul: (a, b) => mul33([], a, b), + mulm: (a, b) => mulM33([], a, b), + mulmv: (a, b) => mulV33([], a, b), + mulnv: (a, b) => mulN33([], b, a), + mulvm: (a, b) => mulVM33([], a, b), + mulvn: (a, b) => mulN33([], a, b), + sub: (a, b) => sub33([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub33(null, vecOf(9, a), b), + subvn: (a, b) => subN33([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/mat4.ts b/packages/shader-ast-js/src/env/mat4.ts new file mode 100644 index 0000000000..7a17278a53 --- /dev/null +++ b/packages/shader-ast-js/src/env/mat4.ts @@ -0,0 +1,36 @@ +import { + add44, + addN44, + div44, + divN44, + mul44, + mulM44, + mulN44, + mulV44, + mulVM44, + sub44, + subN44 +} from "@thi.ng/matrices"; +import { neg, vecOf } from "@thi.ng/vectors"; +import { JSBuiltinsMat } from "../api"; + +export const MAT4: JSBuiltinsMat = { + add: (a, b) => add44([], a, b), + addnv: (a, b) => addN44([], b, a), + addvn: (a, b) => addN44([], a, b), + dec: (a) => subN44([], a, 1), + div: (a, b) => div44([], a, b), + divnv: (a, b) => mulN44([], b, 1 / a), + divvn: (a, b) => divN44([], a, b), + inc: (a) => addN44([], a, 1), + mul: (a, b) => mul44([], a, b), + mulm: (a, b) => mulM44([], a, b), + mulmv: (a, b) => mulV44([], a, b), + mulnv: (a, b) => mulN44([], b, a), + mulvm: (a, b) => mulVM44([], a, b), + mulvn: (a, b) => mulN44([], a, b), + sub: (a, b) => sub44([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub44(null, vecOf(16, a), b), + subvn: (a, b) => subN44([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/uint.ts b/packages/shader-ast-js/src/env/uint.ts new file mode 100644 index 0000000000..72e1a872c0 --- /dev/null +++ b/packages/shader-ast-js/src/env/uint.ts @@ -0,0 +1,24 @@ +import { clamp } from "@thi.ng/math"; +import { JSBuiltinsInt } from "../api"; + +export const UINT: JSBuiltinsInt = { + abs: Math.abs, + add: (a, b) => (a + b) >>> 0, + bitand: (a, b) => (a & b) >>> 0, + bitnot1: (a) => ~a >>> 0, + bitor: (a, b) => (a | b) >>> 0, + bitxor: (a, b) => (a ^ b) >>> 0, + clamp, + dec: (a) => (a - 1) >>> 0, + div: (a, b) => (a / b) >>> 0, + inc: (a) => (a + 1) >>> 0, + lshift: (a, b) => (a << b) >>> 0, + max: Math.max, + min: Math.min, + modi: (a, b) => a % b, + mul: (a, b) => (a * b) >>> 0, + rshift: (a, b) => a >>> b, + sign: Math.sign, + sub: (a, b) => (a - b) >>> 0, + sub1: (a) => -a >>> 0 +}; diff --git a/packages/shader-ast-js/src/env/uvec2.ts b/packages/shader-ast-js/src/env/uvec2.ts new file mode 100644 index 0000000000..cc90be6b78 --- /dev/null +++ b/packages/shader-ast-js/src/env/uvec2.ts @@ -0,0 +1,45 @@ +import { + addNU2, + addU2, + bitAndU2, + bitNotU2, + bitOrU2, + bitXorU2, + divNU2, + divU2, + lshiftU2, + mod2, + modN2, + mulNU2, + mulU2, + rshiftU2, + subNU2, + subU2 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC2 } from "./vec2"; + +export const UVEC2: JSBuiltinsIntVec = { + ...VEC2, + add: (a, b) => addU2([], a, b), + addvn: (a, b) => addNU2([], a, b), + addnv: (a, b) => addNU2([], b, a), + div: (a, b) => divU2([], a, b), + divvn: (a, b) => divNU2([], a, b), + divnv: (a, b) => mulNU2([], b, 1 / a), + modi: (a, b) => mod2([], a, b), + modivn: (a, b) => modN2([], a, b), + modinv: (a, b) => mod2([], [a, a], b), + mul: (a, b) => mulU2([], a, b), + mulvn: (a, b) => mulNU2([], a, b), + mulnv: (a, b) => mulNU2([], b, a), + sub: (a, b) => subU2([], a, b), + subvn: (a, b) => subNU2([], a, b), + subnv: (a, b) => subU2([], [a, a], b), + bitand: (a, b) => bitAndU2([], a, b), + lshift: (a, b) => lshiftU2([], a, b), + bitnot1: (a) => bitNotU2([], a), + bitor: (a, b) => bitOrU2([], a, b), + rshift: (a, b) => rshiftU2([], a, b), + bitxor: (a, b) => bitXorU2([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/uvec3.ts b/packages/shader-ast-js/src/env/uvec3.ts new file mode 100644 index 0000000000..dc1290e7e9 --- /dev/null +++ b/packages/shader-ast-js/src/env/uvec3.ts @@ -0,0 +1,45 @@ +import { + addNU3, + addU3, + bitAndU3, + bitNotU3, + bitOrU3, + bitXorU3, + divNU3, + divU3, + lshiftU3, + mod3, + modN3, + mulNU3, + mulU3, + rshiftU3, + subNU3, + subU3 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC3 } from "./vec3"; + +export const UVEC3: JSBuiltinsIntVec = { + ...VEC3, + add: (a, b) => addU3([], a, b), + addvn: (a, b) => addNU3([], a, b), + addnv: (a, b) => addNU3([], b, a), + div: (a, b) => divU3([], a, b), + divvn: (a, b) => divNU3([], a, b), + divnv: (a, b) => mulNU3([], b, 1 / a), + modi: (a, b) => mod3([], a, b), + modivn: (a, b) => modN3([], a, b), + modinv: (a, b) => mod3([], [a, a, a], b), + mul: (a, b) => mulU3([], a, b), + mulvn: (a, b) => mulNU3([], a, b), + mulnv: (a, b) => mulNU3([], b, a), + sub: (a, b) => subU3([], a, b), + subvn: (a, b) => subNU3([], a, b), + subnv: (a, b) => subU3([], [a, a, a], b), + bitand: (a, b) => bitAndU3([], a, b), + lshift: (a, b) => lshiftU3([], a, b), + bitnot1: (a) => bitNotU3([], a), + bitor: (a, b) => bitOrU3([], a, b), + rshift: (a, b) => rshiftU3([], a, b), + bitxor: (a, b) => bitXorU3([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/uvec4.ts b/packages/shader-ast-js/src/env/uvec4.ts new file mode 100644 index 0000000000..f2755b9586 --- /dev/null +++ b/packages/shader-ast-js/src/env/uvec4.ts @@ -0,0 +1,45 @@ +import { + addNU4, + addU4, + bitAndU4, + bitNotU4, + bitOrU4, + bitXorU4, + divNU4, + divU4, + lshiftU4, + mod4, + modN4, + mulNU4, + mulU4, + rshiftU4, + subNU4, + subU4 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC4 } from "./vec4"; + +export const UVEC4: JSBuiltinsIntVec = { + ...VEC4, + add: (a, b) => addU4([], a, b), + addvn: (a, b) => addNU4([], a, b), + addnv: (a, b) => addNU4([], b, a), + div: (a, b) => divU4([], a, b), + divvn: (a, b) => divNU4([], a, b), + divnv: (a, b) => mulNU4([], b, 1 / a), + modi: (a, b) => mod4([], a, b), + modivn: (a, b) => modN4([], a, b), + modinv: (a, b) => mod4([], [a, a, a, a], b), + mul: (a, b) => mulU4([], a, b), + mulvn: (a, b) => mulNU4([], a, b), + mulnv: (a, b) => mulNU4([], b, a), + sub: (a, b) => subU4([], a, b), + subvn: (a, b) => subNU4([], a, b), + subnv: (a, b) => subU4([], [a, a, a, a], b), + bitand: (a, b) => bitAndU4([], a, b), + lshift: (a, b) => lshiftU4([], a, b), + bitnot1: (a) => bitNotU4([], a), + bitor: (a, b) => bitOrU4([], a, b), + rshift: (a, b) => rshiftU4([], a, b), + bitxor: (a, b) => bitXorU4([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/vec2.ts b/packages/shader-ast-js/src/env/vec2.ts new file mode 100644 index 0000000000..2da481bbac --- /dev/null +++ b/packages/shader-ast-js/src/env/vec2.ts @@ -0,0 +1,108 @@ +import { + abs2, + acos2, + add2, + addN2, + asin2, + atan_22, + atan2, + ceil2, + clamp2, + cos2, + degrees2, + dist, + div2, + divN2, + dot2, + exp_22, + exp2, + faceForward, + floor2, + fmod2, + fmodN2, + fract2, + invSqrt2, + log_22, + log2, + mag, + max2, + min2, + mix2, + mixN2, + mul2, + mulN2, + neg, + normalize, + pow2, + radians2, + reflect, + refract, + sign2, + sin2, + smoothStep2, + sqrt2, + step2, + sub2, + subN2, + tan2, + ZERO2 +} from "@thi.ng/vectors"; +import { JSBuiltinsVec } from "../api"; + +export const VEC2: JSBuiltinsVec = { + abs: (a) => abs2([], a), + acos: (a) => acos2([], a), + add: (a, b) => add2([], a, b), + addnv: (a, b) => addN2([], b, a), + addvn: (a, b) => addN2([], a, b), + asin: (a) => asin2([], a), + atan: (a) => atan2([], a), + atannn: (a, b) => atan_22([], a, b), + ceil: (a) => ceil2([], a), + clamp: (x, a, b) => clamp2([], x, a, b), + cos: (a) => cos2([], a), + dec: (a) => subN2([], a, 1), + degrees: (a) => degrees2([], a), + dFdx: () => ZERO2, + dFdy: () => ZERO2, + distance: dist, + div: (a, b) => div2([], a, b), + divnv: (a, b) => mulN2([], b, 1 / a), + divvn: (a, b) => divN2([], a, b), + dot: (a, b) => dot2(a, b), + exp: (a) => exp2([], a), + exp2: (a) => exp_22([], a), + faceForward: (a, b, c) => faceForward([], a, b, c), + floor: (a) => floor2([], a), + fract: (a) => fract2([], a), + fwidth: () => ZERO2, + inc: (a) => addN2([], a, 1), + inversesqrt: (a) => invSqrt2([], a), + length: mag, + log: (a) => log2([], a), + log2: (a) => log_22([], a), + max: (a, b) => max2([], a, b), + min: (a, b) => min2([], a, b), + mix: (a, b, t) => mix2([], a, b, t), + mixn: (a, b, t) => mixN2([], a, b, t), + mod: (a, b) => fmod2([], a, b), + modn: (a, b) => fmodN2([], a, b), + mul: (a, b) => mul2([], a, b), + mulnv: (a, b) => mulN2([], b, a), + mulvn: (a, b) => mulN2([], a, b), + normalize: (a) => normalize([], a), + pow: (a, b) => pow2([], a, b), + radians: (a) => radians2([], a), + reflect: (a, b) => reflect([], a, b), + refract: (a, b, c) => refract([], a, b, c), + sign: (a) => sign2([], a), + sin: (a) => sin2([], a), + smoothstep: (a, b, t) => smoothStep2([], a, b, t), + sqrt: (a) => sqrt2([], a), + step: (a, b) => step2([], a, b), + sub: (a, b) => sub2([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub2(null, [a, a], b), + subvn: (a, b) => subN2([], a, b), + tan: (a) => tan2([], a) +}; diff --git a/packages/shader-ast-js/src/env/vec3.ts b/packages/shader-ast-js/src/env/vec3.ts new file mode 100644 index 0000000000..acb6278f1a --- /dev/null +++ b/packages/shader-ast-js/src/env/vec3.ts @@ -0,0 +1,110 @@ +import { + abs3, + acos3, + add3, + addN3, + asin3, + atan_23, + atan3, + ceil3, + clamp3, + cos3, + cross3, + degrees3, + dist, + div3, + divN3, + dot3, + exp_23, + exp3, + faceForward, + floor3, + fmod3, + fmodN3, + fract3, + invSqrt3, + log_23, + log3, + mag, + max3, + min3, + mix3, + mixN3, + mul3, + mulN3, + neg, + normalize, + pow3, + radians3, + reflect, + refract, + sign3, + sin3, + smoothStep3, + sqrt3, + step3, + sub3, + subN3, + tan3, + ZERO3 +} from "@thi.ng/vectors"; +import { JSBuiltinsVec3 } from "../api"; + +export const VEC3: JSBuiltinsVec3 = { + abs: (a) => abs3([], a), + acos: (a) => acos3([], a), + add: (a, b) => add3([], a, b), + addnv: (a, b) => addN3([], b, a), + addvn: (a, b) => addN3([], a, b), + asin: (a) => asin3([], a), + atan: (a) => atan3([], a), + atannn: (a, b) => atan_23([], a, b), + ceil: (a) => ceil3([], a), + clamp: (x, a, b) => clamp3([], x, a, b), + cos: (a) => cos3([], a), + cross: (a, b) => cross3([], a, b), + dec: (a) => subN3([], a, 1), + degrees: (a) => degrees3([], a), + dFdx: () => ZERO3, + dFdy: () => ZERO3, + distance: dist, + div: (a, b) => div3([], a, b), + divnv: (a, b) => mulN3([], b, 1 / a), + divvn: (a, b) => divN3([], a, b), + dot: (a, b) => dot3(a, b), + exp: (a) => exp3([], a), + exp2: (a) => exp_23([], a), + faceForward: (a, b, c) => faceForward([], a, b, c), + floor: (a) => floor3([], a), + fract: (a) => fract3([], a), + fwidth: () => ZERO3, + inc: (a) => addN3([], a, 1), + inversesqrt: (a) => invSqrt3([], a), + length: mag, + log: (a) => log3([], a), + log2: (a) => log_23([], a), + max: (a, b) => max3([], a, b), + min: (a, b) => min3([], a, b), + mix: (a, b, t) => mix3([], a, b, t), + mixn: (a, b, t) => mixN3([], a, b, t), + mod: (a, b) => fmod3([], a, b), + modn: (a, b) => fmodN3([], a, b), + mul: (a, b) => mul3([], a, b), + mulnv: (a, b) => mulN3([], b, a), + mulvn: (a, b) => mulN3([], a, b), + normalize: (a) => normalize([], a), + pow: (a, b) => pow3([], a, b), + radians: (a) => radians3([], a), + reflect: (a, b) => reflect([], a, b), + refract: (a, b, c) => refract([], a, b, c), + sign: (a) => sign3([], a), + sin: (a) => sin3([], a), + smoothstep: (a, b, t) => smoothStep3([], a, b, t), + sqrt: (a) => sqrt3([], a), + step: (a, b) => step3([], a, b), + sub: (a, b) => sub3([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub3(null, [a, a, a], b), + subvn: (a, b) => subN3([], a, b), + tan: (a) => tan3([], a) +}; diff --git a/packages/shader-ast-js/src/env/vec4.ts b/packages/shader-ast-js/src/env/vec4.ts new file mode 100644 index 0000000000..5a53bca273 --- /dev/null +++ b/packages/shader-ast-js/src/env/vec4.ts @@ -0,0 +1,108 @@ +import { + abs4, + acos4, + add4, + addN4, + asin4, + atan_24, + atan4, + ceil4, + clamp4, + cos4, + degrees4, + dist, + div4, + divN4, + dot4, + exp_24, + exp4, + faceForward, + floor4, + fmod4, + fmodN4, + fract4, + invSqrt4, + log_24, + log4, + mag, + max4, + min4, + mix4, + mixN4, + mul4, + mulN4, + neg, + normalize, + pow4, + radians4, + reflect, + refract, + sign4, + sin4, + smoothStep4, + sqrt4, + step4, + sub4, + subN4, + tan4, + ZERO4 +} from "@thi.ng/vectors"; +import { JSBuiltinsVec } from "../api"; + +export const VEC4: JSBuiltinsVec = { + abs: (a) => abs4([], a), + acos: (a) => acos4([], a), + add: (a, b) => add4([], a, b), + addnv: (a, b) => addN4([], b, a), + addvn: (a, b) => addN4([], a, b), + asin: (a) => asin4([], a), + atan: (a) => atan4([], a), + atannn: (a, b) => atan_24([], a, b), + ceil: (a) => ceil4([], a), + clamp: (x, a, b) => clamp4([], x, a, b), + cos: (a) => cos4([], a), + dec: (a) => subN4([], a, 1), + degrees: (a) => degrees4([], a), + dFdx: () => ZERO4, + dFdy: () => ZERO4, + distance: dist, + div: (a, b) => div4([], a, b), + divnv: (a, b) => mulN4([], b, 1 / a), + divvn: (a, b) => divN4([], a, b), + dot: (a, b) => dot4(a, b), + exp: (a) => exp4([], a), + exp2: (a) => exp_24([], a), + faceForward: (a, b, c) => faceForward([], a, b, c), + floor: (a) => floor4([], a), + fract: (a) => fract4([], a), + fwidth: () => ZERO4, + inc: (a) => addN4([], a, 1), + inversesqrt: (a) => invSqrt4([], a), + length: mag, + log: (a) => log4([], a), + log2: (a) => log_24([], a), + max: (a, b) => max4([], a, b), + min: (a, b) => min4([], a, b), + mix: (a, b, t) => mix4([], a, b, t), + mixn: (a, b, t) => mixN4([], a, b, t), + mod: (a, b) => fmod4([], a, b), + modn: (a, b) => fmodN4([], a, b), + mul: (a, b) => mul4([], a, b), + mulnv: (a, b) => mulN4([], b, a), + mulvn: (a, b) => mulN4([], a, b), + normalize: (a) => normalize([], a), + pow: (a, b) => pow4([], a, b), + radians: (a) => radians4([], a), + reflect: (a, b) => reflect([], a, b), + refract: (a, b, c) => refract([], a, b, c), + sign: (a) => sign4([], a), + sin: (a) => sin4([], a), + smoothstep: (a, b, t) => smoothStep4([], a, b, t), + sqrt: (a) => sqrt4([], a), + step: (a, b) => step4([], a, b), + sub: (a, b) => sub4([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub4(null, [a, a, a, a], b), + subvn: (a, b) => subN4([], a, b), + tan: (a) => tan4([], a) +}; diff --git a/packages/shader-ast-js/src/index.ts b/packages/shader-ast-js/src/index.ts index 1434fa24f7..6c670b4828 100644 --- a/packages/shader-ast-js/src/index.ts +++ b/packages/shader-ast-js/src/index.ts @@ -1,2 +1,4 @@ +export * from "./api"; +export * from "./env"; export * from "./runtime"; export * from "./target"; diff --git a/packages/shader-ast-js/src/target.ts b/packages/shader-ast-js/src/target.ts index d610d03b9e..84a9804176 100644 --- a/packages/shader-ast-js/src/target.ts +++ b/packages/shader-ast-js/src/target.ts @@ -1,65 +1,6 @@ -import { - Fn, - Fn2, - Fn3, - Fn4, - Fn5, - Fn6 -} from "@thi.ng/api"; +import { Fn } from "@thi.ng/api"; import { isBoolean, isNumber } from "@thi.ng/checks"; import { unsupported } from "@thi.ng/errors"; -import { - clamp, - deg, - fmod, - fract, - mix, - rad, - smoothStep, - step -} from "@thi.ng/math"; -import { - add22, - add33, - add44, - addN22, - addN33, - addN44, - div22, - div33, - div44, - divN22, - divN33, - divN44, - Mat, - mat22n, - mat22v, - mat33n, - mat33v, - mat44n, - mat44v, - mul22, - mul33, - mul44, - mulM22, - mulM33, - mulM44, - mulN22, - mulN33, - mulN44, - mulV22, - mulV33, - mulV44, - mulVM22, - mulVM33, - mulVM44, - sub22, - sub33, - sub44, - subN22, - subN33, - subN44 -} from "@thi.ng/matrices"; import { defTarget, Func, @@ -75,1007 +16,95 @@ import { Sym, Term } from "@thi.ng/shader-ast"; -import { - abs2, - abs3, - abs4, - acos2, - acos3, - acos4, - add2, - add3, - add4, - addI2, - addI3, - addI4, - addN2, - addN3, - addN4, - addNI2, - addNI3, - addNI4, - addNU2, - addNU3, - addNU4, - addU2, - addU3, - addU4, - asin2, - asin3, - asin4, - atan2, - atan3, - atan4, - bitAndI2, - bitAndI3, - bitAndI4, - bitAndU2, - bitAndU3, - bitAndU4, - bitNotI2, - bitNotI3, - bitNotI4, - bitNotU2, - bitNotU3, - bitNotU4, - bitOrI2, - bitOrI3, - bitOrI4, - bitOrU2, - bitOrU3, - bitOrU4, - bitXorI2, - bitXorI3, - bitXorI4, - bitXorU2, - bitXorU3, - bitXorU4, - ceil2, - ceil3, - ceil4, - clamp2, - clamp3, - clamp4, - cos2, - cos3, - cos4, - cross3, - degrees2, - degrees3, - degrees4, - dist, - div2, - div3, - div4, - divI2, - divI3, - divI4, - divN2, - divN3, - divN4, - divNI2, - divNI3, - divNI4, - divNU2, - divNU3, - divNU4, - divU2, - divU3, - divU4, - dot2, - dot3, - dot4, - exp_22, - exp_23, - exp_24, - exp2, - exp3, - exp4, - faceForward, - floor2, - floor3, - floor4, - fmod2, - fmod3, - fmod4, - fmodN2, - fmodN3, - fmodN4, - fract2, - fract3, - fract4, - invSqrt2, - invSqrt3, - invSqrt4, - log_22, - log_23, - log_24, - log2, - log3, - log4, - lshiftI2, - lshiftI3, - lshiftI4, - lshiftU2, - lshiftU3, - lshiftU4, - mag, - max2, - max3, - max4, - min2, - min3, - min4, - mix2, - mix3, - mix4, - mixN2, - mixN3, - mixN4, - mod2, - mod3, - mod4, - modN2, - modN3, - modN4, - mul2, - mul3, - mul4, - mulI2, - mulI3, - mulI4, - mulN2, - mulN3, - mulN4, - mulNI2, - mulNI3, - mulNI4, - mulNU2, - mulNU3, - mulNU4, - mulU2, - mulU3, - mulU4, - neg, - normalize, - pow2, - pow3, - pow4, - radians2, - radians3, - radians4, - reflect, - refract, - rshiftI2, - rshiftI3, - rshiftI4, - rshiftU2, - rshiftU3, - rshiftU4, - setSwizzle2, - setSwizzle3, - setSwizzle4, - setVN3, - setVN4, - setVV4, - sign2, - sign3, - sign4, - sin2, - sin3, - sin4, - smoothStep2, - smoothStep3, - smoothStep4, - sqrt2, - sqrt3, - sqrt4, - step2, - step3, - step4, - sub2, - sub3, - sub4, - subI2, - subI3, - subI4, - subN2, - subN3, - subN4, - subNI2, - subNI3, - subNI4, - subNU2, - subNU3, - subNU4, - subU2, - subU3, - subU4, - swizzle2, - swizzle3, - swizzle4, - tan2, - tan3, - tan4, - Vec, - vecOf, - ZERO2, - ZERO3, - ZERO4 -} from "@thi.ng/vectors"; - -export interface JSTarget extends Fn, string> { - /** - * Compiles given AST to JavaScript, using optional `env` as backend - * for various operators / builtins. If `env` is not given the - * bundled `JS_DEFAULT_ENV` is used (based on thi.ng/vectors and - * thi.ng/matrices packages). - * - * Any functions defined in the given AST will be exported using - * their defined name via the returned object. - * - * ``` - * const js = targetJS(); - * const module = js.compile( - * defn("float", "foo", [["float"]], (x)=> [ret(mul(x, float(10)))]) - * ); - * - * module.foo(42) - * // 420 - * - * module.foo.toString() - * // function foo(_sym0) { - * // return (_sym0 * 10); - * // } - * ``` - * - * @param tree - * @param env - */ - compile(tree: Term, env?: JSEnv): any; -} - -export interface JSBuiltinsCommon { - abs: Fn; - clamp: Fn3; - max: Fn2; - min: Fn2; - sign: Fn; -} - -export interface JSBuiltinsMath { - sub1: Fn; - add: Fn2; - sub: Fn2; - mul: Fn2; - div: Fn2; - inc: Fn; - dec: Fn; -} - -export interface JSBuiltinsBinary { - bitand: Fn2; - lshift: Fn2; - bitnot1: Fn2; - bitor: Fn2; - rshift: Fn2; - bitxor: Fn2; -} - -export interface JSBuiltinsFloat extends JSBuiltinsCommon { - acos: Fn; - asin: Fn; - atan: Fn; - ceil: Fn; - cos: Fn; - degrees: Fn; - dFdx: Fn; - dFdy: Fn; - exp: Fn; - exp2: Fn; - floor: Fn; - fract: Fn; - fwidth: Fn; - inversesqrt: Fn; - log: Fn; - log2: Fn; - mix: Fn3; - mixn: Fn3; - mod: Fn2; - modn: Fn2; - pow: Fn2; - radians: Fn; - sin: Fn; - smoothstep: Fn3; - sqrt: Fn; - step: Fn2; - tan: Fn; -} - -export interface JSBuiltinsInt - extends JSBuiltinsCommon, - JSBuiltinsMath, - JSBuiltinsBinary { - modi: Fn2; -} - -export interface JSBuiltinsVecScalar { - addvn: Fn2; - subvn: Fn2; - mulvn: Fn2; - divvn: Fn2; - addnv: Fn2; - subnv: Fn2; - mulnv: Fn2; - divnv: Fn2; -} - -export interface JSBuiltinsVec - extends JSBuiltinsFloat, - JSBuiltinsMath, - JSBuiltinsVecScalar { - distance: Fn2; - dot: Fn2; - faceForward: Fn3; - length: Fn; - normalize: Fn; - reflect: Fn2; - refract: Fn3; -} - -export interface JSBuiltinsVec3 extends JSBuiltinsVec { - cross: Fn2; -} - -export interface JSBuiltinsIntVec - extends JSBuiltinsInt, - JSBuiltinsVecScalar, - JSBuiltinsBinary { - modivn: Fn2; - modinv: Fn2; -} - -export interface JSBuiltinsMat - extends JSBuiltinsMath, - JSBuiltinsVecScalar { - mulm: Fn2; - mulvm: Fn2; - mulmv: Fn2; -} - -export interface JSBuiltinsSampler { - texelFetch: Fn3; - texelFetchOffset: Fn4; - texture: Fn3; - texturen: Fn3; - textureGrad: Fn4; - textureGradn: Fn4; - textureLod: Fn3; - textureLodn: Fn3; - textureOffset: Fn4; - textureOffsetn: Fn4; - textureProj: Fn3; - textureProjn: Fn3; - textureSize: Fn2; -} - -export interface JSEnv { - vec2n: Fn; - vec3n: Fn; - vec3vn: Fn2; - vec4n: Fn; - vec4vn: Fn2; - vec4vnn: Fn3; - vec4vv: Fn2; - mat2n: Fn; - mat2vv: Fn2; - mat3n: Fn; - mat3vvv: Fn3; - mat4n: Fn; - mat4vvvv: Fn4; - // swizzle1: Fn2; - swizzle2: Fn3; - swizzle3: Fn4; - swizzle4: Fn5; - // set_swizzle1: Fn3; - set_swizzle2: Fn4; - set_swizzle3: Fn5; - set_swizzle4: Fn6; - float: JSBuiltinsFloat; - int: JSBuiltinsInt; - uint: JSBuiltinsInt; - vec2: JSBuiltinsVec; - vec3: JSBuiltinsVec3; - vec4: JSBuiltinsVec; - ivec2: JSBuiltinsIntVec; - ivec3: JSBuiltinsIntVec; - ivec4: JSBuiltinsIntVec; - uvec2: JSBuiltinsIntVec; - uvec3: JSBuiltinsIntVec; - uvec4: JSBuiltinsIntVec; - mat2: JSBuiltinsMat; - mat3: JSBuiltinsMat; - mat4: JSBuiltinsMat; - sampler1D: JSBuiltinsSampler; - sampler2D: JSBuiltinsSampler; - sampler3D: JSBuiltinsSampler; - samplerCube: JSBuiltinsSampler; - sampler2DShadow: JSBuiltinsSampler; - samplerCubeShadow: JSBuiltinsSampler; -} - -// TODO texture lookups -// all texture fns currently return [0,0,0,0] or 0 -const SAMPLER_TODO: JSBuiltinsSampler = { - texelFetch: () => ZERO4, - texelFetchOffset: () => ZERO4, - texture: () => ZERO4, - texturen: () => 0, - textureGrad: () => ZERO4, - textureGradn: () => 0, - textureLod: () => ZERO4, - textureLodn: () => 0, - textureOffset: () => ZERO4, - textureOffsetn: () => 0, - textureProj: () => ZERO4, - textureProjn: () => 0, - textureSize: () => ZERO3 -}; - -const env: Partial = { - vec2n: (n) => [n, n], - vec3n: (n) => [n, n, n], - vec4n: (n) => [n, n, n, n], - vec3vn: (a, n) => setVN3([], a, n), - vec4vn: (a, n) => setVN4([], a, n), - vec4vnn: (a, z, w) => setVV4([], a, [z, w]), - vec4vv: (a, b) => setVV4([], a, b), - mat2n: (n) => mat22n([], n), - mat2vv: (a, b) => mat22v([], a, b), - mat3n: (n) => mat33n([], n), - mat3vvv: (a, b, c) => mat33v([], a, b, c), - mat4n: (n) => mat44n([], n), - mat4vvvv: (a, b, c, d) => mat44v([], a, b, c, d), - swizzle2: (a, b, c) => swizzle2([], a, b, c), - swizzle3: (a, b, c, d) => swizzle3([], a, b, c, d), - swizzle4: (a, b, c, d, e) => swizzle4([], a, b, c, d, e), - set_swizzle2: setSwizzle2, - set_swizzle3: setSwizzle3, - set_swizzle4: setSwizzle4, - float: { - abs: Math.abs, - acos: Math.acos, - asin: Math.asin, - atan: Math.atan, - ceil: Math.ceil, - clamp, - cos: Math.cos, - degrees: deg, - dFdx: () => 0, - dFdy: () => 0, - exp: Math.exp, - exp2: (x) => Math.pow(2, x), - floor: Math.floor, - fract, - fwidth: () => 0, - inversesqrt: (x) => 1 / Math.sqrt(x), - log: Math.log, - log2: Math.log2, - max: Math.max, - min: Math.min, - mix, - mixn: mix, - mod: fmod, - modn: fmod, - pow: Math.pow, - radians: rad, - sign: Math.sign, - sin: Math.sin, - smoothstep: smoothStep, - sqrt: Math.sqrt, - step, - tan: Math.tan - }, - int: { - abs: Math.abs, - add: (a, b) => (a + b) | 0, - bitand: (a, b) => a & b, - bitnot1: (a) => ~a, - bitor: (a, b) => a | b, - bitxor: (a, b) => a ^ b, - clamp, - dec: (a) => (a - 1) | 0, - div: (a, b) => (a / b) | 0, - inc: (a) => (a + 1) | 0, - lshift: (a, b) => a << b, - max: Math.max, - min: Math.min, - modi: (a, b) => a % b, - mul: (a, b) => (a * b) | 0, - rshift: (a, b) => a >> b, - sign: Math.sign, - sub: (a, b) => (a - b) | 0, - sub1: (a) => -a | 0 - }, - uint: { - abs: Math.abs, - add: (a, b) => (a + b) >>> 0, - bitand: (a, b) => (a & b) >>> 0, - bitnot1: (a) => ~a >>> 0, - bitor: (a, b) => (a | b) >>> 0, - bitxor: (a, b) => (a ^ b) >>> 0, - clamp, - dec: (a) => (a - 1) >>> 0, - div: (a, b) => (a / b) >>> 0, - inc: (a) => (a + 1) >>> 0, - lshift: (a, b) => (a << b) >>> 0, - max: Math.max, - min: Math.min, - modi: (a, b) => a % b, - mul: (a, b) => (a * b) >>> 0, - rshift: (a, b) => a >>> b, - sign: Math.sign, - sub: (a, b) => (a - b) >>> 0, - sub1: (a) => -a >>> 0 - }, - vec2: { - abs: (a) => abs2([], a), - acos: (a) => acos2([], a), - add: (a, b) => add2([], a, b), - addnv: (a, b) => addN2([], b, a), - addvn: (a, b) => addN2([], a, b), - asin: (a) => asin2([], a), - atan: (a) => atan2([], a), - ceil: (a) => ceil2([], a), - clamp: (x, a, b) => clamp2([], x, a, b), - cos: (a) => cos2([], a), - dec: (a) => subN2([], a, 1), - degrees: (a) => degrees2([], a), - dFdx: () => ZERO2, - dFdy: () => ZERO2, - distance: dist, - div: (a, b) => div2([], a, b), - divnv: (a, b) => mulN2([], b, 1 / a), - divvn: (a, b) => divN2([], a, b), - dot: (a, b) => dot2(a, b), - exp: (a) => exp2([], a), - exp2: (a) => exp_22([], a), - faceForward: (a, b, c) => faceForward([], a, b, c), - floor: (a) => floor2([], a), - fract: (a) => fract2([], a), - fwidth: () => ZERO2, - inc: (a) => addN2([], a, 1), - inversesqrt: (a) => invSqrt2([], a), - length: mag, - log: (a) => log2([], a), - log2: (a) => log_22([], a), - max: (a, b) => max2([], a, b), - min: (a, b) => min2([], a, b), - mix: (a, b, t) => mix2([], a, b, t), - mixn: (a, b, t) => mixN2([], a, b, t), - mod: (a, b) => fmod2([], a, b), - modn: (a, b) => fmodN2([], a, b), - mul: (a, b) => mul2([], a, b), - mulnv: (a, b) => mulN2([], b, a), - mulvn: (a, b) => mulN2([], a, b), - normalize: (a) => normalize([], a), - pow: (a, b) => pow2([], a, b), - radians: (a) => radians2([], a), - reflect: (a, b) => reflect([], a, b), - refract: (a, b, c) => refract([], a, b, c), - sign: (a) => sign2([], a), - sin: (a) => sin2([], a), - smoothstep: (a, b, t) => smoothStep2([], a, b, t), - sqrt: (a) => sqrt2([], a), - step: (a, b) => step2([], a, b), - sub: (a, b) => sub2([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub2(null, [a, a], b), - subvn: (a, b) => subN2([], a, b), - tan: (a) => tan2([], a) - }, - vec3: { - abs: (a) => abs3([], a), - acos: (a) => acos3([], a), - add: (a, b) => add3([], a, b), - addnv: (a, b) => addN3([], b, a), - addvn: (a, b) => addN3([], a, b), - asin: (a) => asin3([], a), - atan: (a) => atan3([], a), - ceil: (a) => ceil3([], a), - clamp: (x, a, b) => clamp3([], x, a, b), - cos: (a) => cos3([], a), - cross: (a, b) => cross3([], a, b), - dec: (a) => subN3([], a, 1), - degrees: (a) => degrees3([], a), - dFdx: () => ZERO3, - dFdy: () => ZERO3, - distance: dist, - div: (a, b) => div3([], a, b), - divnv: (a, b) => mulN3([], b, 1 / a), - divvn: (a, b) => divN3([], a, b), - dot: (a, b) => dot3(a, b), - exp: (a) => exp3([], a), - exp2: (a) => exp_23([], a), - faceForward: (a, b, c) => faceForward([], a, b, c), - floor: (a) => floor3([], a), - fract: (a) => fract3([], a), - fwidth: () => ZERO3, - inc: (a) => addN3([], a, 1), - inversesqrt: (a) => invSqrt3([], a), - length: mag, - log: (a) => log3([], a), - log2: (a) => log_23([], a), - max: (a, b) => max3([], a, b), - min: (a, b) => min3([], a, b), - mix: (a, b, t) => mix3([], a, b, t), - mixn: (a, b, t) => mixN3([], a, b, t), - mod: (a, b) => fmod3([], a, b), - modn: (a, b) => fmodN3([], a, b), - mul: (a, b) => mul3([], a, b), - mulnv: (a, b) => mulN3([], b, a), - mulvn: (a, b) => mulN3([], a, b), - normalize: (a) => normalize([], a), - pow: (a, b) => pow3([], a, b), - radians: (a) => radians3([], a), - reflect: (a, b) => reflect([], a, b), - refract: (a, b, c) => refract([], a, b, c), - sign: (a) => sign3([], a), - sin: (a) => sin3([], a), - smoothstep: (a, b, t) => smoothStep3([], a, b, t), - sqrt: (a) => sqrt3([], a), - step: (a, b) => step3([], a, b), - sub: (a, b) => sub3([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub3(null, [a, a, a], b), - subvn: (a, b) => subN3([], a, b), - tan: (a) => tan3([], a) - }, - vec4: { - abs: (a) => abs4([], a), - acos: (a) => acos4([], a), - add: (a, b) => add4([], a, b), - addnv: (a, b) => addN4([], b, a), - addvn: (a, b) => addN4([], a, b), - asin: (a) => asin4([], a), - atan: (a) => atan4([], a), - ceil: (a) => ceil4([], a), - clamp: (x, a, b) => clamp4([], x, a, b), - cos: (a) => cos4([], a), - dec: (a) => subN4([], a, 1), - degrees: (a) => degrees4([], a), - dFdx: () => ZERO4, - dFdy: () => ZERO4, - distance: dist, - div: (a, b) => div4([], a, b), - divnv: (a, b) => mulN4([], b, 1 / a), - divvn: (a, b) => divN4([], a, b), - dot: (a, b) => dot4(a, b), - exp: (a) => exp4([], a), - exp2: (a) => exp_24([], a), - faceForward: (a, b, c) => faceForward([], a, b, c), - floor: (a) => floor4([], a), - fract: (a) => fract4([], a), - fwidth: () => ZERO4, - inc: (a) => addN4([], a, 1), - inversesqrt: (a) => invSqrt4([], a), - length: mag, - log: (a) => log4([], a), - log2: (a) => log_24([], a), - max: (a, b) => max4([], a, b), - min: (a, b) => min4([], a, b), - mix: (a, b, t) => mix4([], a, b, t), - mixn: (a, b, t) => mixN4([], a, b, t), - mod: (a, b) => fmod4([], a, b), - modn: (a, b) => fmodN4([], a, b), - mul: (a, b) => mul4([], a, b), - mulnv: (a, b) => mulN4([], b, a), - mulvn: (a, b) => mulN4([], a, b), - normalize: (a) => normalize([], a), - pow: (a, b) => pow4([], a, b), - radians: (a) => radians4([], a), - reflect: (a, b) => reflect([], a, b), - refract: (a, b, c) => refract([], a, b, c), - sign: (a) => sign4([], a), - sin: (a) => sin4([], a), - smoothstep: (a, b, t) => smoothStep4([], a, b, t), - sqrt: (a) => sqrt4([], a), - step: (a, b) => step4([], a, b), - sub: (a, b) => sub4([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub4(null, [a, a, a, a], b), - subvn: (a, b) => subN4([], a, b), - tan: (a) => tan4([], a) - }, - mat2: { - add: (a, b) => add22([], a, b), - addnv: (a, b) => addN22([], b, a), - addvn: (a, b) => addN22([], a, b), - dec: (a) => subN22([], a, 1), - div: (a, b) => div22([], a, b), - divnv: (a, b) => mulN22([], b, 1 / a), - divvn: (a, b) => divN22([], a, b), - inc: (a) => addN22([], a, 1), - mul: (a, b) => mul22([], a, b), - mulm: (a, b) => mulM22([], a, b), - mulmv: (a, b) => mulV22([], a, b), - mulnv: (a, b) => mulN22([], b, a), - mulvm: (a, b) => mulVM22([], a, b), - mulvn: (a, b) => mulN22([], a, b), - sub: (a, b) => sub22([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub22(null, [a, a, a, a], b), - subvn: (a, b) => subN22([], a, b) - }, - mat3: { - add: (a, b) => add33([], a, b), - addnv: (a, b) => addN33([], b, a), - addvn: (a, b) => addN33([], a, b), - dec: (a) => subN33([], a, 1), - div: (a, b) => div33([], a, b), - divnv: (a, b) => mulN33([], b, 1 / a), - divvn: (a, b) => divN33([], a, b), - inc: (a) => addN33([], a, 1), - mul: (a, b) => mul33([], a, b), - mulm: (a, b) => mulM33([], a, b), - mulmv: (a, b) => mulV33([], a, b), - mulnv: (a, b) => mulN33([], b, a), - mulvm: (a, b) => mulVM33([], a, b), - mulvn: (a, b) => mulN33([], a, b), - sub: (a, b) => sub33([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub33(null, vecOf(9, a), b), - subvn: (a, b) => subN33([], a, b) - }, - mat4: { - add: (a, b) => add44([], a, b), - addnv: (a, b) => addN44([], b, a), - addvn: (a, b) => addN44([], a, b), - dec: (a) => subN44([], a, 1), - div: (a, b) => div44([], a, b), - divnv: (a, b) => mulN44([], b, 1 / a), - divvn: (a, b) => divN44([], a, b), - inc: (a) => addN44([], a, 1), - mul: (a, b) => mul44([], a, b), - mulm: (a, b) => mulM44([], a, b), - mulmv: (a, b) => mulV44([], a, b), - mulnv: (a, b) => mulN44([], b, a), - mulvm: (a, b) => mulVM44([], a, b), - mulvn: (a, b) => mulN44([], a, b), - sub: (a, b) => sub44([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub44(null, vecOf(16, a), b), - subvn: (a, b) => subN44([], a, b) - }, - sampler1D: SAMPLER_TODO, - sampler2D: SAMPLER_TODO, - sampler3D: SAMPLER_TODO, - samplerCube: SAMPLER_TODO, - sampler2DShadow: SAMPLER_TODO, - samplerCubeShadow: SAMPLER_TODO +import { JSTarget } from "./api"; +import { JS_DEFAULT_ENV } from "./env"; + +const CMP_OPS: Partial> = { + "!": "not", + "<": "lt", + "<=": "lte", + "==": "eq", + "!=": "neq", + ">=": "gte", + ">": "gt" }; - -env.ivec2 = { - ...env.vec2!, - add: (a, b) => addI2([], a, b), - addvn: (a, b) => addNI2([], a, b), - addnv: (a, b) => addNI2([], b, a), - div: (a, b) => divI2([], a, b), - divvn: (a, b) => divNI2([], a, b), - divnv: (a, b) => mulNI2([], b, 1 / a), - modi: (a, b) => mod2([], a, b), - modivn: (a, b) => modN2([], a, b), - modinv: (a, b) => mod2([], [a, a], b), - mul: (a, b) => mulI2([], a, b), - mulvn: (a, b) => mulNI2([], a, b), - mulnv: (a, b) => mulNI2([], b, a), - sub: (a, b) => subI2([], a, b), - subvn: (a, b) => subNI2([], a, b), - subnv: (a, b) => subI2([], [a, a], b), - bitand: (a, b) => bitAndI2([], a, b), - lshift: (a, b) => lshiftI2([], a, b), - bitnot1: (a) => bitNotI2([], a), - bitor: (a, b) => bitOrI2([], a, b), - rshift: (a, b) => rshiftI2([], a, b), - bitxor: (a, b) => bitXorI2([], a, b) -}; - -env.ivec3 = { - ...env.vec3!, - add: (a, b) => addI3([], a, b), - addvn: (a, b) => addNI3([], a, b), - addnv: (a, b) => addNI3([], b, a), - div: (a, b) => divI3([], a, b), - divvn: (a, b) => divNI3([], a, b), - divnv: (a, b) => mulNI3([], b, 1 / a), - modi: (a, b) => mod3([], a, b), - modivn: (a, b) => modN3([], a, b), - modinv: (a, b) => mod3([], [a, a, a], b), - mul: (a, b) => mulI3([], a, b), - mulvn: (a, b) => mulNI3([], a, b), - mulnv: (a, b) => mulNI3([], b, a), - sub: (a, b) => subI3([], a, b), - subvn: (a, b) => subNI3([], a, b), - subnv: (a, b) => subI3([], [a, a, a], b), - bitand: (a, b) => bitAndI3([], a, b), - lshift: (a, b) => lshiftI3([], a, b), - bitnot1: (a) => bitNotI3([], a), - bitor: (a, b) => bitOrI3([], a, b), - rshift: (a, b) => rshiftI3([], a, b), - bitxor: (a, b) => bitXorI3([], a, b) -}; - -env.ivec4 = { - ...env.vec4!, - add: (a, b) => addI4([], a, b), - addvn: (a, b) => addNI4([], a, b), - addnv: (a, b) => addNI4([], b, a), - div: (a, b) => divI4([], a, b), - divvn: (a, b) => divNI4([], a, b), - divnv: (a, b) => mulNI4([], b, 1 / a), - modi: (a, b) => mod4([], a, b), - modivn: (a, b) => modN4([], a, b), - modinv: (a, b) => mod4([], [a, a, a, a], b), - mul: (a, b) => mulI4([], a, b), - mulvn: (a, b) => mulNI4([], a, b), - mulnv: (a, b) => mulNI4([], b, a), - sub: (a, b) => subI4([], a, b), - subvn: (a, b) => subNI4([], a, b), - subnv: (a, b) => subI4([], [a, a, a, a], b), - bitand: (a, b) => bitAndI4([], a, b), - lshift: (a, b) => lshiftI4([], a, b), - bitnot1: (a) => bitNotI4([], a), - bitor: (a, b) => bitOrI4([], a, b), - rshift: (a, b) => rshiftI4([], a, b), - bitxor: (a, b) => bitXorI4([], a, b) -}; - -env.uvec2 = { - ...env.vec2!, - add: (a, b) => addU2([], a, b), - addvn: (a, b) => addNU2([], a, b), - addnv: (a, b) => addNU2([], b, a), - div: (a, b) => divU2([], a, b), - divvn: (a, b) => divNU2([], a, b), - divnv: (a, b) => mulNU2([], b, 1 / a), - modi: (a, b) => mod2([], a, b), - modivn: (a, b) => modN2([], a, b), - modinv: (a, b) => mod2([], [a, a], b), - mul: (a, b) => mulU2([], a, b), - mulvn: (a, b) => mulNU2([], a, b), - mulnv: (a, b) => mulNU2([], b, a), - sub: (a, b) => subU2([], a, b), - subvn: (a, b) => subNU2([], a, b), - subnv: (a, b) => subU2([], [a, a], b), - bitand: (a, b) => bitAndU2([], a, b), - lshift: (a, b) => lshiftU2([], a, b), - bitnot1: (a) => bitNotU2([], a), - bitor: (a, b) => bitOrU2([], a, b), - rshift: (a, b) => rshiftU2([], a, b), - bitxor: (a, b) => bitXorU2([], a, b) -}; - -env.uvec3 = { - ...env.vec3!, - add: (a, b) => addU3([], a, b), - addvn: (a, b) => addNU3([], a, b), - addnv: (a, b) => addNU3([], b, a), - div: (a, b) => divU3([], a, b), - divvn: (a, b) => divNU3([], a, b), - divnv: (a, b) => mulNU3([], b, 1 / a), - modi: (a, b) => mod3([], a, b), - modivn: (a, b) => modN3([], a, b), - modinv: (a, b) => mod3([], [a, a, a], b), - mul: (a, b) => mulU3([], a, b), - mulvn: (a, b) => mulNU3([], a, b), - mulnv: (a, b) => mulNU3([], b, a), - sub: (a, b) => subU3([], a, b), - subvn: (a, b) => subNU3([], a, b), - subnv: (a, b) => subU3([], [a, a, a], b), - bitand: (a, b) => bitAndU3([], a, b), - lshift: (a, b) => lshiftU3([], a, b), - bitnot1: (a) => bitNotU3([], a), - bitor: (a, b) => bitOrU3([], a, b), - rshift: (a, b) => rshiftU3([], a, b), - bitxor: (a, b) => bitXorU3([], a, b) -}; - -env.uvec4 = { - ...env.vec4!, - add: (a, b) => addU4([], a, b), - addvn: (a, b) => addNU4([], a, b), - addnv: (a, b) => addNU4([], b, a), - div: (a, b) => divU4([], a, b), - divvn: (a, b) => divNU4([], a, b), - divnv: (a, b) => mulNU4([], b, 1 / a), - modi: (a, b) => mod4([], a, b), - modivn: (a, b) => modN4([], a, b), - modinv: (a, b) => mod4([], [a, a, a, a], b), - mul: (a, b) => mulU4([], a, b), - mulvn: (a, b) => mulNU4([], a, b), - mulnv: (a, b) => mulNU4([], b, a), - sub: (a, b) => subU4([], a, b), - subvn: (a, b) => subNU4([], a, b), - subnv: (a, b) => subU4([], [a, a, a, a], b), - bitand: (a, b) => bitAndU4([], a, b), - lshift: (a, b) => lshiftU4([], a, b), - bitnot1: (a) => bitNotU4([], a), - bitor: (a, b) => bitOrU4([], a, b), - rshift: (a, b) => rshiftU4([], a, b), - bitxor: (a, b) => bitXorU4([], a, b) +const OP_IDS: Record = { + ...(CMP_OPS), + "+": "add", + "-": "sub", + "*": "mul", + "/": "div", + "%": "modi", + "++": "inc", + "--": "dec", + "||": "or", + "&&": "and", + "|": "bitor", + "&": "bitand", + "^": "bitxor", + "~": "bitnot", + "<<": "lshift", + ">>": "rshift" }; -export const JS_DEFAULT_ENV = env; +const PRELUDE = [ + "float", + "int", + "uint", + "vec2", + "vec3", + "vec4", + "ivec2", + "ivec3", + "ivec4", + "uvec2", + "uvec3", + "uvec4", + "mat2", + "mat3", + "mat4", + "sampler2D", + "sampler3D", + "samplerCube", + "sampler2DShadow", + "samplerCubeShadow" +] + .map((x) => `const ${x} = env.${x};`) + .join("\n"); + +const COMPS: any = { x: 0, y: 1, z: 2, w: 3 }; + +const RE_SEMI = /[};]$/; + +const isIntOrBool = (l: Term) => isInt(l) || isUint(l) || isBool(l); + +const isVecOrMat = (l: Term) => isVec(l) || isMat(l); + +const swizzle = (id: string) => [...id].map((x) => COMPS[x]).join(", "); + +const buildComments = (t: Func) => + `/**\n${t.args.map((p) => ` * @param ${p.id} ${p.type}`).join("\n")}\n */`; + +const buildExports = (tree: Term) => + tree.tag === "scope" + ? (tree).body + .filter((x) => x.tag === "fn") + .map((f) => `${(>f).id}: ${(>f).id}`) + .join(",\n") + : tree.tag === "fn" + ? `${(>tree).id}: ${(>tree).id}` + : ""; export const targetJS = () => { - const CMP_OPS: Partial> = { - "!": "not", - "<": "lt", - "<=": "lte", - "==": "eq", - "!=": "neq", - ">=": "gte", - ">": "gt" - }; - const OP_IDS: Record = { - ...(CMP_OPS), - "+": "add", - "-": "sub", - "*": "mul", - "/": "div", - "%": "modi", - "++": "inc", - "--": "dec", - "||": "or", - "&&": "and", - "|": "bitor", - "&": "bitand", - "^": "bitxor", - "~": "bitnot", - "<<": "lshift", - ">>": "rshift" - }; - - const PRELUDE = - [ - "float", - "int", - "uint", - "vec2", - "vec3", - "vec4", - "ivec2", - "ivec3", - "ivec4", - "uvec2", - "uvec3", - "uvec4", - "mat2", - "mat3", - "mat4", - "sampler2D", - "sampler3D", - "samplerCube", - "sampler2DShadow", - "samplerCubeShadow" - ] - .map((x) => `const ${x} = env.${x};`) - .join("\n") + "\n"; - - const COMPS: any = { x: 0, y: 1, z: 2, w: 3 }; - - const RE_SEMI = /[};]$/; - const $list = (body: Term[], sep = ", ") => body.map(emit).join(sep); - const $docParam = (p: Sym) => ` * @param ${p.id} ${p.type}`; - const $fn = (name: string, args: Term[]) => `${name}(${$list(args)})`; const $vec = ({ val, info, type }: Lit) => !info ? `[${$list(val)}]` : `env.${type}${info}(${$list(val)})`; - const $swizzle = (id: string) => [...id].map((x) => COMPS[x]).join(", "); + const $num = (v: any, f: Fn) => + isNumber(v) ? String(v) : f(v); const emit: Fn, string> = defTarget({ arg: (t) => t.id, @@ -1083,15 +112,16 @@ export const targetJS = () => { array_init: (t) => `[${$list(t.init)}]`, assign: (t) => { + const rhs = emit(t.r); if (t.l.tag === "swizzle") { const s = >t.l; + const id = swizzle(s.id); + const val = emit(s.val); return s.id.length > 1 - ? `env.set_swizzle${s.id.length}(${emit(s.val)}, ${emit( - t.r - )}, ${$swizzle(s.id)})` - : `(${emit(s.val)}[${$swizzle(s.id)}] = ${emit(t.r)})`; + ? `env.set_swizzle${s.id.length}(${val}, ${rhs}, ${id})` + : `(${val}[${id}] = ${rhs})`; } - return emit(t.l) + " = " + emit(t.r); + return `${emit(t.l)} = ${rhs}`; }, ctrl: (t) => t.id, @@ -1102,22 +132,19 @@ export const targetJS = () => { decl: ({ type, id }) => { const res: string[] = []; - res.push(id.opts.const ? "const" : "let"); - res.push(`/*${type}*/`); - res.push(id.id); + res.push(id.opts.const ? "const" : "let", `/*${type}*/`, id.id); id.init - ? res.push("=", emit(id.init)) + ? res.push(`= ${emit(id.init)}`) : id.opts.num !== undefined - ? res.push("=", `new Array(${id.opts.num})`) + ? res.push(`= new Array(${id.opts.num})`) : undefined; return res.join(" "); }, fn: (t) => - "/**\n" + - t.args.map($docParam).join("\n") + - "\n */\n" + - `function ${t.id}(${$list(t.args)}) ${emit(t.scope)}`, + `${buildComments(t)}\nfunction ${t.id}(${$list(t.args)}) ${emit( + t.scope + )}`, for: (t) => `for(${t.init ? emit(t.init) : ""}; ${emit(t.test)}; ${ @@ -1127,8 +154,8 @@ export const targetJS = () => { idx: (t) => `${emit(t.val)}[${emit(t.id)}]`, if: (t) => { - const res = `if (${emit(t.test)}) ` + emit(t.t); - return t.f ? res + " else " + emit(t.f) : res; + const res = `if (${emit(t.test)}) ${emit(t.t)}`; + return t.f ? `${res} else ${emit(t.f)}` : res; }, lit: (t) => { @@ -1137,15 +164,13 @@ export const targetJS = () => { case "bool": return isBoolean(v) ? String(v) : `!!(${emit(v)})`; case "float": - return isNumber(v) - ? String(v) - : isBool(v) - ? `(${emit(v)} & 1)` - : emit(v); + return $num(v, () => + isBool(v) ? `(${emit(v)} & 1)` : emit(v) + ); case "int": - return isNumber(v) ? String(v) : `(${emit(v)} | 0)`; + return $num(v, () => `(${emit(v)} | 0)`); case "uint": - return isNumber(v) ? String(v) : `(${emit(v)} >>> 0)`; + return $num(v, () => `(${emit(v)} >>> 0)`); case "vec2": case "vec3": case "vec4": @@ -1168,31 +193,29 @@ export const targetJS = () => { }, op1: (t) => { - const complex = isVec(t) || isMat(t) || isInt(t); - if (complex && t.post) { - const s = >t.val; - return `${s.id} = ${t.type}.${OP_IDS[t.op]}(${emit(s)})`; - } else { - return complex - ? `${t.type}.${OP_IDS[t.op]}1(${emit(t.val)})` - : t.post - ? `(${emit(t.val)}${t.op})` - : `${t.op}${emit(t.val)}`; - } + const complex = isVecOrMat(t) || isInt(t); + const op = t.op; + const val = emit(t.val); + return complex && t.post + ? `${(>t.val).id} = ${t.type}.${OP_IDS[op]}(${val})` + : complex + ? `${t.type}.${OP_IDS[op]}1(${val})` + : t.post + ? `(${val}${op})` + : `${op}${val}`; }, op2: (t) => { const { l, r } = t; - const vec = - isVec(l) || isMat(l) - ? l.type - : isVec(r) || isMat(r) - ? r.type - : undefined; + const vec = isVecOrMat(l) + ? l.type + : isVecOrMat(r) + ? r.type + : undefined; const int = !vec - ? isInt(l) || isUint(l) || isBool(l) + ? isIntOrBool(l) ? l.type - : isInt(r) || isUint(r) || isBool(r) + : isIntOrBool(r) ? r.type : undefined : undefined; @@ -1213,8 +236,8 @@ export const targetJS = () => { swizzle: (t) => t.id.length > 1 - ? `env.swizzle${t.id.length}(${emit(t.val)}, ${$swizzle(t.id)})` - : `${emit(t.val)}[${$swizzle(t.id)}]`, + ? `env.swizzle${t.id.length}(${emit(t.val)}, ${swizzle(t.id)})` + : `${emit(t.val)}[${swizzle(t.id)}]`, sym: (t) => t.id, @@ -1225,21 +248,10 @@ export const targetJS = () => { Object.assign(emit, { compile: (tree, env = JS_DEFAULT_ENV) => { - const exports = - tree.tag === "scope" - ? (tree).body - .filter((x) => x.tag === "fn") - .map( - (f) => - (>f).id + ": " + (>f).id - ) - .join(",\n") - : tree.tag === "fn" - ? `${(>tree).id}: ${(>tree).id}` - : ""; + const exports = buildExports(tree); return new Function( "env", - PRELUDE + emit(tree) + "\nreturn {\n" + exports + "\n};" + [PRELUDE, emit(tree), "return {", exports, "};"].join("\n") )(env); } }); diff --git a/packages/shader-ast-stdlib/CHANGELOG.md b/packages/shader-ast-stdlib/CHANGELOG.md index 0f204f4f69..c58b7a9cdc 100644 --- a/packages/shader-ast-stdlib/CHANGELOG.md +++ b/packages/shader-ast-stdlib/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.2.2...@thi.ng/shader-ast-stdlib@0.2.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/shader-ast-stdlib + + + + + +## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.2.1...@thi.ng/shader-ast-stdlib@0.2.2) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/shader-ast-stdlib + + + + + ## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.2.0...@thi.ng/shader-ast-stdlib@0.2.1) (2019-08-16) **Note:** Version bump only for package @thi.ng/shader-ast-stdlib diff --git a/packages/shader-ast-stdlib/package.json b/packages/shader-ast-stdlib/package.json index 44e130c244..5dbef11b48 100644 --- a/packages/shader-ast-stdlib/package.json +++ b/packages/shader-ast-stdlib/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/shader-ast-stdlib", - "version": "0.2.1", + "version": "0.2.3", "description": "Useful functions for GPGPU / shader programming w/ @thi.ng/shader-ast", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib color fog lighting math matrix noise raymarch screen sdf tex", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib color fog light math matrix noise raymarch screen sdf tex", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -33,7 +33,7 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/shader-ast": "^0.2.2" + "@thi.ng/shader-ast": "^0.3.0" }, "keywords": [ "AST", diff --git a/packages/shader-ast-stdlib/src/color/porter-duff.ts b/packages/shader-ast-stdlib/src/color/porter-duff.ts index a53c732ad1..f8b9db7a98 100644 --- a/packages/shader-ast-stdlib/src/color/porter-duff.ts +++ b/packages/shader-ast-stdlib/src/color/porter-duff.ts @@ -8,10 +8,18 @@ import { FloatTerm, mul, ret, - sub + sub, + vec4, + Vec4Sym } from "@thi.ng/shader-ast"; import { clamp01 } from "../math/clamp"; +const coeff = ( + f: Fn2, + a: Vec4Sym, + b: Vec4Sym +) => (f === ZERO ? FLOAT0 : f === ONE ? a : mul(a, f($w(a), $w(b)))); + /** * Higher-order Porter-Duff alpha compositing operator. See * thi.ng/porter-duff for reference. Returns an optimized AST function @@ -23,6 +31,9 @@ import { clamp01 } from "../math/clamp"; * for src/dest colors and are called with the alpha components of both * colors. * + * Optimization only happens for cases where either `fa` and/or `fb` are + * `ZERO`. + * * @param name function name * @param fa src coeff fn * @param fb dest coeff fn @@ -33,19 +44,15 @@ export const porterDuff = ( fb: Fn2 ) => defn("vec4", name, ["vec4", "vec4"], (a, b) => { - const src = - fa === ZERO ? FLOAT0 : fa === ONE ? a : mul(a, fa($w(a), $w(b))); - const dest = - fb === ZERO ? FLOAT0 : fb === ONE ? b : mul(b, fb($w(a), $w(b))); + const src = coeff(fa, a, b); + const dest = coeff(fb, a, b); + const srcZero = src === FLOAT0; + const destZero = dest === FLOAT0; return [ ret( - clamp01( - src === FLOAT0 - ? dest - : dest === FLOAT0 - ? src - : add(src, dest) - ) + srcZero && destZero + ? vec4() + : clamp01(srcZero ? dest : destZero ? src : add(src, dest)) ) ]; }); diff --git a/packages/shader-ast-stdlib/src/math/clamp.ts b/packages/shader-ast-stdlib/src/math/clamp.ts index 8f088fcc63..7634a2550f 100644 --- a/packages/shader-ast-stdlib/src/math/clamp.ts +++ b/packages/shader-ast-stdlib/src/math/clamp.ts @@ -1,8 +1,6 @@ import { clamp, float, - FLOAT0, - FLOAT1, FloatTerm, Prim, Term, @@ -14,34 +12,29 @@ import { Vec4Term } from "@thi.ng/shader-ast"; -/** - * Inline function, expands to equivalent of `clamp(x, 0, 1)`. - * - * @param x - */ -export const clamp01 = (x: Term): Term => +const __clamp = (min: number, max: number) => ( + x: Term +): Term => >( (x.type === "float" - ? clamp(x, FLOAT0, FLOAT1) + ? clamp(x, float(min), float(max)) : x.type === "vec2" - ? clamp(x, vec2(), vec2(1)) + ? clamp(x, vec2(min), vec2(max)) : x.type === "vec3" - ? clamp(x, vec3(), vec3(1)) - : clamp(x, vec4(), vec4(1))) + ? clamp(x, vec3(min), vec3(max)) + : clamp(x, vec4(min), vec4(max))) ); +/** + * Inline function, expands to equivalent of `clamp(x, 0, 1)`. + * + * @param x + */ +export const clamp01 = __clamp(0, 1); + /** * Inline function, expands to equivalent of `clamp(x, -1, 1)`. * * @param x */ -export const clamp11 = (x: Term): Term => - >( - (x.type === "float" - ? clamp(x, float(-1), FLOAT1) - : x.type === "vec2" - ? clamp(x, vec2(-1), vec2(1)) - : x.type === "vec3" - ? clamp(x, vec3(-1), vec3(1)) - : clamp(x, vec4(-1), vec4(1))) - ); +export const clamp11 = __clamp(-1, 1); diff --git a/packages/shader-ast-stdlib/src/noise/permute.ts b/packages/shader-ast-stdlib/src/noise/permute.ts index 11c426d164..e4a6ff61b3 100644 --- a/packages/shader-ast-stdlib/src/noise/permute.ts +++ b/packages/shader-ast-stdlib/src/noise/permute.ts @@ -5,21 +5,19 @@ import { FLOAT1, mod, mul, + Prim, ret } from "@thi.ng/shader-ast"; -export const permute = defn("float", "permute", ["float"], (v) => [ - ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) -]); +const __permute = (type: T, suffix = "") => + defn(type, `permute${suffix}`, [type], (v) => [ + ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) + ]); -export const permute2 = defn("vec2", "permute2", ["vec2"], (v) => [ - ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) -]); +export const permute = __permute("float"); -export const permute3 = defn("vec3", "permute3", ["vec3"], (v) => [ - ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) -]); +export const permute2 = __permute("vec2", "2"); -export const permute4 = defn("vec4", "permute4", ["vec4"], (v) => [ - ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) -]); +export const permute3 = __permute("vec3", "3"); + +export const permute4 = __permute("vec4", "4"); diff --git a/packages/shader-ast-stdlib/src/tex/read-index.ts b/packages/shader-ast-stdlib/src/tex/read-index.ts index 698dc7099e..9d0b72f18e 100644 --- a/packages/shader-ast-stdlib/src/tex/read-index.ts +++ b/packages/shader-ast-stdlib/src/tex/read-index.ts @@ -17,7 +17,7 @@ import { indexToUV } from "./index-uv"; * @param size */ export const readIndex1 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => - $x(texture(tex, indexToUV(i, size))); + $x(readIndex4(tex, i, size)); /** * Inline function. Returns vec2 (x,y components) at index `i` in `tex`. @@ -27,7 +27,7 @@ export const readIndex1 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => * @param size */ export const readIndex2 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => - $xy(texture(tex, indexToUV(i, size))); + $xy(readIndex4(tex, i, size)); /** * Inline function. Returns vec3 (x,y,z components) at index `i` in `tex`. @@ -37,7 +37,7 @@ export const readIndex2 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => * @param size */ export const readIndex3 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => - $xyz(texture(tex, indexToUV(i, size))); + $xyz(readIndex4(tex, i, size)); /** * Inline function. Returns vec4 at index `i` in `tex`. diff --git a/packages/shader-ast/CHANGELOG.md b/packages/shader-ast/CHANGELOG.md index ceb94bc88d..c9ce3bbac8 100644 --- a/packages/shader-ast/CHANGELOG.md +++ b/packages/shader-ast/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.2.3...@thi.ng/shader-ast@0.3.0) (2019-08-21) + + +### Features + +* **shader-ast:** add modf(), isnan(), isinf() built-ins ([7fae67b](https://github.com/thi-ng/umbrella/commit/7fae67b)) + + + + + +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.2.2...@thi.ng/shader-ast@0.2.3) (2019-08-17) + + +### Bug Fixes + +* **shader-ast:** update atan built-in handling ([9f0c739](https://github.com/thi-ng/umbrella/commit/9f0c739)) + + + + + ## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.2.1...@thi.ng/shader-ast@0.2.2) (2019-08-16) **Note:** Version bump only for package @thi.ng/shader-ast diff --git a/packages/shader-ast/README.md b/packages/shader-ast/README.md index 978f7fe063..894b755929 100644 --- a/packages/shader-ast/README.md +++ b/packages/shader-ast/README.md @@ -326,7 +326,7 @@ Swizzle patterns are type checked in the editor (and at compile time), i.e. ### Built-in functions The most common set of GLSL ES 3.0 builtins are supported. See -[builtins.ts](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast/src/builtins.ts) +[/builtin](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast/src/builtin/) for reference. ### User defined functions @@ -402,7 +402,7 @@ instantiated, typed symbols representing each arg and can use any name within that function (also as shown in the above example). See `SymOpts` interface in -[api.ts](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast/src/api.ts) +[/api/syms.ts](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast/src/api/syms.ts) for more details about the options object... #### Inline functions diff --git a/packages/shader-ast/package.json b/packages/shader-ast/package.json index bf37a6a7fc..81a63d87c2 100644 --- a/packages/shader-ast/package.json +++ b/packages/shader-ast/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/shader-ast", - "version": "0.2.2", + "version": "0.3.0", "description": "DSL to define shader code in TypeScript and cross-compile to GLSL, JS and other targets", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib std", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib api ast builtin", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -33,11 +33,11 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/checks": "^2.3.0", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/dgraph": "^1.1.13", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/defmulti": "^1.1.3", + "@thi.ng/dgraph": "^1.1.14", + "@thi.ng/errors": "^1.2.0" }, "keywords": [ "AST", diff --git a/packages/shader-ast/src/api.ts b/packages/shader-ast/src/api.ts deleted file mode 100644 index a6e5a13e25..0000000000 --- a/packages/shader-ast/src/api.ts +++ /dev/null @@ -1,736 +0,0 @@ -import { - Fn, - Fn0, - Fn2, - Fn3, - Fn4, - Fn5, - Fn6, - Fn7, - Fn8 -} from "@thi.ng/api"; - -export type Tag = - | "arg" - | "array_init" - | "assign" - | "call" - | "call_i" - | "ctrl" - | "decl" - | "fn" - | "for" - | "idx" - | "if" - | "lit" - | "op1" - | "op2" - | "ret" - | "scope" - | "swizzle" - | "sym" - | "ternary" - | "while"; - -export type Type = - | "void" - | "bool" - | "bool[]" - | "float" - | "float[]" - | "int" - | "int[]" - | "uint" - | "uint[]" - | "vec2" - | "vec2[]" - | "vec3" - | "vec3[]" - | "vec4" - | "vec4[]" - | "ivec2" - | "ivec2[]" - | "ivec3" - | "ivec3[]" - | "ivec4" - | "ivec4[]" - | "uvec2" - | "uvec2[]" - | "uvec3" - | "uvec3[]" - | "uvec4" - | "uvec4[]" - | "bvec2" - | "bvec2[]" - | "bvec3" - | "bvec3[]" - | "bvec4" - | "bvec4[]" - | "mat2" - | "mat2[]" - | "mat3" - | "mat3[]" - | "mat4" - | "mat4[]" - | "sampler2D" - | "sampler2D[]" - | "sampler3D" - | "sampler3D[]" - | "samplerCube" - | "samplerCube[]" - | "sampler2DShadow" - | "sampler2DShadow[]" - | "samplerCubeShadow" - | "samplerCubeShadow[]" - | "isampler2D" - | "isampler2D[]" - | "isampler3D" - | "isampler3D[]" - | "isamplerCube" - | "isamplerCube[]" - | "usampler2D" - | "usampler2D[]" - | "usampler3D" - | "usampler3D[]" - | "usamplerCube" - | "usamplerCube[]"; - -export interface ArrayTypeMap { - bool: "bool[]"; - float: "float[]"; - int: "int[]"; - uint: "uint[]"; - vec2: "vec2[]"; - vec3: "vec3[]"; - vec4: "vec4[]"; - ivec2: "ivec2[]"; - ivec3: "ivec3[]"; - ivec4: "ivec4[]"; - uvec2: "uvec2[]"; - uvec3: "uvec3[]"; - uvec4: "uvec4[]"; - bvec2: "bvec2[]"; - bvec3: "bvec3[]"; - bvec4: "bvec4[]"; - mat2: "mat2[]"; - mat3: "mat3[]"; - mat4: "mat4[]"; - sampler2D: "sampler2D[]"; - sampler3D: "sampler3D[]"; - samplerCube: "samplerCube[]"; - sampler2DShadow: "sampler2DShadow[]"; - samplerCubeShadow: "samplerCubeShadow[]"; - isampler2D: "isampler2D[]"; - isampler3D: "isampler3D[]"; - isamplerCube: "isamplerCube[]"; - usampler2D: "usampler2D[]"; - usampler3D: "usampler3D[]"; - usamplerCube: "usamplerCube[]"; -} - -export interface IndexTypeMap { - "bool[]": "bool"; - "float[]": "float"; - "int[]": "int"; - "uint[]": "uint"; - "vec2[]": "vec2"; - "vec3[]": "vec3"; - "vec4[]": "vec4"; - "ivec2[]": "ivec2"; - "ivec3[]": "ivec3"; - "ivec4[]": "ivec4"; - "uvec2[]": "uvec2"; - "uvec3[]": "uvec3"; - "uvec4[]": "uvec4"; - "bvec2[]": "bvec2"; - "bvec3[]": "bvec3"; - "bvec4[]": "bvec4"; -} - -export type BoolTerm = Term<"bool">; -export type FloatTerm = Term<"float">; -export type IntTerm = Term<"int">; -export type UintTerm = Term<"uint">; -export type Vec2Term = Term<"vec2">; -export type Vec3Term = Term<"vec3">; -export type Vec4Term = Term<"vec4">; -export type IVec2Term = Term<"ivec2">; -export type IVec3Term = Term<"ivec3">; -export type IVec4Term = Term<"ivec4">; -export type UVec2Term = Term<"uvec2">; -export type UVec3Term = Term<"uvec3">; -export type UVec4Term = Term<"uvec4">; -export type BVec2Term = Term<"bvec2">; -export type BVec3Term = Term<"bvec3">; -export type BVec4Term = Term<"bvec4">; -export type Mat2Term = Term<"mat2">; -export type Mat3Term = Term<"mat3">; -export type Mat4Term = Term<"mat4">; -export type Sampler2DTerm = Term<"sampler2D">; -export type Sampler3DTerm = Term<"sampler3D">; -export type SamplerCubeTerm = Term<"samplerCube">; -export type ISampler2DTerm = Term<"isampler2D">; -export type ISampler3DTerm = Term<"isampler3D">; -export type ISamplerCubeTerm = Term<"isamplerCube">; -export type USampler2DTerm = Term<"usampler2D">; -export type USampler3DTerm = Term<"usampler3D">; -export type USamplerCubeTerm = Term<"usamplerCube">; - -export type BoolSym = Sym<"bool">; -export type FloatSym = Sym<"float">; -export type IntSym = Sym<"int">; -export type UintSym = Sym<"uint">; -export type Vec2Sym = Sym<"vec2">; -export type Vec3Sym = Sym<"vec3">; -export type Vec4Sym = Sym<"vec4">; -export type IVec2Sym = Sym<"ivec2">; -export type IVec3Sym = Sym<"ivec3">; -export type IVec4Sym = Sym<"ivec4">; -export type UVec2Sym = Sym<"uvec2">; -export type UVec3Sym = Sym<"uvec3">; -export type UVec4Sym = Sym<"uvec4">; -export type BVec2Sym = Sym<"bvec2">; -export type BVec3Sym = Sym<"bvec3">; -export type BVec4Sym = Sym<"bvec4">; -export type Mat2Sym = Sym<"mat2">; -export type Mat3Sym = Sym<"mat3">; -export type Mat4Sym = Sym<"mat4">; -export type Sampler2DSym = Sym<"sampler2D">; -export type Sampler3DSym = Sym<"sampler3D">; -export type SamplerCubeSym = Sym<"samplerCube">; -export type ISampler2DSym = Sym<"isampler2D">; -export type ISampler3DSym = Sym<"isampler3D">; -export type ISamplerCubeSym = Sym<"isamplerCube">; -export type USampler2DSym = Sym<"usampler2D">; -export type USampler3DSym = Sym<"usampler3D">; -export type USamplerCubeSym = Sym<"usamplerCube">; - -export interface MatIndexTypeMap { - mat2: "vec2"; - mat3: "vec3"; - mat4: "vec4"; -} - -export type Indexable = keyof IndexTypeMap; - -export type Vec = "vec2" | "vec3" | "vec4"; -export type IVec = "ivec2" | "ivec3" | "ivec4"; -export type UVec = "uvec2" | "uvec3" | "uvec4"; -export type BVec = "bvec2" | "bvec3" | "bvec4"; -export type Mat = "mat2" | "mat3" | "mat4"; -export type Sampler = - | "sampler2D" - | "sampler3D" - | "samplerCube" - | "sampler2DShadow" - | "samplerCubeShadow" - | "isampler2D" - | "isampler3D" - | "isamplerCube" - | "usampler2D" - | "usampler3D" - | "usamplerCube"; - -export type Prim = "float" | Vec; -export type Int = "int" | "uint"; -export type Comparable = "float" | Int; -export type Numeric = number | FloatTerm | IntTerm | UintTerm; -export type NumericF = number | FloatTerm; -export type NumericI = number | IntTerm; -export type NumericU = number | UintTerm; -export type NumericB = boolean | Numeric | BoolTerm; - -export type Assignable = Sym | Swizzle | Index; - -export type MathOperator = "+" | "-" | "*" | "/" | "%" | "++" | "--"; -export type LogicOperator = "!" | "||" | "&&"; -export type ComparisonOperator = "<" | "<=" | "==" | "!=" | ">=" | ">"; -export type BitOperator = "<<" | ">>" | "|" | "&" | "^" | "~"; -export type Operator = - | MathOperator - | LogicOperator - | ComparisonOperator - | BitOperator; - -// swizzle gen: -// console.log([...permutations("xyz","xyz")].map((x) =>`"${x.join("")}"`).join(" | ")) - -export type Swizzle2_1 = "x" | "y"; -export type Swizzle2_2 = "xx" | "xy" | "yx" | "yy"; -// prettier-ignore -export type Swizzle2_3 = "xxx" | "xxy" | "xyx" | "xyy" | "yxx" | "yxy" | "yyx" | "yyy"; -// prettier-ignore -export type Swizzle2_4 = "xxxx" | "xxxy" | "xxyx" | "xxyy" | "xyxx" | "xyxy" | "xyyx" | "xyyy" | "yxxx" | "yxxy" | "yxyx" | "yxyy" | "yyxx" | "yyxy" | "yyyx" | "yyyy"; - -export type Swizzle3_1 = "x" | "y" | "z"; -// prettier-ignore -export type Swizzle3_2 = "xx" | "xy" | "xz" | "yx" | "yy" | "yz" | "zx" | "zy" | "zz"; -// prettier-ignore -export type Swizzle3_3 = "xxx" | "xxy" | "xxz" | "xyx" | "xyy" | "xyz" | "xzx" | "xzy" | "xzz" | "yxx" | "yxy" | "yxz" | "yyx" | "yyy" | "yyz" | "yzx" | "yzy" | "yzz" | "zxx" | "zxy" | "zxz" | "zyx" | "zyy" | "zyz" | "zzx" | "zzy" | "zzz"; -// prettier-ignore -export type Swizzle3_4 = "xxxx" | "xxxy" | "xxxz" | "xxyx" | "xxyy" | "xxyz" | "xxzx" | "xxzy" | "xxzz" | "xyxx" | "xyxy" | "xyxz" | "xyyx" | "xyyy" | "xyyz" | "xyzx" | "xyzy" | "xyzz" | "xzxx" | "xzxy" | "xzxz" | "xzyx" | "xzyy" | "xzyz" | "xzzx" | "xzzy" | "xzzz" | "yxxx" | "yxxy" | "yxxz" | "yxyx" | "yxyy" | "yxyz" | "yxzx" | "yxzy" | "yxzz" | "yyxx" | "yyxy" | "yyxz" | "yyyx" | "yyyy" | "yyyz" | "yyzx" | "yyzy" | "yyzz" | "yzxx" | "yzxy" | "yzxz" | "yzyx" | "yzyy" | "yzyz" | "yzzx" | "yzzy" | "yzzz" | "zxxx" | "zxxy" | "zxxz" | "zxyx" | "zxyy" | "zxyz" | "zxzx" | "zxzy" | "zxzz" | "zyxx" | "zyxy" | "zyxz" | "zyyx" | "zyyy" | "zyyz" | "zyzx" | "zyzy" | "zyzz" | "zzxx" | "zzxy" | "zzxz" | "zzyx" | "zzyy" | "zzyz" | "zzzx" | "zzzy" | "zzzz"; - -export type Swizzle4_1 = "x" | "y" | "z" | "w"; -// prettier-ignore -export type Swizzle4_2 = "xx" | "xy" | "xz" | "xw" | "yx" | "yy" | "yz" | "yw" | "zx" | "zy" | "zz" | "zw" | "wx" | "wy" | "wz" | "ww"; -// prettier-ignore -export type Swizzle4_3 = "xxx" | "xxy" | "xxz" | "xxw" | "xyx" | "xyy" | "xyz" | "xyw" | "xzx" | "xzy" | "xzz" | "xzw" | "xwx" | "xwy" | "xwz" | "xww" | "yxx" | "yxy" | "yxz" | "yxw" | "yyx" | "yyy" | "yyz" | "yyw" | "yzx" | "yzy" | "yzz" | "yzw" | "ywx" | "ywy" | "ywz" | "yww" | "zxx" | "zxy" | "zxz" | "zxw" | "zyx" | "zyy" | "zyz" | "zyw" | "zzx" | "zzy" | "zzz" | "zzw" | "zwx" | "zwy" | "zwz" | "zww" | "wxx" | "wxy" | "wxz" | "wxw" | "wyx" | "wyy" | "wyz" | "wyw" | "wzx" | "wzy" | "wzz" | "wzw" | "wwx" | "wwy" | "wwz" | "www"; -// prettier-ignore -export type Swizzle4_4 = "xxxx" | "xxxy" | "xxxz" | "xxxw" | "xxyx" | "xxyy" | "xxyz" | "xxyw" | "xxzx" | "xxzy" | "xxzz" | "xxzw" | "xxwx" | "xxwy" | "xxwz" | "xxww" | "xyxx" | "xyxy" | "xyxz" | "xyxw" | "xyyx" | "xyyy" | "xyyz" | "xyyw" | "xyzx" | "xyzy" | "xyzz" | "xyzw" | "xywx" | "xywy" | "xywz" | "xyww" | "xzxx" | "xzxy" | "xzxz" | "xzxw" | "xzyx" | "xzyy" | "xzyz" | "xzyw" | "xzzx" | "xzzy" | "xzzz" | "xzzw" | "xzwx" | "xzwy" | "xzwz" | "xzww" | "xwxx" | "xwxy" | "xwxz" | "xwxw" | "xwyx" | "xwyy" | "xwyz" | "xwyw" | "xwzx" | "xwzy" | "xwzz" | "xwzw" | "xwwx" | "xwwy" | "xwwz" | "xwww" | "yxxx" | "yxxy" | "yxxz" | "yxxw" | "yxyx" | "yxyy" | "yxyz" | "yxyw" | "yxzx" | "yxzy" | "yxzz" | "yxzw" | "yxwx" | "yxwy" | "yxwz" | "yxww" | "yyxx" | "yyxy" | "yyxz" | "yyxw" | "yyyx" | "yyyy" | "yyyz" | "yyyw" | "yyzx" | "yyzy" | "yyzz" | "yyzw" | "yywx" | "yywy" | "yywz" | "yyww" | "yzxx" | "yzxy" | "yzxz" | "yzxw" | "yzyx" | "yzyy" | "yzyz" | "yzyw" | "yzzx" | "yzzy" | "yzzz" | "yzzw" | "yzwx" | "yzwy" | "yzwz" | "yzww" | "ywxx" | "ywxy" | "ywxz" | "ywxw" | "ywyx" | "ywyy" | "ywyz" | "ywyw" | "ywzx" | "ywzy" | "ywzz" | "ywzw" | "ywwx" | "ywwy" | "ywwz" | "ywww" | "zxxx" | "zxxy" | "zxxz" | "zxxw" | "zxyx" | "zxyy" | "zxyz" | "zxyw" | "zxzx" | "zxzy" | "zxzz" | "zxzw" | "zxwx" | "zxwy" | "zxwz" | "zxww" | "zyxx" | "zyxy" | "zyxz" | "zyxw" | "zyyx" | "zyyy" | "zyyz" | "zyyw" | "zyzx" | "zyzy" | "zyzz" | "zyzw" | "zywx" | "zywy" | "zywz" | "zyww" | "zzxx" | "zzxy" | "zzxz" | "zzxw" | "zzyx" | "zzyy" | "zzyz" | "zzyw" | "zzzx" | "zzzy" | "zzzz" | "zzzw" | "zzwx" | "zzwy" | "zzwz" | "zzww" | "zwxx" | "zwxy" | "zwxz" | "zwxw" | "zwyx" | "zwyy" | "zwyz" | "zwyw" | "zwzx" | "zwzy" | "zwzz" | "zwzw" | "zwwx" | "zwwy" | "zwwz" | "zwww" | "wxxx" | "wxxy" | "wxxz" | "wxxw" | "wxyx" | "wxyy" | "wxyz" | "wxyw" | "wxzx" | "wxzy" | "wxzz" | "wxzw" | "wxwx" | "wxwy" | "wxwz" | "wxww" | "wyxx" | "wyxy" | "wyxz" | "wyxw" | "wyyx" | "wyyy" | "wyyz" | "wyyw" | "wyzx" | "wyzy" | "wyzz" | "wyzw" | "wywx" | "wywy" | "wywz" | "wyww" | "wzxx" | "wzxy" | "wzxz" | "wzxw" | "wzyx" | "wzyy" | "wzyz" | "wzyw" | "wzzx" | "wzzy" | "wzzz" | "wzzw" | "wzwx" | "wzwy" | "wzwz" | "wzww" | "wwxx" | "wwxy" | "wwxz" | "wwxw" | "wwyx" | "wwyy" | "wwyz" | "wwyw" | "wwzx" | "wwzy" | "wwzz" | "wwzw" | "wwwx" | "wwwy" | "wwwz" | "wwww"; - -export type Swizzle2 = Swizzle2_1 | Swizzle2_2 | Swizzle2_3 | Swizzle2_4; -export type Swizzle3 = Swizzle3_1 | Swizzle3_2 | Swizzle3_3 | Swizzle3_4; -export type Swizzle4 = Swizzle4_1 | Swizzle4_2 | Swizzle4_3 | Swizzle4_4; - -export type Arg = A | [A, string?, SymOpts?]; -export type Arg1 = [Arg]; -export type Arg2 = [Arg, Arg]; -export type Arg3 = [ - Arg, - Arg, - Arg -]; -export type Arg4< - A extends Type, - B extends Type, - C extends Type, - D extends Type -> = [Arg, Arg, Arg, Arg]; -export type Arg5< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type -> = [Arg, Arg, Arg, Arg, Arg]; -export type Arg6< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type -> = [Arg, Arg, Arg, Arg, Arg, Arg]; -export type Arg7< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type -> = [Arg, Arg, Arg, Arg, Arg, Arg, Arg]; -export type Arg8< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - H extends Type -> = [Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg]; - -export type FnBody0 = Fn0; -export type FnBody1 = Fn, ScopeBody>; -export type FnBody2 = Fn2< - Sym, - Sym, - ScopeBody ->; -export type FnBody3 = Fn3< - Sym, - Sym, - Sym, - ScopeBody ->; -export type FnBody4< - A extends Type, - B extends Type, - C extends Type, - D extends Type -> = Fn4, Sym, Sym, Sym, ScopeBody>; -export type FnBody5< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type -> = Fn5, Sym, Sym, Sym, Sym, ScopeBody>; -export type FnBody6< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type -> = Fn6, Sym, Sym, Sym, Sym, Sym, ScopeBody>; -export type FnBody7< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type -> = Fn7, Sym, Sym, Sym, Sym, Sym, Sym, ScopeBody>; -export type FnBody8< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - H extends Type -> = Fn8< - Sym, - Sym, - Sym, - Sym, - Sym, - Sym, - Sym, - Sym, - ScopeBody ->; - -export type Func0 = Fn0>; -export type Func1 = Fn, FnCall>; -export type Func2 = Fn2< - Term, - Term, - FnCall ->; -export type Func3< - A extends Type, - B extends Type, - C extends Type, - T extends Type -> = Fn3, Term, Term, FnCall>; -export type Func4< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - T extends Type -> = Fn4, Term, Term, Term, FnCall>; -export type Func5< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - T extends Type -> = Fn5, Term, Term, Term, Term, FnCall>; -export type Func6< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - T extends Type -> = Fn6, Term, Term, Term, Term, Term, FnCall>; -export type Func7< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - T extends Type -> = Fn7< - Term, - Term, - Term, - Term, - Term, - Term, - Term, - FnCall ->; -export type Func8< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - H extends Type, - T extends Type -> = Fn8< - Term, - Term, - Term, - Term, - Term, - Term, - Term, - Term, - FnCall ->; - -export type SymQualifier = "in" | "out" | "inout"; - -export type SymType = "in" | "out" | "uni"; - -export type Precision = "lowp" | "mediump" | "highp"; - -export type ScopeBody = (Term | null | undefined)[]; - -export interface Term { - tag: Tag; - type: T; -} - -export interface Scoped { - scope: Scope; -} - -export interface Lit extends Term { - val: any; - info?: string; -} - -export interface Sym extends Term { - id: string; - opts: SymOpts; - init?: Term; -} - -export interface SymOpts { - /** - * If in global scope, used for: - * - * - `in` => attribute (in VS), varying (in FS) - * - `out` => varying (in VS), output (in FS) - * - * For parameters / fn args: - * - * - `in` => passed into a function - * - `out` => passed back out of a function, but not initialized - * - `inout` => passed both into and out of a function - */ - q?: SymQualifier; - /** - * Symbol type, only used for global scope in/out vars, e.g. - * attribute, varying, uniform. - */ - type?: SymType; - /** - * Const symbol - */ - const?: boolean; - /** - * Precision qualifier - */ - prec?: Precision; - /** - * Arrays only. Length - */ - num?: number; - /** - * Layout location - */ - loc?: number; -} - -export interface ArrayInit extends Term { - init: (Sym | Lit)[]; -} - -export interface Decl extends Term { - id: Sym; -} - -export interface Swizzle extends Term { - id: string; - val: Term; -} - -export interface Index extends Term { - id: Term<"int"> | Term<"uint">; - val: Term; -} - -export interface Assign extends Term { - l: Assignable; - r: Term; -} - -export interface Op1 extends Term { - op: Operator; - val: Term; - post?: boolean; -} - -export interface Op2 extends Term { - info?: string; - op: Operator; - l: Term; - r: Term; -} - -export interface Scope extends Term<"void"> { - body: Term[]; - global: boolean; -} - -export interface Branch extends Term<"void"> { - test: BoolTerm; - t: Scope; - f?: Scope; -} - -export interface Ternary extends Term { - test: BoolTerm; - t: Term; - f: Term; -} - -export interface ControlFlow extends Term<"void"> { - tag: "ctrl"; - id: string; -} - -export interface FuncReturn extends Term { - val?: Term; -} - -export interface FuncArg extends Term { - id: string; - opts: SymOpts; -} - -export interface Func extends Term, Scoped { - id: string; - args: Sym[]; - deps: Func[]; -} - -export interface TaggedFn0 extends Func0, Func { - args: []; -} - -export interface TaggedFn1 - extends Func1, - Func { - args: [Sym]; -} - -export interface TaggedFn2 - extends Func2, - Func { - args: [Sym, Sym]; -} - -export interface TaggedFn3< - A extends Type, - B extends Type, - C extends Type, - T extends Type -> extends Func3, Func { - args: [Sym, Sym, Sym]; -} - -export interface TaggedFn4< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - T extends Type -> extends Func4, Func { - args: [Sym, Sym, Sym, Sym]; -} - -export interface TaggedFn5< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - T extends Type -> extends Func5, Func { - args: [Sym, Sym, Sym, Sym, Sym]; -} - -export interface TaggedFn6< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - T extends Type -> extends Func6, Func { - args: [Sym, Sym, Sym, Sym, Sym, Sym]; -} - -export interface TaggedFn7< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - T extends Type -> extends Func7, Func { - args: [Sym, Sym, Sym, Sym, Sym, Sym, Sym]; -} - -export interface TaggedFn8< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - H extends Type, - T extends Type -> extends Func8, Func { - args: [Sym, Sym, Sym, Sym, Sym, Sym, Sym, Sym]; -} - -export interface FnCall extends Term { - id: string; - args: Term[]; - info?: string; - fn?: Func; -} - -export interface ForLoop extends Term<"void">, Scoped { - init?: Decl; - test: BoolTerm; - iter?: Term; -} - -export interface WhileLoop extends Term<"void">, Scoped { - test: BoolTerm; -} - -export interface TargetImpl extends Record> { - arg: Fn, T>; - array_init: Fn, T>; - assign: Fn, T>; - call: Fn, T>; - call_i: Fn, T>; - decl: Fn, T>; - fn: Fn, T>; - for: Fn; - idx: Fn, T>; - if: Fn; - lit: Fn, T>; - op1: Fn, T>; - op2: Fn, T>; - ret: Fn, T>; - scope: Fn; - swizzle: Fn, T>; - sym: Fn, T>; - ternary: Fn, T>; - while: Fn; -} diff --git a/packages/shader-ast/src/api/function.ts b/packages/shader-ast/src/api/function.ts new file mode 100644 index 0000000000..075c6c72d0 --- /dev/null +++ b/packages/shader-ast/src/api/function.ts @@ -0,0 +1,231 @@ +import { + Fn, + Fn0, + Fn2, + Fn3, + Fn4, + Fn5, + Fn6, + Fn7, + Fn8 +} from "@thi.ng/api"; +import { FnCall, Sym, Term } from "./nodes"; +import { SymOpts } from "./syms"; +import { Type } from "./types"; + +export type ScopeBody = (Term | null | undefined)[]; + +export type Arg = A | [A, string?, SymOpts?]; + +export type Arg1 = [Arg]; + +export type Arg2 = [Arg, Arg]; + +export type Arg3 = [ + Arg, + Arg, + Arg +]; + +export type Arg4< + A extends Type, + B extends Type, + C extends Type, + D extends Type +> = [Arg, Arg, Arg, Arg]; + +export type Arg5< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type +> = [Arg, Arg, Arg, Arg, Arg]; + +export type Arg6< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type +> = [Arg, Arg, Arg, Arg, Arg, Arg]; + +export type Arg7< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type +> = [Arg, Arg, Arg, Arg, Arg, Arg, Arg]; + +export type Arg8< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + H extends Type +> = [Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg]; + +export type FnBody0 = Fn0; + +export type FnBody1 = Fn, ScopeBody>; + +export type FnBody2 = Fn2< + Sym, + Sym, + ScopeBody +>; + +export type FnBody3 = Fn3< + Sym, + Sym, + Sym, + ScopeBody +>; + +export type FnBody4< + A extends Type, + B extends Type, + C extends Type, + D extends Type +> = Fn4, Sym, Sym, Sym, ScopeBody>; + +export type FnBody5< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type +> = Fn5, Sym, Sym, Sym, Sym, ScopeBody>; + +export type FnBody6< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type +> = Fn6, Sym, Sym, Sym, Sym, Sym, ScopeBody>; + +export type FnBody7< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type +> = Fn7, Sym, Sym, Sym, Sym, Sym, Sym, ScopeBody>; + +export type FnBody8< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + H extends Type +> = Fn8< + Sym, + Sym, + Sym, + Sym, + Sym, + Sym, + Sym, + Sym, + ScopeBody +>; + +export type Func0 = Fn0>; + +export type Func1 = Fn, FnCall>; + +export type Func2 = Fn2< + Term, + Term, + FnCall +>; + +export type Func3< + A extends Type, + B extends Type, + C extends Type, + T extends Type +> = Fn3, Term, Term, FnCall>; + +export type Func4< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + T extends Type +> = Fn4, Term, Term, Term, FnCall>; + +export type Func5< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + T extends Type +> = Fn5, Term, Term, Term, Term, FnCall>; + +export type Func6< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + T extends Type +> = Fn6, Term, Term, Term, Term, Term, FnCall>; + +export type Func7< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + T extends Type +> = Fn7< + Term, + Term, + Term, + Term, + Term, + Term, + Term, + FnCall +>; + +export type Func8< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + H extends Type, + T extends Type +> = Fn8< + Term, + Term, + Term, + Term, + Term, + Term, + Term, + Term, + FnCall +>; diff --git a/packages/shader-ast/src/api/nodes.ts b/packages/shader-ast/src/api/nodes.ts new file mode 100644 index 0000000000..f9d7c92766 --- /dev/null +++ b/packages/shader-ast/src/api/nodes.ts @@ -0,0 +1,216 @@ +import { + Func0, + Func1, + Func2, + Func3, + Func4, + Func5, + Func6, + Func7, + Func8 +} from "./function"; +import { Operator } from "./ops"; +import { SymOpts } from "./syms"; +import { Tag } from "./tags"; +import { BoolTerm } from "./terms"; +import { + Assignable, + Indexable, + Type, + Vec +} from "./types"; + +export interface Term { + tag: Tag; + type: T; +} + +export interface Scoped { + scope: Scope; +} + +export interface Lit extends Term { + val: any; + info?: string; +} + +export interface Sym extends Term { + id: string; + opts: SymOpts; + init?: Term; +} + +export interface ArrayInit extends Term { + init: (Sym | Lit)[]; +} + +export interface Decl extends Term { + id: Sym; +} + +export interface Swizzle extends Term { + id: string; + val: Term; +} + +export interface Index extends Term { + id: Term<"int"> | Term<"uint">; + val: Term; +} + +export interface Assign extends Term { + l: Assignable; + r: Term; +} + +export interface Op1 extends Term { + op: Operator; + val: Term; + post?: boolean; +} + +export interface Op2 extends Term { + info?: string; + op: Operator; + l: Term; + r: Term; +} + +export interface Scope extends Term<"void"> { + body: Term[]; + global: boolean; +} + +export interface Branch extends Term<"void"> { + test: BoolTerm; + t: Scope; + f?: Scope; +} + +export interface Ternary extends Term { + test: BoolTerm; + t: Term; + f: Term; +} + +export interface ControlFlow extends Term<"void"> { + tag: "ctrl"; + id: string; +} + +export interface FuncReturn extends Term { + val?: Term; +} + +export interface FuncArg extends Term { + id: string; + opts: SymOpts; +} + +export interface Func extends Term, Scoped { + id: string; + args: Sym[]; + deps: Func[]; +} + +export interface TaggedFn0 extends Func0, Func { + args: []; +} + +export interface TaggedFn1 + extends Func1, + Func { + args: [Sym]; +} + +export interface TaggedFn2 + extends Func2, + Func { + args: [Sym, Sym]; +} + +export interface TaggedFn3< + A extends Type, + B extends Type, + C extends Type, + T extends Type +> extends Func3, Func { + args: [Sym, Sym, Sym]; +} + +export interface TaggedFn4< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + T extends Type +> extends Func4, Func { + args: [Sym, Sym, Sym, Sym]; +} + +export interface TaggedFn5< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + T extends Type +> extends Func5, Func { + args: [Sym, Sym, Sym, Sym, Sym]; +} + +export interface TaggedFn6< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + T extends Type +> extends Func6, Func { + args: [Sym, Sym, Sym, Sym, Sym, Sym]; +} + +export interface TaggedFn7< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + T extends Type +> extends Func7, Func { + args: [Sym, Sym, Sym, Sym, Sym, Sym, Sym]; +} + +export interface TaggedFn8< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + H extends Type, + T extends Type +> extends Func8, Func { + args: [Sym, Sym, Sym, Sym, Sym, Sym, Sym, Sym]; +} + +export interface FnCall extends Term { + id: string; + args: Term[]; + info?: string; + fn?: Func; +} + +export interface ForLoop extends Term<"void">, Scoped { + init?: Decl; + test: BoolTerm; + iter?: Term; +} + +export interface WhileLoop extends Term<"void">, Scoped { + test: BoolTerm; +} diff --git a/packages/shader-ast/src/api/ops.ts b/packages/shader-ast/src/api/ops.ts new file mode 100644 index 0000000000..9a1ef9ab7f --- /dev/null +++ b/packages/shader-ast/src/api/ops.ts @@ -0,0 +1,9 @@ +export type MathOperator = "+" | "-" | "*" | "/" | "%" | "++" | "--"; +export type LogicOperator = "!" | "||" | "&&"; +export type ComparisonOperator = "<" | "<=" | "==" | "!=" | ">=" | ">"; +export type BitOperator = "<<" | ">>" | "|" | "&" | "^" | "~"; +export type Operator = + | MathOperator + | LogicOperator + | ComparisonOperator + | BitOperator; diff --git a/packages/shader-ast/src/api/precision.ts b/packages/shader-ast/src/api/precision.ts new file mode 100644 index 0000000000..954e2c97b2 --- /dev/null +++ b/packages/shader-ast/src/api/precision.ts @@ -0,0 +1 @@ +export type Precision = "lowp" | "mediump" | "highp"; diff --git a/packages/shader-ast/src/api/swizzles.ts b/packages/shader-ast/src/api/swizzles.ts new file mode 100644 index 0000000000..33b6da7194 --- /dev/null +++ b/packages/shader-ast/src/api/swizzles.ts @@ -0,0 +1,29 @@ +// swizzle gen: +// console.log([...permutations("xyz","xyz")].map((x) =>`"${x.join("")}"`).join(" | ")) + +export type Swizzle2_1 = "x" | "y"; +export type Swizzle2_2 = "xx" | "xy" | "yx" | "yy"; +// prettier-ignore +export type Swizzle2_3 = "xxx" | "xxy" | "xyx" | "xyy" | "yxx" | "yxy" | "yyx" | "yyy"; +// prettier-ignore +export type Swizzle2_4 = "xxxx" | "xxxy" | "xxyx" | "xxyy" | "xyxx" | "xyxy" | "xyyx" | "xyyy" | "yxxx" | "yxxy" | "yxyx" | "yxyy" | "yyxx" | "yyxy" | "yyyx" | "yyyy"; + +export type Swizzle3_1 = "x" | "y" | "z"; +// prettier-ignore +export type Swizzle3_2 = "xx" | "xy" | "xz" | "yx" | "yy" | "yz" | "zx" | "zy" | "zz"; +// prettier-ignore +export type Swizzle3_3 = "xxx" | "xxy" | "xxz" | "xyx" | "xyy" | "xyz" | "xzx" | "xzy" | "xzz" | "yxx" | "yxy" | "yxz" | "yyx" | "yyy" | "yyz" | "yzx" | "yzy" | "yzz" | "zxx" | "zxy" | "zxz" | "zyx" | "zyy" | "zyz" | "zzx" | "zzy" | "zzz"; +// prettier-ignore +export type Swizzle3_4 = "xxxx" | "xxxy" | "xxxz" | "xxyx" | "xxyy" | "xxyz" | "xxzx" | "xxzy" | "xxzz" | "xyxx" | "xyxy" | "xyxz" | "xyyx" | "xyyy" | "xyyz" | "xyzx" | "xyzy" | "xyzz" | "xzxx" | "xzxy" | "xzxz" | "xzyx" | "xzyy" | "xzyz" | "xzzx" | "xzzy" | "xzzz" | "yxxx" | "yxxy" | "yxxz" | "yxyx" | "yxyy" | "yxyz" | "yxzx" | "yxzy" | "yxzz" | "yyxx" | "yyxy" | "yyxz" | "yyyx" | "yyyy" | "yyyz" | "yyzx" | "yyzy" | "yyzz" | "yzxx" | "yzxy" | "yzxz" | "yzyx" | "yzyy" | "yzyz" | "yzzx" | "yzzy" | "yzzz" | "zxxx" | "zxxy" | "zxxz" | "zxyx" | "zxyy" | "zxyz" | "zxzx" | "zxzy" | "zxzz" | "zyxx" | "zyxy" | "zyxz" | "zyyx" | "zyyy" | "zyyz" | "zyzx" | "zyzy" | "zyzz" | "zzxx" | "zzxy" | "zzxz" | "zzyx" | "zzyy" | "zzyz" | "zzzx" | "zzzy" | "zzzz"; + +export type Swizzle4_1 = "x" | "y" | "z" | "w"; +// prettier-ignore +export type Swizzle4_2 = "xx" | "xy" | "xz" | "xw" | "yx" | "yy" | "yz" | "yw" | "zx" | "zy" | "zz" | "zw" | "wx" | "wy" | "wz" | "ww"; +// prettier-ignore +export type Swizzle4_3 = "xxx" | "xxy" | "xxz" | "xxw" | "xyx" | "xyy" | "xyz" | "xyw" | "xzx" | "xzy" | "xzz" | "xzw" | "xwx" | "xwy" | "xwz" | "xww" | "yxx" | "yxy" | "yxz" | "yxw" | "yyx" | "yyy" | "yyz" | "yyw" | "yzx" | "yzy" | "yzz" | "yzw" | "ywx" | "ywy" | "ywz" | "yww" | "zxx" | "zxy" | "zxz" | "zxw" | "zyx" | "zyy" | "zyz" | "zyw" | "zzx" | "zzy" | "zzz" | "zzw" | "zwx" | "zwy" | "zwz" | "zww" | "wxx" | "wxy" | "wxz" | "wxw" | "wyx" | "wyy" | "wyz" | "wyw" | "wzx" | "wzy" | "wzz" | "wzw" | "wwx" | "wwy" | "wwz" | "www"; +// prettier-ignore +export type Swizzle4_4 = "xxxx" | "xxxy" | "xxxz" | "xxxw" | "xxyx" | "xxyy" | "xxyz" | "xxyw" | "xxzx" | "xxzy" | "xxzz" | "xxzw" | "xxwx" | "xxwy" | "xxwz" | "xxww" | "xyxx" | "xyxy" | "xyxz" | "xyxw" | "xyyx" | "xyyy" | "xyyz" | "xyyw" | "xyzx" | "xyzy" | "xyzz" | "xyzw" | "xywx" | "xywy" | "xywz" | "xyww" | "xzxx" | "xzxy" | "xzxz" | "xzxw" | "xzyx" | "xzyy" | "xzyz" | "xzyw" | "xzzx" | "xzzy" | "xzzz" | "xzzw" | "xzwx" | "xzwy" | "xzwz" | "xzww" | "xwxx" | "xwxy" | "xwxz" | "xwxw" | "xwyx" | "xwyy" | "xwyz" | "xwyw" | "xwzx" | "xwzy" | "xwzz" | "xwzw" | "xwwx" | "xwwy" | "xwwz" | "xwww" | "yxxx" | "yxxy" | "yxxz" | "yxxw" | "yxyx" | "yxyy" | "yxyz" | "yxyw" | "yxzx" | "yxzy" | "yxzz" | "yxzw" | "yxwx" | "yxwy" | "yxwz" | "yxww" | "yyxx" | "yyxy" | "yyxz" | "yyxw" | "yyyx" | "yyyy" | "yyyz" | "yyyw" | "yyzx" | "yyzy" | "yyzz" | "yyzw" | "yywx" | "yywy" | "yywz" | "yyww" | "yzxx" | "yzxy" | "yzxz" | "yzxw" | "yzyx" | "yzyy" | "yzyz" | "yzyw" | "yzzx" | "yzzy" | "yzzz" | "yzzw" | "yzwx" | "yzwy" | "yzwz" | "yzww" | "ywxx" | "ywxy" | "ywxz" | "ywxw" | "ywyx" | "ywyy" | "ywyz" | "ywyw" | "ywzx" | "ywzy" | "ywzz" | "ywzw" | "ywwx" | "ywwy" | "ywwz" | "ywww" | "zxxx" | "zxxy" | "zxxz" | "zxxw" | "zxyx" | "zxyy" | "zxyz" | "zxyw" | "zxzx" | "zxzy" | "zxzz" | "zxzw" | "zxwx" | "zxwy" | "zxwz" | "zxww" | "zyxx" | "zyxy" | "zyxz" | "zyxw" | "zyyx" | "zyyy" | "zyyz" | "zyyw" | "zyzx" | "zyzy" | "zyzz" | "zyzw" | "zywx" | "zywy" | "zywz" | "zyww" | "zzxx" | "zzxy" | "zzxz" | "zzxw" | "zzyx" | "zzyy" | "zzyz" | "zzyw" | "zzzx" | "zzzy" | "zzzz" | "zzzw" | "zzwx" | "zzwy" | "zzwz" | "zzww" | "zwxx" | "zwxy" | "zwxz" | "zwxw" | "zwyx" | "zwyy" | "zwyz" | "zwyw" | "zwzx" | "zwzy" | "zwzz" | "zwzw" | "zwwx" | "zwwy" | "zwwz" | "zwww" | "wxxx" | "wxxy" | "wxxz" | "wxxw" | "wxyx" | "wxyy" | "wxyz" | "wxyw" | "wxzx" | "wxzy" | "wxzz" | "wxzw" | "wxwx" | "wxwy" | "wxwz" | "wxww" | "wyxx" | "wyxy" | "wyxz" | "wyxw" | "wyyx" | "wyyy" | "wyyz" | "wyyw" | "wyzx" | "wyzy" | "wyzz" | "wyzw" | "wywx" | "wywy" | "wywz" | "wyww" | "wzxx" | "wzxy" | "wzxz" | "wzxw" | "wzyx" | "wzyy" | "wzyz" | "wzyw" | "wzzx" | "wzzy" | "wzzz" | "wzzw" | "wzwx" | "wzwy" | "wzwz" | "wzww" | "wwxx" | "wwxy" | "wwxz" | "wwxw" | "wwyx" | "wwyy" | "wwyz" | "wwyw" | "wwzx" | "wwzy" | "wwzz" | "wwzw" | "wwwx" | "wwwy" | "wwwz" | "wwww"; + +export type Swizzle2 = Swizzle2_1 | Swizzle2_2 | Swizzle2_3 | Swizzle2_4; +export type Swizzle3 = Swizzle3_1 | Swizzle3_2 | Swizzle3_3 | Swizzle3_4; +export type Swizzle4 = Swizzle4_1 | Swizzle4_2 | Swizzle4_3 | Swizzle4_4; diff --git a/packages/shader-ast/src/api/syms.ts b/packages/shader-ast/src/api/syms.ts new file mode 100644 index 0000000000..ef8d34abf2 --- /dev/null +++ b/packages/shader-ast/src/api/syms.ts @@ -0,0 +1,72 @@ +import { Sym } from "./nodes"; +import { Precision } from "./precision"; + +export type SymQualifier = "in" | "out" | "inout"; + +export type SymType = "in" | "out" | "uni"; + +export type BoolSym = Sym<"bool">; +export type FloatSym = Sym<"float">; +export type IntSym = Sym<"int">; +export type UintSym = Sym<"uint">; +export type Vec2Sym = Sym<"vec2">; +export type Vec3Sym = Sym<"vec3">; +export type Vec4Sym = Sym<"vec4">; +export type IVec2Sym = Sym<"ivec2">; +export type IVec3Sym = Sym<"ivec3">; +export type IVec4Sym = Sym<"ivec4">; +export type UVec2Sym = Sym<"uvec2">; +export type UVec3Sym = Sym<"uvec3">; +export type UVec4Sym = Sym<"uvec4">; +export type BVec2Sym = Sym<"bvec2">; +export type BVec3Sym = Sym<"bvec3">; +export type BVec4Sym = Sym<"bvec4">; +export type Mat2Sym = Sym<"mat2">; +export type Mat3Sym = Sym<"mat3">; +export type Mat4Sym = Sym<"mat4">; +export type Sampler2DSym = Sym<"sampler2D">; +export type Sampler3DSym = Sym<"sampler3D">; +export type SamplerCubeSym = Sym<"samplerCube">; +export type ISampler2DSym = Sym<"isampler2D">; +export type ISampler3DSym = Sym<"isampler3D">; +export type ISamplerCubeSym = Sym<"isamplerCube">; +export type USampler2DSym = Sym<"usampler2D">; +export type USampler3DSym = Sym<"usampler3D">; +export type USamplerCubeSym = Sym<"usamplerCube">; + +export interface SymOpts { + /** + * If in global scope, used for: + * + * - `in` => attribute (in VS), varying (in FS) + * - `out` => varying (in VS), output (in FS) + * + * For parameters / fn args: + * + * - `in` => passed into a function + * - `out` => passed back out of a function, but not initialized + * - `inout` => passed both into and out of a function + */ + q?: SymQualifier; + /** + * Symbol type, only used for global scope in/out vars, e.g. + * attribute, varying, uniform. + */ + type?: SymType; + /** + * Const symbol + */ + const?: boolean; + /** + * Precision qualifier + */ + prec?: Precision; + /** + * Arrays only. Length + */ + num?: number; + /** + * Layout location + */ + loc?: number; +} diff --git a/packages/shader-ast/src/api/tags.ts b/packages/shader-ast/src/api/tags.ts new file mode 100644 index 0000000000..a73fc24f1d --- /dev/null +++ b/packages/shader-ast/src/api/tags.ts @@ -0,0 +1,21 @@ +export type Tag = + | "arg" + | "array_init" + | "assign" + | "call" + | "call_i" + | "ctrl" + | "decl" + | "fn" + | "for" + | "idx" + | "if" + | "lit" + | "op1" + | "op2" + | "ret" + | "scope" + | "swizzle" + | "sym" + | "ternary" + | "while"; diff --git a/packages/shader-ast/src/api/target.ts b/packages/shader-ast/src/api/target.ts new file mode 100644 index 0000000000..b2a000ad91 --- /dev/null +++ b/packages/shader-ast/src/api/target.ts @@ -0,0 +1,44 @@ +import { Fn } from "@thi.ng/api"; +import { + ArrayInit, + Assign, + Branch, + Decl, + FnCall, + ForLoop, + Func, + FuncArg, + FuncReturn, + Index, + Lit, + Op1, + Op2, + Scope, + Swizzle, + Sym, + Ternary, + WhileLoop +} from "./nodes"; +import { Tag } from "./tags"; + +export interface TargetImpl extends Record> { + arg: Fn, T>; + array_init: Fn, T>; + assign: Fn, T>; + call: Fn, T>; + call_i: Fn, T>; + decl: Fn, T>; + fn: Fn, T>; + for: Fn; + idx: Fn, T>; + if: Fn; + lit: Fn, T>; + op1: Fn, T>; + op2: Fn, T>; + ret: Fn, T>; + scope: Fn; + swizzle: Fn, T>; + sym: Fn, T>; + ternary: Fn, T>; + while: Fn; +} diff --git a/packages/shader-ast/src/api/terms.ts b/packages/shader-ast/src/api/terms.ts new file mode 100644 index 0000000000..09882cc085 --- /dev/null +++ b/packages/shader-ast/src/api/terms.ts @@ -0,0 +1,30 @@ +import { Term } from "./nodes"; + +export type BoolTerm = Term<"bool">; +export type FloatTerm = Term<"float">; +export type IntTerm = Term<"int">; +export type UintTerm = Term<"uint">; +export type Vec2Term = Term<"vec2">; +export type Vec3Term = Term<"vec3">; +export type Vec4Term = Term<"vec4">; +export type IVec2Term = Term<"ivec2">; +export type IVec3Term = Term<"ivec3">; +export type IVec4Term = Term<"ivec4">; +export type UVec2Term = Term<"uvec2">; +export type UVec3Term = Term<"uvec3">; +export type UVec4Term = Term<"uvec4">; +export type BVec2Term = Term<"bvec2">; +export type BVec3Term = Term<"bvec3">; +export type BVec4Term = Term<"bvec4">; +export type Mat2Term = Term<"mat2">; +export type Mat3Term = Term<"mat3">; +export type Mat4Term = Term<"mat4">; +export type Sampler2DTerm = Term<"sampler2D">; +export type Sampler3DTerm = Term<"sampler3D">; +export type SamplerCubeTerm = Term<"samplerCube">; +export type ISampler2DTerm = Term<"isampler2D">; +export type ISampler3DTerm = Term<"isampler3D">; +export type ISamplerCubeTerm = Term<"isamplerCube">; +export type USampler2DTerm = Term<"usampler2D">; +export type USampler3DTerm = Term<"usampler3D">; +export type USamplerCubeTerm = Term<"usamplerCube">; diff --git a/packages/shader-ast/src/api/types.ts b/packages/shader-ast/src/api/types.ts new file mode 100644 index 0000000000..500d61e8f6 --- /dev/null +++ b/packages/shader-ast/src/api/types.ts @@ -0,0 +1,159 @@ +import { Index, Swizzle, Sym } from "./nodes"; +import { + BoolTerm, + FloatTerm, + IntTerm, + UintTerm +} from "./terms"; + +export type Type = + | "void" + | "bool" + | "bool[]" + | "float" + | "float[]" + | "int" + | "int[]" + | "uint" + | "uint[]" + | "vec2" + | "vec2[]" + | "vec3" + | "vec3[]" + | "vec4" + | "vec4[]" + | "ivec2" + | "ivec2[]" + | "ivec3" + | "ivec3[]" + | "ivec4" + | "ivec4[]" + | "uvec2" + | "uvec2[]" + | "uvec3" + | "uvec3[]" + | "uvec4" + | "uvec4[]" + | "bvec2" + | "bvec2[]" + | "bvec3" + | "bvec3[]" + | "bvec4" + | "bvec4[]" + | "mat2" + | "mat2[]" + | "mat3" + | "mat3[]" + | "mat4" + | "mat4[]" + | "sampler2D" + | "sampler2D[]" + | "sampler3D" + | "sampler3D[]" + | "samplerCube" + | "samplerCube[]" + | "sampler2DShadow" + | "sampler2DShadow[]" + | "samplerCubeShadow" + | "samplerCubeShadow[]" + | "isampler2D" + | "isampler2D[]" + | "isampler3D" + | "isampler3D[]" + | "isamplerCube" + | "isamplerCube[]" + | "usampler2D" + | "usampler2D[]" + | "usampler3D" + | "usampler3D[]" + | "usamplerCube" + | "usamplerCube[]"; + +export interface ArrayTypeMap { + bool: "bool[]"; + float: "float[]"; + int: "int[]"; + uint: "uint[]"; + vec2: "vec2[]"; + vec3: "vec3[]"; + vec4: "vec4[]"; + ivec2: "ivec2[]"; + ivec3: "ivec3[]"; + ivec4: "ivec4[]"; + uvec2: "uvec2[]"; + uvec3: "uvec3[]"; + uvec4: "uvec4[]"; + bvec2: "bvec2[]"; + bvec3: "bvec3[]"; + bvec4: "bvec4[]"; + mat2: "mat2[]"; + mat3: "mat3[]"; + mat4: "mat4[]"; + sampler2D: "sampler2D[]"; + sampler3D: "sampler3D[]"; + samplerCube: "samplerCube[]"; + sampler2DShadow: "sampler2DShadow[]"; + samplerCubeShadow: "samplerCubeShadow[]"; + isampler2D: "isampler2D[]"; + isampler3D: "isampler3D[]"; + isamplerCube: "isamplerCube[]"; + usampler2D: "usampler2D[]"; + usampler3D: "usampler3D[]"; + usamplerCube: "usamplerCube[]"; +} + +export interface IndexTypeMap { + "bool[]": "bool"; + "float[]": "float"; + "int[]": "int"; + "uint[]": "uint"; + "vec2[]": "vec2"; + "vec3[]": "vec3"; + "vec4[]": "vec4"; + "ivec2[]": "ivec2"; + "ivec3[]": "ivec3"; + "ivec4[]": "ivec4"; + "uvec2[]": "uvec2"; + "uvec3[]": "uvec3"; + "uvec4[]": "uvec4"; + "bvec2[]": "bvec2"; + "bvec3[]": "bvec3"; + "bvec4[]": "bvec4"; +} + +export interface MatIndexTypeMap { + mat2: "vec2"; + mat3: "vec3"; + mat4: "vec4"; +} + +export type Indexable = keyof IndexTypeMap; + +export type Assignable = Sym | Swizzle | Index; + +export type Vec = "vec2" | "vec3" | "vec4"; +export type IVec = "ivec2" | "ivec3" | "ivec4"; +export type UVec = "uvec2" | "uvec3" | "uvec4"; +export type BVec = "bvec2" | "bvec3" | "bvec4"; +export type Mat = "mat2" | "mat3" | "mat4"; +export type Sampler = + | "sampler2D" + | "sampler3D" + | "samplerCube" + | "sampler2DShadow" + | "samplerCubeShadow" + | "isampler2D" + | "isampler3D" + | "isamplerCube" + | "usampler2D" + | "usampler3D" + | "usamplerCube"; + +export type Prim = "float" | Vec; +export type Int = "int" | "uint"; +export type Comparable = "float" | Int; +export type Numeric = number | FloatTerm | IntTerm | UintTerm; +export type NumericF = number | FloatTerm; +export type NumericI = number | IntTerm; +export type NumericU = number | UintTerm; +export type NumericB = boolean | Numeric | BoolTerm; diff --git a/packages/shader-ast/src/ast.ts b/packages/shader-ast/src/ast.ts deleted file mode 100644 index 24a52086f7..0000000000 --- a/packages/shader-ast/src/ast.ts +++ /dev/null @@ -1,1416 +0,0 @@ -import { - assert, - Fn, - Fn2, - IObjectOf, - Select4 -} from "@thi.ng/api"; -import { - isArray, - isBoolean, - isNumber, - isString -} from "@thi.ng/checks"; -import { DGraph } from "@thi.ng/dgraph"; -import { illegalArgs } from "@thi.ng/errors"; -import { - Arg, - Arg1, - Arg2, - Arg3, - Arg4, - Arg5, - Arg6, - Arg7, - Arg8, - ArrayTypeMap, - Assign, - Assignable, - BoolTerm, - Branch, - BVec, - BVec2Term, - BVec3Term, - BVec4Term, - Comparable, - ComparisonOperator, - ControlFlow, - Decl, - FloatTerm, - FnBody0, - FnBody1, - FnBody2, - FnBody3, - FnBody4, - FnBody5, - FnBody6, - FnBody7, - FnBody8, - FnCall, - ForLoop, - Func, - FuncArg, - FuncReturn, - Index, - Indexable, - IndexTypeMap, - Int, - IntTerm, - IVec, - IVec2Term, - IVec3Term, - IVec4Term, - Lit, - Mat, - Mat2Term, - Mat3Term, - Mat4Term, - MatIndexTypeMap, - NumericB, - NumericF, - NumericI, - NumericU, - Op1, - Op2, - Operator, - Prim, - Scope, - ScopeBody, - Swizzle, - Swizzle2, - Swizzle2_1, - Swizzle2_2, - Swizzle2_3, - Swizzle3, - Swizzle3_1, - Swizzle3_2, - Swizzle3_3, - Swizzle4, - Swizzle4_1, - Swizzle4_2, - Swizzle4_3, - Sym, - SymOpts, - TaggedFn0, - TaggedFn1, - TaggedFn2, - TaggedFn3, - TaggedFn4, - TaggedFn5, - TaggedFn6, - TaggedFn7, - TaggedFn8, - Term, - Ternary, - Type, - UintTerm, - UVec, - UVec2Term, - UVec3Term, - UVec4Term, - Vec, - Vec2Term, - Vec3Term, - Vec4Term, - WhileLoop -} from "./api"; - -let symID = 0; - -const RE_VEC = /^[iub]?vec[234]$/; -const RE_MAT = /^mat[234]$/; - -/** - * Helper for deterministic code generation / testing. Resets sym ID - * counter. - */ -export const resetSymID = () => (symID = 0); - -/** - * Generates a new symbol name, e.g. `_sa2`. Uses base36 for counter to - * keep names short. - */ -export const gensym = () => `_s${(symID++).toString(36)}`; - -/** - * Returns true, if given term evaluates to a boolean value. - */ -export const isBool = (t: Term) => t.type === "bool"; - -/** - * Returns true, if given term evaluates to a float value. - */ -export const isFloat = (t: Term) => t.type === "float"; - -/** - * Returns true, if given term evaluates to a signed integer value. - */ -export const isInt = (t: Term) => t.type === "int"; - -/** - * Returns true, if given term evaluates to an unsigned integer value. - */ -export const isUint = (t: Term) => t.type === "uint"; - -/** - * Returns true, if given term is a literal. - */ -export const isLit = (t: Term) => t.tag === "lit"; - -/** - * Returns true, if given term is a float literal. - */ -export const isLitFloat = (t: Term) => isLit(t) && isFloat(t); - -/** - * Returns true, if given term is a signed integer literal. - */ -export const isLitInt = (t: Term) => isLit(t) && isInt(t); - -/** - * Returns true, if given term is a numeric literal (float, int, uint). - */ -export const isLitNumeric = (t: Term) => - isLit(t) && (isFloat(t) || isInt(t) || isUint(t)); - -/** - * Returns true, if given term evaluates to a vector value (vec, ivec, bvec). - */ -export const isVec = (t: Term) => RE_VEC.test(t.type); - -/** - * Returns true, if given term evaluates to a matrix value. - */ -export const isMat = (t: Term) => RE_MAT.test(t.type); - -/** - * Returns base type for given term. Used for array ops. - * - * ``` - * itemType("vec2[]") => "vec2" - * ``` - */ -export const itemType = (type: Type) => type.replace("[]", ""); - -/** - * Takes a numeric term and a plain number, returns number wrapped in - * typed literal compatible with term. - * - * @param t - * @param x - */ -export const numberWithMatchingType = (t: Term, x: number) => { - const id = t.type[0]; - return id === "i" - ? int(x) - : id === "u" - ? uint(x) - : id === "b" - ? bool(x) - : float(x); -}; - -export const matchingPrimFor = ( - t: Term, - x: FloatTerm -): Term => { - const ctor = ({ vec2, vec3, vec4 })[t.type]; - return ctor ? ctor(x) : x; -}; - -/** - * Helper function for `walk()`. Returns child nodes for any control - * flow nodes containing a child scope. - * - * @see allChildren - */ -export const scopedChildren = (t: Term) => - t.tag === "fn" || t.tag === "for" || t.tag == "while" - ? (>t).scope.body - : t.tag === "if" - ? (t).f - ? (t).t.body.concat((t).f!.body) - : (t).t.body - : undefined; - -/** - * Helper function for `walk()`. Returns an array of all child nodes for - * a given term (if any). - * - * @see scopedChildren - */ -export const allChildren = (t: Term) => - t.tag === "scope" - ? (t).body - : t.tag === "fn" || t.tag === "for" || t.tag == "while" - ? (>t).scope.body - : t.tag === "if" - ? (t).f - ? (t).t.body.concat((t).f!.body) - : (t).t.body - : t.tag === "ternary" - ? [(>t).t, (>t).f] - : t.tag === "ret" - ? [(>t).val] - : t.tag === "call" || t.tag === "call_i" - ? (>t).args - : t.tag === "sym" && (>t).init - ? [(>t).init] - : t.tag === "decl" - ? [(>t).id] - : t.tag === "op1" || t.tag === "swizzle" - ? [(>t).val] - : t.tag === "op2" - ? [(>t).l, (>t).r] - : t.tag === "assign" - ? [(>t).r] - : isVec(t) || isMat(t) - ? (>t).val - : undefined; - -/** - * Traverses given AST in depth-first order and applies `visit` and - * `children` fns to each node. Descends only further if `children` - * returns an array of child nodes. The `visit` function must accept 2 - * args: the accumulator (`acc`) given to `walk` and a tree node. The - * return value of `visit` becomes the new `acc` value, much like in a - * reduce operation. `walk` itself returns the final `acc`. - * - * If `pre` is true (default), the `visit` function will be called prior - * to visiting a node's children. If false, the visitor is called on the - * way back up. - * - * @param visit - * @param children - * @param acc - * @param tree - * @param pre - */ -export const walk = ( - visit: Fn2, T>, - children: Fn, Term[] | undefined>, - acc: T, - tree: Term | Term[], - pre = true -) => { - if (isArray(tree)) { - tree.forEach((x) => (acc = walk(visit, children, acc, x, pre))); - } else { - pre && (acc = visit(acc, tree)); - const c = children(tree); - c && (acc = walk(visit, children, acc, c, pre)); - !pre && (acc = visit(acc, tree)); - } - return acc; -}; - -/** - * Builds dependency graph of given function, by recursively adding all - * function dependencies. Returns graph. - * - * @param fn - * @param graph - */ -export const buildCallGraph = ( - fn: Func, - graph: DGraph> = new DGraph() -): DGraph> => - fn.deps && fn.deps.length - ? fn.deps.reduce( - (graph, d) => buildCallGraph(d, graph.addDependency(fn, d)), - graph - ) - : graph.addNode(fn); - -export const lit = ( - type: T, - val: any, - info?: string -): Lit => ({ - tag: "lit", - type, - info, - val -}); - -export const bool = (x: NumericB) => lit("bool", isNumber(x) ? !!x : x); - -export const float = (x: NumericB) => - lit("float", isBoolean(x) ? (x) & 1 : x); - -export const int = (x: NumericB) => - lit("int", isBoolean(x) ? (x) & 1 : isNumber(x) ? x | 0 : x); - -export const uint = (x: NumericB) => - lit("uint", isBoolean(x) ? (x) & 1 : isNumber(x) ? x >>> 0 : x); - -export const TRUE = lit("bool", true); -export const FALSE = lit("bool", false); - -export const FLOAT0: FloatTerm = float(0); -export const FLOAT1: FloatTerm = float(1); -export const FLOAT2: FloatTerm = float(2); -export const FLOAT05: FloatTerm = float(0.5); - -export const INT0: IntTerm = int(0); -export const INT1: IntTerm = int(1); - -export const UINT0: UintTerm = uint(0); -export const UINT1: UintTerm = uint(1); - -export const PI: FloatTerm = float(Math.PI); -export const TAU: FloatTerm = float(Math.PI * 2); -export const HALF_PI: FloatTerm = float(Math.PI / 2); -export const SQRT2: FloatTerm = float(Math.SQRT2); - -const wrap = (type: T, ctor: Fn>) => ( - x?: any -): Term | undefined => - isNumber(x) - ? ctor(x) - : x !== undefined && !isVec(x) && x.type !== type - ? ctor(x) - : x; - -/** - * Takes a plain number or numeric term and wraps it as float literal if - * needed. - * - * @param x - */ -export const wrapFloat = wrap("float", float); - -/** - * Takes a plain number or numeric term and wraps it as signed integer - * literal if needed. - * - * @param x - */ -export const wrapInt = wrap("int", int); - -/** - * Takes a plain number or numeric term and wraps it as unsigned integer - * literal if needed. - * - * @param x - */ -export const wrapUint = wrap("uint", uint); - -/** - * Takes a plain number or numeric term and wraps it as boolean literal - * if needed. - * - * @param x - */ -export const wrapBool = wrap("bool", bool); - -export function sym(init: Term): Sym; -export function sym(type: T): Sym; -export function sym(type: T, opts: SymOpts): Sym; -export function sym(type: T, init: Term): Sym; -export function sym(type: T, id: string): Sym; -// prettier-ignore -export function sym(type: T, id: string, opts: SymOpts): Sym; -// prettier-ignore -export function sym(type: T, opts: SymOpts, init: Term): Sym; -// prettier-ignore -export function sym(type: T, id: string, opts: SymOpts, init: Term): Sym; -export function sym(type: any, ...xs: any[]): Sym { - let id: string; - let opts: SymOpts; - let init: Term; - switch (xs.length) { - case 0: - if (!isString(type)) { - init = type; - type = init.type; - } - break; - case 1: - if (isString(xs[0])) { - id = xs[0]; - } else if (xs[0].tag) { - init = xs[0]; - } else { - opts = xs[0]; - } - break; - case 2: - if (isString(xs[0])) { - [id, opts] = xs; - } else { - [opts, init] = xs; - } - break; - case 3: - [id, opts, init] = xs; - break; - default: - illegalArgs(); - } - return { - tag: "sym", - type, - id: id! || gensym(), - opts: opts! || {}, - init: init! - }; -} - -export const constSym = ( - type: T, - id?: string, - opts?: SymOpts, - init?: Term -) => sym(type, id || gensym(), { const: true, ...opts }, init!); - -/** - * Defines a new symbol with optional initial array values. - * - * Important: Array initializers are UNSUPPORTED in GLSL ES v1 (WebGL), - * any code using such initializers will only work under WebGL2 or other - * targets. - */ -export const arraySym = ( - type: T, - id?: string, - opts: SymOpts = {}, - init?: (Lit | Sym)[] -): Sym => { - if (init && opts.num == null) { - opts.num = init.length; - } - assert(opts.num != null, "missing array length"); - init && - assert( - opts.num === init.length, - `expected ${opts.num} items in array, but got ${init.length}` - ); - const atype = (type + "[]"); - return { - tag: "sym", - type: atype, - id: id || gensym(), - opts, - init: init - ? { - tag: "array_init", - type: atype, - init - } - : undefined - }; -}; - -export const input = (type: T, id: string, opts?: SymOpts) => - sym(type, id, { q: "in", type: "in", ...opts }); - -export const output = (type: T, id: string, opts?: SymOpts) => - sym(type, id, { q: "out", type: "out", ...opts }); - -export const uniform = (type: T, id: string, opts?: SymOpts) => - sym(type, id, { q: "in", type: "uni", ...opts }); - -const decl = (id: Sym): Decl => ({ - tag: "decl", - type: id.type, - id -}); - -// prettier-ignore -export function $(a: Vec2Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: Vec3Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: Vec4Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: IVec2Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: IVec3Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: IVec4Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: UVec2Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: UVec3Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: UVec4Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: BVec2Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: BVec3Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: BVec4Term, id: T): Swizzle>; -export function $(val: Term, id: string): Swizzle { - const type = val.type[0]; - const rtype = (a: Type, b: string) => - id.length === 1 ? a : (b + id.length); - return { - tag: "swizzle", - type: - type === "i" - ? rtype("int", "ivec") - : type === "u" - ? rtype("uint", "uvec") - : type === "b" - ? rtype("bool", "bvec") - : rtype("float", "vec"), - val, - id - }; -} - -export const $x = ( - val: Term -): Swizzle> => - $(val, "x"); - -export const $y = ( - val: Term -): Swizzle> => - $(val, "y"); - -export const $z = < - T extends - | "vec3" - | "vec4" - | "ivec3" - | "ivec4" - | "uvec3" - | "uvec4" - | "bvec3" - | "bvec4" ->( - val: Term -): Swizzle> => - $(val, "z"); - -export const $w = ( - val: Term -): Swizzle> => - $(val, "w"); - -export function $xy(val: Term): Swizzle<"vec2">; -export function $xy(val: Term): Swizzle<"ivec2">; -export function $xy(val: Term): Swizzle<"uvec2">; -export function $xy(val: Term): Swizzle<"bvec2">; -export function $xy(val: any): Swizzle { - return $(val, "xy"); -} - -export function $xyz(val: Term<"vec3" | "vec4">): Swizzle<"vec3">; -export function $xyz(val: Term<"ivec3" | "ivec4">): Swizzle<"ivec3">; -export function $xyz(val: Term<"uvec3" | "uvec4">): Swizzle<"uvec3">; -export function $xyz(val: Term<"bvec3" | "bvec4">): Swizzle<"bvec3">; -export function $xyz(val: any): Swizzle { - return $(val, "xyz"); -} - -export const index = ( - val: Sym, - id: NumericI | UintTerm -): Index => ({ - tag: "idx", - type: val.type.substr(0, val.type.length - 2), - id: isNumber(id) ? int(id) : id, - val -}); - -// prettier-ignore -export function indexMat(m: Sym, id: number): Index; -// prettier-ignore -export function indexMat(m: Sym, a: number, b: number): Index<"float">; -export function indexMat(m: Sym, a: number, b?: number): Index { - const idx: any = { - tag: "idx", - type: m.type.replace("mat", "vec"), - id: int(a), - val: m - }; - return b !== undefined - ? { tag: "idx", type: "float", id: int(b), val: idx } - : idx; -} - -export const assign = ( - l: Assignable, - r: Term -): Assign => { - assert( - l.tag !== "swizzle" || (>l).val.tag === "sym", - "can't assign to non-symbol swizzle" - ); - return { - tag: "assign", - type: l.type, - l, - r - }; -}; - -const $vec = (xs: any[], init = FLOAT0) => [ - xs[0] === undefined ? init : wrapFloat(xs[0]), - ...xs.slice(1).map(wrapFloat) -]; - -const $ivec = (xs: any[], init = INT0) => [ - xs[0] === undefined ? init : wrapInt(xs[0]), - ...xs.slice(1).map(wrapInt) -]; - -const $uvec = (xs: any[], init = UINT0) => [ - xs[0] === undefined ? init : wrapUint(xs[0]), - ...xs.slice(1).map(wrapUint) -]; - -const $bvec = (xs: any[], init = FALSE) => [ - xs[0] === undefined ? init : wrapBool(xs[0]), - ...xs.slice(1).map(wrapBool) -]; - -const $mat = (xs: any[], init = FLOAT0) => [ - xs[0] === undefined ? init : wrapFloat(xs[0]), - ...xs.slice(1).map(wrapInt) -]; - -export function vec2(): Lit<"vec2">; -export function vec2(x: NumericF): Lit<"vec2">; -// export function vec2(x: Term): Lit<"vec2">; -// prettier-ignore -export function vec2(x: NumericF, y: NumericF): Lit<"vec2">; -// prettier-ignore -export function vec2(...xs: any[]): Lit<"vec2"> { - xs = $vec(xs); - return lit("vec2", xs, ["n","n"][xs.length]); -} - -export function vec3(): Lit<"vec3">; -export function vec3(x: NumericF): Lit<"vec3">; -export function vec3(x: Vec2Term, y: NumericF): Lit<"vec3">; -// prettier-ignore -export function vec3(x: NumericF, y: NumericF, z: NumericF): Lit<"vec3">; -export function vec3(...xs: any[]): Lit<"vec3"> { - xs = $vec(xs); - return lit("vec3", xs, ["n", "n", "vn"][xs.length]); -} - -export function vec4(): Lit<"vec4">; -export function vec4(x: NumericF): Lit<"vec4">; -export function vec4(x: Vec3Term, y: NumericF): Lit<"vec4">; -export function vec4(x: Vec2Term, y: Vec2Term): Lit<"vec4">; -// prettier-ignore -export function vec4(x: Vec2Term, y: NumericF, z: NumericF): Lit<"vec4">; -// prettier-ignore -export function vec4(x: NumericF, y: NumericF, z: NumericF, w: NumericF): Lit<"vec4">; -export function vec4(...xs: any[]): Lit<"vec4"> { - xs = $vec(xs); - return lit( - "vec4", - xs, - xs.length === 2 - ? isVec(xs[1]) - ? "vv" - : "vn" - : ["n", "n", , "vnn"][xs.length] - ); -} - -export function ivec2(): Lit<"ivec2">; -export function ivec2(x: NumericI): Lit<"ivec2">; -// prettier-ignore -export function ivec2(x: NumericI, y: NumericI): Lit<"ivec2">; -// prettier-ignore -export function ivec2(...xs: any[]): Lit<"ivec2"> { - return lit("ivec2", $ivec(xs), ["n","n"][xs.length]); -} - -export function ivec3(): Lit<"ivec3">; -export function ivec3(x: NumericI): Lit<"ivec3">; -export function ivec3(x: Vec2Term, y: NumericI): Lit<"ivec3">; -// prettier-ignore -export function ivec3(x: NumericI, y: NumericI, z: NumericI): Lit<"ivec3">; -export function ivec3(...xs: any[]): Lit<"ivec3"> { - return lit("ivec3", (xs = $ivec(xs)), ["n", "n", "vn"][xs.length]); -} - -export function ivec4(): Lit<"ivec4">; -export function ivec4(x: NumericI): Lit<"ivec4">; -export function ivec4(x: Vec3Term, y: NumericI): Lit<"ivec4">; -export function ivec4(x: Vec2Term, y: Vec2Term): Lit<"ivec4">; -// prettier-ignore -export function ivec4(x: Vec2Term, y: NumericI, z: NumericI): Lit<"ivec4">; -// prettier-ignore -export function ivec4(x: NumericI, y: NumericI, z: NumericI, w: NumericI): Lit<"ivec4">; -export function ivec4(...xs: any[]): Lit<"ivec4"> { - return lit( - "ivec4", - (xs = $ivec(xs)), - xs.length === 2 - ? isVec(xs[1]) - ? "vv" - : "vn" - : ["n", "n", , "vnn"][xs.length] - ); -} - -export function uvec2(): Lit<"uvec2">; -export function uvec2(x: NumericU): Lit<"uvec2">; -// prettier-ignore -export function uvec2(x: NumericU, y: NumericU): Lit<"uvec2">; -// prettier-ignore -export function uvec2(...xs: any[]): Lit<"uvec2"> { - return lit("uvec2", $uvec(xs), ["n","n"][xs.length]); -} - -export function uvec3(): Lit<"uvec3">; -export function uvec3(x: NumericU): Lit<"uvec3">; -export function uvec3(x: Vec2Term, y: NumericU): Lit<"uvec3">; -// prettier-ignore -export function uvec3(x: NumericU, y: NumericU, z: NumericU): Lit<"uvec3">; -export function uvec3(...xs: any[]): Lit<"uvec3"> { - return lit("uvec3", (xs = $uvec(xs)), ["n", "n", "vn"][xs.length]); -} - -export function uvec4(): Lit<"uvec4">; -export function uvec4(x: NumericU): Lit<"uvec4">; -export function uvec4(x: Vec3Term, y: NumericU): Lit<"uvec4">; -export function uvec4(x: Vec2Term, y: Vec2Term): Lit<"uvec4">; -// prettier-ignore -export function uvec4(x: Vec2Term, y: NumericU, z: NumericU): Lit<"uvec4">; -// prettier-ignore -export function uvec4(x: NumericU, y: NumericU, z: NumericU, w: NumericU): Lit<"uvec4">; -export function uvec4(...xs: any[]): Lit<"uvec4"> { - return lit( - "uvec4", - (xs = $uvec(xs)), - xs.length === 2 - ? isVec(xs[1]) - ? "vv" - : "vn" - : ["n", "n", , "vnn"][xs.length] - ); -} - -export function bvec2(): Lit<"bvec2">; -export function bvec2(x: NumericB): Lit<"bvec2">; -// prettier-ignore -export function bvec2(x: NumericB, y: NumericB): Lit<"bvec2">; -// prettier-ignore -export function bvec2(...xs: any[]): Lit<"bvec2"> { - return lit("bvec2", $bvec(xs), ["n","n"][xs.length]); -} - -export function bvec3(): Lit<"bvec3">; -export function bvec3(x: NumericB): Lit<"bvec3">; -export function bvec3(x: Vec2Term, y: NumericB): Lit<"bvec3">; -// prettier-ignore -export function bvec3(x: NumericB, y: NumericB, z: NumericB): Lit<"bvec3">; -export function bvec3(...xs: any[]): Lit<"bvec3"> { - return lit("bvec3", (xs = $bvec(xs)), ["n", "n", "vn"][xs.length]); -} - -export function bvec4(): Lit<"bvec4">; -export function bvec4(x: NumericB): Lit<"bvec4">; -export function bvec4(x: Vec3Term, y: NumericB): Lit<"bvec4">; -export function bvec4(x: Vec2Term, y: Vec2Term): Lit<"bvec4">; -// prettier-ignore -export function bvec4(x: Vec2Term, y: NumericB, z: NumericB): Lit<"bvec4">; -// prettier-ignore -export function bvec4(x: NumericB, y: NumericB, z: NumericB, w: NumericB): Lit<"bvec4">; -export function bvec4(...xs: any[]): Lit<"bvec4"> { - return lit( - "bvec4", - (xs = $bvec(xs)), - xs.length === 2 - ? isVec(xs[1]) - ? "vv" - : "vn" - : ["n", "n", , "vnn"][xs.length] - ); -} - -export function mat2(): Lit<"mat2">; -export function mat2(x: NumericF): Lit<"mat2">; -export function mat2(x: Vec2Term, y: Vec2Term): Lit<"mat2">; -// prettier-ignore -export function mat2(a: NumericF, b: NumericF, c: NumericF, d: NumericF): Lit<"mat2">; -export function mat2(...xs: any[]): Lit<"mat2"> { - return lit("mat2", (xs = $mat(xs, FLOAT1)), ["n", "n", "vv"][xs.length]); -} - -export function mat3(): Lit<"mat3">; -export function mat3(x: NumericF): Lit<"mat3">; -// prettier-ignore -export function mat3(x: Vec3Term, y: Vec3Term, z: Vec3Term): Lit<"mat3">; -// prettier-ignore -export function mat3(a: NumericF, b: NumericF, c: NumericF, d: NumericF, e: NumericF, f: NumericF, g: NumericF, h: NumericF, i: NumericF): Lit<"mat3">; -export function mat3(...xs: any[]): Lit<"mat3"> { - return lit("mat3", (xs = $mat(xs, FLOAT1)), ["n", "n", , "vvv"][xs.length]); -} - -export function mat4(): Lit<"mat4">; -export function mat4(x: NumericF): Lit<"mat4">; -// prettier-ignore -export function mat4(x: Vec4Term, y: Vec4Term, z: Vec4Term, w: Vec4Term): Lit<"mat4">; -// prettier-ignore -export function mat4(a: NumericF, b: NumericF, c: NumericF, d: NumericF, e: NumericF, f: NumericF, g: NumericF, h: NumericF, i: NumericF, j: NumericF, k: NumericF, l: NumericF, m: NumericF, n: NumericF, o: NumericF, p: NumericF): Lit<"mat4">; -export function mat4(...xs: any[]): Lit<"mat4"> { - return lit( - "mat4", - (xs = $mat(xs, FLOAT1)), - ["n", "n", , , "vvvv"][xs.length] - ); -} - -export const op1 = ( - op: Operator, - val: Term, - post = false -): Op1 => ({ - tag: "op1", - type: val.type, - op, - val, - post -}); - -const OP_INFO: IObjectOf = { - mave: "mv", - vema: "vm", - vefl: "vn", - mafl: "vn", - flve: "nv", - flma: "nv", - ivin: "vn", - iniv: "nv", - uvui: "vn", - uiuv: "nv" -}; - -export const op2 = ( - op: Operator, - _l: Term | number, - _r: Term | number, - rtype?: Type, - info?: string -): Op2 => { - const nl = isNumber(_l); - const nr = isNumber(_r); - let type: Type; - let l: Term; - let r: Term; - if (nl) { - if (nr) { - // (number, number) - l = float(_l); - r = float(_r); - type = "float"; - } else { - // (number, term) - r = >_r; - l = numberWithMatchingType(r, _l); - type = r.type; - } - } else if (nr) { - // (term, number) - l = >_l; - r = numberWithMatchingType(l, _r); - type = l.type; - } else { - // (term, term) - l = >_l; - r = >_r; - type = - rtype || - (isVec(l) - ? l.type - : isVec(r) - ? r.type - : isMat(r) - ? r.type - : l.type); - } - return { - tag: "op2", - type: rtype || type!, - info: info || OP_INFO[l!.type.substr(0, 2) + r!.type.substr(0, 2)], - op, - l: l!, - r: r! - }; -}; - -export const inc = (t: Sym): Op1 => - op1("++", t, true); - -export const dec = (t: Sym): Op1 => - op1("--", t, true); - -// prettier-ignore -export function add(l: Term, r: Term): Op2; -export function add(l: number, r: Term): Op2; -export function add(l: Term, r: number): Op2; -// prettier-ignore -export function add(l: FloatTerm | number, r: Term): Op2; -// prettier-ignore -export function add(l: Term, r: FloatTerm | number): Op2; -// prettier-ignore -export function add(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function add(l: Term, r: IntTerm | number): Op2; -// prettier-ignore -export function add(l: Term | number, r: Term | number): Op2 { - return op2("+", l, r); -} - -// prettier-ignore -export function sub(l: Term, r: Term): Op2; -export function sub(l: number, r: Term): Op2; -export function sub(l: Term, r: number): Op2; -// prettier-ignore -export function sub(l: FloatTerm | number, r: Term): Op2; -// prettier-ignore -export function sub(l: Term, r: FloatTerm | number): Op2; -// prettier-ignore -export function sub(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function sub(l: Term, r: IntTerm| number): Op2; -export function sub(l: Term | number, r: Term | number): Op2 { - return op2("-", l, r); -} - -// prettier-ignore -export function mul(l: Term, r: Term): Op2; -export function mul(l: number, r: Term): Op2; -export function mul(l: Term, r: number): Op2; -// prettier-ignore -export function mul(l: FloatTerm | number, r: Term): Op2; -// prettier-ignore -export function mul(l: Term, r: FloatTerm | number): Op2; -// prettier-ignore -export function mul(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function mul(l: Term, r: IntTerm | number): Op2; -export function mul(l: Mat2Term, r: Vec2Term): Op2<"vec2">; -export function mul(l: Mat3Term, r: Vec3Term): Op2<"vec3">; -export function mul(l: Mat4Term, r: Vec4Term): Op2<"vec4">; -export function mul(l: Vec2Term, r: Mat2Term): Op2<"vec2">; -export function mul(l: Vec3Term, r: Mat3Term): Op2<"vec3">; -export function mul(l: Vec4Term, r: Mat4Term): Op2<"vec4">; -export function mul(l: Term | number, r: Term | number): Op2 { - return op2( - "*", - l, - r, - !isNumber(l) && !isNumber(r) && isMat(l) && isVec(r) - ? r.type - : undefined - ); -} - -// prettier-ignore -export function div(l: Term, r: Term): Op2; -export function div(l: number, r: Term): Op2; -export function div(l: Term, r: number): Op2; -// prettier-ignore -export function div(l: FloatTerm | number, r: Term): Op2; -// prettier-ignore -export function div(l: Term, r: FloatTerm | number): Op2; -// prettier-ignore -export function div(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function div(l: Term, r: IntTerm | number): Op2; -export function div(l: Term | number, r: Term | number): Op2 { - return op2("/", l, r); -} - -/** - * Integer % (modulo) operator - * - * @param l - * @param b - */ -// prettier-ignore -export function modi(l: Term, r: Term): Op2; -// prettier-ignore -export function modi(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function modi(l: Term, r: IntTerm | number): Op2; -// prettier-ignore -export function modi(l: UintTerm | number, r: Term): Op2; -// prettier-ignore -export function modi(l: Term, r: UintTerm | number): Op2; -export function modi(l: Term | number, r: Term | number): Op2 { - return op2( - "%", - isNumber(l) ? numberWithMatchingType(>r, l) : l, - isNumber(r) ? numberWithMatchingType(>l, r) : r - ); -} - -export const neg = (val: Term) => - op1("-", val); - -/** - * Multiply-add: a * b + c. All must be of same type. - * - * @param a - * @param b - * @param c - */ -export const madd = < - A extends Prim | IVec | UVec | "int" | "uint", - B extends A, - C extends B ->( - a: Term, - b: Term, - c: Term -): Term => add(mul(>a, b), c); - -/** - * Add-multiply: (a + b) * c. All must be of same type. - * - * @param a - * @param b - * @param c - */ -export const addm = ( - a: Term, - b: Term, - c: Term -): Term => mul(add(>a, b), c); - -export const not = (val: BoolTerm) => op1("!", val); -export const or = (a: BoolTerm, b: BoolTerm) => op2("||", a, b); -export const and = (a: BoolTerm, b: BoolTerm) => op2("&&", a, b); - -const cmp = (op: ComparisonOperator) => ( - a: Term, - b: Term -): BoolTerm => op2(op, a, b, "bool"); - -export const eq = cmp("=="); -export const neq = cmp("!="); -export const lt = cmp("<"); -export const lte = cmp("<="); -export const gt = cmp(">"); -export const gte = cmp(">="); - -export const bitnot = | Term>( - val: T -) => op1("~", val); - -// prettier-ignore -export function bitand(l: Term, r: Term): Term; -// prettier-ignore -export function bitand(l: Term, r: IntTerm | number): Term; -// prettier-ignore -export function bitand(l: IntTerm | number, r: Term): Term; -// prettier-ignore -export function bitand(l: Term, r: UintTerm | number): Term; -// prettier-ignore -export function bitand(l: UintTerm | number, r: Term): Term; -// prettier-ignore -export function bitand(l: Term | number, r: Term | number): Op2 { - return op2("&", l, r, undefined); -} - -// prettier-ignore -export function bitor(l: Term, r: Term): Term; -// prettier-ignore -export function bitor(l: Term, r: IntTerm | number): Term; -// prettier-ignore -export function bitor(l: IntTerm | number, r: Term): Term; -// prettier-ignore -export function bitor(l: Term, r: UintTerm | number): Term; -// prettier-ignore -export function bitor(l: UintTerm | number, r: Term): Term; -// prettier-ignore -export function bitor(l: Term | number, r: Term | number): Op2 { - return op2("|", l, r, undefined); -} - -// prettier-ignore -export function bitxor(l: Term, r: Term): Term; -// prettier-ignore -export function bitxor(l: Term, r: IntTerm | number): Term; -// prettier-ignore -export function bitxor(l: IntTerm | number, r: Term): Term; -// prettier-ignore -export function bitxor(l: Term, r: UintTerm | number): Term; -// prettier-ignore -export function bitxor(l: UintTerm | number, r: Term): Term; -// prettier-ignore -export function bitxor(l: Term | number, r: Term | number): Op2 { - return op2("^", l, r, undefined); -} - -/** - * Wraps the given AST node array in `scope` node, optionally as global - * scope (default false). The interpretation of the global flag is - * dependent on the target code gen. I.e. for GLSL / JS, the flag - * disables wrapping the scope's body in `{}`, but else has no - * difference. In general this node type only serves as internal - * mechanism for various control flow AST nodes and should not need to - * be used directly from user land code (though might be useful to - * create custom / higher level control flow nodes). - * - * @param body - * @param global - */ -export const scope = (body: (Term | null)[], global = false): Scope => ({ - tag: "scope", - type: "void", - body: []>( - body - .filter((x) => x != null) - .map((x) => (x!.tag === "sym" ? decl(>x) : x)) - ), - global -}); - -/** - * Takes an array of global sym/var definitions (`input()`, `output()`, - * `uniform()`) and functions defined via `defn()`. Constructs the call - * graph of all transitively used functions and bundles everything in - * topological order within a global scope object, which is then - * returned to the user and can be passed to a target codegen for full - * program output. - * - * @see scope - * @see input - * @see output - * @see uniform - * - * @param body - */ -export const program = (body: (Sym | Func)[]) => { - const syms = body.filter((x) => x.tag !== "fn"); - const g = body.reduce( - (acc, x) => (x.tag === "fn" ? buildCallGraph(>x, acc) : acc), - new DGraph>() - ); - return scope(syms.concat(g.sort()), true); -}; - -const defArg = (a: Arg): FuncArg => { - const [type, id, opts] = isString(a) ? <[T, string?, SymOpts?]>[a] : a; - return { - tag: "arg", - type, - id: id || gensym(), - opts: { q: "in", ...opts } - }; -}; - -/** - * Defines a new function with up to 8 typed checked arguments. - * - * @param type return type - * @param name function name - * @param args arg types / names / opts - * @param body function body closure - * @param deps array of userland functions called from this function - */ -// prettier-ignore -export function defn(type: T, name: string, args: [], body: FnBody0): TaggedFn0; -// prettier-ignore -export function defn(type: T, name: string, args: Arg1, body: FnBody1): TaggedFn1; -// prettier-ignore -export function defn(type: T, name: string, args: Arg2, body: FnBody2): TaggedFn2; -// prettier-ignore -export function defn(type: T, name: string, args: Arg3, body: FnBody3): TaggedFn3; -// prettier-ignore -export function defn(type: T, name: string, args: Arg4, body: FnBody4): TaggedFn4; -// prettier-ignore -export function defn(type: T, name: string, args: Arg5, body: FnBody5): TaggedFn5; -// prettier-ignore -export function defn(type: T, name: string, args: Arg6, body: FnBody6): TaggedFn6; -// prettier-ignore -export function defn(type: T, name: string, args: Arg7, body: FnBody7): TaggedFn7; -// prettier-ignore -export function defn(type: T, name: string, args: Arg8, body: FnBody8): TaggedFn8; -// prettier-ignore -export function defn(type: Type, id: string, _args: Arg[], _body: (...xs: Sym[]) => ScopeBody): Func { - const args = _args.map(defArg); - const body = []>( - _body(...args.map((x) => sym(x.type, x.id, x.opts))).filter( - (x) => x != null - ) - ); - // count & check returns - const returns = walk( - (n, t) => { - if (t.tag === "ret") { - assert( - t.type === type, - `wrong return type for function '${id}', expected ${type}, got ${ - t.type - }` - ); - n++; - } - return n; - }, - scopedChildren, - 0, - body - ); - if (type !== "void" && !returns) { - throw new Error(`function '${id}' must return a value of type ${type}`); - } - // verify all non-builtin functions called are also - // provided as deps to ensure complete call graph later - const deps = walk( - (acc, t) => { - if (t.tag === "call" && (>t).fn) { - acc.push((>t).fn!); - } - return acc; - }, - allChildren, - []>[], - body - ); - const $: any = (...xs: any[]) => (funcall)($, ...xs); - return Object.assign($, >{ - tag: "fn", - type, - id, - args, - deps, - scope: scope(body) - }); -} - -/** - * Syntax sugar for defining `void main()` functions. - * - * @param body - */ -export const defMain = (body: FnBody0) => defn("void", "main", [], body); - -export function ret(): FuncReturn<"void">; -export function ret(val: Term): FuncReturn; -export function ret(val?: Term): FuncReturn { - return { - tag: "ret", - type: val ? val.type : "void", - val - }; -} - -// prettier-ignore -export function funcall(fn: string, type: T, ...args: Term[]): FnCall; -export function funcall(fn: TaggedFn0): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn1, a: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn2, a: Term, b: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn3, a: Term, b: Term, c: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn4, a: Term, b: Term, c: Term, d: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn5, a: Term, b: Term, c: Term, d: Term, e: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn6, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn7, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term, g: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn8, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term, g: Term, h: Term): FnCall; -// prettier-ignore -export function funcall(fn: string | Func, ...args: Term[]): FnCall { - return isString(fn) - ? { - tag: "call", - type: args[0], - id: fn, - args: args.slice(1) - } - : { - tag: "call", - type: fn.type, - id: fn.id, - args, - fn - }; -} - -export const builtinCall = ( - id: string, - type: T, - ...args: Term[] -): FnCall => ({ - tag: "call_i", - type, - id, - args -}); - -export const ifThen = ( - test: BoolTerm, - truthy: Term[], - falsey?: Term[] -): Branch => ({ - tag: "if", - type: "void", - test, - t: scope(truthy), - f: falsey ? scope(falsey) : undefined -}); - -export const ternary = ( - test: BoolTerm, - t: Term, - f: Term -): Ternary => ({ - tag: "ternary", - type: t.type, - test, - t, - f -}); - -// prettier-ignore -export function forLoop(test: Fn, BoolTerm>, body: FnBody1): ForLoop; -// prettier-ignore -export function forLoop(init: Sym | undefined, test: Fn, BoolTerm>, body: FnBody1): ForLoop; -// prettier-ignore -export function forLoop(init: Sym | undefined, test: Fn, BoolTerm>, iter: Fn, Term>, body: FnBody1): ForLoop; -export function forLoop(...xs: any[]): ForLoop { - const [init, test, iter, body] = - xs.length === 2 - ? [, xs[0], , xs[1]] - : xs.length === 3 - ? [xs[0], xs[1], , xs[2]] - : xs; - return { - tag: "for", - type: "void", - init: init ? decl(init) : undefined, - test: test(init!), - iter: iter ? iter(init!) : undefined, - scope: scope(body(init!)) - }; -} - -export const whileLoop = (test: BoolTerm, body: Term[]): WhileLoop => ({ - tag: "while", - type: "void", - test, - scope: scope(body) -}); - -const ctrl = (id: string): ControlFlow => ({ - tag: "ctrl", - type: "void", - id -}); - -export const brk = ctrl("break"); - -export const cont = ctrl("continue"); - -export const discard = ctrl("discard"); diff --git a/packages/shader-ast/src/ast/assign.ts b/packages/shader-ast/src/ast/assign.ts new file mode 100644 index 0000000000..966d6798be --- /dev/null +++ b/packages/shader-ast/src/ast/assign.ts @@ -0,0 +1,19 @@ +import { assert } from "@thi.ng/api"; +import { Assign, Swizzle, Term } from "../api/nodes"; +import { Assignable, Type } from "../api/types"; + +export const assign = ( + l: Assignable, + r: Term +): Assign => { + assert( + l.tag !== "swizzle" || (>l).val.tag === "sym", + "can't assign to non-symbol swizzle" + ); + return { + tag: "assign", + type: l.type, + l, + r + }; +}; diff --git a/packages/shader-ast/src/ast/checks.ts b/packages/shader-ast/src/ast/checks.ts new file mode 100644 index 0000000000..e6983b20af --- /dev/null +++ b/packages/shader-ast/src/ast/checks.ts @@ -0,0 +1,55 @@ +import { Term } from "../api/nodes"; + +const RE_VEC = /^[iub]?vec[234]$/; +const RE_MAT = /^mat[234]$/; + +/** + * Returns true, if given term evaluates to a boolean value. + */ +export const isBool = (t: Term) => t.type === "bool"; + +/** + * Returns true, if given term evaluates to a float value. + */ +export const isFloat = (t: Term) => t.type === "float"; + +/** + * Returns true, if given term evaluates to a signed integer value. + */ +export const isInt = (t: Term) => t.type === "int"; + +/** + * Returns true, if given term evaluates to an unsigned integer value. + */ +export const isUint = (t: Term) => t.type === "uint"; + +/** + * Returns true, if given term is a literal. + */ +export const isLit = (t: Term) => t.tag === "lit"; + +/** + * Returns true, if given term is a float literal. + */ +export const isLitFloat = (t: Term) => isLit(t) && isFloat(t); + +/** + * Returns true, if given term is a signed integer literal. + */ +export const isLitInt = (t: Term) => isLit(t) && isInt(t); + +/** + * Returns true, if given term is a numeric literal (float, int, uint). + */ +export const isLitNumeric = (t: Term) => + isLit(t) && (isFloat(t) || isInt(t) || isUint(t)); + +/** + * Returns true, if given term evaluates to a vector value (vec, ivec, bvec). + */ +export const isVec = (t: Term) => RE_VEC.test(t.type); + +/** + * Returns true, if given term evaluates to a matrix value. + */ +export const isMat = (t: Term) => RE_MAT.test(t.type); diff --git a/packages/shader-ast/src/ast/controlflow.ts b/packages/shader-ast/src/ast/controlflow.ts new file mode 100644 index 0000000000..44c2e2645e --- /dev/null +++ b/packages/shader-ast/src/ast/controlflow.ts @@ -0,0 +1,80 @@ +import { FnBody1 } from ".."; +import { Fn } from "@thi.ng/api"; +import { + Branch, + ControlFlow, + ForLoop, + Sym, + Term, + Ternary, + WhileLoop +} from "../api/nodes"; +import { BoolTerm } from "../api/terms"; +import { Type } from "../api/types"; +import { decl, scope } from "./scope"; + +export const ifThen = ( + test: BoolTerm, + truthy: Term[], + falsey?: Term[] +): Branch => ({ + tag: "if", + type: "void", + test, + t: scope(truthy), + f: falsey ? scope(falsey) : undefined +}); + +export const ternary = ( + test: BoolTerm, + t: Term, + f: Term +): Ternary => ({ + tag: "ternary", + type: t.type, + test, + t, + f +}); + +// prettier-ignore +export function forLoop(test: Fn, BoolTerm>, body: FnBody1): ForLoop; +// prettier-ignore +export function forLoop(init: Sym | undefined, test: Fn, BoolTerm>, body: FnBody1): ForLoop; +// prettier-ignore +export function forLoop(init: Sym | undefined, test: Fn, BoolTerm>, iter: Fn, Term>, body: FnBody1): ForLoop; +export function forLoop(...xs: any[]): ForLoop { + const [init, test, iter, body] = + xs.length === 2 + ? [, xs[0], , xs[1]] + : xs.length === 3 + ? [xs[0], xs[1], , xs[2]] + : xs; + return { + tag: "for", + type: "void", + init: init ? decl(init) : undefined, + test: test(init!), + iter: iter ? iter(init!) : undefined, + scope: scope(body(init!)) + }; +} + +export const whileLoop = (test: BoolTerm, body: Term[]): WhileLoop => ({ + tag: "while", + type: "void", + test, + scope: scope(body) +}); + +const ctrl = (id: string): ControlFlow => ({ + tag: "ctrl", + type: "void", + id +}); + +export const brk = ctrl("break"); + +export const cont = ctrl("continue"); + +export const discard = ctrl("discard"); diff --git a/packages/shader-ast/src/ast/function.ts b/packages/shader-ast/src/ast/function.ts new file mode 100644 index 0000000000..9db08fffa9 --- /dev/null +++ b/packages/shader-ast/src/ast/function.ts @@ -0,0 +1,205 @@ +import { assert } from "@thi.ng/api"; +import { isString } from "@thi.ng/checks"; +import { + Arg, + Arg1, + Arg2, + Arg3, + Arg4, + Arg5, + Arg6, + Arg7, + Arg8, + FnBody0, + FnBody1, + FnBody2, + FnBody3, + FnBody4, + FnBody5, + FnBody6, + FnBody7, + FnBody8, + ScopeBody +} from "../api/function"; +import { + FnCall, + Func, + FuncArg, + FuncReturn, + Sym, + TaggedFn0, + TaggedFn1, + TaggedFn2, + TaggedFn3, + TaggedFn4, + TaggedFn5, + TaggedFn6, + TaggedFn7, + TaggedFn8, + Term +} from "../api/nodes"; +import { SymOpts } from "../api/syms"; +import { Type } from "../api/types"; +import { gensym } from "./idgen"; +import { + allChildren, + scope, + scopedChildren, + walk +} from "./scope"; +import { sym } from "./sym"; + +const defArg = (a: Arg): FuncArg => { + const [type, id, opts] = isString(a) ? <[T, string?, SymOpts?]>[a] : a; + return { + tag: "arg", + type, + id: id || gensym(), + opts: { q: "in", ...opts } + }; +}; + +/** + * Defines a new function with up to 8 typed checked arguments. + * + * @param type return type + * @param name function name + * @param args arg types / names / opts + * @param body function body closure + * @param deps array of userland functions called from this function + */ +// prettier-ignore +export function defn(type: T, name: string, args: [], body: FnBody0): TaggedFn0; +// prettier-ignore +export function defn(type: T, name: string, args: Arg1, body: FnBody1): TaggedFn1; +// prettier-ignore +export function defn(type: T, name: string, args: Arg2, body: FnBody2): TaggedFn2; +// prettier-ignore +export function defn(type: T, name: string, args: Arg3, body: FnBody3): TaggedFn3; +// prettier-ignore +export function defn(type: T, name: string, args: Arg4, body: FnBody4): TaggedFn4; +// prettier-ignore +export function defn(type: T, name: string, args: Arg5, body: FnBody5): TaggedFn5; +// prettier-ignore +export function defn(type: T, name: string, args: Arg6, body: FnBody6): TaggedFn6; +// prettier-ignore +export function defn(type: T, name: string, args: Arg7, body: FnBody7): TaggedFn7; +// prettier-ignore +export function defn(type: T, name: string, args: Arg8, body: FnBody8): TaggedFn8; +// prettier-ignore +export function defn(type: Type, id: string, _args: Arg[], _body: (...xs: Sym[]) => ScopeBody): Func { + const args = _args.map(defArg); + const body = []>( + _body(...args.map((x) => sym(x.type, x.id, x.opts))).filter( + (x) => x != null + ) + ); + // count & check returns + const returns = walk( + (n, t) => { + if (t.tag === "ret") { + assert( + t.type === type, + `wrong return type for function '${id}', expected ${type}, got ${ + t.type + }` + ); + n++; + } + return n; + }, + scopedChildren, + 0, + body + ); + if (type !== "void" && !returns) { + throw new Error(`function '${id}' must return a value of type ${type}`); + } + // verify all non-builtin functions called are also + // provided as deps to ensure complete call graph later + const deps = walk( + (acc, t) => { + if (t.tag === "call" && (>t).fn) { + acc.push((>t).fn!); + } + return acc; + }, + allChildren, + []>[], + body + ); + const $: any = (...xs: any[]) => (funcall)($, ...xs); + return Object.assign($, >{ + tag: "fn", + type, + id, + args, + deps, + scope: scope(body) + }); +} + +/** + * Syntax sugar for defining `void main()` functions. + * + * @param body + */ +export const defMain = (body: FnBody0) => defn("void", "main", [], body); + +export function ret(): FuncReturn<"void">; +export function ret(val: Term): FuncReturn; +export function ret(val?: Term): FuncReturn { + return { + tag: "ret", + type: val ? val.type : "void", + val + }; +} + +// prettier-ignore +export function funcall(fn: string, type: T, ...args: Term[]): FnCall; +export function funcall(fn: TaggedFn0): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn1, a: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn2, a: Term, b: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn3, a: Term, b: Term, c: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn4, a: Term, b: Term, c: Term, d: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn5, a: Term, b: Term, c: Term, d: Term, e: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn6, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn7, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term, g: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn8, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term, g: Term, h: Term): FnCall; +// prettier-ignore +export function funcall(fn: string | Func, ...args: Term[]): FnCall { + return isString(fn) + ? { + tag: "call", + type: args[0], + id: fn, + args: args.slice(1) + } + : { + tag: "call", + type: fn.type, + id: fn.id, + args, + fn + }; +} + +export const builtinCall = ( + id: string, + type: T, + ...args: Term[] +): FnCall => ({ + tag: "call_i", + type, + id, + args +}); diff --git a/packages/shader-ast/src/ast/idgen.ts b/packages/shader-ast/src/ast/idgen.ts new file mode 100644 index 0000000000..cb0435f409 --- /dev/null +++ b/packages/shader-ast/src/ast/idgen.ts @@ -0,0 +1,13 @@ +let symID = 0; + +/** + * Helper for deterministic code generation / testing. Resets sym ID + * counter. + */ +export const resetSymID = () => (symID = 0); + +/** + * Generates a new symbol name, e.g. `_sa2`. Uses base36 for counter to + * keep names short. + */ +export const gensym = () => `_s${(symID++).toString(36)}`; diff --git a/packages/shader-ast/src/ast/indexed.ts b/packages/shader-ast/src/ast/indexed.ts new file mode 100644 index 0000000000..3ec2f94da1 --- /dev/null +++ b/packages/shader-ast/src/ast/indexed.ts @@ -0,0 +1,36 @@ +import { isNumber } from "@thi.ng/checks"; +import { Index, Sym } from "../api/nodes"; +import { UintTerm } from "../api/terms"; +import { + Indexable, + IndexTypeMap, + MatIndexTypeMap, + NumericI +} from "../api/types"; +import { int } from "./lit"; + +export const index = ( + val: Sym, + id: NumericI | UintTerm +): Index => ({ + tag: "idx", + type: val.type.substr(0, val.type.length - 2), + id: isNumber(id) ? int(id) : id, + val +}); + +// prettier-ignore +export function indexMat(m: Sym, id: number): Index; +// prettier-ignore +export function indexMat(m: Sym, a: number, b: number): Index<"float">; +export function indexMat(m: Sym, a: number, b?: number): Index { + const idx: any = { + tag: "idx", + type: m.type.replace("mat", "vec"), + id: int(a), + val: m + }; + return b !== undefined + ? { tag: "idx", type: "float", id: int(b), val: idx } + : idx; +} diff --git a/packages/shader-ast/src/ast/item.ts b/packages/shader-ast/src/ast/item.ts new file mode 100644 index 0000000000..89ba26ee88 --- /dev/null +++ b/packages/shader-ast/src/ast/item.ts @@ -0,0 +1,72 @@ +import { IObjectOf } from "@thi.ng/api"; +import { Term } from "../api/nodes"; +import { FloatTerm } from "../api/terms"; +import { + Int, + IVec, + Prim, + Type, + UVec +} from "../api/types"; +import { + bool, + float, + int, + uint, + vec2, + vec3, + vec4 +} from "./lit"; + +/** + * Returns base type for given term. Used for array ops. + * + * ``` + * itemType("vec2[]") => "vec2" + * ``` + */ +export const itemType = (type: Type) => type.replace("[]", ""); + +/** + * Takes a numeric term and a plain number, returns number wrapped in + * typed literal compatible with term. + * + * @param t + * @param x + */ +export const numberWithMatchingType = (t: Term, x: number) => { + const id = t.type[0]; + return id === "i" + ? int(x) + : id === "u" + ? uint(x) + : id === "b" + ? bool(x) + : float(x); +}; + +export const matchingPrimFor = ( + t: Term, + x: FloatTerm +): Term => { + const ctor = ({ vec2, vec3, vec4 })[t.type]; + return ctor ? ctor(x) : x; +}; + +export const matchingBoolType = ( + t: Term +) => + (>{ + float: "bool", + int: "bool", + uint: "bool", + vec2: "bvec2", + ivec2: "bvec2", + uvec2: "bvec2", + vec3: "bvec3", + ivec3: "bvec3", + uvec3: "bvec3", + vec4: "bvec4", + ivec4: "bvec4", + uvec4: "bvec4" + })[t.type]; diff --git a/packages/shader-ast/src/ast/lit.ts b/packages/shader-ast/src/ast/lit.ts new file mode 100644 index 0000000000..f066d04313 --- /dev/null +++ b/packages/shader-ast/src/ast/lit.ts @@ -0,0 +1,296 @@ +import { Fn } from "@thi.ng/api"; +import { isBoolean, isNumber } from "@thi.ng/checks"; +import { Lit, Term } from "../api/nodes"; +import { + FloatTerm, + IntTerm, + UintTerm, + Vec2Term, + Vec3Term, + Vec4Term +} from "../api/terms"; +import { + NumericB, + NumericF, + NumericI, + NumericU, + Type +} from "../api/types"; +import { isVec } from "./checks"; + +export const lit = ( + type: T, + val: any, + info?: string +): Lit => ({ + tag: "lit", + type, + info, + val +}); + +export const bool = (x: NumericB) => lit("bool", isNumber(x) ? !!x : x); + +export const float = (x: NumericB) => + lit("float", isBoolean(x) ? (x) & 1 : x); + +export const int = (x: NumericB) => + lit("int", isBoolean(x) ? (x) & 1 : isNumber(x) ? x | 0 : x); + +export const uint = (x: NumericB) => + lit("uint", isBoolean(x) ? (x) & 1 : isNumber(x) ? x >>> 0 : x); + +const wrap = (type: T, ctor: Fn>) => ( + x?: any +): Term | undefined => + isNumber(x) + ? ctor(x) + : x !== undefined && !isVec(x) && x.type !== type + ? ctor(x) + : x; + +/** + * Takes a plain number or numeric term and wraps it as float literal if + * needed. + * + * @param x + */ +export const wrapFloat = wrap("float", float); + +/** + * Takes a plain number or numeric term and wraps it as signed integer + * literal if needed. + * + * @param x + */ +export const wrapInt = wrap("int", int); + +/** + * Takes a plain number or numeric term and wraps it as unsigned integer + * literal if needed. + * + * @param x + */ +export const wrapUint = wrap("uint", uint); + +/** + * Takes a plain number or numeric term and wraps it as boolean literal + * if needed. + * + * @param x + */ +export const wrapBool = wrap("bool", bool); + +export const TRUE = lit("bool", true); +export const FALSE = lit("bool", false); + +export const FLOAT0: FloatTerm = float(0); +export const FLOAT1: FloatTerm = float(1); +export const FLOAT2: FloatTerm = float(2); +export const FLOAT05: FloatTerm = float(0.5); + +export const INT0: IntTerm = int(0); +export const INT1: IntTerm = int(1); + +export const UINT0: UintTerm = uint(0); +export const UINT1: UintTerm = uint(1); + +export const PI: FloatTerm = float(Math.PI); +export const TAU: FloatTerm = float(Math.PI * 2); +export const HALF_PI: FloatTerm = float(Math.PI / 2); +export const SQRT2: FloatTerm = float(Math.SQRT2); + +const $gvec = (wrap: Fn | undefined>, init: Term) => ( + xs: any[] +) => [xs[0] === undefined ? init : wrap(xs[0]), ...xs.slice(1).map(wrap)]; + +const $vec = $gvec(wrapFloat, FLOAT0); + +const $ivec = $gvec(wrapInt, INT0); + +const $uvec = $gvec(wrapUint, UINT0); + +const $bvec = $gvec(wrapBool, FALSE); + +const $gvec2 = ( + type: T, + ctor: Fn | undefined)[]>, + xs: any[] +) => lit(type, (xs = ctor(xs)), ["n", "n"][xs.length]); + +const $gvec3 = ( + type: T, + ctor: Fn | undefined)[]>, + xs: any[] +) => lit(type, (xs = ctor(xs)), ["n", "n", "vn"][xs.length]); + +const $gvec4 = ( + type: T, + ctor: Fn | undefined)[]>, + xs: any[] +) => + lit( + type, + (xs = ctor(xs)), + xs.length === 2 + ? isVec(xs[1]) + ? "vv" + : "vn" + : ["n", "n", , "vnn"][xs.length] + ); + +const $gmat = ( + type: T, + info: (string | undefined)[], + xs: any[] +) => lit(type, (xs = $vec(xs)), info[xs.length]); + +export function vec2(): Lit<"vec2">; +export function vec2(x: NumericF): Lit<"vec2">; +// export function vec2(x: Term): Lit<"vec2">; +// prettier-ignore +export function vec2(x: NumericF, y: NumericF): Lit<"vec2">; +// prettier-ignore +export function vec2(...xs: any[]): Lit<"vec2"> { + return $gvec2("vec2", $vec, xs); +} + +export function vec3(): Lit<"vec3">; +export function vec3(x: NumericF): Lit<"vec3">; +export function vec3(x: Vec2Term, y: NumericF): Lit<"vec3">; +// prettier-ignore +export function vec3(x: NumericF, y: NumericF, z: NumericF): Lit<"vec3">; +export function vec3(...xs: any[]): Lit<"vec3"> { + return $gvec3("vec3", $vec, xs); +} + +export function vec4(): Lit<"vec4">; +export function vec4(x: NumericF): Lit<"vec4">; +export function vec4(x: Vec3Term, y: NumericF): Lit<"vec4">; +export function vec4(x: Vec2Term, y: Vec2Term): Lit<"vec4">; +// prettier-ignore +export function vec4(x: Vec2Term, y: NumericF, z: NumericF): Lit<"vec4">; +// prettier-ignore +export function vec4(x: NumericF, y: NumericF, z: NumericF, w: NumericF): Lit<"vec4">; +export function vec4(...xs: any[]): Lit<"vec4"> { + return $gvec4("vec4", $vec, xs); +} + +export function ivec2(): Lit<"ivec2">; +export function ivec2(x: NumericI): Lit<"ivec2">; +// prettier-ignore +export function ivec2(x: NumericI, y: NumericI): Lit<"ivec2">; +// prettier-ignore +export function ivec2(...xs: any[]): Lit<"ivec2"> { + return $gvec2("ivec2", $ivec, xs); +} + +export function ivec3(): Lit<"ivec3">; +export function ivec3(x: NumericI): Lit<"ivec3">; +export function ivec3(x: Vec2Term, y: NumericI): Lit<"ivec3">; +// prettier-ignore +export function ivec3(x: NumericI, y: NumericI, z: NumericI): Lit<"ivec3">; +export function ivec3(...xs: any[]): Lit<"ivec3"> { + return $gvec3("ivec3", $ivec, xs); +} + +export function ivec4(): Lit<"ivec4">; +export function ivec4(x: NumericI): Lit<"ivec4">; +export function ivec4(x: Vec3Term, y: NumericI): Lit<"ivec4">; +export function ivec4(x: Vec2Term, y: Vec2Term): Lit<"ivec4">; +// prettier-ignore +export function ivec4(x: Vec2Term, y: NumericI, z: NumericI): Lit<"ivec4">; +// prettier-ignore +export function ivec4(x: NumericI, y: NumericI, z: NumericI, w: NumericI): Lit<"ivec4">; +export function ivec4(...xs: any[]): Lit<"ivec4"> { + return $gvec4("ivec4", $ivec, xs); +} + +export function uvec2(): Lit<"uvec2">; +export function uvec2(x: NumericU): Lit<"uvec2">; +// prettier-ignore +export function uvec2(x: NumericU, y: NumericU): Lit<"uvec2">; +// prettier-ignore +export function uvec2(...xs: any[]): Lit<"uvec2"> { + return $gvec2("uvec2", $uvec, xs); +} + +export function uvec3(): Lit<"uvec3">; +export function uvec3(x: NumericU): Lit<"uvec3">; +export function uvec3(x: Vec2Term, y: NumericU): Lit<"uvec3">; +// prettier-ignore +export function uvec3(x: NumericU, y: NumericU, z: NumericU): Lit<"uvec3">; +export function uvec3(...xs: any[]): Lit<"uvec3"> { + return $gvec3("uvec3", $uvec, xs); +} + +export function uvec4(): Lit<"uvec4">; +export function uvec4(x: NumericU): Lit<"uvec4">; +export function uvec4(x: Vec3Term, y: NumericU): Lit<"uvec4">; +export function uvec4(x: Vec2Term, y: Vec2Term): Lit<"uvec4">; +// prettier-ignore +export function uvec4(x: Vec2Term, y: NumericU, z: NumericU): Lit<"uvec4">; +// prettier-ignore +export function uvec4(x: NumericU, y: NumericU, z: NumericU, w: NumericU): Lit<"uvec4">; +export function uvec4(...xs: any[]): Lit<"uvec4"> { + return $gvec4("uvec4", $uvec, xs); +} + +export function bvec2(): Lit<"bvec2">; +export function bvec2(x: NumericB): Lit<"bvec2">; +// prettier-ignore +export function bvec2(x: NumericB, y: NumericB): Lit<"bvec2">; +// prettier-ignore +export function bvec2(...xs: any[]): Lit<"bvec2"> { + return $gvec2("bvec2", $bvec, xs); +} + +export function bvec3(): Lit<"bvec3">; +export function bvec3(x: NumericB): Lit<"bvec3">; +export function bvec3(x: Vec2Term, y: NumericB): Lit<"bvec3">; +// prettier-ignore +export function bvec3(x: NumericB, y: NumericB, z: NumericB): Lit<"bvec3">; +export function bvec3(...xs: any[]): Lit<"bvec3"> { + return $gvec3("bvec3", $bvec, xs); +} + +export function bvec4(): Lit<"bvec4">; +export function bvec4(x: NumericB): Lit<"bvec4">; +export function bvec4(x: Vec3Term, y: NumericB): Lit<"bvec4">; +export function bvec4(x: Vec2Term, y: Vec2Term): Lit<"bvec4">; +// prettier-ignore +export function bvec4(x: Vec2Term, y: NumericB, z: NumericB): Lit<"bvec4">; +// prettier-ignore +export function bvec4(x: NumericB, y: NumericB, z: NumericB, w: NumericB): Lit<"bvec4">; +export function bvec4(...xs: any[]): Lit<"bvec4"> { + return $gvec4("bvec4", $bvec, xs); +} + +export function mat2(): Lit<"mat2">; +export function mat2(x: NumericF): Lit<"mat2">; +export function mat2(x: Vec2Term, y: Vec2Term): Lit<"mat2">; +// prettier-ignore +export function mat2(a: NumericF, b: NumericF, c: NumericF, d: NumericF): Lit<"mat2">; +export function mat2(...xs: any[]): Lit<"mat2"> { + return $gmat("mat2", ["n", "n", "vv"], xs); +} + +export function mat3(): Lit<"mat3">; +export function mat3(x: NumericF): Lit<"mat3">; +// prettier-ignore +export function mat3(x: Vec3Term, y: Vec3Term, z: Vec3Term): Lit<"mat3">; +// prettier-ignore +export function mat3(a: NumericF, b: NumericF, c: NumericF, d: NumericF, e: NumericF, f: NumericF, g: NumericF, h: NumericF, i: NumericF): Lit<"mat3">; +export function mat3(...xs: any[]): Lit<"mat3"> { + return $gmat("mat3", ["n", "n", , "vvv"], xs); +} + +export function mat4(): Lit<"mat4">; +export function mat4(x: NumericF): Lit<"mat4">; +// prettier-ignore +export function mat4(x: Vec4Term, y: Vec4Term, z: Vec4Term, w: Vec4Term): Lit<"mat4">; +// prettier-ignore +export function mat4(a: NumericF, b: NumericF, c: NumericF, d: NumericF, e: NumericF, f: NumericF, g: NumericF, h: NumericF, i: NumericF, j: NumericF, k: NumericF, l: NumericF, m: NumericF, n: NumericF, o: NumericF, p: NumericF): Lit<"mat4">; +export function mat4(...xs: any[]): Lit<"mat4"> { + return $gmat("mat4", ["n", "n", , , "vvvv"], xs); +} diff --git a/packages/shader-ast/src/ast/ops.ts b/packages/shader-ast/src/ast/ops.ts new file mode 100644 index 0000000000..74a5bc5ff7 --- /dev/null +++ b/packages/shader-ast/src/ast/ops.ts @@ -0,0 +1,318 @@ +import { IObjectOf } from "@thi.ng/api"; +import { isNumber } from "@thi.ng/checks"; +import { + Op1, + Op2, + Sym, + Term +} from "../api/nodes"; +import { ComparisonOperator, Operator } from "../api/ops"; +import { + BoolTerm, + FloatTerm, + IntTerm, + Mat2Term, + Mat3Term, + Mat4Term, + UintTerm, + Vec2Term, + Vec3Term, + Vec4Term +} from "../api/terms"; +import { + Comparable, + Int, + IVec, + Mat, + Prim, + Type, + UVec, + Vec +} from "../api/types"; +import { isMat, isVec } from "./checks"; +import { numberWithMatchingType } from "./item"; +import { float } from "./lit"; + +export const op1 = ( + op: Operator, + val: Term, + post = false +): Op1 => ({ + tag: "op1", + type: val.type, + op, + val, + post +}); + +const OP_INFO: IObjectOf = { + mave: "mv", + vema: "vm", + vefl: "vn", + mafl: "vn", + flve: "nv", + flma: "nv", + ivin: "vn", + iniv: "nv", + uvui: "vn", + uiuv: "nv" +}; + +export const op2 = ( + op: Operator, + _l: Term | number, + _r: Term | number, + rtype?: Type, + info?: string +): Op2 => { + const nl = isNumber(_l); + const nr = isNumber(_r); + let type: Type; + let l: Term; + let r: Term; + if (nl) { + if (nr) { + // (number, number) + l = float(_l); + r = float(_r); + type = "float"; + } else { + // (number, term) + r = >_r; + l = numberWithMatchingType(r, _l); + type = r.type; + } + } else if (nr) { + // (term, number) + l = >_l; + r = numberWithMatchingType(l, _r); + type = l.type; + } else { + // (term, term) + l = >_l; + r = >_r; + type = + rtype || + (isVec(l) + ? l.type + : isVec(r) + ? r.type + : isMat(r) + ? r.type + : l.type); + } + return { + tag: "op2", + type: rtype || type!, + info: info || OP_INFO[l!.type.substr(0, 2) + r!.type.substr(0, 2)], + op, + l: l!, + r: r! + }; +}; + +export const inc = (t: Sym): Op1 => + op1("++", t, true); + +export const dec = (t: Sym): Op1 => + op1("--", t, true); + +// prettier-ignore +export function add(l: Term, r: Term): Op2; +export function add(l: number, r: Term): Op2; +export function add(l: Term, r: number): Op2; +// prettier-ignore +export function add(l: FloatTerm | number, r: Term): Op2; +// prettier-ignore +export function add(l: Term, r: FloatTerm | number): Op2; +// prettier-ignore +export function add(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function add(l: Term, r: IntTerm | number): Op2; +// prettier-ignore +export function add(l: Term | number, r: Term | number): Op2 { + return op2("+", l, r); +} + +// prettier-ignore +export function sub(l: Term, r: Term): Op2; +export function sub(l: number, r: Term): Op2; +export function sub(l: Term, r: number): Op2; +// prettier-ignore +export function sub(l: FloatTerm | number, r: Term): Op2; +// prettier-ignore +export function sub(l: Term, r: FloatTerm | number): Op2; +// prettier-ignore +export function sub(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function sub(l: Term, r: IntTerm| number): Op2; +export function sub(l: Term | number, r: Term | number): Op2 { + return op2("-", l, r); +} + +// prettier-ignore +export function mul(l: Term, r: Term): Op2; +export function mul(l: number, r: Term): Op2; +export function mul(l: Term, r: number): Op2; +// prettier-ignore +export function mul(l: FloatTerm | number, r: Term): Op2; +// prettier-ignore +export function mul(l: Term, r: FloatTerm | number): Op2; +// prettier-ignore +export function mul(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function mul(l: Term, r: IntTerm | number): Op2; +export function mul(l: Mat2Term, r: Vec2Term): Op2<"vec2">; +export function mul(l: Mat3Term, r: Vec3Term): Op2<"vec3">; +export function mul(l: Mat4Term, r: Vec4Term): Op2<"vec4">; +export function mul(l: Vec2Term, r: Mat2Term): Op2<"vec2">; +export function mul(l: Vec3Term, r: Mat3Term): Op2<"vec3">; +export function mul(l: Vec4Term, r: Mat4Term): Op2<"vec4">; +export function mul(l: Term | number, r: Term | number): Op2 { + return op2( + "*", + l, + r, + !isNumber(l) && !isNumber(r) && isMat(l) && isVec(r) + ? r.type + : undefined + ); +} + +// prettier-ignore +export function div(l: Term, r: Term): Op2; +export function div(l: number, r: Term): Op2; +export function div(l: Term, r: number): Op2; +// prettier-ignore +export function div(l: FloatTerm | number, r: Term): Op2; +// prettier-ignore +export function div(l: Term, r: FloatTerm | number): Op2; +// prettier-ignore +export function div(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function div(l: Term, r: IntTerm | number): Op2; +export function div(l: Term | number, r: Term | number): Op2 { + return op2("/", l, r); +} + +/** + * Integer % (modulo) operator + * + * @param l + * @param b + */ +// prettier-ignore +export function modi(l: Term, r: Term): Op2; +// prettier-ignore +export function modi(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function modi(l: Term, r: IntTerm | number): Op2; +// prettier-ignore +export function modi(l: UintTerm | number, r: Term): Op2; +// prettier-ignore +export function modi(l: Term, r: UintTerm | number): Op2; +export function modi(l: Term | number, r: Term | number): Op2 { + return op2( + "%", + isNumber(l) ? numberWithMatchingType(>r, l) : l, + isNumber(r) ? numberWithMatchingType(>l, r) : r + ); +} + +export const neg = (val: Term) => + op1("-", val); + +/** + * Multiply-add: a * b + c. All must be of same type. + * + * @param a + * @param b + * @param c + */ +export const madd = < + A extends Prim | IVec | UVec | "int" | "uint", + B extends A, + C extends B +>( + a: Term, + b: Term, + c: Term +): Term => add(mul(>a, b), c); + +/** + * Add-multiply: (a + b) * c. All must be of same type. + * + * @param a + * @param b + * @param c + */ +export const addm = ( + a: Term, + b: Term, + c: Term +): Term => mul(add(>a, b), c); + +export const not = (val: BoolTerm) => op1("!", val); +export const or = (a: BoolTerm, b: BoolTerm) => op2("||", a, b); +export const and = (a: BoolTerm, b: BoolTerm) => op2("&&", a, b); + +const cmp = (op: ComparisonOperator) => ( + a: Term, + b: Term +): BoolTerm => op2(op, a, b, "bool"); + +export const eq = cmp("=="); +export const neq = cmp("!="); +export const lt = cmp("<"); +export const lte = cmp("<="); +export const gt = cmp(">"); +export const gte = cmp(">="); + +export const bitnot = | Term>( + val: T +) => op1("~", val); + +// prettier-ignore +export function bitand(l: Term, r: Term): Term; +// prettier-ignore +export function bitand(l: Term, r: IntTerm | number): Term; +// prettier-ignore +export function bitand(l: IntTerm | number, r: Term): Term; +// prettier-ignore +export function bitand(l: Term, r: UintTerm | number): Term; +// prettier-ignore +export function bitand(l: UintTerm | number, r: Term): Term; +// prettier-ignore +export function bitand(l: Term | number, r: Term | number): Op2 { + return op2("&", l, r, undefined); +} + +// prettier-ignore +export function bitor(l: Term, r: Term): Term; +// prettier-ignore +export function bitor(l: Term, r: IntTerm | number): Term; +// prettier-ignore +export function bitor(l: IntTerm | number, r: Term): Term; +// prettier-ignore +export function bitor(l: Term, r: UintTerm | number): Term; +// prettier-ignore +export function bitor(l: UintTerm | number, r: Term): Term; +// prettier-ignore +export function bitor(l: Term | number, r: Term | number): Op2 { + return op2("|", l, r, undefined); +} + +// prettier-ignore +export function bitxor(l: Term, r: Term): Term; +// prettier-ignore +export function bitxor(l: Term, r: IntTerm | number): Term; +// prettier-ignore +export function bitxor(l: IntTerm | number, r: Term): Term; +// prettier-ignore +export function bitxor(l: Term, r: UintTerm | number): Term; +// prettier-ignore +export function bitxor(l: UintTerm | number, r: Term): Term; +// prettier-ignore +export function bitxor(l: Term | number, r: Term | number): Op2 { + return op2("^", l, r, undefined); +} diff --git a/packages/shader-ast/src/ast/scope.ts b/packages/shader-ast/src/ast/scope.ts new file mode 100644 index 0000000000..adeda40c35 --- /dev/null +++ b/packages/shader-ast/src/ast/scope.ts @@ -0,0 +1,173 @@ +import { Fn, Fn2 } from "@thi.ng/api"; +import { isArray } from "@thi.ng/checks"; +import { DGraph } from "@thi.ng/dgraph"; +import { + Assign, + Branch, + Decl, + FnCall, + Func, + FuncReturn, + Lit, + Op1, + Op2, + Scope, + Sym, + Term, + Ternary +} from "../api/nodes"; +import { Type } from "../api/types"; +import { isMat, isVec } from "./checks"; + +/** + * Helper function for `walk()`. Returns child nodes for any control + * flow nodes containing a child scope. + * + * @see allChildren + */ +export const scopedChildren = (t: Term) => + t.tag === "fn" || t.tag === "for" || t.tag == "while" + ? (>t).scope.body + : t.tag === "if" + ? (t).f + ? (t).t.body.concat((t).f!.body) + : (t).t.body + : undefined; + +/** + * Helper function for `walk()`. Returns an array of all child nodes for + * a given term (if any). + * + * @see scopedChildren + */ +export const allChildren = (t: Term) => + scopedChildren(t) || + (t.tag === "scope" + ? (t).body + : t.tag === "ternary" + ? [(>t).t, (>t).f] + : t.tag === "ret" + ? [(>t).val] + : t.tag === "call" || t.tag === "call_i" + ? (>t).args + : t.tag === "sym" && (>t).init + ? [(>t).init] + : t.tag === "decl" + ? [(>t).id] + : t.tag === "op1" || t.tag === "swizzle" + ? [(>t).val] + : t.tag === "op2" + ? [(>t).l, (>t).r] + : t.tag === "assign" + ? [(>t).r] + : isVec(t) || isMat(t) + ? (>t).val + : undefined); + +/** + * Traverses given AST in depth-first order and applies `visit` and + * `children` fns to each node. Descends only further if `children` + * returns an array of child nodes. The `visit` function must accept 2 + * args: the accumulator (`acc`) given to `walk` and a tree node. The + * return value of `visit` becomes the new `acc` value, much like in a + * reduce operation. `walk` itself returns the final `acc`. + * + * If `pre` is true (default), the `visit` function will be called prior + * to visiting a node's children. If false, the visitor is called on the + * way back up. + * + * @param visit + * @param children + * @param acc + * @param tree + * @param pre + */ +export const walk = ( + visit: Fn2, T>, + children: Fn, Term[] | undefined>, + acc: T, + tree: Term | Term[], + pre = true +) => { + if (isArray(tree)) { + tree.forEach((x) => (acc = walk(visit, children, acc, x, pre))); + } else { + pre && (acc = visit(acc, tree)); + const c = children(tree); + c && (acc = walk(visit, children, acc, c, pre)); + !pre && (acc = visit(acc, tree)); + } + return acc; +}; + +/** + * Builds dependency graph of given function, by recursively adding all + * function dependencies. Returns graph. + * + * @param fn + * @param graph + */ +export const buildCallGraph = ( + fn: Func, + graph: DGraph> = new DGraph() +): DGraph> => + fn.deps && fn.deps.length + ? fn.deps.reduce( + (graph, d) => buildCallGraph(d, graph.addDependency(fn, d)), + graph + ) + : graph.addNode(fn); + +export const decl = (id: Sym): Decl => ({ + tag: "decl", + type: id.type, + id +}); + +/** + * Wraps the given AST node array in `scope` node, optionally as global + * scope (default false). The interpretation of the global flag is + * dependent on the target code gen. I.e. for GLSL / JS, the flag + * disables wrapping the scope's body in `{}`, but else has no + * difference. In general this node type only serves as internal + * mechanism for various control flow AST nodes and should not need to + * be used directly from user land code (though might be useful to + * create custom / higher level control flow nodes). + * + * @param body + * @param global + */ +export const scope = (body: (Term | null)[], global = false): Scope => ({ + tag: "scope", + type: "void", + body: []>( + body + .filter((x) => x != null) + .map((x) => (x!.tag === "sym" ? decl(>x) : x)) + ), + global +}); + +/** + * Takes an array of global sym/var definitions (`input()`, `output()`, + * `uniform()`) and functions defined via `defn()`. Constructs the call + * graph of all transitively used functions and bundles everything in + * topological order within a global scope object, which is then + * returned to the user and can be passed to a target codegen for full + * program output. + * + * @see scope + * @see input + * @see output + * @see uniform + * + * @param body + */ +export const program = (body: (Sym | Func)[]) => { + const syms = body.filter((x) => x.tag !== "fn"); + const g = body.reduce( + (acc, x) => (x.tag === "fn" ? buildCallGraph(>x, acc) : acc), + new DGraph>() + ); + return scope(syms.concat(g.sort()), true); +}; diff --git a/packages/shader-ast/src/ast/swizzle.ts b/packages/shader-ast/src/ast/swizzle.ts new file mode 100644 index 0000000000..8ad57669e6 --- /dev/null +++ b/packages/shader-ast/src/ast/swizzle.ts @@ -0,0 +1,126 @@ +import { Select4 } from "@thi.ng/api"; +import { Swizzle, Term } from "../api/nodes"; +import { + Swizzle2, + Swizzle2_1, + Swizzle2_2, + Swizzle2_3, + Swizzle3, + Swizzle3_1, + Swizzle3_2, + Swizzle3_3, + Swizzle4, + Swizzle4_1, + Swizzle4_2, + Swizzle4_3 +} from "../api/swizzles"; +import { + BVec2Term, + BVec3Term, + BVec4Term, + IVec2Term, + IVec3Term, + IVec4Term, + UVec2Term, + UVec3Term, + UVec4Term, + Vec2Term, + Vec3Term, + Vec4Term +} from "../api/terms"; +import { + BVec, + IVec, + Type, + UVec, + Vec +} from "../api/types"; + +// prettier-ignore +export function $(a: Vec2Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: Vec3Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: Vec4Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: IVec2Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: IVec3Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: IVec4Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: UVec2Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: UVec3Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: UVec4Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: BVec2Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: BVec3Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: BVec4Term, id: T): Swizzle>; +export function $(val: Term, id: string): Swizzle { + const type = val.type[0]; + const rtype = (a: Type, b: string) => + id.length === 1 ? a : (b + id.length); + return { + tag: "swizzle", + type: + type === "i" + ? rtype("int", "ivec") + : type === "u" + ? rtype("uint", "uvec") + : type === "b" + ? rtype("bool", "bvec") + : rtype("float", "vec"), + val, + id + }; +} + +export const $x = ( + val: Term +): Swizzle> => + $(val, "x"); + +export const $y = ( + val: Term +): Swizzle> => + $(val, "y"); + +export const $z = < + T extends + | "vec3" + | "vec4" + | "ivec3" + | "ivec4" + | "uvec3" + | "uvec4" + | "bvec3" + | "bvec4" +>( + val: Term +): Swizzle> => + $(val, "z"); + +export const $w = ( + val: Term +): Swizzle> => + $(val, "w"); + +export function $xy(val: Term): Swizzle<"vec2">; +export function $xy(val: Term): Swizzle<"ivec2">; +export function $xy(val: Term): Swizzle<"uvec2">; +export function $xy(val: Term): Swizzle<"bvec2">; +export function $xy(val: any): Swizzle { + return $(val, "xy"); +} + +export function $xyz(val: Term<"vec3" | "vec4">): Swizzle<"vec3">; +export function $xyz(val: Term<"ivec3" | "ivec4">): Swizzle<"ivec3">; +export function $xyz(val: Term<"uvec3" | "uvec4">): Swizzle<"uvec3">; +export function $xyz(val: Term<"bvec3" | "bvec4">): Swizzle<"bvec3">; +export function $xyz(val: any): Swizzle { + return $(val, "xyz"); +} diff --git a/packages/shader-ast/src/ast/sym.ts b/packages/shader-ast/src/ast/sym.ts new file mode 100644 index 0000000000..d4d5c3392d --- /dev/null +++ b/packages/shader-ast/src/ast/sym.ts @@ -0,0 +1,114 @@ +import { assert } from "@thi.ng/api"; +import { isString } from "@thi.ng/checks"; +import { illegalArgs } from "@thi.ng/errors"; +import { Lit, Sym, Term } from "../api/nodes"; +import { SymOpts } from "../api/syms"; +import { ArrayTypeMap, Type } from "../api/types"; +import { gensym } from "./idgen"; + +export function sym(init: Term): Sym; +export function sym(type: T): Sym; +export function sym(type: T, opts: SymOpts): Sym; +export function sym(type: T, init: Term): Sym; +export function sym(type: T, id: string): Sym; +// prettier-ignore +export function sym(type: T, id: string, opts: SymOpts): Sym; +// prettier-ignore +export function sym(type: T, opts: SymOpts, init: Term): Sym; +// prettier-ignore +export function sym(type: T, id: string, opts: SymOpts, init: Term): Sym; +export function sym(type: any, ...xs: any[]): Sym { + let id: string; + let opts: SymOpts; + let init: Term; + switch (xs.length) { + case 0: + if (!isString(type)) { + init = type; + type = init.type; + } + break; + case 1: + if (isString(xs[0])) { + id = xs[0]; + } else if (xs[0].tag) { + init = xs[0]; + } else { + opts = xs[0]; + } + break; + case 2: + if (isString(xs[0])) { + [id, opts] = xs; + } else { + [opts, init] = xs; + } + break; + case 3: + [id, opts, init] = xs; + break; + default: + illegalArgs(); + } + return { + tag: "sym", + type, + id: id! || gensym(), + opts: opts! || {}, + init: init! + }; +} + +export const constSym = ( + type: T, + id?: string, + opts?: SymOpts, + init?: Term +) => sym(type, id || gensym(), { const: true, ...opts }, init!); + +/** + * Defines a new symbol with optional initial array values. + * + * Important: Array initializers are UNSUPPORTED in GLSL ES v1 (WebGL), + * any code using such initializers will only work under WebGL2 or other + * targets. + */ +export const arraySym = ( + type: T, + id?: string, + opts: SymOpts = {}, + init?: (Lit | Sym)[] +): Sym => { + if (init && opts.num == null) { + opts.num = init.length; + } + assert(opts.num != null, "missing array length"); + init && + assert( + opts.num === init.length, + `expected ${opts.num} items in array, but got ${init.length}` + ); + const atype = (type + "[]"); + return { + tag: "sym", + type: atype, + id: id || gensym(), + opts, + init: init + ? { + tag: "array_init", + type: atype, + init + } + : undefined + }; +}; + +export const input = (type: T, id: string, opts?: SymOpts) => + sym(type, id, { q: "in", type: "in", ...opts }); + +export const output = (type: T, id: string, opts?: SymOpts) => + sym(type, id, { q: "out", type: "out", ...opts }); + +export const uniform = (type: T, id: string, opts?: SymOpts) => + sym(type, id, { q: "in", type: "uni", ...opts }); diff --git a/packages/shader-ast/src/builtin/bvec.ts b/packages/shader-ast/src/builtin/bvec.ts new file mode 100644 index 0000000000..9181627c6f --- /dev/null +++ b/packages/shader-ast/src/builtin/bvec.ts @@ -0,0 +1,77 @@ +import { FnCall, Term } from "../api/nodes"; +import { BVec } from "../api/types"; +import { builtinCall } from "../ast/function"; + +const $bvec = (t: string) => ("bvec" + t[t.length - 1]); + +const $call = (fn: string, a: Term, b: Term) => + builtinCall(fn, $bvec(a.type), a, b); + +// prettier-ignore +export function lessThan(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function lessThan(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function lessThan(a: Term, b: Term): FnCall<"bvec4">; +export function lessThan(a: Term, b: Term) { + return $call("lessThan", a, b); +} + +// prettier-ignore +export function lessThanEqual(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function lessThanEqual(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function lessThanEqual(a: Term, b: Term): FnCall<"bvec4">; +export function lessThanEqual(a: Term, b: Term) { + return $call("lessThanEqual", a, b); +} + +// prettier-ignore +export function greaterThan(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function greaterThan(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function greaterThan(a: Term, b: Term): FnCall<"bvec4">; +export function greaterThan(a: Term, b: Term) { + return $call("greaterThan", a, b); +} + +// prettier-ignore +export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec4">; +export function greaterThanEqual(a: Term, b: Term) { + return $call("greaterThanEqual", a, b); +} + +// prettier-ignore +export function equal(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function equal(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function equal(a: Term, b: Term): FnCall<"bvec4">; +export function equal(a: Term, b: Term) { + return $call("equal", a, b); +} + +// prettier-ignore +export function notEqual(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function notEqual(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function notEqual(a: Term, b: Term): FnCall<"bvec4">; +export function notEqual(a: Term, b: Term) { + return $call("notEqual", a, b); +} + +export const _any = (v: Term): FnCall<"bool"> => + builtinCall("any", "bool", v); + +export const all = (v: Term): FnCall<"bool"> => + builtinCall("all", "bool", v); + +export const _not = (v: Term) => + builtinCall("not", v.type, v); diff --git a/packages/shader-ast/src/builtin/math.ts b/packages/shader-ast/src/builtin/math.ts new file mode 100644 index 0000000000..48d4d6c956 --- /dev/null +++ b/packages/shader-ast/src/builtin/math.ts @@ -0,0 +1,193 @@ +import { FnCall, Sym, Term } from "../api/nodes"; +import { + BoolTerm, + BVec2Term, + BVec3Term, + BVec4Term, + FloatTerm, + IntTerm, + IVec2Term, + IVec3Term, + IVec4Term, + UintTerm, + UVec2Term, + UVec3Term, + UVec4Term, + Vec2Term, + Vec3Term, + Vec4Term +} from "../api/terms"; +import { Mat, Prim, Vec } from "../api/types"; +import { builtinCall } from "../ast/function"; +import { matchingBoolType, matchingPrimFor } from "../ast/item"; + +const primOp1 = (name: string) => (a: Term) => + builtinCall(name, a.type, a); + +const primOp2 = (name: string) => ( + a: Term, + b: Term +) => builtinCall(name, a.type, a, b); + +const primOp3 = (name: string) => ( + a: Term, + b: Term, + c: Term +) => builtinCall(name, a.type, a, b, c); + +/** + * Returns normalized version of given vector. + * + * @param v + */ +export const normalize = (v: Term) => + builtinCall("normalize", v.type, v); + +/** + * Returns length / magnitude of given vector. + * + * @param v + */ +export const length = (v: Term) => + builtinCall("length", "float", v); + +export const distance = (a: Term, b: Term) => + builtinCall("distance", "float", a, b); + +/** + * Returns dot product of given vectors. + * + * @param a + * @param b + */ +export const dot = (a: Term, b: Term) => + builtinCall("dot", "float", a, b); + +/** + * Returns cross product of given 3D vectors. + * + * @param a + * @param b + */ +export const cross = (a: Vec3Term, b: Vec3Term) => + builtinCall("cross", a.type, a, b); + +export const reflect = (i: Term, n: Term) => + builtinCall("reflect", i.type, i, n); + +export const refract = ( + i: Term, + n: Term, + ior: FloatTerm +) => builtinCall("refract", i.type, i, n, ior); + +export const faceForward = ( + i: Term, + n: Term, + nref: Term +) => builtinCall("faceForward", i.type, i, n, nref); + +export const min = primOp2("min"); +export const max = primOp2("max"); +export const clamp = primOp3("clamp"); + +export const step = primOp2("step"); +export const smoothstep = primOp3("smoothstep"); + +export const radians = primOp1("radians"); +export const degrees = primOp1("degrees"); + +export const cos = primOp1("cos"); +export const sin = primOp1("sin"); +export const tan = primOp1("tan"); +export const acos = primOp1("acos"); +export const asin = primOp1("asin"); + +export function atan(a: Term): FnCall; +// prettier-ignore +export function atan(a: Term, b: Term): FnCall; +export function atan(a: Term, b?: Term) { + const f = b + ? builtinCall("atan", a.type, a, b) + : builtinCall("atan", a.type, a); + b && (f.info = "nn"); + return f; +} + +export const pow = primOp2("pow"); +export const exp = primOp1("exp"); +export const log = primOp1("log"); +export const exp2 = primOp1("exp2"); +export const log2 = primOp1("log2"); +export const sqrt = primOp1("sqrt"); +export const inversesqrt = primOp1("inversesqrt"); + +export const abs = primOp1("abs"); +export const sign = primOp1("sign"); +export const floor = primOp1("floor"); +export const ceil = primOp1("ceil"); +export const fract = primOp1("fract"); + +export const powf = (x: Term, y: FloatTerm) => + pow(x, matchingPrimFor(x, y)); + +// prettier-ignore +export function mod(a: Term, b: Term): FnCall; +export function mod(a: Term, b: FloatTerm): FnCall; +export function mod(a: Term, b: Term): FnCall { + const f = builtinCall("mod", a.type, a, b); + b.type === "float" && (f.info = "n"); + return f; +} + +/** + * Important: Currently unsupported by JS target. + * + * TODO add additional metadata for JS (`b` is an output var), issue #96 + * + * @param a + * @param b + */ +// prettier-ignore +export const modf = (a: Term, b: Sym): FnCall => + builtinCall("modf", a.type, a, b); + +// prettier-ignore +export function mix(a: Term, b: Term, c: Term): FnCall; +// prettier-ignore +export function mix(a: Term, b: Term, c: FloatTerm): FnCall; +export function mix(a: Term, b: Term, c: Term): FnCall { + const f = builtinCall("mix", a.type, a, b, c); + c.type === "float" && (f.info = "n"); + return f; +} + +// prettier-ignore +export const matrixCompMult = (a: Term, b: Term) => + builtinCall("matrixCompMult", a.type, a, b); + +/** + * Important: Not available in JS / GLSL ES 100 + * + * @param a + */ +export function isnan(a: FloatTerm | IntTerm | UintTerm): BoolTerm; +export function isnan(a: Vec2Term | IVec2Term | UVec2Term): BVec2Term; +export function isnan(a: Vec3Term | IVec3Term | UVec3Term): BVec3Term; +export function isnan(a: Vec4Term | IVec4Term | UVec4Term): BVec4Term; +export function isnan(a: any): FnCall { + return builtinCall("isnan", matchingBoolType(a), a); +} + +/** + * Important: Not available in JS / GLSL ES 100 + * + * @param a + */ +export function isinf(a: FloatTerm | IntTerm | UintTerm): BoolTerm; +export function isinf(a: Vec2Term | IVec2Term | UVec2Term): BVec2Term; +export function isinf(a: Vec3Term | IVec3Term | UVec3Term): BVec3Term; +export function isinf(a: Vec4Term | IVec4Term | UVec4Term): BVec4Term; +export function isinf(a: any): FnCall { + return builtinCall("isinf", matchingBoolType(a), a); +} diff --git a/packages/shader-ast/src/builtins.ts b/packages/shader-ast/src/builtin/texture.ts similarity index 58% rename from packages/shader-ast/src/builtins.ts rename to packages/shader-ast/src/builtin/texture.ts index 8146bbf511..70d0dce8f4 100644 --- a/packages/shader-ast/src/builtins.ts +++ b/packages/shader-ast/src/builtin/texture.ts @@ -1,240 +1,32 @@ import { illegalArgs } from "@thi.ng/errors"; +import { FnCall, Sym, Term } from "../api/nodes"; import { - BVec, FloatTerm, - FnCall, IntTerm, ISampler2DTerm, ISampler3DTerm, ISamplerCubeTerm, - IVec, IVec2Term, IVec3Term, - Mat, - Prim, - Sampler, Sampler2DTerm, Sampler3DTerm, SamplerCubeTerm, - Sym, - Term, USampler2DTerm, USampler3DTerm, USamplerCubeTerm, - Vec, Vec2Term, Vec3Term, Vec4Term -} from "./api"; +} from "../api/terms"; import { - builtinCall, - FLOAT0, - INT0, - isVec, - matchingPrimFor -} from "./ast"; - -const primOp1 = (name: string) => (a: Term) => - builtinCall(name, a.type, a); - -const primOp2 = (name: string) => ( - a: Term, - b: Term -) => builtinCall(name, a.type, a, b); - -const primOp3 = (name: string) => ( - a: Term, - b: Term, - c: Term -) => builtinCall(name, a.type, a, b, c); - -const $bvec = (t: string) => ("bvec" + t[t.length - 1]); - -/** - * Returns normalized version of given vector. - * - * @param v - */ -export const normalize = (v: Term) => - builtinCall("normalize", v.type, v); - -/** - * Returns length / magnitude of given vector. - * - * @param v - */ -export const length = (v: Term) => - builtinCall("length", "float", v); - -export const distance = (a: Term, b: Term) => - builtinCall("distance", "float", a, b); - -/** - * Returns dot product of given vectors. - * - * @param a - * @param b - */ -export const dot = (a: Term, b: Term) => - builtinCall("dot", "float", a, b); - -/** - * Returns cross product of given 3D vectors. - * - * @param a - * @param b - */ -export const cross = (a: Vec3Term, b: Vec3Term) => - builtinCall("cross", a.type, a, b); - -export const reflect = (i: Term, n: Term) => - builtinCall("reflect", i.type, i, n); - -export const refract = ( - i: Term, - n: Term, - ior: FloatTerm -) => builtinCall("refract", i.type, i, n, ior); - -export const faceForward = ( - i: Term, - n: Term, - nref: Term -) => builtinCall("faceForward", i.type, i, n, nref); - -export const min = primOp2("min"); -export const max = primOp2("max"); -export const clamp = primOp3("clamp"); - -export const step = primOp2("step"); -export const smoothstep = primOp3("smoothstep"); - -export const radians = primOp1("radians"); -export const degrees = primOp1("degrees"); - -export const cos = primOp1("cos"); -export const sin = primOp1("sin"); -export const tan = primOp1("tan"); -export const acos = primOp1("acos"); -export const asin = primOp1("asin"); - -export function atan(a: Term): FnCall; -// prettier-ignore -export function atan(a: Term, b: Term): FnCall; -export function atan(a: Term, b?: Term) { - return b - ? builtinCall("atan", a.type, a, b) - : builtinCall("atan", a.type, a); -} - -export const pow = primOp2("pow"); -export const exp = primOp1("exp"); -export const log = primOp1("log"); -export const exp2 = primOp1("exp2"); -export const log2 = primOp1("log2"); -export const sqrt = primOp1("sqrt"); -export const inversesqrt = primOp1("inversesqrt"); - -export const abs = primOp1("abs"); -export const sign = primOp1("sign"); -export const floor = primOp1("floor"); -export const ceil = primOp1("ceil"); -export const fract = primOp1("fract"); - -export const powf = (x: Term, y: FloatTerm) => - pow(x, matchingPrimFor(x, y)); - -// prettier-ignore -export function mod(a: Term, b: Term): FnCall; -export function mod(a: Term, b: FloatTerm): FnCall; -export function mod(a: Term, b: Term): FnCall { - const f = builtinCall("mod", a.type, a, b); - b.type === "float" && (f.info = "n"); - return f; -} - -// prettier-ignore -export function mix(a: Term, b: Term, c: Term): FnCall; -// prettier-ignore -export function mix(a: Term, b: Term, c: FloatTerm): FnCall; -export function mix(a: Term, b: Term, c: Term): FnCall { - const f = builtinCall("mix", a.type, a, b, c); - c.type === "float" && (f.info = "n"); - return f; -} - -// prettier-ignore -export const matrixCompMult = (a: Term, b: Term) => - builtinCall("matrixCompMult", a.type, a, b); - -// prettier-ignore -export function lessThan(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function lessThan(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function lessThan(a: Term, b: Term): FnCall<"bvec4">; -export function lessThan(a: Term, b: Term) { - return builtinCall("lessThan", $bvec(a.type), a, b); -} - -// prettier-ignore -export function lessThanEqual(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function lessThanEqual(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function lessThanEqual(a: Term, b: Term): FnCall<"bvec4">; -export function lessThanEqual(a: Term, b: Term) { - return builtinCall("lessThanEqual", $bvec(a.type), a, b); -} - -// prettier-ignore -export function greaterThan(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function greaterThan(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function greaterThan(a: Term, b: Term): FnCall<"bvec4">; -export function greaterThan(a: Term, b: Term) { - return builtinCall("greaterThan", $bvec(a.type), a, b); -} - -// prettier-ignore -export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec4">; -export function greaterThanEqual(a: Term, b: Term) { - return builtinCall("greaterThanEqual", $bvec(a.type), a, b); -} - -// prettier-ignore -export function equal(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function equal(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function equal(a: Term, b: Term): FnCall<"bvec4">; -export function equal(a: Term, b: Term) { - return builtinCall("equal", $bvec(a.type), a, b); -} - -// prettier-ignore -export function notEqual(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function notEqual(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function notEqual(a: Term, b: Term): FnCall<"bvec4">; -export function notEqual(a: Term, b: Term) { - return builtinCall("notEqual", $bvec(a.type), a, b); -} - -export const _any = (v: Term): FnCall<"bool"> => - builtinCall("any", "bool", v); - -export const all = (v: Term): FnCall<"bool"> => - builtinCall("all", "bool", v); - -export const _not = (v: Term) => - builtinCall("not", v.type, v); + IVec, + Prim, + Sampler, + Vec +} from "../api/types"; +import { isVec } from "../ast/checks"; +import { builtinCall } from "../ast/function"; +import { FLOAT0, INT0 } from "../ast/lit"; const texRetType = (sampler: Term) => { const t = sampler.type[0]; @@ -254,6 +46,23 @@ const texRetType = (sampler: Term) => { : illegalArgs(`unknown sampler type ${sampler.type}`); }; +const $call = ( + name: string, + sampler: Term, + uv: Term, + bias?: FloatTerm +) => { + const f = builtinCall( + name, + texRetType(sampler), + sampler, + uv, + bias || FLOAT0 + ); + !isVec(f) && (f.info = "n"); + return f; +}; + // prettier-ignore export function textureSize(sampler: Sampler2DTerm, lod: IntTerm): FnCall<"ivec2">; // prettier-ignore @@ -298,15 +107,7 @@ export function texture(sampler: Term<"sampler2DShadow">, uvw: Vec3Term, bias?: export function texture(sampler: Term<"samplerCubeShadow">, uvw: Vec4Term, bias?: FloatTerm): FnCall<"float">; // prettier-ignore export function texture(sampler: Term, uv: Term, bias?: FloatTerm): FnCall { - const f = builtinCall( - "texture", - texRetType(sampler), - sampler, - uv, - bias || FLOAT0 - ); - !isVec(f) && (f.info = "n"); - return f; + return $call("texture", sampler, uv, bias); } // prettier-ignore @@ -325,15 +126,7 @@ export function textureProj(sampler: USampler3DTerm, uvw: Vec4Term, bias?: Float export function textureProj(sampler: Term<"sampler2DShadow">, uvw: Vec4Term, bias?: FloatTerm): FnCall<"float">; // prettier-ignore export function textureProj(sampler: Term, uv: Term, bias?: FloatTerm): FnCall { - const f = builtinCall( - "textureProj", - texRetType(sampler), - sampler, - uv, - bias || FLOAT0 - ); - !isVec(f) && (f.info = "n"); - return f; + return $call("textureProj", sampler, uv, bias); } // prettier-ignore @@ -358,15 +151,7 @@ export function textureLod(sampler: USamplerCubeTerm, uvw: Vec3Term, bias?: Floa export function textureLod(sampler: Term<"sampler2DShadow">, uvw: Vec3Term, bias?: FloatTerm): FnCall<"float">; // prettier-ignore export function textureLod(sampler: Term, uv: Term, bias?: FloatTerm): FnCall { - const f = builtinCall( - "textureLod", - texRetType(sampler), - sampler, - uv, - bias || FLOAT0 - ); - !isVec(f) && (f.info = "n"); - return f; + return $call("textureLod", sampler, uv, bias); } // prettier-ignore diff --git a/packages/shader-ast/src/index.ts b/packages/shader-ast/src/index.ts index fd3cf2858b..ae8963e02f 100644 --- a/packages/shader-ast/src/index.ts +++ b/packages/shader-ast/src/index.ts @@ -1,5 +1,29 @@ -export * from "./api"; -export * from "./ast"; -export * from "./builtins"; +export * from "./api/function"; +export * from "./api/nodes"; +export * from "./api/ops"; +export * from "./api/precision"; +export * from "./api/swizzles"; +export * from "./api/syms"; +export * from "./api/tags"; +export * from "./api/target"; +export * from "./api/terms"; +export * from "./api/types"; + +export * from "./ast/assign"; +export * from "./ast/checks"; +export * from "./ast/controlflow"; +export * from "./ast/function"; +export * from "./ast/indexed"; +export * from "./ast/item"; +export * from "./ast/lit"; +export * from "./ast/ops"; +export * from "./ast/scope"; +export * from "./ast/swizzle"; +export * from "./ast/sym"; + +export * from "./builtin/bvec"; +export * from "./builtin/math"; +export * from "./builtin/texture"; + export * from "./optimize"; export * from "./target"; diff --git a/packages/shader-ast/src/optimize.ts b/packages/shader-ast/src/optimize.ts index 4a41c77d93..74a798eda3 100644 --- a/packages/shader-ast/src/optimize.ts +++ b/packages/shader-ast/src/optimize.ts @@ -1,15 +1,15 @@ +import { NO_OP } from "@thi.ng/api"; +import { DEFAULT, defmulti } from "@thi.ng/defmulti"; import { Lit, Op1, Op2, Term -} from "./api"; -import { - allChildren, - isLitNumeric, - lit, - walk -} from "./ast"; +} from "./api/nodes"; +import { Operator } from "./api/ops"; +import { isLitNumeric } from "./ast/checks"; +import { lit } from "./ast/lit"; +import { allChildren, walk } from "./ast/scope"; const replaceNode = (node: any, next: any) => { for (let k in node) { @@ -18,6 +18,44 @@ const replaceNode = (node: any, next: any) => { return Object.assign(node, next); }; +const maybeFoldMath = (op: Operator, l: any, r: any) => + op === "+" + ? l + r + : op === "-" + ? l - r + : op === "*" + ? l * r + : op === "/" + ? l / r + : undefined; + +export const foldNode = defmulti, void>((t) => t.tag); +foldNode.add(DEFAULT, NO_OP); + +foldNode.addAll({ + op1: (t) => { + const op = >t; + if (op.op == "-" && isLitNumeric(op.val)) { + replaceNode(t, >op.val); + (op).val = -(op).val; + } + }, + + op2: (node) => { + const op = >node; + if (isLitNumeric(op.l) && isLitNumeric(op.r)) { + const vl = (>op.l).val; + const vr = (>op.r).val; + let res = maybeFoldMath(op.op, vl, vr); + if (res !== undefined) { + op.type === "int" && (res |= 0); + op.type === "uint" && (res >>>= 0); + replaceNode(node, lit(op.type, res)); + } + } + } +}); + /** * Traverses given AST and applies constant folding optimizations where * possible. Returns possibly updated tree (mutates original). @@ -50,47 +88,6 @@ const replaceNode = (node: any, next: any) => { * @param tree */ export const constantFolding = (tree: Term) => { - walk( - (_, node) => { - switch (node.tag) { - case "op1": { - const n = >node; - if (n.op == "-" && isLitNumeric(n.val)) { - replaceNode(node, >n.val); - (n).val = -(n).val; - } - break; - } - case "op2": { - const n = >node; - if (isLitNumeric(n.l) && isLitNumeric(n.r)) { - const vl = (>n.l).val; - const vr = (>n.r).val; - let res = - n.op === "+" - ? vl + vr - : n.op === "-" - ? vl - vr - : n.op === "*" - ? vl * vr - : n.op === "/" - ? vl / vr - : undefined; - if (res !== undefined) { - n.type === "int" && (res |= 0); - n.type === "uint" && (res >>>= 0); - replaceNode(node, lit(n.type, res)); - } - } - break; - } - default: - } - }, - allChildren, - null, - tree, - false - ); + walk((_, node) => foldNode(node), allChildren, null, tree, false); return tree; }; diff --git a/packages/shader-ast/src/target.ts b/packages/shader-ast/src/target.ts index 018f629ae1..040a350d44 100644 --- a/packages/shader-ast/src/target.ts +++ b/packages/shader-ast/src/target.ts @@ -1,7 +1,8 @@ import { Fn } from "@thi.ng/api"; import { DEFAULT, defmulti } from "@thi.ng/defmulti"; import { unsupported } from "@thi.ng/errors"; -import { TargetImpl, Term } from "./api"; +import { Term } from "./api/nodes"; +import { TargetImpl } from "./api/target"; /** * Takes an object of code generator functions and returns a new code diff --git a/packages/sparse/CHANGELOG.md b/packages/sparse/CHANGELOG.md index a43645faa1..e52f39c7b2 100644 --- a/packages/sparse/CHANGELOG.md +++ b/packages/sparse/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/sparse@0.1.19...@thi.ng/sparse@0.1.20) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/sparse + + + + + ## [0.1.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/sparse@0.1.18...@thi.ng/sparse@0.1.19) (2019-08-16) **Note:** Version bump only for package @thi.ng/sparse diff --git a/packages/sparse/package.json b/packages/sparse/package.json index 417a9682b6..1f14a84452 100644 --- a/packages/sparse/package.json +++ b/packages/sparse/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/sparse", - "version": "0.1.19", + "version": "0.1.20", "description": "Sparse vector & matrix implementations", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/api": "^6.3.3", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "adjacency", diff --git a/packages/strings/CHANGELOG.md b/packages/strings/CHANGELOG.md index b2ccaf8f13..8901cefb2d 100644 --- a/packages/strings/CHANGELOG.md +++ b/packages/strings/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/strings@1.2.2...@thi.ng/strings@1.2.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/strings + + + + + ## [1.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/strings@1.2.1...@thi.ng/strings@1.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/strings diff --git a/packages/strings/package.json b/packages/strings/package.json index c679a6e9a1..23fb2083a1 100644 --- a/packages/strings/package.json +++ b/packages/strings/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/strings", - "version": "1.2.2", + "version": "1.2.3", "description": "Various string formatting & utility functions", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/errors": "^1.1.2", - "@thi.ng/memoize": "^1.1.2" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/memoize": "^1.1.3" }, "keywords": [ "composition", diff --git a/packages/transducers-binary/CHANGELOG.md b/packages/transducers-binary/CHANGELOG.md index f2a965c452..356afda23c 100644 --- a/packages/transducers-binary/CHANGELOG.md +++ b/packages/transducers-binary/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.4.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-binary@0.4.3...@thi.ng/transducers-binary@0.4.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers-binary + + + + + ## [0.4.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-binary@0.4.2...@thi.ng/transducers-binary@0.4.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/transducers-binary diff --git a/packages/transducers-binary/package.json b/packages/transducers-binary/package.json index 92c3b01471..dce4cb06a2 100644 --- a/packages/transducers-binary/package.json +++ b/packages/transducers-binary/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-binary", - "version": "0.4.3", + "version": "0.4.4", "description": "Binary data related transducers & reducers", "module": "./index.js", "main": "./lib/index.js", @@ -33,10 +33,10 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/compose": "^1.3.2", - "@thi.ng/random": "^1.1.10", - "@thi.ng/strings": "^1.2.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/compose": "^1.3.3", + "@thi.ng/random": "^1.1.11", + "@thi.ng/strings": "^1.2.3", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "base64", diff --git a/packages/transducers-binary/src/bytes.ts b/packages/transducers-binary/src/bytes.ts index 65c991498e..6fe9b2b614 100644 --- a/packages/transducers-binary/src/bytes.ts +++ b/packages/transducers-binary/src/bytes.ts @@ -65,11 +65,26 @@ export function bytes(cap = 1024, src?: Iterable) { return buf; }; + const setArray = ( + fn: string, + stride: number, + acc: Uint8Array, + x: any, + le: boolean + ) => { + const n = x.length; + acc = ensure(acc, stride * n); + for (let i = 0; i < n; i++, pos += stride) { + (view)[fn](pos, x[i], le); + } + return acc; + }; + return src ? reduce(bytes(cap), src) : >[ () => new Uint8Array(cap), - (acc) => acc.slice(0, pos), + (acc) => acc.subarray(0, pos), (acc, [type, x, le = false]) => { if (!view || view.buffer !== acc.buffer) { cap = acc.byteLength; @@ -108,79 +123,49 @@ export function bytes(cap = 1024, src?: Iterable) { view.setInt16(pos, x, le); pos += 2; break; - case Type.I16_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 2 * n); - for (let i = 0; i < n; i++, pos += 2) - view.setInt16(pos, x[i], le); + case Type.I16_ARRAY: + acc = setArray("setInt16", 2, acc, x, le); break; - } case Type.U16: acc = ensure(acc, 4); view.setUint16(pos, x, le); pos += 4; break; - case Type.U16_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 2 * n); - for (let i = 0; i < n; i++, pos += 2) - view.setUint16(pos, x[i], le); + case Type.U16_ARRAY: + acc = setArray("setUint16", 2, acc, x, le); break; - } case Type.I32: acc = ensure(acc, 4); view.setInt32(pos, x, le); pos += 4; break; - case Type.I32_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 4 * n); - for (let i = 0; i < n; i++, pos += 4) - view.setInt32(pos, x[i], le); + case Type.I32_ARRAY: + acc = setArray("setInt32", 4, acc, x, le); break; - } case Type.U32: acc = ensure(acc, 4); view.setUint32(pos, x, le); pos += 4; break; - case Type.U32_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 4 * n); - for (let i = 0; i < n; i++, pos += 4) - view.setUint32(pos, x[i], le); + case Type.U32_ARRAY: + acc = setArray("setUint32", 4, acc, x, le); break; - } case Type.F32: acc = ensure(acc, 4); view.setFloat32(pos, x, le); pos += 4; break; - case Type.F32_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 4 * n); - for (let i = 0; i < n; i++, pos += 4) - view.setFloat32(pos, x[i], le); + case Type.F32_ARRAY: + acc = setArray("setFloat32", 4, acc, x, le); break; - } case Type.F64: acc = ensure(acc, 8); view.setFloat64(pos, x, le); pos += 8; break; - case Type.F64_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 8 * n); - for (let i = 0; i < n; i++, pos += 8) - view.setFloat64(pos, x[i], le); + case Type.F64_ARRAY: + acc = setArray("setFloat64", 8, acc, x, le); break; - } case Type.STR: { let utf = [...utf8Encode(x)]; acc = ensure(acc, utf.length); diff --git a/packages/transducers-binary/src/utf8.ts b/packages/transducers-binary/src/utf8.ts index 87f7638272..3fb82d8b72 100644 --- a/packages/transducers-binary/src/utf8.ts +++ b/packages/transducers-binary/src/utf8.ts @@ -201,19 +201,18 @@ export const utf8Length = (str: string) => { if (u >= 0xd800 && u <= 0xdfff) { u = (0x10000 + ((u & 0x3ff) << 10)) | (str.charCodeAt(++i) & 0x3ff); } - if (u <= 0x7f) { - len++; - } else if (u <= 0x7ff) { - len += 2; - } else if (u <= 0xffff) { - len += 3; - } else if (u <= 0x1fffff) { - len += 4; - } else if (u <= 0x3ffffff) { - len += 5; - } else { - len += 6; - } + len += + u <= 0x7f + ? 1 + : u <= 0x7ff + ? 2 + : u <= 0xffff + ? 3 + : u <= 0x1fffff + ? 4 + : u <= 0x3ffffff + ? 5 + : 6; } return len; }; diff --git a/packages/transducers-fsm/CHANGELOG.md b/packages/transducers-fsm/CHANGELOG.md index aaeef1777b..1beb4e3e26 100644 --- a/packages/transducers-fsm/CHANGELOG.md +++ b/packages/transducers-fsm/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@1.1.3...@thi.ng/transducers-fsm@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers-fsm + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@1.1.2...@thi.ng/transducers-fsm@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/transducers-fsm diff --git a/packages/transducers-fsm/package.json b/packages/transducers-fsm/package.json index ca5e1cc698..039188dfed 100644 --- a/packages/transducers-fsm/package.json +++ b/packages/transducers-fsm/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-fsm", - "version": "1.1.3", + "version": "1.1.4", "description": "Transducer-based Finite State Machine transformer", "module": "./index.js", "main": "./lib/index.js", @@ -33,8 +33,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/api": "^6.3.3", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "ES6", diff --git a/packages/transducers-hdom/CHANGELOG.md b/packages/transducers-hdom/CHANGELOG.md index 488399f20e..e712b93905 100644 --- a/packages/transducers-hdom/CHANGELOG.md +++ b/packages/transducers-hdom/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.0.28](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-hdom@2.0.27...@thi.ng/transducers-hdom@2.0.28) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers-hdom + + + + + ## [2.0.27](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-hdom@2.0.26...@thi.ng/transducers-hdom@2.0.27) (2019-08-16) **Note:** Version bump only for package @thi.ng/transducers-hdom diff --git a/packages/transducers-hdom/package.json b/packages/transducers-hdom/package.json index 5a532e1ebb..c2a0f5cacb 100644 --- a/packages/transducers-hdom/package.json +++ b/packages/transducers-hdom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-hdom", - "version": "2.0.27", + "version": "2.0.28", "description": "Transducer based UI updater for @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -33,9 +33,9 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/hdom": "^8.0.3", - "@thi.ng/hiccup": "^3.2.3", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/hdom": "^8.0.4", + "@thi.ng/hiccup": "^3.2.4", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "diff", diff --git a/packages/transducers-stats/CHANGELOG.md b/packages/transducers-stats/CHANGELOG.md index 2ad9456858..ab95c3b9f0 100644 --- a/packages/transducers-stats/CHANGELOG.md +++ b/packages/transducers-stats/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-stats@1.1.3...@thi.ng/transducers-stats@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers-stats + + + + + ## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-stats@1.1.2...@thi.ng/transducers-stats@1.1.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/transducers-stats diff --git a/packages/transducers-stats/package.json b/packages/transducers-stats/package.json index 5a00a2562c..bd961ffcf9 100644 --- a/packages/transducers-stats/package.json +++ b/packages/transducers-stats/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-stats", - "version": "1.1.3", + "version": "1.1.4", "description": "Transducers for statistical / technical analysis", "module": "./index.js", "main": "./lib/index.js", @@ -34,9 +34,9 @@ }, "dependencies": { "@thi.ng/checks": "^2.3.0", - "@thi.ng/dcons": "^2.1.3", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/dcons": "^2.1.4", + "@thi.ng/errors": "^1.2.0", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "ES6", diff --git a/packages/transducers/CHANGELOG.md b/packages/transducers/CHANGELOG.md index f4781f7fed..bab2b881f3 100644 --- a/packages/transducers/CHANGELOG.md +++ b/packages/transducers/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.4.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@5.4.3...@thi.ng/transducers@5.4.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers + + + + + ## [5.4.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@5.4.2...@thi.ng/transducers@5.4.3) (2019-08-16) **Note:** Version bump only for package @thi.ng/transducers diff --git a/packages/transducers/package.json b/packages/transducers/package.json index ca29265477..46d0c12737 100644 --- a/packages/transducers/package.json +++ b/packages/transducers/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers", - "version": "5.4.3", + "version": "5.4.4", "description": "Lightweight transducer implementations for ES6 / TypeScript", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib func iter rfn xform", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib func internal iter rfn xform", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -33,15 +33,15 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/arrays": "^0.2.4", "@thi.ng/checks": "^2.3.0", "@thi.ng/compare": "^1.0.9", - "@thi.ng/compose": "^1.3.2", + "@thi.ng/compose": "^1.3.3", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/random": "^1.1.10", - "@thi.ng/strings": "^1.2.2" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/random": "^1.1.11", + "@thi.ng/strings": "^1.2.3" }, "keywords": [ "array", diff --git a/packages/transducers/src/internal/drain.ts b/packages/transducers/src/internal/drain.ts new file mode 100644 index 0000000000..eeaca8b398 --- /dev/null +++ b/packages/transducers/src/internal/drain.ts @@ -0,0 +1,22 @@ +import { Fn } from "@thi.ng/api"; +import { ReductionFn } from "../api"; +import { isReduced } from "../reduced"; + +/** + * Helper HOF yielding a buffer drain completion function for some + * transducers. + * + * @param buf + * @param complete + * @param reduce + */ +export const __drain = ( + buf: T[], + complete: Fn, + reduce: ReductionFn +) => (acc: T[]) => { + while (buf.length && !isReduced(acc)) { + acc = reduce(acc, buf.shift()!); + } + return complete(acc); +}; diff --git a/packages/transducers/src/internal/group-opts.ts b/packages/transducers/src/internal/group-opts.ts new file mode 100644 index 0000000000..7c10ef7ed0 --- /dev/null +++ b/packages/transducers/src/internal/group-opts.ts @@ -0,0 +1,17 @@ +import { identity } from "@thi.ng/compose"; +import { GroupByOpts } from "../api"; +import { push } from "../rfn/push"; + +/** + * Shared helper function for groupBy* reducers + * + * @param opts + */ +export const __groupByOpts = ( + opts?: Partial> +) => + >{ + key: identity, + group: push(), + ...opts + }; diff --git a/packages/transducers/src/internal/mathop.ts b/packages/transducers/src/internal/mathop.ts new file mode 100644 index 0000000000..0a80f2cf80 --- /dev/null +++ b/packages/transducers/src/internal/mathop.ts @@ -0,0 +1,17 @@ +import { FnAny } from "@thi.ng/api"; +import { Reducer, ReductionFn } from "../api"; +import { $$reduce, reducer } from "../reduce"; + +export const __mathop = ( + rfn: FnAny>, + fn: ReductionFn, + initDefault: number, + args: any[] +) => { + const res = $$reduce(rfn, args); + if (res !== undefined) { + return res; + } + const init = args[0] || initDefault; + return reducer(() => init, fn); +}; diff --git a/packages/transducers/src/internal/sort-opts.ts b/packages/transducers/src/internal/sort-opts.ts new file mode 100644 index 0000000000..604b0cdc84 --- /dev/null +++ b/packages/transducers/src/internal/sort-opts.ts @@ -0,0 +1,10 @@ +import { compare } from "@thi.ng/compare"; +import { identity } from "@thi.ng/compose"; +import { SortOpts } from "../api"; + +export const __sortOpts = (opts?: Partial>) => + >{ + key: identity, + compare, + ...opts + }; diff --git a/packages/transducers/src/reduce.ts b/packages/transducers/src/reduce.ts index c9a608d44c..81005e4b25 100644 --- a/packages/transducers/src/reduce.ts +++ b/packages/transducers/src/reduce.ts @@ -4,53 +4,66 @@ import { illegalArity } from "@thi.ng/errors"; import { IReducible, Reducer, ReductionFn } from "./api"; import { isReduced, unreduced } from "./reduced"; +const parseArgs = (args: any[]) => + args.length === 2 + ? [undefined, args[1]] + : args.length === 3 + ? [args[1], args[2]] + : illegalArity(args.length); + export function reduce(rfn: Reducer, xs: Iterable): A; export function reduce(rfn: Reducer, acc: A, xs: Iterable): A; export function reduce(rfn: Reducer, xs: IReducible): A; -export function reduce( - rfn: Reducer, - acc: A, - xs: IReducible -): A; +// prettier-ignore +export function reduce(rfn: Reducer, acc: A, xs: IReducible): A; export function reduce(...args: any[]): A { - let acc!: A, xs!: Iterable | IReducible; - switch (args.length) { - case 3: - xs = args[2]; - acc = args[1]; - break; - case 2: - xs = args[1]; - break; - default: - illegalArity(args.length); - } const rfn = args[0]; const init = rfn[0]; const complete = rfn[1]; const reduce = rfn[2]; - acc = acc == null ? init() : acc; - if (implementsFunction(xs, "$reduce")) { - acc = (>xs).$reduce(reduce, acc); - } else if (isArrayLike(xs)) { - for (let i = 0, n = xs.length; i < n; i++) { - acc = reduce(acc, xs[i]); - if (isReduced(acc)) { - acc = (acc).deref(); - break; - } + args = parseArgs(args); + const acc: A = args[0] == null ? init() : args[0]; + const xs: Iterable | IReducible = args[1]; + return unreduced( + complete( + implementsFunction(xs, "$reduce") + ? (>xs).$reduce(reduce, acc) + : isArrayLike(xs) + ? reduceArray(reduce, acc, xs) + : reduceIterable(reduce, acc, >xs) + ) + ); +} + +const reduceArray = ( + rfn: ReductionFn, + acc: A, + xs: ArrayLike +) => { + for (let i = 0, n = xs.length; i < n; i++) { + acc = rfn(acc, xs[i]); + if (isReduced(acc)) { + acc = (acc).deref(); + break; } - } else { - for (let x of >xs) { - acc = reduce(acc, x); - if (isReduced(acc)) { - acc = (acc).deref(); - break; - } + } + return acc; +}; + +const reduceIterable = ( + rfn: ReductionFn, + acc: A, + xs: Iterable +) => { + for (let x of xs) { + acc = rfn(acc, x); + if (isReduced(acc)) { + acc = (acc).deref(); + break; } } - return unreduced(complete(acc)); -} + return acc; +}; /** * Convenience helper for building a full `Reducer` using the identity diff --git a/packages/transducers/src/rfn/add.ts b/packages/transducers/src/rfn/add.ts index 17aa67c9ea..b86a7b443c 100644 --- a/packages/transducers/src/rfn/add.ts +++ b/packages/transducers/src/rfn/add.ts @@ -1,17 +1,12 @@ import { Reducer } from "../api"; -import { $$reduce, reducer } from "../reduce"; +import { __mathop } from "../internal/mathop"; /** - * Reducer to compute sum of values with given `init` value. + * Reducer to compute sum of values with given `init` value. Default: 0 */ export function add(init?: number): Reducer; export function add(xs: Iterable): number; export function add(init: number, xs: Iterable): number; export function add(...args: any[]): any { - const res = $$reduce(add, args); - if (res !== undefined) { - return res; - } - const init = args[0] || 0; - return reducer(() => init, (acc, x: number) => acc + x); + return __mathop(add, (acc, x: number) => acc + x, 0, args); } diff --git a/packages/transducers/src/rfn/group-by-map.ts b/packages/transducers/src/rfn/group-by-map.ts index b60be89279..accb00e8d0 100644 --- a/packages/transducers/src/rfn/group-by-map.ts +++ b/packages/transducers/src/rfn/group-by-map.ts @@ -1,28 +1,20 @@ -import { identity } from "@thi.ng/compose"; import { GroupByOpts, Reducer } from "../api"; +import { __groupByOpts } from "../internal/group-opts"; import { $$reduce, reducer } from "../reduce"; -import { push } from "./push"; -export function groupByMap( - opts?: Partial> -): Reducer, SRC>; +// prettier-ignore +export function groupByMap(opts?: Partial>): Reducer, SRC>; export function groupByMap(xs: Iterable): Map; -export function groupByMap( - opts: Partial>, - xs: Iterable -): Map; +// prettier-ignore +export function groupByMap(opts: Partial>, xs: Iterable): Map; export function groupByMap(...args: any[]): any { const res = $$reduce(groupByMap, args); if (res !== undefined) { return res; } - const opts = >{ - key: identity, - group: push(), - ...args[0] - }; + const opts = __groupByOpts(args[0]); const [init, _, reduce] = opts.group; - _; + _; // ignore return reducer, SRC>( () => new Map(), (acc, x) => { diff --git a/packages/transducers/src/rfn/group-by-obj.ts b/packages/transducers/src/rfn/group-by-obj.ts index 883406f445..3b50a29942 100644 --- a/packages/transducers/src/rfn/group-by-obj.ts +++ b/packages/transducers/src/rfn/group-by-obj.ts @@ -1,33 +1,25 @@ import { IObjectOf } from "@thi.ng/api"; -import { identity } from "@thi.ng/compose"; import { GroupByOpts, Reducer } from "../api"; +import { __groupByOpts } from "../internal/group-opts"; import { $$reduce, reducer } from "../reduce"; -import { push } from "./push"; -export function groupByObj( - opts?: Partial> -): Reducer, SRC>; +// prettier-ignore +export function groupByObj(opts?: Partial>): Reducer, SRC>; export function groupByObj(xs: Iterable): IObjectOf; -export function groupByObj( - opts: Partial>, - xs: Iterable -): IObjectOf; +// prettier-ignore +export function groupByObj(opts: Partial>, xs: Iterable): IObjectOf; export function groupByObj(...args: any[]): any { const res = $$reduce(groupByObj, args); if (res) { return res; } - const _opts = >{ - key: identity, - group: push(), - ...args[0] - }; - const [_init, _, _reduce] = _opts.group; - _; + const opts = __groupByOpts(args[0]); + const [_init, _, _reduce] = opts.group; + _; // ignore return reducer, SRC>( () => ({}), (acc, x: SRC) => { - const k: any = _opts.key(x); + const k: any = opts.key(x); acc[k] = acc[k] ? _reduce(acc[k], x) : _reduce(_init(), x); diff --git a/packages/transducers/src/rfn/mul.ts b/packages/transducers/src/rfn/mul.ts index f6c98442ef..26c19aa3c7 100644 --- a/packages/transducers/src/rfn/mul.ts +++ b/packages/transducers/src/rfn/mul.ts @@ -1,5 +1,5 @@ import { Reducer } from "../api"; -import { $$reduce, reducer } from "../reduce"; +import { __mathop } from "../internal/mathop"; /** * Reducer to compute product of values with optional `init` value @@ -9,10 +9,5 @@ export function mul(init?: number): Reducer; export function mul(xs: Iterable): number; export function mul(init: number, xs: Iterable): number; export function mul(...args: any[]): any { - const res = $$reduce(mul, args); - if (res !== undefined) { - return res; - } - const init = args[0] || 1; - return reducer(() => init, (acc, x: number) => acc * x); + return __mathop(mul, (acc, x: number) => acc * x, 1, args); } diff --git a/packages/transducers/src/rfn/sub.ts b/packages/transducers/src/rfn/sub.ts index 80ff9349ef..769ef4cb33 100644 --- a/packages/transducers/src/rfn/sub.ts +++ b/packages/transducers/src/rfn/sub.ts @@ -1,5 +1,5 @@ import { Reducer } from "../api"; -import { $$reduce, reducer } from "../reduce"; +import { __mathop } from "../internal/mathop"; /** * Reducer to successively subtract values from optional `init` value @@ -9,10 +9,5 @@ export function sub(init?: number): Reducer; export function sub(xs: Iterable): number; export function sub(init: number, xs: Iterable): number; export function sub(...args: any[]): any { - const res = $$reduce(sub, args); - if (res !== undefined) { - return res; - } - const init = args[0] || 0; - return reducer(() => init, (acc, x: number) => acc - x); + return __mathop(sub, (acc, x: number) => acc - x, 0, args); } diff --git a/packages/transducers/src/xform/convolve.ts b/packages/transducers/src/xform/convolve.ts index 8568e72cc9..48e3a785c5 100644 --- a/packages/transducers/src/xform/convolve.ts +++ b/packages/transducers/src/xform/convolve.ts @@ -85,12 +85,12 @@ const kernelLookup1d = ( border: number ): Fn<[number, number], number> => wrap - ? ([w, ox]) => { + ? ({ 0: w, 1: ox }) => { const xx = x < -ox ? width + ox : x >= width - ox ? ox - 1 : x + ox; return w * src[xx]; } - : ([w, ox]) => { + : ({ 0: w, 1: ox }) => { return x < -ox || x >= width - ox ? border : w * src[x + ox]; }; @@ -104,14 +104,14 @@ const kernelLookup2d = ( border: number ): Fn<[number, [number, number]], number> => wrap - ? ([w, [ox, oy]]) => { + ? ({ 0: w, 1: { 0: ox, 1: oy } }) => { const xx = x < -ox ? width + ox : x >= width - ox ? ox - 1 : x + ox; const yy = y < -oy ? height + oy : y >= height - oy ? oy - 1 : y + oy; return w * src[yy * width + xx]; } - : ([w, [ox, oy]]) => { + : ({ 0: w, 1: { 0: ox, 1: oy } }) => { return x < -ox || y < -oy || x >= width - ox || y >= height - oy ? border : w * src[(y + oy) * width + x + ox]; diff --git a/packages/transducers/src/xform/moving-average.ts b/packages/transducers/src/xform/moving-average.ts index d9dc216068..5d44ad7988 100644 --- a/packages/transducers/src/xform/moving-average.ts +++ b/packages/transducers/src/xform/moving-average.ts @@ -18,10 +18,8 @@ import { iterator1 } from "../iterator"; * @param src */ export function movingAverage(period: number): Transducer; -export function movingAverage( - period: number, - src: Iterable -): IterableIterator; +// prettier-ignore +export function movingAverage(period: number, src: Iterable): IterableIterator; export function movingAverage(period: number, src?: Iterable): any { return src ? iterator1(movingAverage(period), src) diff --git a/packages/transducers/src/xform/moving-median.ts b/packages/transducers/src/xform/moving-median.ts index 1c86b70c6c..2a55a38652 100644 --- a/packages/transducers/src/xform/moving-median.ts +++ b/packages/transducers/src/xform/moving-median.ts @@ -1,7 +1,6 @@ -import { compare as cmp } from "@thi.ng/compare"; -import { identity } from "@thi.ng/compose"; import { SortOpts, Transducer } from "../api"; import { comp } from "../func/comp"; +import { __sortOpts } from "../internal/sort-opts"; import { $iter } from "../iterator"; import { map } from "./map"; import { partition } from "./partition"; @@ -16,29 +15,18 @@ import { partition } from "./partition"; * @param opts * @param src */ -export function movingMedian( - n: number, - opts?: Partial> -): Transducer; -export function movingMedian( - n: number, - src: Iterable -): IterableIterator; -export function movingMedian( - n: number, - opts: Partial>, - src: Iterable -): IterableIterator; +// prettier-ignore +export function movingMedian(n: number, opts?: Partial>): Transducer; +// prettier-ignore +export function movingMedian(n: number, src: Iterable): IterableIterator; +// prettier-ignore +export function movingMedian(n: number, opts: Partial>, src: Iterable): IterableIterator; export function movingMedian(...args: any[]): any { const iter = $iter(movingMedian, args); if (iter) { return iter; } - const { key, compare } = >{ - key: identity, - compare: cmp, - ...args[1] - }; + const { key, compare } = __sortOpts(args[1]); const n = args[0]; const m = n >> 1; return comp( diff --git a/packages/transducers/src/xform/partition-sort.ts b/packages/transducers/src/xform/partition-sort.ts index 12af29d595..ab5c499b0b 100644 --- a/packages/transducers/src/xform/partition-sort.ts +++ b/packages/transducers/src/xform/partition-sort.ts @@ -1,7 +1,6 @@ -import { compare as cmp } from "@thi.ng/compare"; -import { identity } from "@thi.ng/compose"; import { SortOpts, Transducer } from "../api"; import { comp } from "../func/comp"; +import { __sortOpts } from "../internal/sort-opts"; import { $iter, iterator } from "../iterator"; import { mapcat } from "./mapcat"; import { partition } from "./partition"; @@ -32,29 +31,18 @@ import { partition } from "./partition"; * @param key sort key lookup * @param cmp comparator */ -export function partitionSort( - n: number, - opts?: Partial> -): Transducer; -export function partitionSort( - n: number, - src: Iterable -): IterableIterator; -export function partitionSort( - n: number, - opts: Partial>, - src: Iterable -): IterableIterator; +// prettier-ignore +export function partitionSort(n: number, opts?: Partial>): Transducer; +// prettier-ignore +export function partitionSort(n: number, src: Iterable): IterableIterator; +// prettier-ignore +export function partitionSort(n: number, opts: Partial>, src: Iterable): IterableIterator; export function partitionSort(...args: any[]): any { const iter = $iter(partitionSort, args, iterator); if (iter) { return iter; } - const { key, compare } = >{ - key: identity, - compare: cmp, - ...args[1] - }; + const { key, compare } = __sortOpts(args[1]); return comp( partition(args[0], true), mapcat((window: A[]) => diff --git a/packages/transducers/src/xform/stream-sort.ts b/packages/transducers/src/xform/stream-sort.ts index e9822ee722..42d0152b5f 100644 --- a/packages/transducers/src/xform/stream-sort.ts +++ b/packages/transducers/src/xform/stream-sort.ts @@ -1,9 +1,8 @@ import { binarySearch } from "@thi.ng/arrays"; -import { compare as cmp } from "@thi.ng/compare"; -import { identity } from "@thi.ng/compose"; import { Reducer, SortOpts, Transducer } from "../api"; +import { __drain } from "../internal/drain"; +import { __sortOpts } from "../internal/sort-opts"; import { $iter, iterator } from "../iterator"; -import { isReduced } from "../reduced"; /** * Transducer. Similar to `partitionSort()`, however uses proper sliding @@ -19,40 +18,24 @@ import { isReduced } from "../reduced"; * @param key * @param cmp */ -export function streamSort( - n: number, - opts?: Partial> -): Transducer; -export function streamSort( - n: number, - src: Iterable -): IterableIterator; -export function streamSort( - n: number, - opts: Partial>, - src: Iterable -): IterableIterator; +// prettier-ignore +export function streamSort(n: number, opts?: Partial>): Transducer; +// prettier-ignore +export function streamSort(n: number, src: Iterable): IterableIterator; +// prettier-ignore +export function streamSort(n: number, opts: Partial>, src: Iterable): IterableIterator; export function streamSort(...args: any[]): any { const iter = $iter(streamSort, args, iterator); if (iter) { return iter; } - const { key, compare } = >{ - key: identity, - compare: cmp, - ...args[1] - }; + const { key, compare } = __sortOpts(args[1]); const n = args[0]; return ([init, complete, reduce]: Reducer) => { const buf: A[] = []; return >[ init, - (acc) => { - while (buf.length && !isReduced(acc)) { - acc = reduce(acc, buf.shift()!); - } - return complete(acc); - }, + __drain(buf, complete, reduce), (acc, x) => { const idx = binarySearch(buf, x, key, compare); buf.splice(idx < 0 ? -(idx + 1) : idx, 0, x); diff --git a/packages/transducers/src/xform/take-last.ts b/packages/transducers/src/xform/take-last.ts index c4e0f7f026..b8da2e80fb 100644 --- a/packages/transducers/src/xform/take-last.ts +++ b/packages/transducers/src/xform/take-last.ts @@ -1,6 +1,6 @@ import { Reducer, Transducer } from "../api"; +import { __drain } from "../internal/drain"; import { iterator } from "../iterator"; -import { isReduced } from "../reduced"; /** * Transducer which only yields the last `n` values. Assumes @@ -23,12 +23,7 @@ export function takeLast(n: number, src?: Iterable): any { const buf: T[] = []; return >[ init, - (acc) => { - while (buf.length && !isReduced(acc)) { - acc = reduce(acc, buf.shift()!); - } - return complete(acc); - }, + __drain(buf, complete, reduce), (acc, x) => { if (buf.length === n) { buf.shift(); diff --git a/packages/vector-pools/CHANGELOG.md b/packages/vector-pools/CHANGELOG.md index 7f29fd76b4..da20cd441e 100644 --- a/packages/vector-pools/CHANGELOG.md +++ b/packages/vector-pools/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.6...@thi.ng/vector-pools@1.0.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/vector-pools + + + + + +## [1.0.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.5...@thi.ng/vector-pools@1.0.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/vector-pools + + + + + ## [1.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.4...@thi.ng/vector-pools@1.0.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/vector-pools diff --git a/packages/vector-pools/package.json b/packages/vector-pools/package.json index 5f5da18759..e31c0de650 100644 --- a/packages/vector-pools/package.json +++ b/packages/vector-pools/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/vector-pools", - "version": "1.0.5", + "version": "1.0.7", "description": "Data structures for managing & working with strided, memory mapped vectors", "module": "./index.js", "main": "./lib/index.js", @@ -33,12 +33,12 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/binary": "^1.1.0", "@thi.ng/checks": "^2.3.0", - "@thi.ng/malloc": "^4.0.3", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/malloc": "^4.0.4", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "ES6", diff --git a/packages/vector-pools/src/api.ts b/packages/vector-pools/src/api.ts index d6a0b7dccd..10e7222fe4 100644 --- a/packages/vector-pools/src/api.ts +++ b/packages/vector-pools/src/api.ts @@ -1,6 +1,8 @@ import { + ILogger, IObjectOf, IRelease, + NULL_LOGGER, Type, TypedArray } from "@thi.ng/api"; @@ -98,3 +100,7 @@ export const TYPE2GL: { [id: number]: GLType } = { [Type.U32]: GLType.U32, [Type.F32]: GLType.F32 }; + +export let LOGGER = NULL_LOGGER; + +export const setLogger = (logger: ILogger) => (LOGGER = logger); diff --git a/packages/vector-pools/src/attrib-pool.ts b/packages/vector-pools/src/attrib-pool.ts index f4186c2d48..c96de931a9 100644 --- a/packages/vector-pools/src/attrib-pool.ts +++ b/packages/vector-pools/src/attrib-pool.ts @@ -6,10 +6,11 @@ import { TypedArray } from "@thi.ng/api"; import { align, Pow2 } from "@thi.ng/binary"; +import { isNumber } from "@thi.ng/checks"; import { MemPool, TYPEDARRAY_CTORS, wrap } from "@thi.ng/malloc"; import { range } from "@thi.ng/transducers"; import { ReadonlyVec, Vec, zeroes } from "@thi.ng/vectors"; -import { AttribPoolOpts, AttribSpec } from "./api"; +import { AttribPoolOpts, AttribSpec, LOGGER } from "./api"; import { asNativeType } from "./convert"; /* @@ -90,7 +91,7 @@ export class AttribPool implements IRelease { attribValue(id: string, i: number): number | Vec | undefined { const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); + ensureSpec(spec, id); if (i >= this.capacity) return; i *= spec.stride!; return spec.size > 1 @@ -100,7 +101,7 @@ export class AttribPool implements IRelease { *attribValues(id: string) { const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); + ensureSpec(spec, id); const buf = this.attribs[id]; const stride = spec.stride!; const size = spec.size; @@ -117,7 +118,7 @@ export class AttribPool implements IRelease { attribArray(id: string) { const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); + ensureSpec(spec, id); const n = this.capacity; const size = spec.size; const stride = spec.stride!; @@ -137,22 +138,13 @@ export class AttribPool implements IRelease { setAttribValue(id: string, index: number, v: number | ReadonlyVec) { const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); this.ensure(index + 1); + const isNum = isNumber(v); + ensureAttrib(spec, id, isNum); const buf = this.attribs[id]; index *= spec.stride!; - const isNum = typeof v === "number"; - assert( - () => (!isNum && spec.size > 1) || (isNum && spec.size === 1), - `incompatible value for attrib: ${id}` - ); if (!isNum) { - assert( - (v).length <= spec.size, - `wrong attrib val size, expected ${spec.size}, got ${ - (v).length - }` - ); + ensureValueSize(v, spec.size); buf.set(v, index); } else { buf[index] = v; @@ -161,25 +153,16 @@ export class AttribPool implements IRelease { } setAttribValues(id: string, vals: ReadonlyVec | ReadonlyVec[], index = 0) { + const v = vals[0]; const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); + const isNum = isNumber(v); + ensureAttrib(spec, id, isNum); const n = vals.length; this.ensure(index + n); const stride = spec.stride!; const buf = this.attribs[id]; - const v = vals[0]; - const isNum = typeof v === "number"; - assert( - (!isNum && spec.size > 1) || (isNum && spec.size === 1), - `incompatible value(s) for attrib: ${id}` - ); if (!isNum) { - assert( - (v).length <= spec.size, - `wrong attrib val size, expected ${spec.size}, got ${ - (v).length - }` - ); + ensureValueSize(v, spec.size); for (let i = 0, j = index * stride; i < n; i++, j += stride) { buf.set(vals[i], j); } @@ -259,7 +242,7 @@ export class AttribPool implements IRelease { assert(a.size > 0, `attrib ${id}: illegal or missing size`); const size = SIZEOF[asNativeType(a.type)]; a.default == null && (a.default = a.size > 1 ? zeroes(a.size) : 0); - const isNum = typeof a.default === "number"; + const isNum = isNumber(a.default); assert( () => (!isNum && a.size === (a.default).length) || @@ -312,9 +295,9 @@ export class AttribPool implements IRelease { const buf = this.attribs[id]; const s = a.stride!; const v = a.default; - if (typeof v === "number") { + if (a.size === 1) { for (let i = start; i < end; i++) { - buf[i * s] = v; + buf[i * s] = v; } } else { for (let i = start; i < end; i++) { @@ -327,11 +310,11 @@ export class AttribPool implements IRelease { protected realign(newByteStride: number) { if (this.order.length === 0 || newByteStride === this.byteStride) return; - console.warn(`realigning ${this.byteStride} -> ${newByteStride}...`); + LOGGER.info(`realigning ${this.byteStride} -> ${newByteStride}...`); const grow = newByteStride > this.byteStride; let newAddr = this.addr; if (grow) { - assert(this.resizable, `pool resizing disabled`); + assert(this.resizable, `pool growth disabled`); newAddr = this.pool.realloc( this.addr, this.capacity * newByteStride @@ -343,47 +326,23 @@ export class AttribPool implements IRelease { const sameBlock = newAddr === this.addr; const num = this.capacity - 1; const attribs = this.attribs; - const newAttribs: IObjectOf<[TypedArray, number]> = {}; const specs = this.specs; const order = grow ? [...this.order].reverse() : this.order; // create resized attrib views (in old or new address space) - for (let id in specs) { - const a = specs[id]; - const type = asNativeType(a.type); - const dStride = newByteStride / SIZEOF[type]; - newAttribs[id] = [ - wrap( - type, - this.pool.buf, - newAddr + a.byteOffset, - num * dStride + a.size - ), - dStride - ]; - } - // process in opposite directions based on new stride size + const newAttribs = resizeAttribs( + specs, + this.pool.buf, + newAddr, + newByteStride, + num + ); + // process in opposite directions based on new stride size and + // in offset order to avoid successor attrib vals getting + // overwritten... for (let i of newByteStride < this.byteStride ? range(num + 1) : range(num, -1, -1)) { - // ...in offset order to avoid successor attrib vals - for (let id of order) { - const a = specs[id]; - const sStride = a.stride!; - const src = attribs[id]; - const [dest, dStride] = newAttribs[id]; - if (typeof a.default === "number") { - dest[i * dStride] = src[i * sStride]; - } else { - const j = i * sStride; - sameBlock - ? (grow ? dest : src).copyWithin( - i * dStride, - j, - j + a.size - ) - : dest.set(src.subarray(j, j + a.size), i * dStride); - } - } + moveAttribs(order, specs, attribs, newAttribs, i, sameBlock, grow); } this.addr = newAddr; this.byteStride = newByteStride; @@ -394,3 +353,66 @@ export class AttribPool implements IRelease { } } } + +const resizeAttribs = ( + specs: IObjectOf, + buf: ArrayBuffer, + dest: number, + stride: number, + num: number +) => { + const newAttribs: IObjectOf<[TypedArray, number]> = {}; + for (let id in specs) { + const a = specs[id]; + const type = asNativeType(a.type); + const dStride = stride / SIZEOF[type]; + newAttribs[id] = [ + wrap(type, buf, dest + a.byteOffset, num * dStride + a.size), + dStride + ]; + } + return newAttribs; +}; + +const moveAttribs = ( + order: string[], + specs: IObjectOf, + attribs: IObjectOf, + newAttribs: IObjectOf<[TypedArray, number]>, + i: number, + sameBlock: boolean, + grow: boolean +) => { + for (let id of order) { + const a = specs[id]; + const sStride = a.stride!; + const src = attribs[id]; + const [dest, dStride] = newAttribs[id]; + if (a.size === 1) { + dest[i * dStride] = src[i * sStride]; + } else { + const saddr = i * sStride; + const daddr = i * dStride; + sameBlock + ? (grow ? dest : src).copyWithin(daddr, saddr, saddr + a.size) + : dest.set(src.subarray(saddr, saddr + a.size), daddr); + } + } +}; + +const ensureSpec = (spec: AttribSpec, id: string) => + assert(!!spec, `invalid attrib: ${id}`); + +const ensureAttrib = (spec: AttribSpec, id: string, isNum: boolean) => { + ensureSpec(spec, id); + assert( + () => (!isNum && spec.size > 1) || (isNum && spec.size === 1), + `incompatible value for attrib: ${id}` + ); +}; + +const ensureValueSize = (v: ReadonlyVec, size: number) => + assert( + v.length <= size, + `wrong attrib val size, expected ${size}, got ${v.length}` + ); diff --git a/packages/vectors/CHANGELOG.md b/packages/vectors/CHANGELOG.md index 4c1a0a46e4..3faa28b15a 100644 --- a/packages/vectors/CHANGELOG.md +++ b/packages/vectors/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.2.0...@thi.ng/vectors@3.3.0) (2019-08-21) + + +### Features + +* **vectors:** add isNaN(), isInf() vec ops, update readme ([ed60d09](https://github.com/thi-ng/umbrella/commit/ed60d09)) + + + + + +# [3.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.1.1...@thi.ng/vectors@3.2.0) (2019-08-17) + + +### Features + +* **vectors:** add atan_2/22/23/24, update readme ([e9b156b](https://github.com/thi-ng/umbrella/commit/e9b156b)) + + + + + ## [3.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.1.0...@thi.ng/vectors@3.1.1) (2019-08-16) **Note:** Version bump only for package @thi.ng/vectors diff --git a/packages/vectors/README.md b/packages/vectors/README.md index d9f12e0b8a..2fa769cfa9 100644 --- a/packages/vectors/README.md +++ b/packages/vectors/README.md @@ -66,7 +66,7 @@ ops for signed & unsigned integer vectors. - Small & fast: The vast majority of functions are code generated with fixed-sized versions not using any loops. Minified + gzipped, the - entire package is ~9.2KB. + entire package is ~9.5KB (though you'll hardly ever use all functions). - Unified API: Any `ArrayLike` type can be used as vector containers (e.g. JS arrays, typed arrays, custom impls). Most functions are implemented as multi-methods, dispatching to any potentially optimized @@ -107,8 +107,10 @@ ops for signed & unsigned integer vectors. - [@thi.ng/color](https://github.com/thi-ng/umbrella/tree/master/packages/color) - vector based color operations / conversions - [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/master/packages/geom) - 2D/3D geometry types & operations +- [@thi.ng/imgui](https://github.com/thi-ng/umbrella/tree/master/packages/imgui) - immediate mode GUI - [@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/master/packages/matrices) - 2x2, 2x3, 3x3, 4x4 matrix & quaternion ops -- [@thi.ng/shader-ast](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast) +- [@thi.ng/shader-ast](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast) - Shader DSL & cross-compilation +- [@thi.ng/shader-ast-js](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast-js) - JS code generator for shader-ast - [@thi.ng/vector-pools](https://github.com/thi-ng/umbrella/tree/master/packages/vector-pools) - operations on memory mapped data ## Installation @@ -404,6 +406,7 @@ Functions for memory mapped, strided vectors (without requiring wrappers): - `angleBetween2` / `angleBetween3` - `angleRatio` +- `atan_2` / `atan_22` / `atan_23` / `atan_24` (i.e. `Math.atan2(y, x)`) - `bisect2` - `degrees` / `degrees2` / `degrees3` / `degrees4` - `direction` @@ -447,7 +450,7 @@ All ops support custom PRNG impls based on the - `abs` / `abs2` / `abs3` / `abs4` - `acos` / `acos2` / `acos3` / `acos4` - `asin` / `asin2` / `asin3` / `asin4` -- `atan` / `atan2` / `atan3` / `atan4` +- `atan` / `atan2` / `atan3` / `atan4` (i.e. `Math.atan(y / x)`) - `ceil` / `ceil2` / `ceil3` / `ceil4` - `cos` / `cos2` / `cos3` / `cos4` - `cosh` / `cosh2` / `cosh3` / `cosh4` @@ -458,6 +461,8 @@ All ops support custom PRNG impls based on the - `fromHomogeneous` / `fromHomogeneous3` / `fromHomogeneous4` - `invert` / `invert2` / `invert3` / `invert4` - `invSqrt` / `invSqrt2` / `invSqrt3` / `invSqrt4` +- `isInf` / `isInf2` / `isInf3` / `isInf4` +- `isNaN` / `isNaN2` / `isNaN3` / `isNaN4` - `log` / `log2` / `log3` / `log4` - `major` / `major2` / `major3` / `major4` - `minor` / `minor2` / `minor3` / `minor4` diff --git a/packages/vectors/package.json b/packages/vectors/package.json index e2207d5522..b8a234bf2a 100644 --- a/packages/vectors/package.json +++ b/packages/vectors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/vectors", - "version": "3.1.1", + "version": "3.3.0", "description": "Optimized 2d/3d/4d and arbitrary length vector operations", "module": "./index.js", "main": "./lib/index.js", @@ -33,15 +33,15 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", + "@thi.ng/api": "^6.3.3", "@thi.ng/binary": "^1.1.0", "@thi.ng/checks": "^2.3.0", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", + "@thi.ng/errors": "^1.2.0", "@thi.ng/math": "^1.4.2", - "@thi.ng/memoize": "^1.1.2", - "@thi.ng/random": "^1.1.10", - "@thi.ng/transducers": "^5.4.3" + "@thi.ng/memoize": "^1.1.3", + "@thi.ng/random": "^1.1.11", + "@thi.ng/transducers": "^5.4.4" }, "keywords": [ "2D", diff --git a/packages/vectors/src/api.ts b/packages/vectors/src/api.ts index 0eea8227ef..1fdf3dc9c4 100644 --- a/packages/vectors/src/api.ts +++ b/packages/vectors/src/api.ts @@ -160,6 +160,8 @@ export type BVecOpV = Fn2; export type BVecOpVV = Fn3; export type BVecOpVN = Fn3; +export type ToBVecOpV = Fn2; + export type CompareOp = Fn3; export interface MultiBVecOpV extends BVecOpV, MultiVecOp {} @@ -169,6 +171,8 @@ export interface MultiBVecOpRoV extends BVecOpRoV, MultiVecOp> {} +export interface MultiToBVecOpV extends ToBVecOpV, MultiVecOp {} + export interface MultiCompareOp extends CompareOp, MultiVecOp {} const mi = -Infinity; diff --git a/packages/vectors/src/atan.ts b/packages/vectors/src/atan.ts index 690649bef4..aac62e4dcd 100644 --- a/packages/vectors/src/atan.ts +++ b/packages/vectors/src/atan.ts @@ -1,6 +1,17 @@ -import { MultiVecOpV, VecOpV } from "./api"; -import { defFnOp } from "./internal/codegen"; +import { + MultiVecOpV, + MultiVecOpVV, + VecOpV, + VecOpVV +} from "./api"; +import { ARGS_VV, defFnOp, defOp } from "./internal/codegen"; +import { FN2 } from "./internal/templates"; export const [atan, atan2, atan3, atan4] = defFnOp( "Math.atan" ); + +export const [atan_2, atan_22, atan_23, atan_24] = defOp( + FN2("Math.atan2"), + ARGS_VV +); diff --git a/packages/vectors/src/index.ts b/packages/vectors/src/index.ts index 31596eb82a..d9e1dc0564 100644 --- a/packages/vectors/src/index.ts +++ b/packages/vectors/src/index.ts @@ -68,6 +68,8 @@ export * from "./heading-segment"; export * from "./homogeneous"; export * from "./invert"; export * from "./invsqrt"; +export * from "./is-inf"; +export * from "./is-nan"; export * from "./jitter"; export * from "./limit"; export * from "./log"; diff --git a/packages/vectors/src/is-inf.ts b/packages/vectors/src/is-inf.ts new file mode 100644 index 0000000000..24f770fbae --- /dev/null +++ b/packages/vectors/src/is-inf.ts @@ -0,0 +1,7 @@ +import { MultiToBVecOpV, ToBVecOpV } from "./api"; +import { defFnOp } from "./internal/codegen"; + +export const [isInf, isInf2, isInf3, isInf4] = defFnOp< + MultiToBVecOpV, + ToBVecOpV +>("!isFinite"); diff --git a/packages/vectors/src/is-nan.ts b/packages/vectors/src/is-nan.ts new file mode 100644 index 0000000000..97ae4805f2 --- /dev/null +++ b/packages/vectors/src/is-nan.ts @@ -0,0 +1,7 @@ +import { MultiToBVecOpV, ToBVecOpV } from "./api"; +import { defFnOp } from "./internal/codegen"; + +export const [isNaN, isNaN2, isNaN3, isNaN4] = defFnOp< + MultiToBVecOpV, + ToBVecOpV +>("isNaN"); diff --git a/packages/webgl-msdf/CHANGELOG.md b/packages/webgl-msdf/CHANGELOG.md index dbb6b3dca5..09ce8c530c 100644 --- a/packages/webgl-msdf/CHANGELOG.md +++ b/packages/webgl-msdf/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.6...@thi.ng/webgl-msdf@0.1.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.5...@thi.ng/webgl-msdf@0.1.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + ## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.4...@thi.ng/webgl-msdf@0.1.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/webgl-msdf diff --git a/packages/webgl-msdf/package.json b/packages/webgl-msdf/package.json index 7fc66845a8..ce965e4d01 100644 --- a/packages/webgl-msdf/package.json +++ b/packages/webgl-msdf/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/webgl-msdf", - "version": "0.1.5", + "version": "0.1.7", "description": "TODO", "module": "./index.js", "main": "./lib/index.js", @@ -33,12 +33,12 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/shader-ast": "^0.2.2", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vector-pools": "^1.0.5", - "@thi.ng/vectors": "^3.1.1", - "@thi.ng/webgl": "^0.1.5" + "@thi.ng/api": "^6.3.3", + "@thi.ng/shader-ast": "^0.3.0", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vector-pools": "^1.0.7", + "@thi.ng/vectors": "^3.3.0", + "@thi.ng/webgl": "^0.1.7" }, "keywords": [ "ES6", diff --git a/packages/webgl/CHANGELOG.md b/packages/webgl/CHANGELOG.md index 5088fffc4f..084f2601e0 100644 --- a/packages/webgl/CHANGELOG.md +++ b/packages/webgl/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.6...@thi.ng/webgl@0.1.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/webgl + + + + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.5...@thi.ng/webgl@0.1.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/webgl + + + + + ## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.4...@thi.ng/webgl@0.1.5) (2019-08-16) **Note:** Version bump only for package @thi.ng/webgl diff --git a/packages/webgl/package.json b/packages/webgl/package.json index 4158bc554a..49a5a18fcf 100644 --- a/packages/webgl/package.json +++ b/packages/webgl/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/webgl", - "version": "0.1.5", + "version": "0.1.7", "description": "WebGL abstraction layer", "module": "./index.js", "main": "./lib/index.js", @@ -33,20 +33,20 @@ "typescript": "^3.5.3" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/associative": "^2.4.3", + "@thi.ng/api": "^6.3.3", + "@thi.ng/associative": "^3.0.0", "@thi.ng/binary": "^1.1.0", "@thi.ng/checks": "^2.3.0", "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/matrices": "^0.5.5", - "@thi.ng/pixel": "^0.1.2", - "@thi.ng/shader-ast": "^0.2.2", - "@thi.ng/shader-ast-glsl": "^0.1.4", - "@thi.ng/shader-ast-stdlib": "^0.2.1", - "@thi.ng/transducers": "^5.4.3", - "@thi.ng/vector-pools": "^1.0.5", - "@thi.ng/vectors": "^3.1.1" + "@thi.ng/errors": "^1.2.0", + "@thi.ng/matrices": "^0.5.7", + "@thi.ng/pixel": "^0.1.3", + "@thi.ng/shader-ast": "^0.3.0", + "@thi.ng/shader-ast-glsl": "^0.1.6", + "@thi.ng/shader-ast-stdlib": "^0.2.3", + "@thi.ng/transducers": "^5.4.4", + "@thi.ng/vector-pools": "^1.0.7", + "@thi.ng/vectors": "^3.3.0" }, "keywords": [ "declarative",