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 { 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 { 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; } }