The Esoteric Reactive Machine
npm install erm-jsjavascript
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.
$3
- 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.
$3
> __match__(_...input_)(__make__(_...predicate:unary|...value_)\.__until__(_haltpredicate:unary|value_)\[,...])
__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
$3
npm install erm-js
$3
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)),
_
)
`
$3
`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*$/))
``