regexoid/index.mjs
2026-10-22 17:09:21 +05:00

862 lines
17 KiB
JavaScript

function cons(a, b) {
return [a, b];
}
function *listIter(l) {
while (l) {
yield l[0];
l = l[1];
}
}
function keySymVals(obj) {
return [...Object.getOwnPropertyNames(obj),...Object.getOwnPropertySymbols(obj)]
.map(e => [e,obj[e]]);
}
class EaterBase {
result = {}; ownsResult = true;
static #bannedSymbols = new WeakSet;
static isBannedSymbol(sym) {
return this.#bannedSymbols.has(sym);
}
static banSymbol(sym) {
this.#bannedSymbols.add(sym);
}
inherit(...from) {
from = from.filter(e => e);
if (!from.length) from = [this];
if (from.length == 1) {
if (from[0] == this) return;
this.result = from[0].result;
from[0].ownsResult = false;
this.ownsResult = false;
} else {
let iter = from.map(e => ({ result: e.result, eater: e })).values();
do {
let val = iter.next();
if (val.done) break;
val.value.eater.ownsResult = false;
this.result = { ...val.value.result };
this.ownsResult = true;
} while (((o)=>{
for (let i in o) return false;
if (Object.getOwnPropertySymbols(o).length) return false;
return true;
})(this.result))
for (let k of Object.getOwnPropertySymbols(this.result)) {
if (EaterBase.isBannedSymbol(k))
delete this.result[k];
}
let vals = iter.flatMap(e => (e.eater.ownsResult=false,keySymVals(e.result)))
.filter(([k,v]) => !EaterBase.isBannedSymbol(k));
for (let [k, v] of vals) {
v.type == 'list' ? [...listIter(v.data)]
.reverse()
.forEach(e => this.addResult(k, e))
: this.mapResult(k, () => v.data);
}
}
return this;
}
addResult(k, v) {
if (!this.ownsResult) {
this.result = { ...this.result };
this.ownsResult = true;
}
if ((this.result[k]?.type??'list') != 'list') {
return false;
}
this.result[k] = {
type: 'list',
data: cons(v, this.result[k]?.data)
};
return true;
}
setResult(k, v) {
if (!this.ownsResult) {
this.result = { ...this.result };
this.ownsResult = true;
}
if (this.result[k]) {
return false;
}
this.result[k] = {
type: 'value',
data: v
};
if (v == null)
delete this.result[k];
return true;
}
getResult(k) {
if (this.result[k]?.type == 'value')
return this.result[k].data;
else if (this.result[k]?.type == 'list')
return listIter(this.result[k].data);
return null;
}
mapResult(k, f) {
if (!this.ownsResult) {
this.result = { ...this.result };
this.ownsResult = true;
}
let v = f(this.getResult(k));
if (v == null && (k in this.result))
delete this.result[k]
else if (v != null)
this.result[k] = {
type: 'value',
data: v
};
return false;
}
}
class EaterResult extends EaterBase {
build() {
return Object.fromEntries(
Object.entries(this.result)
.map(([k,v]) => ([k,({
'value': v => v.data,
'list': v => [...listIter(v.data)].reverse(),
})[v.type](v)]))
)
}
}
class TransitionController {
#decider = null;
next = () => {
return this.#decider();
};
runWith(f, decider) {
try {
let prevDecider = this.#decider;
this.#decider = decider;
return f();
} finally {
this.#decider = prevDecider;
}
}
setDecider(decider) { this.#decider = decider; }
}
export class Eater extends EaterBase {
constructor() {
super();
}
#autoInherit(o, par, ...other) {
par = par ?? [this];
if (par instanceof Eater || par instanceof EaterResult)
par = [par];
o.inherit(...par, ...other, o);
}
rawNext;
get next() {
let next = this.rawNext;
if (typeof next == 'function')
return (...par) => {
let o = next.apply(this);
if (!o)
o = new EaterResult;
this.#autoInherit(o, ...par);
if (o instanceof Eater)
o.start();
return o;
};
else if (next == null) {
return (...par) => {
let o = new EaterResult;
this.#autoInherit(o, ...par);
return o;
}
}
return () => next;
}
set next(v) {
if (v == null)
v = undefined;
else if (!(
typeof v == 'function' ||
v instanceof Eater ||
v instanceof EaterResult
)) {
throw new TypeError('Invalid value for \'Eater.next\'');
}
this.rawNext = v;
}
isDone() { return true; }
peek(c) { return false; }
eat(c) { return this.peek(c); }
start() {}
end(c) {}
eof() {
let eater = this, ok;
({ ok, eater } = this.endResolve());
if (ok && !(eater instanceof EaterResult))
throw new Error('EOF but still Eater');
return ok
? eater
: false;
}
eatResolve(c) {
let eater = this, ok;
({ ok, eater } = this.endResolve(c));
if (!ok)
return {
ate: false,
eater: eater instanceof EaterResult ? eater
: (e => e instanceof EaterResult ? e : false)(eater.endResolve())
};
if (!eater.eat(c))
throw new Error('peek() reported edibility but eat() failed');
return { ate: true, eater: eater.resolve() };
}
endResolve(...c) {
let o = this;
let e = !!c.length;
let check = e
? () => o instanceof Eater && o.peek(...c)
: () => o instanceof EaterResult;
let checked = null;
while (1) {
if (checked ? checked.res : check()) {
return { ok: true, eater: o };
}
checked = null;
if (!(o instanceof Eater))
return { ok: false, eater: o };
o.end(...c);
let old = o;
o = o.resolve();
if ((checked = { res: check() }).res) {
continue;
}
if (old == o)
return { ok: false, eater: o };
}
}
resolve() {
if (this.isDone()) {
let v = this.next();
if (v instanceof Eater)
v = v.resolve();
return v;
}
return this;
}
}
class EatGate extends Eater {
#d = false; #fn;
constructor(fn) {
super();
this.#fn = fn;
}
isDone() { return this.#d; }
peek(c) { return false; }
eat(c) { return false; }
end(...c) {
this.#fn(this);
this.#d = true;
}
}
class AbortEater extends Eater {
isDone() { return false; }
peek(c) { return false; }
}
class DisjunctionEater extends Eater {
#u = []; #f = []; #d = false; #ctrl; #count=0;
#syms = { count: Symbol('DisjunctionEater.count'), };
constructor(ctrl, ...u) {
super();
ctrl.setDecider(() => {
let o = new EatGate((o) => {
o.setResult(this.#syms.count, this.#count);
});
o.next = this.rawNext;
return o;
});
this.#ctrl = ctrl;
this.#u = [...u];
}
#checkFinish() {
if (this.#f.length && !this.#u.length) {
this.#d = true;
let next = this.#f.pop();
for (let i in this.#syms) {
EaterBase.banSymbol(this.#syms[i]);
}
this.next = next;
this.#f = [];
}
}
start() {
let nextU = [];
let nextF = [];
for (let o of this.#u) {
o.inherit(this);
o.start();
o = o.resolve();
let oi = o.getResult(this.#syms.count);
if (oi == null)
nextU.push(o);
else
nextF[oi] ??= o;
}
this.#u = nextU;
this.#f = nextF;
this.#checkFinish();
}
end(...c) {
let nextU = [];
let nextF = [];
for (let i in this.#f) {
let o = this.#f[i];
if (o instanceof EaterResult) {
let oi = o.getResult(this.#syms.count);
nextF[oi] ??= o;
continue;
}
let ok;
({ ok, eater: o } = o.endResolve(...c));
let oi = o.getResult(this.#syms.count);
nextF[oi] ??= o;
}
for (let o of this.#u) {
let ok;
({ ok, eater: o } = o.endResolve(...c));
let oi = o.getResult(this.#syms.count);
if (oi == null)
nextU.push(o);
else
nextF[oi] ??= o;
}
this.#u = nextU;
this.#f = nextF;
this.#checkFinish();
}
peek(c) {
for (let o of this.#u) {
if (o.peek(c))
return true;
}
for (let i in this.#f) {
let o = this.#f[i];
if (o instanceof EaterResult)
continue;
if (o.peek(c))
return true;
}
return false;
}
isDone() {
return this.#d;
}
eat(c) {
if (!this.#u.length)
return false;
let nextU = [];
let nextF = [];
for (let i in this.#f) {
let eater = this.#f[i];
if (!(eater instanceof Eater)) {
continue;
}
let ate;
({ ate, eater } = eater.eatResolve(c));
let oi = eater.getResult(this.#syms.count);
if (ate) {
nextF[oi] ??= eater;
}
}
for (let eater of this.#u) {
let ate;
({ ate, eater } = eater.eatResolve(c));
if (ate) {
let oi = eater.getResult(this.#syms.count);
if (oi == null) {
nextU.push(eater);
} else {
nextF[oi] ??= eater;
}
}
}
this.#u = nextU;
this.#f = nextF;
let ok = this.#u.length || this.#f.length
this.#checkFinish();
this.#count++;
return ok;
}
}
class StringEater extends Eater {
#s; #i=0; constructor(s) { super(); this.#s = s; }
isDone() {
return this.#i >= this.#s.length;
}
peek(c) {
return this.#s[this.#i] == c;
}
eat(c) {
let ok = this.peek(c);
this.#i++;
return ok;
}
}
class RepeatEater extends Eater {
#min; #max; #spawn; #ctrl; #sym;
#cur = null; #cont = null; #ctrlSetNext;
get #i() { return this.getResult(this.#sym.i) ?? 0; }
constructor(ctrl, spawn, min, max, sym={ i: Symbol('RepeatEater.i'), over: Symbol('RepeatEater.over') }) {
super();
this.#ctrl = ctrl;
let next;
this.#ctrlSetNext = x => next = x;
ctrl.setDecider(() => {
let u = new (class extends Eater {
start() {
this.mapResult(sym.i, i => (i??0) + 1);
this.setResult(sym.over, true);
let next = this.next(this);
next.sus = true;
this.next = next;
}
});
u.next = () => {
let o = new RepeatEater(ctrl, spawn, min, max, sym);
o.next = next;
o.id=Math.random();
return o;
}
return u
});
this.#spawn = function(...par) {
let ns = spawn()
ns.inherit(...par);
ns.mapResult(sym.over, () => null);
ns.start();
return ns;
};
this.#min = min ?? 0;
this.#max = max ?? Infinity;
this.#sym = sym;
}
#ended = false;
#check() {
if (this.#cur?.getResult(this.#sym.over)) {
this.#ended = true;
this.next = this.#cur;
return;
}
if (((!!this.#cur) + (!!this.#cont)) == 1) {
this.#ended = true;
this.next = this.#cur || this.#cont;
return;
}
}
start() {
this.#ctrlSetNext(this.rawNext);
if (this.#i < this.#max)
this.#cur = this.#spawn(this);
if (this.#i >= this.#min) {
let [over, i] = ['over','i'].map(e => this.getResult(this.#sym[e]));
this.mapResult(this.#sym.over, () => null);
this.mapResult(this.#sym.i, () => null);
this.#cont = this.next();
this.mapResult(this.#sym.over, () => over);
this.mapResult(this.#sym.i, () => i);
}
this.#check();
}
isDone() {
return this.#ended;
}
peek(c) {
return (
(this.#cur instanceof Eater && this.#cur.peek(c)) ||
(this.#cont instanceof Eater && this.#cont.peek(c))
)
}
#forEaters(f,cl) {
if (this.#cur instanceof Eater) {
let etc, eater, ok;
({ eater, ...etc } = f(this.#cur));
ok = etc.ok ?? etc.ate;
this.#cur = ok ? eater : null;
} else if (cl) {
this.#cur = null;
}
if (this.#cont instanceof Eater) {
let etc, eater, ok;
({ eater, ...etc } = f(this.#cont));
ok = etc.ok ?? etc.ate;
this.#cont = ok ? eater : null;
} else if (cl) {
this.#cont = null;
}
this.#check();
}
eat(c) {
this.#forEaters(o => o.eatResolve(c), 1)
return this.#cur || this.#cont
}
end(...c) {
this.#forEaters(o => o.endResolve(...c), c.length);
}
}
class CaptureEater extends Eater {
#s; #d; #opts;
#syms = { eaten: Symbol('CaptureEater.eaten') };
constructor(s, { key, add, finish, multi, ctrl }) {
super();
this.#s = s;
ctrl.setDecider(() => {
let o = new EatGate(o => {
let ok;
if (this.#opts.multi)
ok = o.addResult(this.#opts.key, this.#opts.finish())
else
ok = o.setResult(this.#opts.key, this.#opts.finish());
o.setResult(this.#syms.eaten, true);
if (!ok)
o.next = () => new AbortEater;
});
o.next = this.rawNext;
return o;
});
this.#opts = { key, add, finish, multi, ctrl };
}
isDone() { return this.#d; }
#check() {
if (this.#s.getResult(this.#syms.eaten) != null) {
this.#d = true;
for (let i in this.#syms) {
EaterBase.banSymbol(this.#syms[i]);
}
this.next = this.#s;
}
}
start() {
this.#s.inherit(this);
this.#s.start();
this.#s = this.#s.resolve();
this.#check();
}
end(...c) {
({ eater: this.#s } = this.#s.endResolve(...c));
this.#check();
}
peek(c) { return (this.#s instanceof Eater) && this.#s.peek(c); }
eat(c) {
let ate;
({ ate, eater: this.#s } = this.#s.eatResolve(c));
this.#opts.add(c);
this.#check();
return ate;
}
}
class CharsetEater extends Eater {
#f; #d = false;
constructor(f) {
super();
this.#f = f;
}
isDone() { return this.#d; }
peek(c) { return this.#f(c); }
eat(c) {
if (this.peek(c))
return this.#d = true;
return false;
}
}
class ResultEditEater extends Eater {
#f; constructor(f) { super(); this.#f = f; }
isDone() { return true; }
peek(c) { return false; }
start() {
let ret = this.#f({
set: (k, v) => {
this.mapResult(k, () => v);
},
get: (k) => {
return this.getResult(k);
},
add: (k, v) => {
return this.addResult(k, v);
}
});
this.#f = null;
if ((!ret) && (ret != null)) {
this.next = () => new AbortEater;
}
}
}
function lastfun(f) {
while (f.next) {
f = f.next;
}
return f;
}
function _nwrapper() {
let wf;
wf = Object.assign((...arg) => {
let v = wf.inner(...arg);
v.next = wf.next;
if (wf.dbg)
v.dbg = wf.dbg;
return v;
}, {
inner: null,
next: null,
prev: null,
regexoidal: true,
});
return wf;
}
function nwrap(f, dbg) {
let wf = _nwrapper();
wf.inner = f;
return wf;
}
function nwrapClone(wf, onLast) {
let cf = nwrap(wf.inner);
let fcf = cf;
cf.prev = wf;
while (wf) {
if (wf.next) {
cf.next = nwrap(wf.next.inner);
cf.next.prev = cf;
}
wf = wf.next;
if ((!wf) && onLast) {
onLast(cf);
}
cf = cf.next;
}
return fcf;
}
function checkprev(f) {
if (f.prev) {
throw new Error('cannot use the same regex element more than once');
}
}
let r = {
seq(...elem) {
elem = elem.map(e => {
if (typeof e == 'function' && e.regexoidal) {
return e;
} else if (e.length != null) {
return nwrap(() => new StringEater(e));
} else {
throw new Error('bad element: '+e);
}
});
if (!elem.length) {
elem = [nwrap(() => new Eater())];
}
let lastfs = elem.map(e => lastfun(e));
for (let i=0; i<elem.length; i++) {
let [f, lf] = [elem[i], lastfs[i]];
checkprev(f);
if (i < elem.length - 1){
lf.next = elem[i+1];
}
if (i > 0) {
f.prev = lastfs[i-1];
}
}
return elem[0];
},
any(...elem) {
elem = elem.map(e => this.seq(e));
let f = nwrap(() => {
let ctrl = new TransitionController;
return new DisjunctionEater(ctrl,
...(elem
.map(e => nwrapClone(e, e => e.next = ctrl.next))
.map(e => e())
)
)
});
elem.forEach(e => (
checkprev(e),
e.prev = f
));
return f;
},
rep([min, max], ...elem) {
let spawn = this.seq(...elem);
let f = nwrap(() => {
let ctrl = new TransitionController;
return new RepeatEater(ctrl,
nwrapClone(spawn, e => e.next = ctrl.next),
min, max
)
});
checkprev(spawn);
spawn.prev = f;
return f;
},
abort() {
return nwrap(() => new AbortEater);
},
map(f) {
return nwrap(() => new ResultEditEater(f));
},
chars(pred) {
return nwrap(() => new CharsetEater(pred));
},
...((o) => Object.fromEntries(
Object.entries(o)
.flatMap(([k,v]) => [
[k, function(...arg) { return v.call(this, false, ...arg); }],
[k+'Multi', function(...arg) { return v.call(this, true, ...arg); }],
])
))({
mark(multi, key, value=true) {
return nwrap(() => {
let ctrl = new TransitionController;
return new CaptureEater((e => (e.next = ctrl.next, e))(new Eater), {
key,
add: () => {},
finish: () => value,
multi,
ctrl,
})
});
},
capture(multi, key, ...elem) {
let s = this.seq(...elem);
let f = nwrap(() => {
let ctrl = new TransitionController;
let val = [];
return new CaptureEater(nwrapClone(s, e => e.next = ctrl.next)(), {
key,
add: c => val.push(c),
finish: () => val.join(''),
multi,
ctrl,
})
});
checkprev(s);
s.prev = f;
return f;
},
}),
custom(f, ...arg) {
return nwrap(() => f(...arg));
},
};
for (let k in r)
r[k] = r[k].bind(r);
export function regexoid(f) {
let spawn = r.seq(f(r));
return () => {
let o = spawn();
o.start();
o = o.resolve();
let eofResult = null;
return function(chunk) {
let i = 0;
if (!chunk) {
if (eofResult) {
return eofResult;
}
o = ((o instanceof Eater) ? o.eof() : o) || false;
return {
done: true,
match: o ? o.build() : o,
count: 0,
}
} else {
let ret;
for (let c of chunk) {
if (eofResult) {
eofResult = null;
break;
}
i++;
if (o instanceof Eater) {
let ate;
({ ate, eater: o } = o.eatResolve(c));
if (!ate) {
let v = o;
o = null;
return {
done: true,
match: v ? v.build() : v,
count: i - 1,
}
}
}
if (o instanceof EaterResult) {
ret = eofResult = {
done: true,
match: o.build(),
count: i,
};
o = null;
}
}
if (ret)
return ret;
}
return {
done: false,
count: i
};
}
}
}