168 lines
5.1 KiB
Markdown
168 lines
5.1 KiB
Markdown
# regexoid
|
|
|
|
funny regex thingy
|
|
|
|
this library exposes a singular function called `regexoid`:
|
|
|
|
```js
|
|
import { regexoid } from 'regexoid';
|
|
// var { regexoid } = require('regexoid');
|
|
|
|
// create a regex factory-function
|
|
|
|
let regex = regexoid(({ seq, any }) => (
|
|
seq(any('Goodbye','Hello'),' ','world!')
|
|
// matches either "Goodbye world!" or "Hello world!"
|
|
));
|
|
|
|
// instantiate a regex
|
|
|
|
let eater = regex()
|
|
|
|
// process text
|
|
|
|
console.log(eater('Hello world!'))
|
|
// { done: true, match: {}, count: 12 }
|
|
```
|
|
|
|
the `regexoid()` function calls the user-provided function `f` with an object
|
|
full of helper functions `r` provided as the first argument. `f` must return
|
|
a "regex element", which is one of:
|
|
|
|
- a string (matches itself)
|
|
- the return value of one of the helper functions:
|
|
- `chars(pred)`: match a single character if the predicate function returns true
|
|
- `seq(...elems)`: match every passed regex element one after another
|
|
- `any(...elems)`: match any of the regex elements (or rather, the longest
|
|
matching; if multiple are the same length, first in the list wins)
|
|
- `rep(lim: [int,int], ...elems)`: repeat the match at least `lim[0]` (or 0) times
|
|
and no more than `lim[1]` (or Infinity) times
|
|
- `capture(key, ...elems)`: "capture" the enclosed elements into a named variable in the results.
|
|
if the value already exists, regex matching aborts
|
|
- `mark(key, value)`: set a named variable to the specified value
|
|
- `captureMulti`, `markMulti`: same as previous two, but append to array instead of setting the variable
|
|
- `map(fn)`: call `fn` passing an object with the following helper functions:
|
|
- `get(k)`: get a value from the captures set (`null` if missing). note that lists/"multi captures"
|
|
make `get` return an iterator from the last capture to the first
|
|
- `set(k,v)`: set a value to the captures set; if any existed it will be replaced. null deletes the value
|
|
- `add(k,v)`: add a value to a list in the captures set; if the key is already taken by a non-list value the operation fails
|
|
and returns false (otherwise true)
|
|
`fn` may return a value. if the value is `false`, regex matching aborts
|
|
- `abort()`: abort regex matching
|
|
|
|
the regex elements may be mixed and matched to produce a complex regex
|
|
|
|
`regexoid()` returns a constructor function that can then be used to instantiate
|
|
a regex processing stream. the regex instance is also a function, taking
|
|
a chunk of text as argument and returning the result of parsing, i.e an object
|
|
with the following fields:
|
|
|
|
- done: `false` if the regex accepts more data, `true` otherwise
|
|
- match: when the regex is done, this will contain any variables captured by the regex.
|
|
- count: sometimes the regex may finish before reading the whole chunk, this allows you
|
|
to know how many characters of the regex were actually read
|
|
|
|
regex matching is always anchored to the beginning. captures are only present in the results table
|
|
when the path the capture is in is actually taken and matched; that also means you can
|
|
re-use the same name for multiple different captures even without captureMulti/markMulti, as long
|
|
as only one of them is matched in any given run (e.g. if they're in distinct branches of an `any()`).
|
|
pass no arguments to the regex object will signal EOF to the regex.
|
|
|
|
More complex example:
|
|
|
|
```js
|
|
import { regexoid } from 'regexoid';
|
|
|
|
let o = regexoid(({
|
|
seq, any, rep, chars,
|
|
mark, markMulti,
|
|
capture, captureMulti,
|
|
map, abort,
|
|
}) => (
|
|
(({field, spc}) => (
|
|
field = (field => () => any(
|
|
field(),
|
|
captureMulti('unknownFields',
|
|
rep([1,], chars(c => c >= 'a' && c <= 'z'
|
|
|| c >= 'A' && c <= 'Z')),
|
|
any('',seq('(',rep([],chars(c => c != ')')),')'))
|
|
),
|
|
))(field),
|
|
seq(
|
|
spc(), rep([], field(), any(',',';'), spc()), field(), spc()
|
|
)
|
|
))({
|
|
field: () => (any(
|
|
...['meow', 'meower', 'sigma', 'susser']
|
|
.map(k => (
|
|
seq(
|
|
seq(
|
|
...[...k]
|
|
.map(e => [e.toLowerCase(),e.toUpperCase()])
|
|
.map(([l,h]) => chars(e => e==l || e==h))
|
|
),
|
|
any(
|
|
mark('value', true),
|
|
seq('(',
|
|
capture('value',
|
|
rep([], chars(c => c != ')'))
|
|
)
|
|
,
|
|
')')
|
|
),
|
|
map(({ get, set, add }) => {
|
|
let v = get('value');
|
|
if (v == 'BAD')
|
|
return false;
|
|
set('value');
|
|
add(k, v);
|
|
}),
|
|
)
|
|
))
|
|
)),
|
|
spc: (lim=[]) => rep(
|
|
lim,
|
|
chars(c => c==' '
|
|
|| c == '\t'
|
|
|| c == '\n'
|
|
|| c == '\r'))
|
|
})
|
|
))();
|
|
|
|
[
|
|
o(`
|
|
meow(sussy);
|
|
Meower, meo`),o(`wer(2), meower(3),
|
|
Unknownus(hmm?),
|
|
meower(BAD),
|
|
sigma(meo`),
|
|
o(`wer)`),
|
|
o('')
|
|
]
|
|
.map(e=>console.log('result:',e))
|
|
|
|
/*
|
|
result: { done: false, count: 33 }
|
|
result: { done: false, count: 70 }
|
|
result: { done: false, count: 4 }
|
|
result: {
|
|
done: true,
|
|
match: {
|
|
meow: [ 'sussy' ],
|
|
meower: [ true, '2', '3' ],
|
|
unknownFields: [ 'Unknownus(hmm?)', 'meower(BAD)' ],
|
|
sigma: [ 'meower' ]
|
|
},
|
|
count: 0
|
|
}
|
|
*/
|
|
```
|
|
|
|
# performance
|
|
|
|
no
|
|
|
|
# bugs
|
|
|
|
Yes
|