Find a file
2026-10-22 17:09:21 +05:00
index.mjs fix 2026-10-22 17:09:21 +05:00
package.json npm™ Ready 2026-10-21 03:14:16 +05:00
README.md abort(), map() 2026-10-21 17:22:13 +05:00

regexoid

funny regex thingy

this library exposes a singular function called regexoid:

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:

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