UNPKG

erm-js

Version:
178 lines (133 loc) 6.44 kB
# erm.js - The Esoteric Reducing Machine erm.js creates composable machines for pattern matching. ```javascript let monkeyKeystrokes = infiniteTypewriters(infiniteMonkeys).readToEnd() // raw (data + location) with closures match(...monkeyKeystrokes)( make(macbeth)(book => worksOfShakespeare.push(book)), make(twogentlemenofverona)(book => worksOfShakespeare.push(book)), make(ayorkshiretragedy)(haltAndCatchFire), _ ) // values with closures match(...monkeyKeystrokes)( make(macbeth).stream(book => worksOfShakespeare.push(book)), make(twogentlemenofverona).stream(book => worksOfShakespeare.push(book)), make(ayorkshiretragedy)(haltAndCatchFire), _ ) // values to arrays match(...monkeyKeystrokes)( make(macbeth).push(worksOfShakespeare), make(twogentlemenofverona).push(worksOfShakespeare), make(ayorkshiretragedy)(haltAndCatchFire), _ ) ``` If like me you've got data, you've tried pattern matching with rxjs, you've tried finding arrays in arrays with the Knuth-Morris-Pratt algorithm, but it's all too much and not quite what you need, then maybe erm is the javascript pattern matching library you're looking for! _note: this project is (pre) alpha right now and discussed APIs are subject to change_ ## Introduction erm.js will work with arrays of anything, including strings. ### Principle of Operation - A _match$machine_ has one iterable input and has many _make$machines_. - A _match$machine_ sends its iterable input in slices of _n(1,∞)_ items to a _make$machine_ accepting _n_ arguments. - When a _make$machine_ predicate returns _false_, the _match$machine_ restarts the slicing cycle sending input to the next _make$machine_ in the chain. - A _match$machine_ will terminate when all input is accepted by the _make$machines_. - A `_` machine is a _make$machine_ that accepts any input and always advances _match$machine_ 1 position. ### The static Match API > __match__(_...input_)(__make__(_...predicate:unary|...value_)\[.__until__(_haltpredicate:unary|value_)](_output_, \[_error_])\[,...]) __match__ accepts _...input_ and returns a __match$machine__ - a callable object that accepts one or more make$machines __make__ accepts _...predicate|...value_ and return a __make$machine__- a callable object that accepts an _output_ callback and optionally and _error_ callback. #### Fixed Size Patterns A __make$machine__ will be activated with the same number of items as _predicate_ has parameters *-or-* the same number of _...values_ provided; which is to say the _make$machine_ has the same arity; so a predicate `p => ...` will produce a machine that activates with 1 parameter, in this instance `p`; and the values `...['4', '2']` will produce a machine that activates with 2 parameters. `4` and `2`. Because of this, predicates with a `...rest` parameter are not compatible-. Variable length patterns can be matched using __make$machine.until__ - _à la Kleene star..._ #### Variable Size Patterns The __make$machine__ also exposes an optional __until__ method which causes the machine to run again until the _haltpredicate_ signals true. To illustrate this with an albeit contrived example, compare it to the Regex \[^] and * operator: ```javascript // regex let username = /[^@]*/.match(emailaddress) saveUsername(username) // erm with predicates match(emailaddress)( make(c => true).until(make(c => c == '@'))(output => saveUsername(output)), _ ) // erm with literals match(emailaddress)( make(_).until('@')(output => saveUsername(output)), _ ) ``` #### Predicates, Values and Callbacks _predicate_ and _haltpredicate_ are as you would expect, p => true|false. If a value _value_ is supplied to make it is automatically converted to _p => p == value_ i.e. `make(3.14) == make(p => p == 3.14)` _output_ is your supplied callback function that is invoked with a single object `{ value, signal, location: { start, length } }` _error_ is your optional callback function that is invoked with a single object `{ error, location: { start, length } }` #### Utility Functions and Constants __Match.not()__ will invert a predicate while preserving arity e.g. `let TRUE = make(p => true); let FALSE = make(not(TRUE))` **Match.\_** is the 'unit' value symbol and acts as a wildcard when used in place of a make$machine. Like the `default:` label in a `switch` statement, `_` catches anything that your make$machines don't. Unlike the `default:` label in a switch statement, a match$machine without a `_` will not be able to read unmatched data and may not terminate. ## Quick Start ### Install `npm install erm-js` ### Usage The Match class exposes the basic building blocks `{ match, make, not, _ }` for composing machines: ```javascript const { match, make, not, _ } = require('erm-js').Match match(..."my input data") ( make((i, n) => i == "i" && n == "n")(_in => console.log(_in)), _ ) ``` ### Examples ```javascript const { match, make, not, _ } = require('erm-js').Match // simple predicates let h = p => p == 'h' let e = p => p == 'e' let l = p => p == 'l' let o = p => p == 'o' let z = p => p == 'z' // still simple but more useful let hello = (ph,pe,pl,pL,po) => h(ph) && e(pe) && l(pl) && l(pL) && o(po) // tada! match(...'hello world')( make(h)(x => console.log('h found:', x)), make(e)(x => console.log('e found:', x)), make(l)(x => console.log('l found:', x)), _ ) // kapow! match(...'hello world')( make(hello)(x => console.log('hello found:', x)), _ ) // zorb! match(...'hello world')( make(l).until(not(o))(x => console.log('ll found:', x)), _ ) // implementing a take-while function let takewhile = input => predicate => { let items = [] match(...input)( make(predicate).until(p => !predicate(p))(result => items.push(...result.value)).break(), _ ) return items } // and using it let taken = takewhile(dumbpasswords)(p => p != "111111") // implementing a partition by function let partition = input => predicate => { let left = [] let right = [] match(...input)( make(predicate)(q => left.push(q.value)), make(p => !predicate(p))(q => right.push(q.value)), _ ) return [left, right] } // and using it let [lefthandside, righthandside] = partition(dumbpasswords)(p => p.match(/^\d*$/)) ```