regexoid/index.mjs

1553 lines
34 KiB
JavaScript

function cons(a, b) {
return [a, b];
}
function *listIter(l) {
while (l) {
yield l[0];
l = l[1];
}
}
let _elemDebugInfo = null;
function _currentElem() {
if (_elemDebugInfo)
return _elemDebugInfo.currentElem();
}
class AstNode {
__elemId;__pure;__createdAt=new (class AstNodeCreation extends Error{})('AstNode');
next = null; prev = null;
compile(ctx) {}
*_getNexts() {}
*getNexts() {
yield *this._getNexts();
if (this.next)
yield this.next;
}
*recurseTree() {
yield this;
let iters = [this.getNexts()];
while (iters.length) {
let citers = iters;
iters = [];
for (let iter of citers) {
for (let o of iter) {
iters.push(o.getNexts());
yield o;
}
}
}
}
_isPure() { return true; }
isPure() {
for (let o of this.recurseTree()) {
if (!o._isPure()) return false;
}
return true;
}
_clone() {
return new this.constructor;
}
clone() {
let oldN = this, newN = this._clone();
let ret = newN;
while (1) {
oldN = this.next;
if (!oldN)
break;
let nextNewN = oldN.clone();
nextNewN.attachTo(newN);
newN = newN.chain(nextNewN);
}
return ret;
}
attachTo(parent) {
if (this.prev) {
let e = this.clone();
e.attachTo(parent);
return e;
}
this.prev = parent;
return this;
}
chain(other) {
let current = this;
while (current.next)
current = current.next;
other = other.attachTo(current);
current.next = other;
return other;
}
}
class AstString extends AstNode {
str;
constructor(str) {
if (str.length == 0) {
return new AstNode;
}
if (str.length == 1) {
return new AstChars(new CharsPredicate(false, [str]));
}
super();this.str = str;
}
_clone() { return new AstString(this.str); }
compile(ctx) {
let sc = ctx.scope();
let s = ctx.newId();
let i = ctx.newId();
let b = ctx.newId();
let c = ctx.newId();
let ava = ctx.newId();
let str = ctx.bind(this.str);
ctx.write(
`{let ${s}=${str};for(let ${i}=0;${i}<${this.str.length};){`+
`while(${ctx.s}.wait())yield;`+
`if(${ctx.s}.eof())`+ctx.exit+
`let ${ava}=${ctx.s}.avail();`+
`if (${ava}>${this.str.length}-${i})${ava}=${this.str.length}-${i};`+
`let ${b}=${s}.slice(${i},${i}+${ava});`+
`let ${c}=${ctx.s}.peek(${ava});`+
`if(${b}!=${c})`+ctx.exit+
`${ctx.s}.consume(${ava});`+
`${i}+=${ava};`+
`}}`
)
ctx.closeScope(sc);
}
}
class XFunction extends Function {
constructor(fn) {
return Object.setPrototypeOf(fn, new.target.prototype);
}
}
class CharsPredicate extends XFunction {
ops; invert;
constructor(invert, ...ops) {
super((c) => {
if (typeof c == 'string') {
c = c.charCodeAt(0);
}
let ok = false;
for (let [op,...b] of this.ops) {
if (op == '==')
ok ||= c == b[0];
else if (op == '<=>')
ok ||= c >= b[0] && c <= b[1];
if (ok) break;
}
if (this.invert) ok = !ok;
return ok;
});
function normChar(c) {
if (typeof c == 'string') {
if (c.length != 1)
throw new TypeError('suspiciously long character')
c = c.charCodeAt(0);
}
if (typeof c != 'number') {
throw new Error('not a char or charcode');
}
return c;
}
this.ops = ops.flatMap(o => {
if (o instanceof CharsPredicate && o.invert == invert) {
return o.ops;
}
if (!(Array.isArray(o) && (o.length == 2 || o.length == 1)))
throw new TypeError('must be array with arity 1 (chars: `[str]`) or 2 (range: `[char,char]`)');
if (o.length == 1) {
o = o[0]
if (typeof o == 'string') {
o = o.split('');
} else {
o = o.map(normChar);
}
return o.map(e => ['==',e.charCodeAt(0)]);
} else if (o.length == 2) {
o = o.map(normChar);
if (o[0] > o[1])
o = [o[1],o[0]];
return [
['<=>', o[0],o[1]],
];
}
});
this.invert = invert;
}
compile(ctx, c) {
let ops;
if (this.ops.length) {
ops = this.ops.map(e => ({
['<=>']: (a,b) => `${c}>=${ctx.bind(a)}&&${c}<=${ctx.bind(b)}`,
['==']: (a) => `${c}==${ctx.bind(a)}`
})[e[0]](...e.slice(1).map(e=>String.fromCharCode(e)))).join('||')
} else {
ops = 'true';
}
ctx.write(this.invert ? `!(${ops})` : ops);
}
}
class AstChars extends AstNode {
pred;
constructor(pred) {
super();
this.pred = pred;
}
_clone() { return new AstChars(this.pred); }
compilePred(ctx, c) {
if (this.pred instanceof CharsPredicate) {
ctx.write('if(!(');
this.pred.compile(ctx, c);
ctx.write('))'+ctx.exit);
} else {
let pred = ctx.bind(this.pred);
ctx.write(
'if(!'+pred+'('+c+'))'+ctx.exit
)
}
}
compile(ctx) {
let sc = ctx.scope();
let c = ctx.newId();
ctx.write('{'+
'while ('+ctx.s+'.wait())yield;'+
'if('+ctx.s+'.eof())'+ctx.exit+
'let '+c+'='+ctx.s+'.peek();');
this.compilePred(ctx, c);
ctx.write(
''+ctx.s+'.consume();'+
'}')
ctx.closeScope(sc);
}
}
class AstCond extends AstNode {
conds;
constructor(conds) {
super();
this.conds = conds.map(([p,c]) => [p.attachTo(this), c.attachTo(this)]);
}
_clone() { return new AstCond(this.conds); }
*_getNexts() { yield *this.conds.flatMap(e => e); }
compile(ctx) {
let sc = ctx.scope();
ctx.write('{')
for (let [cond, ops] of this.conds) {
let res = ctx.newId();
let sv = ctx.newId();
ctx.write(`let ${sv}=${ctx.s}.save();`)
ctx.compileEnclosed(cond, res);
ctx.write(`${ctx.s}.restore(${sv});${sv}.destroy();` +
`if(${res}){`);
ctx.compile(ops);
ctx.write('}else{');
}
ctx.write('}'.repeat(1 + this.conds.length))
ctx.closeScope(sc);
}
}
let breakSymbol = Symbol('break');
class AstRepeat extends AstNode {
min; max; inner; strict; label; canBreak=false;
constructor(lim, inner, strict, label=null) {
super();
if (typeof lim == 'string' || typeof lim == 'symbol' || typeof lim == 'number' || lim == null)
lim = [lim,lim];
let [min,max] = lim;
[min, max] = [min, max].map(e => {
if (e == null) return e;
if (!(({number: 1, string: 1, symbol: 1})[typeof e]))
throw new TypeError('expected number, string or symbol');
return e;
});
min ??= 0;
max ??= Infinity;
this.min = min;
this.max = max;
this.inner = inner.attachTo(this);
this.strict = strict;
this.label = label;
}
_clone() { return new AstRepeat([this.min,this.max],this.inner,this.strict,this.label); }
*_getNexts() { yield this.inner; }
compile(ctx) {
let sc = ctx.scope();
ctx.write('{');
let min, max;
let dynamic = false;
[min, max] = [this.min, this.max].map(e => {
if (typeof e == 'string' || typeof e == 'symbol') {
dynamic = true;
let v = ctx.newId();
ctx.write(`let ${v}=${ctx.result}.get(${ctx.bind(e)});`)
return v;
}
return ctx.bind(e);
});
let i = ctx.newId();
if (this.inner instanceof AstChars && this.inner.next == null) {
let ava = ctx.newId();
let c = ctx.newId();
let j = ctx.newId();
let noop = this.inner.pred instanceof CharsPredicate &&
(this.inner.pred.invert) &&
(!this.inner.pred.ops.length);
ctx.write(
`for(let ${i}=0;${i}<${max};){`+
`while(${ctx.s}.wait())yield;`+
`if(${ctx.s}.eof()){if(${i}<${min})${ctx.exit}else{break}}`+
`let ${ava}=${ctx.s}.avail();`+
`if (${ava}>${max}-${i})${ava}=${max}-${i};`
);
if (!noop) {
ctx.write(
`let ${c}=${ctx.s}.peek(${ava});`+
`let ${j}=0;`+
`for (;${j}<${ava};${j}++){`
);
let oldExit = ctx.exit;
ctx.exit = 'break;'
this.inner.compilePred(ctx, `${c}[${j}]`);
ctx.exit = oldExit;
ctx.write(
`}`
);
ctx.write(
`if(${j}<${ava}){`+
((!this.strict) ? (
`if(${i}+${j}<${min}){`+
`${ctx.exit}`+
`}else{`+
`if(${j}>0){`+
`${ctx.s}.consume(${j})`+
`}`+
`break`+
`}`)
: (ctx.exit)
)+
`}`
);
}
ctx.write(
`${ctx.s}.consume(${noop?ava:j});`+
`${i}+=${noop?ava:j};`+
`}`
)
} else {
let sv = ctx.newId();
let res = ctx.newId();
ctx.write(
`for(let ${i}=0;${i}<${max};${i}++){`+
`let ${sv}=(${i}>=${min})?${this.strict?'true':`${ctx.s}.save()`}:null;`
);
if (this.strict) {
ctx.compile(this.inner);
} else {
ctx.compileEnclosed(this.inner, res);
if (!this.inner.isPure())
ctx.mergeResult(res);
ctx.write(
`if(!${res}){`+
`if(${sv}){`+
`${ctx.s}.restore(${sv});`+
`${sv}.destroy();`+
`break`+
`}else{`+
`${ctx.exit}`+
`}`+
`}`+
`if(${sv})${sv}.destroy()`
)
};
if (this.canBreak) {
let brk = ctx.newId();
let brkSym = ctx.globalBind('breakSymbol', breakSymbol);
ctx.write(
`let ${brk}=${ctx.result}.get(${brkSym});`+
`if(${brk}===true||${brk}===${ctx.bind(this.label)}){if(${sv}){break}else{${ctx.exit}}}`
)
}
ctx.write(
`}`
);
}
ctx.write('}');
ctx.closeScope(sc);
}
}
class AstDisjunction extends AstNode {
branches;
constructor(...branches) {
if (branches.every(e => (
(e instanceof AstChars) &&
(e.next == null) &&
(e.pred instanceof CharsPredicate) &&
(!e.pred.invert)
))) {
return new AstChars(new CharsPredicate(false, ...branches.map(e => e.pred)))
}
super();
this.branches = branches.map(e => e.attachTo(this));
}
_clone() { return new AstDisjunction(...this.branches); }
*_getNexts() { yield*this.branches; }
compile(ctx) {
let sc=ctx.scope();
let sv=ctx.newId();
let finished=ctx.newId();
ctx.write(
'do{'+
`let ${sv}=${ctx.s}.save();`+
`let ${finished}=null;`
);
let cleaned = false;
for (let [i,o] of this.branches.map((v,i)=>[i,v])) {
let sc = ctx.scope();
let oldResult = ctx.scope();
ctx.write(
'{'
);
if (i)
ctx.write(
`${ctx.s}.restore(${sv});`
)
if (i == this.branches.length-1) {
cleaned = true;
ctx.write(
`${sv}=(${sv}.destroy(),null);`
);
}
let res = ctx.newId();
ctx.compileEnclosed(o, res);
let sve=ctx.newId();
ctx.write(
`if(${res}){`+
`let ${sve}=${ctx.s}.save();`+
`if((!${finished})||(${finished}.p.max(${sve})==${sve})){`+
`if(${finished}){${finished}.p.destroy()}`+
`${finished}={p:${sve},v:${res}}`+
`}`+
'}'+
'}'
);
ctx.closeScope(sc);
}
if (!cleaned) {
ctx.write(`${sv}=(${sv}.destroy(),null);`);
}
ctx.write(
`if(${finished}){`+
`${ctx.s}.restore(${finished}.p);`+
`${finished}.p.destroy();`
);
ctx.mergeResult(finished+'.v');
ctx.write(
`}else{`+
ctx.exit+
`}`+
`}while(0)`
);
ctx.closeScope(sc);
}
}
class AstAbort extends AstNode {
compile(ctx) {
ctx.write(ctx.exit);
}
}
class AstResultEdit extends AstNode {
cback;
constructor(cback) {
super();
this.cback = cback;
}
_clone() { return new AstResultEdit(this.cback); }
_isPure() { return false; }
static wrapCall(cback) {
return (result, ...arg) => {
let ret = cback({
set(k, v) { result.set(k, v); },
get(k) { return result.get(k); },
add(k, v) { result.add(k, v); },
}, ...arg);
return !((!ret) && (ret != null))
}
}
compile(ctx) {
let sc = ctx.scope();
let cback = ctx.bind(this.constructor.wrapCall(this.cback));
ctx.write(`if(!${cback}(${ctx.result}))${ctx.exit}`)
ctx.closeScope(sc);
}
}
class AstMark extends AstNode {
key; value; multi;
constructor(key, value, multi) {
super();
this.value = value;
this.key = key;
this.multi = multi;
}
_clone() { return new AstMark(this.key, this.value, this.multi); }
_isPure() { return false; }
compile(ctx) {
if (this.key == breakSymbol) {
let obj = this.prev;
while (1) {
if (obj instanceof AstRepeat && (this.value === true || obj.label === this.value)) {
obj.canBreak = true;
break;
}
obj = obj.prev;
if (!obj) {
throw new Error('misplaced break operator');
}
}
}
let sc = ctx.scope();
let key = (this.key == breakSymbol) ? ctx.globalBind('breakSymbol', this.key) : ctx.bind(this.key);
let value = ctx.bind(this.value);
ctx.write(`${ctx.result}.${this.multi?'add':'set'}(${key}, ${value});`)
ctx.closeScope(sc);
}
}
class AstCapture extends AstNode {
key; inner; multi;
constructor(key, inner, multi) {
super();
this.key = key;
this.inner = inner.attachTo(this);
this.multi = multi;
}
_clone() { return new AstCapture(this.key, this.inner, this.multi); }
*_getNexts() { yield this.inner; }
_isPure() { return false; }
compile(ctx) {
let sc = ctx.scope();
let key = ctx.bind(this.key);
let sv1 = ctx.newId();
let sv2 = ctx.newId();
ctx.write(`{let ${sv1}=${ctx.s}.save();`);
let oldExit = ctx.exit;
ctx.exit = `{${sv1}.destroy();${ctx.exit}}`;
ctx.compile(this.inner);
ctx.exit=oldExit;
ctx.write(
`let ${sv2}=${ctx.s}.save();`+
`${ctx.result}.${this.multi?'add':'set'}(${key},${sv1}.slice(${sv2}));`+
`${sv1}.destroy();${sv2}.destroy()}`
);
ctx.closeScope(sc);
}
}
class AstLimit extends AstNode {
limit; inner;
constructor(limit, inner) {
super();
this.limit = limit;
this.inner = inner.attachTo(this);
}
_clone() { return new AstLimit(this.limit, this.inner); }
*_getNexts() { yield this.inner; }
compile(ctx) {
let sc = ctx.scope();
let limc = ctx.bind(this.limit);
let limk = ctx.newId();
ctx.write(`{let ${limk}=${ctx.s}.setLimit(${limc});`);
let oldExit = ctx.exit;
ctx.exit = `{${ctx.s}.unsetLimit(${limk});${ctx.exit}}`;
ctx.compile(this.inner);
ctx.exit=oldExit;
ctx.write(
`${ctx.s}.unsetLimit(${limk})}`
);
ctx.closeScope(sc);
}
}
class AstCaptureYield extends AstNode {
key; inner;
constructor(key, inner) {
super();
this.key = key;
this.inner = inner.attachTo(this);
}
_clone() { return new AstCaptureYield(this.key, this.inner); }
*_getNexts() { yield this.inner; }
compile(ctx) {
let sc = ctx.scope();
let key = ctx.bind(this.key);
let yld = ctx.newId();
ctx.write(`{let ${yld}=${ctx.s}.addYielder(${key});`);
let oldExit = ctx.exit;
ctx.exit = `{${ctx.s}.removeYielder(${yld});${ctx.exit}}`;
ctx.compile(this.inner);
ctx.exit=oldExit;
ctx.write(
`${ctx.s}.removeYielder(${yld})}`
);
ctx.closeScope(sc);
}
}
class AstYield extends AstNode {
key; value;
constructor(key, value) {
super();
this.value = value ?? key;
this.key = key;
}
_clone() { return new AstYield(this.key, this.value); }
_isPure() { return false; }
compile(ctx) {
let sc = ctx.scope();
let key = ctx.bind(this.key);
let value = ctx.bind(this.value);
ctx.write(`${ctx.s}.yield(${key},${ctx.result}.get(${value},true));`)
ctx.closeScope(sc);
}
}
function compileAst(elem) {
let ctx = {
s: 'D',
result: 'R',
vars: 0,
binds: [],
globals: [],
code: [],
debugInfo: [],
codeLength: 0,
exit: 'return false;',
exitTrapLoop(loop, vname, vval='false') {
let old = this.exit;
this.exit = '{'+(vname?(vname+'='+vval+';'):'')+'break '+loop+'}'
return old;
},
exitTrapFunc() {
let old = this.exit;
this.exit = 'return false;';
return old;
},
exitUntrap(old) {
this.exit = old;
},
scope() {
return this.vars;
},
closeScope(sc) {
this.vars = sc
},
newId() {
let n = '$'+this.vars
this.vars++;
return n;
},
bind(v, force=false) {
if (!force) {
if (typeof v == 'string')
return JSON.stringify(v);
else if (typeof v == 'bigint')
return v+'n';
else if (
typeof v == 'number' ||
typeof v == 'boolean' ||
v == null
)
return v+'';
}
let n = '$_'+this.binds.length;
this.binds.push([n,v]);
return n;
},
globalBind(k, ...v) {
let i = this.globals[k];
if (i != null) {
if (v.length)
this.binds[i][1] = v[0];
return this.binds[i][0]
}
if (!v.length)
return;
i = this.binds.length;
this.bind(v[0], true);
this.globals[k] = i;
return this.binds[i][0];
},
write(...str) {
this.code.push(...str)
this.codeLength += str.map(e=>e.length).reduce((a,b)=>a+b);
},
elemId: 0,
compile(elem) {
while (elem) {
if (elem.elemId == null)
elem.__elemId = this.elemId++;
if (elem.__pure == null)
elem.__pure = elem.isPure();
let name = elem.constructor.name+'#'+elem.__elemId
this.write('/*'+name+'{*/');
this.debugInfo.push({at: this.codeLength, type:'begin', name, createdAt: elem.__createdAt })
elem.compile(this);
this.debugInfo.push({at: this.codeLength, type:'end'});
this.write('/*}*/');
elem = elem.next;
}
},
compileEnclosed(elem, res, pure=elem.isPure()) {
let oldResult;
if(!pure) {
oldResult = this.newId();
this.write(
`let ${oldResult}=${this.result};`+
`${this.result}=${this.result}.clone();`
);
}
this.write(`let ${res}=${this.result};`);
let loop = this.newId();
this.write(''+loop+':do{');
let rcode = this.exitTrapLoop(loop, res);
this.compile(elem);
this.exitUntrap(rcode);
this.write('}while(0);')
if (!pure) {
this.write(
`${this.result}=${oldResult};`
);
}
},
mergeResult(res) {
this.write(
`{if(${res}&&(${this.result}!=${res}))`+
`{${this.result}=${res}.mergeDown()}}`
)
},
};
let resultClass = ctx.globalBind('ResultSet', ResultSet);
ctx.compile(elem);
let code =
[`"use strict";(${ctx.binds.map(e=>e[0]).join(',')})=>function*(${ctx.s}){`,
`let ${ctx.result}=new ${resultClass};`,
`${ctx.code.join('')}`,
`${ctx.s}.checkBarrier();`,
`return ${ctx.result}.build()}`,
];
ctx.debugInfo.forEach(e => e.at += code[0].length);
code = code.join('');
let func = (0,eval)(code)(...ctx.binds.map(e=>e[1]));
let Debug = {
debugInfo: ctx.debugInfo,
currentElem() {
let err = (new Error).stack.split('\n')
let s = err.find(e => e.match('compileAst'));
if (!s) return;
let i = s.match(/:([0-9]*)[^0-9]*$/)[1];
i = +i;
let stk = [];
for (let {at,type,...o} of this.debugInfo) {
if (at > i) break;
if (type == 'begin')
stk.push(o.name);
else if (type == 'end')
stk.pop();
}
return stk.pop();
}
}
return (...arg) => {
let gen = func(...arg);
gen.next = ((next) => function(...arg){
let dbg = _elemDebugInfo;
_elemDebugInfo = Debug;
try {
return next.apply(this, arg);
} finally {
_elemDebugInfo = dbg;
}
})(gen.next);
return gen;
}
}
class ResultSet {
data = Object.create(null); dirty=false; parent;
clone() {
let o = new ResultSet;
o.data = Object.create(this.data);
o.parent = this;
return o;
}
mergeDown() {
if (!this.dirty)
return this.parent;
for (let k of [...Object.getOwnPropertyNames(this.data), ...Object.getOwnPropertySymbols(this.data)]) {
this.parent._set(k, this.data[k]);
}
return this.parent;
}
build() {
return Object.fromEntries(
[...(function*(){ for(let i in this.data) yield [i,this.data[i]]; }).call(this)]
.filter(e => e[1] != null)
.map(([k,v]) => ([k,({
'value': v => v.data,
'list': v => [...listIter(v.data)].reverse(),
})[v.type](v)]))
.filter(e => e[1] != null)
)
}
_set(k, v) {
this.dirty = true;
if (v == null) v = undefined;
if (v == null && !(this.parent && (k in this.parent.data))) {
delete this.data[k];
return;
}
this.data[k] = v;
}
_get(k) { return this.data[k]; }
set(k, v) {
if (v != null)
v = { type: 'value', data: v };
this._set(k, v);
}
add(k, v) {
let o = this._get(k);
if (o && o.type == 'value') {
o = { type: 'list', data: cons(o.data, undefined) };
} else if (!o) {
o = { type: 'list', data: undefined };
}
this._set(k, { type: 'list', data: cons(v, o.data) });
}
get(k, fin=false) {
let o = this._get(k);
if (o && o.type == 'list') {
o = listIter(o.data);
if (fin)
o = [...o].reverse();
} else if (o && o.type == 'value')
o = o.data;
else
o = null;
return o;
}
}
let StreamoidPriv;
export class Streamoid {
static Pin = class StreamoidPin {
target; bufi; i;
constructor(target, bufi, i) {
this.target = target;
this.bufi = bufi;
this.i = i;
}
destroy() {
let n = this.target.#pins[this.bufi]-1;
if (!n)
delete this.target.#pins[this.bufi];
else
this.target.#pins[this.bufi] = n;
this.target.cleanup();
this.target = null;
}
span(dst, cb) {
let target = this.target;
if (target != dst.target)
throw new Error('non-matching targets');
let src = this;
if (src.bufi > dst.bufi || (src.bufi == dst.bufi && src.i > dst.i))
throw new Error('span go from start to end, not end to start');
if (src.bufi == dst.bufi) {
cb({ bufi: src.bufi, start: src.i, end: dst.i })
return;
}
if (!target.#bufs[src.bufi]) {
return;
}
cb({ bufi: src.bufi, start: src.i, end: target.#bufs[src.bufi].length });
for (let i=src.bufi+1; i<dst.bufi; i++) {
cb({ bufi: i, start: 0, end: target.#bufs[i].length });
}
cb({ bufi: dst.bufi, start: 0, end: dst.i });
}
length(other) {
let l = 0;
this.span(other, ({ start, end }) => {
l += end - start;
})
return l;
}
static max(...a) {
let maxBufi = -Infinity, maxI = -Infinity, maxP = null;
for (let p of a) {
if (p.bufi > maxBufi || (p.bufi == maxBufi && p.i > maxI)) {
[maxBufi, maxI, maxP] = [p.bufi, p.i, p];
}
}
return maxP;
}
max(...other) {
return StreamoidPin.max(this, ...other);
}
slice(other, cut=false) {
let a = [];
this.span(other, ({ bufi, start, end }) => {
if (end > 0)
a.push(this.target.#bufs[bufi].slice(start, end));
});
return (cut) ? a : (a.length ? (a.length == 1 ? a[0] : a.join('')) : '');
}
};
static Limit = class StreamoidLimit {
total; remain; constructor(total) { this.remain = this.total = total; }
};
#i=0; #bufi=0; #buf0=0; #bufs=[]; #pins=[]; #limits=[]; #yielders=[];
#minBufi = 0; #minI = 0; #broken=false;
#output = [];
static {
StreamoidPriv = class {
#target;
constructor(target) {
this.#target = target
}
// s/[^#]*#\([a-zA-Z0-9_]*\)[^#]*/\t\t\tget \1() { return this.#target.#\1 } set \1 (v) { return this.#target.#\1 = v }\r/g
get i() { return this.#target.#i } set i (v) { return this.#target.#i = v }
get bufi() { return this.#target.#bufi } set bufi (v) { return this.#target.#bufi = v }
get buf0() { return this.#target.#buf0 } set buf0 (v) { return this.#target.#buf0 = v }
get bufs() { return this.#target.#bufs } set bufs (v) { return this.#target.#bufs = v }
get pins() { return this.#target.#pins } set pins (v) { return this.#target.#pins = v }
get limits() { return this.#target.#limits } set limits (v) { return this.#target.#limits = v }
get yielders() { return this.#target.#yielders } set yielders (v) { return this.#target.#yielders = v }
get minBufi() { return this.#target.#minBufi } set minBufi (v) { return this.#target.#minBufi = v }
get minI() { return this.#target.#minI } set minI (v) { return this.#target.#minI = v }
get broken() { return this.#target.#broken } set broken (v) { return this.#target.#broken = v }
get output() { return this.#target.#output } set output (v) { return this.#target.#output = v }
}
}
wait() {
if (!(this.#bufi in this.#bufs))
return true;
}
checkBarrier() {
if (this.#broken)
throw new Error('crossed a no-backtrack barrier');
}
save() {
this.#pins[this.#bufi] ??= 0;
this.#pins[this.#bufi]++;
let p = new StreamoidPin(this, this.#bufi, this.#i);
return p;
}
barrier() {
this.#minBufi = this.#bufi;
this.#minI = this.#minI;
}
hasOutput() {
return this.#output.length;
}
readOutput() {
let out = this.#output;
this.#output = [];
return out;
}
yield(k,v) {
this.checkBarrier();
this.#output.push([k,v]);
this.barrier();
}
addYielder(k) {
let o = {name: k}
this.#yielders.push(o);
return o;
}
removeYielder(o) {
if (this.#yielders[this.#yielders.length-1] != o)
throw new Error('remove yielder across multiple yielders');
this.#yielders.pop();
}
saveEnd() {
let bufi = this.#bufi;
let i = this.#i;
this.#bufi = this.#bufs.length;
while (this.#bufi>0 && this.#bufs[this.#bufi-1]==null) this.#bufi--;
this.#i = 0;
let p = this.save();
this.#bufi = bufi;
this.#i = i;
return p;
}
saveStart() {
let bufi = this.#bufi;
let i = this.#i;
this.#bufi = this.#buf0;
this.#i = 0;
let p = this.save();
this.#bufi = bufi;
this.#i = i;
return p;
}
#bufSpan(mode, f) {
let a,b;
if (mode == 'back') {
a=this.saveStart();
b=this.save();
} else if (mode == 'front') {
a=this.save();
b=this.saveEnd();
}
try {
return f(a,b)
} finally {
a.destroy();
b.destroy();
}
}
get backBufferLength() { return this.#bufSpan('back', (a,b) => a.length(b)); }
get backBuffer() { return this.#bufSpan('back', (a,b) => a.slice(b)); }
get backBufferSlices() { return this.#bufSpan('back', (a,b) => a.slice(b, true)); }
get frontBufferLength() { return this.#bufSpan('front', (a,b) => a.length(b)); }
get frontBuffer() { return this.#bufSpan('front', (a,b) => a.slice(b)); }
get frontBufferSlices() { return this.#bufSpan('front', (a,b) => a.slice(b, true)); }
restore(p) {
if (p.target != this)
throw new Error('invalid pin');
if (this.#limits.length > 0 && (p.bufi != this.#bufi || p.i != this.#i)) {
let np = this.save();
this.unconsumeLimit(p.length(np));
np.destroy();
}
if (p.bufi < this.#minBufi || (p.bufi == this.#minBufi && p.i < this.#minI)) {
this.#broken = true;
}
this.#bufi = p.bufi;
this.#i = p.i;
}
cleanup() {
for (let i=this.#buf0;;i++) {
if (i >= this.#bufi) break;
if (this.#pins[i]) break;
delete this.#bufs[i];
this.#buf0++;
}
if (!(this.#buf0 in this.#bufs || this.#pins[this.#buf0])) {
this.#bufs = [];
this.#pins = [];
this.#i = 0;
this.#bufi = 0;
this.#buf0 = 0;
this.#minBufi = 0;
this.#minI = 0;
}
}
getLimit() {
if (this.#limits.length == 0)
return Infinity
return this.#limits[this.#limits.length-1].remain;
}
setLimit(limit) {
if (this.getLimit() <= limit) return null;
limit = new StreamoidLimit(limit);
this.#limits.push(limit);
return limit;
}
unsetLimit(limit) {
if (limit == null) return;
if (this.#limits[this.#limits.length-1] != limit)
throw new Error('unset jump over multiple limits');
this.#limits.pop();
}
consumeLimit(count) {
if (this.#limits.length == 0)
return true;
if (count > this.getLimit())
throw new Error('limit overflowed');
this.#limits.forEach(e => {
e.remain -= count;
});
return true;
}
unconsumeLimit(count) {
this.#limits.forEach(e => {
e.remain += count;
if (e.remain > e.total)
throw new Error('Backtrack beyond a limit');
})
}
avail() {
let avail = this.#bufs[this.#bufi].length - this.#i;
let limit = this.getLimit();
if (limit < avail) avail = limit;
return avail;
}
peek() {
if (arguments.length >= 1) {
return this.#bufs[this.#bufi].slice(this.#i, arguments[0] == null ? undefined : (this.#i + arguments[0]))
}
return this.#bufs[this.#bufi][this.#i];
}
consume(count) {
count ??= 1;
this.consumeLimit(count);
let v;
if (this.#yielders.length > 0) {
v = this.peek(count);
}
this.#i += count;
if (this.#i >= this.#bufs[this.#bufi].length) {
this.#bufi++;
this.#i = 0;
}
this.cleanup();
if (this.#yielders.length > 0) {
this.#yielders.forEach(y => {
this.yield(y.name, v);
});
}
}
eof() {
return this.#bufs[this.#bufi] == null || (this.getLimit() == 0);
}
supply(s) {
if (!s) s = null;
if (s == null || s.length > 0)
this.#bufs.push(s);
}
run(f) {
while (1) {
let v = f.next();
if (v.done || this.wait()) return v;
}
}
}
let StreamoidPin = Streamoid.Pin;
let StreamoidLimit = Streamoid.Limit;
export const _tests = (() => {
function die(msg) { throw new Error(msg??'bad') }
function matchObject(a, b) {
if (typeof a != 'object' || typeof b != 'object')
return a == b;
function keySet(s) {
return new Set((function*(s){for(let i in s) yield i; if(Array.isArray(s)) yield 'length';})(s))
}
let ak = keySet(a), bk = keySet(b);
if (ak.symmetricDifference(bk).size) return false;
for (let k of ak)
if (!matchObject(a[k],b[k])) return (console.log('uneq',a,b),false);
return true;
}
return {
streamoid() {
let s = new Streamoid;
let sp = new StreamoidPriv(s);
function eatStr(str) {
for (let c of str) {
if (s.wait()) die('wait');
if (s.eof()) die('eof');
if (s.peek() != c) die('peek');
s.consume();
}
}
s.supply('Hello: sigma');
eatStr('Hello: ');
let a = s.save();
eatStr('sigma');
if (!s.wait()) die('nowait')
s.supply(' ligma;');
eatStr(' ligma');
let b = s.save();
if (a.slice(b) != 'sigma ligma') die('noligma');
if (a.length(b) != 'sigma ligma'.length) die('liglen');
s.restore(a);
eatStr('sigma ligma');
a.destroy();
b.destroy();
eatStr(';');
if (sp.i != 0 || sp.bufi != 0 || sp.bufs.length != 0 || sp.pins.length != 0) die('badform')
s.supply()
if (sp.i != 0 || sp.bufi != 0 || sp.bufs.length != 1 || sp.bufs[0]!=null || sp.pins.length != 0) die('badform')
if (s.wait()) die('wait')
if (!s.eof()) die('noeof')
},
async sussy() {
let { test } = await import('./test/sussy.mjs');
let res = test(regexoid);
if (!matchObject(
res,
[
{ done: false },
{ done: false },
{ done: false },
{
done: true,
match: {
meow: [ 'sussy' ],
meower: [ true, '2', '3' ],
unknownFields: [ 'Unknownus(hmm?)', 'meower(BAD)' ],
sigma: [ 'meower' ]
},
leftoverCount: 0,
leftover: ''
}
]
)) die('unmatch');
},
async http() {
let httpParser = (await import('./test/http.mjs')).httpParser(regexoid);
let req = (await import('node:fs')).readFileSync(new URL('./test/req/req.txt', import.meta.url))+'';
let dbg=false;
for (let cs=1; cs<req.length; cs++) {
if(dbg)console.log('\n=== CHUNKSIZE: ',cs,'===\n');
let result = {};
let proc = httpParser();
for (let [k, v] of (function*(){
for (let i=0; i<=req.length;) {
let chunksize = cs;
let chunk = req.slice(i, i+chunksize);
let res = proc(chunk);
if(dbg)console.dir({
length: chunk.length,
chunk,
result: res,
backBuffer: proc.stream.backBuffer,
frontBuffer: proc.stream.frontBuffer,
}, {depth: null});
if (res.output)
yield *res.output;
if (res.done) {
if (!res.match)
die('Bad');
break;
}
i += chunk.length;
if (chunk.length == 0 && !res.done)
die('Infinity')
}
})()) {
if(dbg)console.log({k,v});
if (!(k in result))
result[k] = v
else
result[k] += v;
}
if (!matchObject(result,
{
type: 'h1',
method: 'PUT',
path: '/',
fields: {
host: [ 'localhost:4534' ],
'user-agent': [ 'curl/8.6.0' ],
accept: [ '*/*' ],
'transfer-encoding': [ 'chunked' ],
connection: [ 'Upgrade, HTTP2-Settings' ],
upgrade: [ 'h2c' ],
'http2-settings': [ 'AAMAAABkAAQAoAAAAAIAAAAA' ]
},
body: 'Hello, world!\n',
trailers: {}
}
))
die('unmatch');
}
}
}
})();
let r;r = {
seq(...elem) {
elem = elem.map(e => {
if (e instanceof AstNode) {
return e;
} else if (e.length != null) {
return new AstString(e);
} else {
throw new Error('bad element: '+e);
}
})
elem.reduce((a,b) => a.chain(b));
return elem[0];
},
noop() {
return new AstNode;
},
any(...elem) {
elem = elem.map(e => r.seq(e));
return new AstDisjunction(...elem);
},
rep: new (class extends XFunction {
#strict = false; #label = null;
constructor(strict, label) {
super((lim, ...elem) => {
elem = r.seq(...elem);
return new AstRepeat(lim, elem, this.#strict, this.#label);
});
this.#strict = strict;
this.#label = label;
}
get strict() {
return new this.constructor(
true,
this.#label
);
}
label(label) {
label = label+'';
return new this.constructor(
this.#strict,
label
);
}
}),
markBreak(label) {
if (label == null)
label = true;
else
label = label+'';
return new AstMark(breakSymbol, label, false);
},
limit(lim, ...elem) {
elem = r.seq(...elem);
return new AstLimit(lim, elem);
},
captureOutput(key, ...elem) {
elem = r.seq(...elem);
return new AstCaptureYield(key, elem);
},
cond(...conds) {
conds = conds.map(([b, ...elem]) => [r.seq(b), r.seq(...elem)]);
return new AstCond(conds);
},
output(key, value) {
return new AstYield(key, value);
},
abort() { return new AstAbort; },
map(f) { return new AstResultEdit(f); },
chars: new (class extends XFunction {
#mode;
constructor(mode) {
super((...arg) => {
return ({
func: this.#newFunc,
any: this.#newAny,
none: this.#newNone,
})[this.#mode??'func'].call(this,...arg);
});
this.#mode = mode;
}
#newFunc(pred) { return new AstChars(pred); }
#newAny(...ops) { return new AstChars(new CharsPredicate(false, ...ops)); }
#newNone(...ops) { return new AstChars(new CharsPredicate(true, ...ops)); }
any(...arg) { return (new this.constructor('any'))(...arg) }
none(...arg) { return (new this.constructor('none'))(...arg) }
}),
...((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 new AstMark(key, value, multi);
},
capture(multi, key, ...elem) {
elem = r.seq(...elem);
return new AstCapture(key, elem, multi);
},
}),
};
export function regexoid(f) {
let code = compileAst(r.seq(f(r)));
return () => {
let stream = new Streamoid;
let regex = code(stream);
let done = null;
let fn = s => {
if (done && (s == null || s == '')) {
return done;
} else if (done && s != null) {
return { done: true, match: false, leftoverCount: s.length, leftover: s };
}
stream.supply(s);
let v = stream.run(regex);
let extra = {};
if (stream.hasOutput()) {
extra.output = stream.readOutput();
}
if (v.done) {
if (!v.value) {
done = null;
return {
done: true,
match: false,
...extra,
};
}
let sv1 = stream.save();
let sv2 = stream.saveEnd();
let leftover = null;
done = {
done: true,
match: v.value,
leftoverCount: sv1.length(sv2),
get leftover() {
if (leftover != null)
return leftover;
leftover = sv1.slice(sv2);
sv1.destroy();
sv2.destroy();
return leftover;
},
...extra
}
let ret = done;
if (done.leftoverCount) {
done = null;
}
return ret;
}
return { done: false, ...extra };
}
fn.stream = stream;
return fn;
}
}