From 493cf466b351472f8639ee6ad2ce673e60adda4f Mon Sep 17 00:00:00 2001 From: barsdeveloper Date: Sun, 19 Nov 2023 20:55:32 +0100 Subject: [PATCH] Fix trivial parser removal --- dist/regexer.js | 40 +++++++++++-------- dist/regexer.min.js | 2 +- src/parser/Parser.js | 4 +- src/parser/TimesParser.js | 2 +- src/transformers/InlineParsersTransformer.js | 8 ++-- src/transformers/ParentChildTransformer.js | 12 +++--- .../RemoveDiscardedMapTransformer.js | 2 + .../RemoveTrivialParsersTransformer.js | 35 ++++++++-------- tests/transformers.spec.js | 12 ++++++ 9 files changed, 70 insertions(+), 47 deletions(-) diff --git a/dist/regexer.js b/dist/regexer.js index 61f4a34..68b33f9 100644 --- a/dist/regexer.js +++ b/dist/regexer.js @@ -35,8 +35,8 @@ class Parser { } /** - * @template {Parser[]} T - * @param {T} parsers + * @template {Parser[]} P + * @param {P} parsers * @returns {Parser} */ wrap(...parsers) { @@ -85,25 +85,35 @@ class Parser { /** * @param {Context} context - * @param {Parser} other + * @param {Parser} rhs * @param {Boolean} strict */ - equals(context, other, strict) { - let self = /** @type {Parser} */(this); - if (self === other) { + equals(context, rhs, strict) { + let lhs = /** @type {Parser} */(this); + if (lhs === rhs) { return true } if (!strict) { - self = this.actualParser(); - other = other.actualParser(); + lhs = this.actualParser(); + rhs = rhs.actualParser(); } - let memoized = context.visited.get(self, other); + if ( + rhs instanceof lhs.constructor && !(lhs instanceof rhs.constructor) + // @ts-expect-error + || rhs.resolve && !lhs.resolve + ) { + // Take advantage of polymorphism or compare a lazy against a non lazy (not the other way around) + const temp = lhs; + lhs = rhs; + rhs = temp; + } + let memoized = context.visited.get(lhs, rhs); if (memoized !== undefined) { return memoized } else if (memoized === undefined) { - context.visited.set(self, other, true); - memoized = self.doEquals(context, other, strict); - context.visited.set(self, other, memoized); + context.visited.set(lhs, rhs, true); + memoized = lhs.doEquals(context, rhs, strict); + context.visited.set(lhs, rhs, memoized); } return memoized } @@ -877,7 +887,7 @@ class TimesParser extends Parser { * @param {P} parsers */ wrap(...parsers) { - return new TimesParser(parsers[0], this.#min, this.#max) + return /** @type {TimesParser} */(new TimesParser(parsers[0], this.#min, this.#max)) } /** @@ -1012,10 +1022,6 @@ class Regexer { static equals(lhs, rhs, strict = false) { const a = lhs.getParser(); const b = rhs.getParser(); - if (b instanceof a.constructor && !(a instanceof b.constructor)) { - // typeof b extends typeof a, invert to take advantage of polymorphism - return b.equals(Reply.makeContext(rhs), a, strict) - } return a.equals(Reply.makeContext(lhs), b, strict) } diff --git a/dist/regexer.min.js b/dist/regexer.min.js index 5bcaa00..ea89f23 100644 --- a/dist/regexer.min.js +++ b/dist/regexer.min.js @@ -1 +1 @@ -class e{static indentation=" ";static isActualParser=!0;static mergeResults(e,r){return r?{status:e.status,position:e.position,value:e.value}:e}dominates(e){}unwrap(){return[]}wrap(...e){return null}parse(e,r){return null}actualParser(e=[],r=[]){let t=(!this.constructor.isActualParser||e.find((e=>this instanceof e)))&&!r.find((e=>this instanceof e)),s=t?this.unwrap():void 0;return t&&=1===s?.length,t?s[0].actualParser(e,r):this}withActualParser(e,r=[],t=[]){let s=(!this.constructor.isActualParser||r.some((e=>this instanceof e)))&&!t.some((e=>this instanceof e)),a=s?this.unwrap():void 0;return s&&=1===a?.length,s?this.wrap(a[0].withActualParser(e,r,t)):e}equals(e,r,t){let s=this;if(s===r)return!0;t||(s=this.actualParser(),r=r.actualParser());let a=e.visited.get(s,r);return void 0!==a||void 0===a&&(e.visited.set(s,r,!0),a=s.doEquals(e,r,t),e.visited.set(s,r,a)),a}doEquals(e,r,t){return!1}toString(e=0){return`${this.constructor.name} does not implement toString()`}}class r{#e=new Map;get(e,r){return this.#e.get(e)?.get(r)}set(e,r,t){let s=this.#e.get(e);return s||(s=new Map,this.#e.set(e,s)),s.set(r,t),this}setGet(e,r,t){return this.set(e,r,t),t}}class t{static makeSuccess(e,r){return{status:!0,value:r,position:e}}static makeFailure(e){return{status:!1,value:null,position:e}}static makeContext(e,t=""){return{regexer:e,input:t,visited:new r}}}class s extends e{#r;get parsers(){return this.#r}constructor(...e){super(),this.#r=e}unwrap(){return[...this.#r]}wrap(...e){return new s(...e)}parse(e,r){let s;for(let t=0;ts+e.toString(r+1))).join("\n"+s+"|\n")+"\n"+t+">"}}class a extends e{#t;get parser(){return this.#t}#s;constructor(e,r){super(),this.#t=e,this.#s=r}unwrap(){return[this.#t]}wrap(...e){return new a(e[0],this.#s)}parse(e,r){let s=this.#t.parse(e,r);return s.status?(s=this.#s(s.value,e.input,s.position)?.getParser().parse(e,s.position)??t.makeFailure(s.position),s):s}doEquals(e,r,t){return r instanceof a&&this.#s===r.#s&&this.#t.equals(e,r.parser,t)}toString(e=0){return this.#t.toString(e)+" => chained"}}class n extends e{parse(e,r){return t.makeFailure(r)}doEquals(e,r,t){return r instanceof n}toString(e=0){return""}}class i extends e{#t;static isActualParser=!1;#a;constructor(e){super(),this.#t=e}resolve(){return this.#a||(this.#a=this.#t().getParser()),this.#a}unwrap(){return[this.resolve()]}wrap(...e){const r=this.#t().constructor;return new i((()=>new r(e[0])))}parse(e,r){return this.resolve(),this.#a.parse(e,r)}doEquals(e,r,t){if(r instanceof i){if(this.#t===r.#t)return!0;r=r.resolve()}return this.resolve(),this.#a.equals(e,r,t)}toString(e=0){return this.resolve().toString(e)}}class u extends e{#t;get parser(){return this.#t}#n;get type(){return this.#n}static Type={NEGATIVE_AHEAD:"?!",NEGATIVE_BEHIND:"?80||r.includes("\n"))&&(r="( ... ) => { ... }"),this.#t.toString(e)+` -> map<${r}>`}}class o extends e{#u;get regexp(){return this.#u}#p;#o;regexpGenerated=!1;regexpFullyGenerated=!0;cyclomaticComplexity=1;constructor(e,r){super(),e instanceof RegExp?(this.#u=e,this.#p=new RegExp(`^(?:${e.source})`,e.flags)):e instanceof o&&(this.#u=e.#u,this.#p=e.#p,this.regexpGenerated=e.regexpGenerated,this.regexpFullyGenerated=e.regexpFullyGenerated,this.cyclomaticComplexity=e.cyclomaticComplexity),this.#o=r}isFullyGenerated(){return this.regexpFullyGenerated}parse(e,r){const s=this.#p.exec(e.input.substring(r));return s?t.makeSuccess(r+s[0].length,this.#o>=0?s[this.#o]:s):t.makeFailure(r)}doEquals(e,r,t){return r instanceof o&&(!t||this.#o===r.#o)&&this.#u.source===r.#u.source}toString(e=0){return"/"+this.#u.source+"/"}}class c extends e{#r;get parsers(){return this.#r}constructor(...e){super(),this.#r=e}unwrap(){return[...this.#r]}wrap(...e){return new c(...e)}parse(e,r){const s=new Array(this.#r.length),a=t.makeSuccess(r,s);for(let r=0;rs+e.toString(r+1))).join("\n")+"\n"+t+">"}}class h extends e{#c;get value(){return this.#c}constructor(e){super(),this.#c=e}dominates(e){if((e=e.actualParser())instanceof h){return e.#c.startsWith(this.#c)}}parse(e,r){const s=r+this.#c.length,a=e.input.substring(r,s);return this.#c===a?t.makeSuccess(s,this.#c):t.makeFailure(r)}doEquals(e,r,t){return r instanceof h&&this.#c===r.#c}toString(e=0){const r=this.value.replaceAll("\n","\\n");return this.value.length>1||" "===this.value[0]?`"${r.replaceAll('"','\\"')}"`:r}}class l extends e{#c;constructor(e){super(),this.#c=e}parse(e,r){return t.makeSuccess(r,this.#c)}doEquals(e,r,t){return r instanceof l}toString(e=0){return""}}class m extends e{#t;get parser(){return this.#t}#h;get min(){return this.#h}#l;get max(){return this.#l}constructor(e,r=0,t=Number.POSITIVE_INFINITY){if(super(),r>t)throw new Error("Min is more than max");this.#t=e,this.#h=r,this.#l=t}unwrap(){return[this.#t]}wrap(...e){return new m(e[0],this.#h,this.#l)}parse(e,r){const s=t.makeSuccess(r,[]);for(let r=0;r=this.#h?s:t;s.value.push(t.value),s.position=t.position}return s}doEquals(e,r,t){return r instanceof m&&this.#h===r.#h&&this.#l===r.#l&&this.#t.equals(e,r.#t,t)}toString(e=0){return this.parser.toString(e)+(0===this.#h&&1===this.#l?"?":0===this.#h&&this.#l===Number.POSITIVE_INFINITY?"*":1===this.#h&&this.#l===Number.POSITIVE_INFINITY?"+":"{"+this.#h+(this.#h!==this.#l?",":this.#l!==Number.POSITIVE_INFINITY?this.#l:"")+"}")}}class g{#t;#m;#g=new Map;static#d=e=>Number(e);static#x=([e,r])=>e;static#w=([e,r])=>r;static#E=([e,r])=>[e,...r];static#f=e=>e instanceof Array?e.join(""):e;static#S=e=>String.raw`[^${e}\\]*(?:\\.[^${e}\\]*)*`;static#v=/[-\+]?(?:\d*\.)?\d+/;static number=g.regexp(new RegExp(g.#v.source+String.raw`(?!\.)`)).map(g.#d);static numberNatural=g.regexp(/\d+/).map(g.#d);static numberExponential=g.regexp(new RegExp(g.#v.source+String.raw`(?:[eE][\+\-]?\d+)?(?!\.)`)).map(g.#d);static numberUnit=g.regexp(/\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/).map(g.#d);static whitespace=g.regexp(/\s+/);static whitespaceInline=g.regexp(/[^\S\n]+/);static whitespaceMultiline=g.regexp(/\s*?\n\s*/);static optWhitespace=g.regexp(/\s*/);static doubleQuotedString=g.regexpGroups(new RegExp(`"(${g.#S('"')})"`)).map(g.#w);static singleQuotedString=g.regexpGroups(new RegExp(`'(${g.#S("'")})'`)).map(g.#w);static backtickQuotedString=g.regexpGroups(new RegExp(`\`(${g.#S("`")})\``)).map(g.#w);constructor(e,r=!1){this.#t=e,this.#m=r}static optimize(e){}static equals(e,r,s=!1){const a=e.getParser(),n=r.getParser();return n instanceof a.constructor&&!(a instanceof n.constructor)?n.equals(t.makeContext(r),a,s):a.equals(t.makeContext(e),n,s)}getParser(){return this.#t}run(e){const r=this.#t.parse(t.makeContext(this,e),0);return r.status&&r.position===e.length?r:t.makeFailure(r.position)}parse(e){const r=this.run(e);if(!r.status)throw new Error("Parsing error");return r.value}static str(e){return new g(new h(e))}static regexp(e,r=0){return new g(new o(e,r))}static regexpGroups(e){return new g(new o(e,-1))}static success(e=void 0){return new g(new l(e))}static failure(){return new g(new n)}static seq(...e){return new g(new c(...e.map((e=>e.getParser()))))}static alt(...e){return new g(new s(...e.map((e=>e.getParser()))))}static lookahead(e){return new g(new u(e.getParser(),u.Type.POSITIVE_AHEAD))}static lazy(e){return new g(new i(e))}times(e,r=e){return new g(new m(this.#t,e,r))}many(){return this.times(0,Number.POSITIVE_INFINITY)}atLeast(e){return this.times(e,Number.POSITIVE_INFINITY)}atMost(e){return this.times(0,e)}opt(){return g.alt(this,g.success(null))}sepBy(e,r=!1){return g.seq(this,g.seq(e,this).map(g.#w).many()).map(g.#E)}skipSpace(){return g.seq(this,g.optWhitespace).map(g.#x)}map(e){return new g(new p(this.#t,e))}chain(e){return new g(new a(this.#t,e))}assert(e){return this.chain(((r,t,s)=>e(r,t,s)?g.success(r):g.failure()))}join(e=""){return this.map(g.#f)}toString(r=0,t=!1){return(t?"\n"+e.indentation.repeat(r):"")+this.#t.toString(r)}}export{g as default}; +class e{static indentation=" ";static isActualParser=!0;static mergeResults(e,r){return r?{status:e.status,position:e.position,value:e.value}:e}dominates(e){}unwrap(){return[]}wrap(...e){return null}parse(e,r){return null}actualParser(e=[],r=[]){let t=(!this.constructor.isActualParser||e.find((e=>this instanceof e)))&&!r.find((e=>this instanceof e)),s=t?this.unwrap():void 0;return t&&=1===s?.length,t?s[0].actualParser(e,r):this}withActualParser(e,r=[],t=[]){let s=(!this.constructor.isActualParser||r.some((e=>this instanceof e)))&&!t.some((e=>this instanceof e)),a=s?this.unwrap():void 0;return s&&=1===a?.length,s?this.wrap(a[0].withActualParser(e,r,t)):e}equals(e,r,t){let s=this;if(s===r)return!0;if(t||(s=this.actualParser(),r=r.actualParser()),r instanceof s.constructor&&!(s instanceof r.constructor)||r.resolve&&!s.resolve){const e=s;s=r,r=e}let a=e.visited.get(s,r);return void 0!==a||void 0===a&&(e.visited.set(s,r,!0),a=s.doEquals(e,r,t),e.visited.set(s,r,a)),a}doEquals(e,r,t){return!1}toString(e=0){return`${this.constructor.name} does not implement toString()`}}class r{#e=new Map;get(e,r){return this.#e.get(e)?.get(r)}set(e,r,t){let s=this.#e.get(e);return s||(s=new Map,this.#e.set(e,s)),s.set(r,t),this}setGet(e,r,t){return this.set(e,r,t),t}}class t{static makeSuccess(e,r){return{status:!0,value:r,position:e}}static makeFailure(e){return{status:!1,value:null,position:e}}static makeContext(e,t=""){return{regexer:e,input:t,visited:new r}}}class s extends e{#r;get parsers(){return this.#r}constructor(...e){super(),this.#r=e}unwrap(){return[...this.#r]}wrap(...e){return new s(...e)}parse(e,r){let s;for(let t=0;ts+e.toString(r+1))).join("\n"+s+"|\n")+"\n"+t+">"}}class a extends e{#t;get parser(){return this.#t}#s;constructor(e,r){super(),this.#t=e,this.#s=r}unwrap(){return[this.#t]}wrap(...e){return new a(e[0],this.#s)}parse(e,r){let s=this.#t.parse(e,r);return s.status?(s=this.#s(s.value,e.input,s.position)?.getParser().parse(e,s.position)??t.makeFailure(s.position),s):s}doEquals(e,r,t){return r instanceof a&&this.#s===r.#s&&this.#t.equals(e,r.parser,t)}toString(e=0){return this.#t.toString(e)+" => chained"}}class n extends e{parse(e,r){return t.makeFailure(r)}doEquals(e,r,t){return r instanceof n}toString(e=0){return""}}class i extends e{#t;static isActualParser=!1;#a;constructor(e){super(),this.#t=e}resolve(){return this.#a||(this.#a=this.#t().getParser()),this.#a}unwrap(){return[this.resolve()]}wrap(...e){const r=this.#t().constructor;return new i((()=>new r(e[0])))}parse(e,r){return this.resolve(),this.#a.parse(e,r)}doEquals(e,r,t){if(r instanceof i){if(this.#t===r.#t)return!0;r=r.resolve()}return this.resolve(),this.#a.equals(e,r,t)}toString(e=0){return this.resolve().toString(e)}}class u extends e{#t;get parser(){return this.#t}#n;get type(){return this.#n}static Type={NEGATIVE_AHEAD:"?!",NEGATIVE_BEHIND:"?80||r.includes("\n"))&&(r="( ... ) => { ... }"),this.#t.toString(e)+` -> map<${r}>`}}class o extends e{#u;get regexp(){return this.#u}#p;#o;regexpGenerated=!1;regexpFullyGenerated=!0;cyclomaticComplexity=1;constructor(e,r){super(),e instanceof RegExp?(this.#u=e,this.#p=new RegExp(`^(?:${e.source})`,e.flags)):e instanceof o&&(this.#u=e.#u,this.#p=e.#p,this.regexpGenerated=e.regexpGenerated,this.regexpFullyGenerated=e.regexpFullyGenerated,this.cyclomaticComplexity=e.cyclomaticComplexity),this.#o=r}isFullyGenerated(){return this.regexpFullyGenerated}parse(e,r){const s=this.#p.exec(e.input.substring(r));return s?t.makeSuccess(r+s[0].length,this.#o>=0?s[this.#o]:s):t.makeFailure(r)}doEquals(e,r,t){return r instanceof o&&(!t||this.#o===r.#o)&&this.#u.source===r.#u.source}toString(e=0){return"/"+this.#u.source+"/"}}class c extends e{#r;get parsers(){return this.#r}constructor(...e){super(),this.#r=e}unwrap(){return[...this.#r]}wrap(...e){return new c(...e)}parse(e,r){const s=new Array(this.#r.length),a=t.makeSuccess(r,s);for(let r=0;rs+e.toString(r+1))).join("\n")+"\n"+t+">"}}class h extends e{#c;get value(){return this.#c}constructor(e){super(),this.#c=e}dominates(e){if((e=e.actualParser())instanceof h){return e.#c.startsWith(this.#c)}}parse(e,r){const s=r+this.#c.length,a=e.input.substring(r,s);return this.#c===a?t.makeSuccess(s,this.#c):t.makeFailure(r)}doEquals(e,r,t){return r instanceof h&&this.#c===r.#c}toString(e=0){const r=this.value.replaceAll("\n","\\n");return this.value.length>1||" "===this.value[0]?`"${r.replaceAll('"','\\"')}"`:r}}class l extends e{#c;constructor(e){super(),this.#c=e}parse(e,r){return t.makeSuccess(r,this.#c)}doEquals(e,r,t){return r instanceof l}toString(e=0){return""}}class m extends e{#t;get parser(){return this.#t}#h;get min(){return this.#h}#l;get max(){return this.#l}constructor(e,r=0,t=Number.POSITIVE_INFINITY){if(super(),r>t)throw new Error("Min is more than max");this.#t=e,this.#h=r,this.#l=t}unwrap(){return[this.#t]}wrap(...e){return new m(e[0],this.#h,this.#l)}parse(e,r){const s=t.makeSuccess(r,[]);for(let r=0;r=this.#h?s:t;s.value.push(t.value),s.position=t.position}return s}doEquals(e,r,t){return r instanceof m&&this.#h===r.#h&&this.#l===r.#l&&this.#t.equals(e,r.#t,t)}toString(e=0){return this.parser.toString(e)+(0===this.#h&&1===this.#l?"?":0===this.#h&&this.#l===Number.POSITIVE_INFINITY?"*":1===this.#h&&this.#l===Number.POSITIVE_INFINITY?"+":"{"+this.#h+(this.#h!==this.#l?",":this.#l!==Number.POSITIVE_INFINITY?this.#l:"")+"}")}}class g{#t;#m;#g=new Map;static#d=e=>Number(e);static#x=([e,r])=>e;static#w=([e,r])=>r;static#E=([e,r])=>[e,...r];static#f=e=>e instanceof Array?e.join(""):e;static#S=e=>String.raw`[^${e}\\]*(?:\\.[^${e}\\]*)*`;static#v=/[-\+]?(?:\d*\.)?\d+/;static number=g.regexp(new RegExp(g.#v.source+String.raw`(?!\.)`)).map(g.#d);static numberNatural=g.regexp(/\d+/).map(g.#d);static numberExponential=g.regexp(new RegExp(g.#v.source+String.raw`(?:[eE][\+\-]?\d+)?(?!\.)`)).map(g.#d);static numberUnit=g.regexp(/\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/).map(g.#d);static whitespace=g.regexp(/\s+/);static whitespaceInline=g.regexp(/[^\S\n]+/);static whitespaceMultiline=g.regexp(/\s*?\n\s*/);static optWhitespace=g.regexp(/\s*/);static doubleQuotedString=g.regexpGroups(new RegExp(`"(${g.#S('"')})"`)).map(g.#w);static singleQuotedString=g.regexpGroups(new RegExp(`'(${g.#S("'")})'`)).map(g.#w);static backtickQuotedString=g.regexpGroups(new RegExp(`\`(${g.#S("`")})\``)).map(g.#w);constructor(e,r=!1){this.#t=e,this.#m=r}static optimize(e){}static equals(e,r,s=!1){const a=e.getParser(),n=r.getParser();return a.equals(t.makeContext(e),n,s)}getParser(){return this.#t}run(e){const r=this.#t.parse(t.makeContext(this,e),0);return r.status&&r.position===e.length?r:t.makeFailure(r.position)}parse(e){const r=this.run(e);if(!r.status)throw new Error("Parsing error");return r.value}static str(e){return new g(new h(e))}static regexp(e,r=0){return new g(new o(e,r))}static regexpGroups(e){return new g(new o(e,-1))}static success(e=void 0){return new g(new l(e))}static failure(){return new g(new n)}static seq(...e){return new g(new c(...e.map((e=>e.getParser()))))}static alt(...e){return new g(new s(...e.map((e=>e.getParser()))))}static lookahead(e){return new g(new u(e.getParser(),u.Type.POSITIVE_AHEAD))}static lazy(e){return new g(new i(e))}times(e,r=e){return new g(new m(this.#t,e,r))}many(){return this.times(0,Number.POSITIVE_INFINITY)}atLeast(e){return this.times(e,Number.POSITIVE_INFINITY)}atMost(e){return this.times(0,e)}opt(){return g.alt(this,g.success(null))}sepBy(e,r=!1){return g.seq(this,g.seq(e,this).map(g.#w).many()).map(g.#E)}skipSpace(){return g.seq(this,g.optWhitespace).map(g.#x)}map(e){return new g(new p(this.#t,e))}chain(e){return new g(new a(this.#t,e))}assert(e){return this.chain(((r,t,s)=>e(r,t,s)?g.success(r):g.failure()))}join(e=""){return this.map(g.#f)}toString(r=0,t=!1){return(t?"\n"+e.indentation.repeat(r):"")+this.#t.toString(r)}}export{g as default}; diff --git a/src/parser/Parser.js b/src/parser/Parser.js index 8e80bf9..2962a76 100644 --- a/src/parser/Parser.js +++ b/src/parser/Parser.js @@ -35,8 +35,8 @@ export default class Parser { } /** - * @template {Parser[]} T - * @param {T} parsers + * @template {Parser[]} P + * @param {P} parsers * @returns {Parser} */ wrap(...parsers) { diff --git a/src/parser/TimesParser.js b/src/parser/TimesParser.js index 5e12325..14146c1 100644 --- a/src/parser/TimesParser.js +++ b/src/parser/TimesParser.js @@ -42,7 +42,7 @@ export default class TimesParser extends Parser { * @param {P} parsers */ wrap(...parsers) { - return new TimesParser(parsers[0], this.#min, this.#max) + return /** @type {TimesParser} */(new TimesParser(parsers[0], this.#min, this.#max)) } /** diff --git a/src/transformers/InlineParsersTransformer.js b/src/transformers/InlineParsersTransformer.js index 741ce0a..6591af4 100644 --- a/src/transformers/InlineParsersTransformer.js +++ b/src/transformers/InlineParsersTransformer.js @@ -17,10 +17,12 @@ export default class InlineParsersTransformer extends ParentChildTransformer { * @returns {Parser[]} */ doTransformChild(parent, child, index) { - return parent instanceof AlternativeParser && child instanceof AlternativeParser + if (parent instanceof AlternativeParser && child instanceof AlternativeParser || parent instanceof SequenceParser && child instanceof SequenceParser - ? child.unwrap() - : null + ) { + return child.unwrap() + } + return [child] } } diff --git a/src/transformers/ParentChildTransformer.js b/src/transformers/ParentChildTransformer.js index 5239088..127473f 100644 --- a/src/transformers/ParentChildTransformer.js +++ b/src/transformers/ParentChildTransformer.js @@ -38,7 +38,7 @@ export default class ParentChildTransformer extends Transformer { /** @type {UnionFromArray} */(current), i ) - if (newParent) { + if (newParent != parser) { return this.doTransform(newParent) } const newChildren = this.doTransformChild( @@ -46,7 +46,7 @@ export default class ParentChildTransformer extends Transformer { /** @type {UnionFromArray} */(current), i ) - if (newChildren) { + if (newChildren.length !== 1 || newChildren[0] !== current) { children.splice( i, 1, @@ -71,12 +71,13 @@ export default class ParentChildTransformer extends Transformer { }) } if (changed) { - return parser.wrap(...children) + return this.doTransform(parser.wrap(...children)) } return parser } /** + * Replace the parent parser with another parser * @protected * @param {UnionFromArray} parent * @param {UnionFromArray} child @@ -84,10 +85,11 @@ export default class ParentChildTransformer extends Transformer { * @returns {Parser?} */ doTransformParent(parent, child, index) { - return null + return parent } /** + * Replace the given child with other children * @protected * @param {UnionFromArray} parent * @param {UnionFromArray} child @@ -95,6 +97,6 @@ export default class ParentChildTransformer extends Transformer { * @returns {Parser[]?} */ doTransformChild(parent, child, index) { - return null + return [child] } } diff --git a/src/transformers/RemoveDiscardedMapTransformer.js b/src/transformers/RemoveDiscardedMapTransformer.js index 13d8ee5..2e647ff 100644 --- a/src/transformers/RemoveDiscardedMapTransformer.js +++ b/src/transformers/RemoveDiscardedMapTransformer.js @@ -20,6 +20,7 @@ export default class RemoveDiscardedMapTransformer extends ParentChildTransforme if (parent instanceof MapParser && child instanceof LookaroundParser) { return parent.parser.withActualParser(child, this.traverse, this.opaque) } + return parent } /** @@ -32,5 +33,6 @@ export default class RemoveDiscardedMapTransformer extends ParentChildTransforme if (parent instanceof LookaroundParser && child instanceof MapParser) { return [child.parser] } + return [child] } } diff --git a/src/transformers/RemoveTrivialParsersTransformer.js b/src/transformers/RemoveTrivialParsersTransformer.js index 370dedc..b02a28a 100644 --- a/src/transformers/RemoveTrivialParsersTransformer.js +++ b/src/transformers/RemoveTrivialParsersTransformer.js @@ -1,19 +1,22 @@ import AlternativeParser from "../parser/AlternativeParser.js" +import CapturingGroupParser from "../parser/CapturingGroupParser.js" import FailureParser from "../parser/FailureParser.js" +import NonCapturingGroupParser from "../parser/NonCapturingGroupParser.js" import ParentChildTransformer from "./ParentChildTransformer.js" import SequenceParser from "../parser/SequenceParser.js" import SuccessParser from "../parser/SuccessParser.js" +import TimesParser from "../parser/TimesParser.js" -/** @extends {ParentChildTransformer<[AlternativeParser, SequenceParser], [SuccessParser, FailureParser]>} */ +/** @extends {ParentChildTransformer<[AlternativeParser, SequenceParser, TimesParser, CapturingGroupParser, NonCapturingGroupParser], [SuccessParser, FailureParser]>} */ export default class RemoveTrivialParsersTransformer extends ParentChildTransformer { constructor() { - super([AlternativeParser, SequenceParser], [SuccessParser, FailureParser]) + super([AlternativeParser, SequenceParser, TimesParser, CapturingGroupParser, NonCapturingGroupParser], [SuccessParser, FailureParser]) } /** * @protected - * @param {AlternativeParser[]> | SequenceParser[]>} parent + * @param {AlternativeParser[]> | SequenceParser[]> | TimesParser> | CapturingGroupParser | NonCapturingGroupParser} parent * @param {SuccessParser | FailureParser} child * @param {Number} index * @returns {Parser?} @@ -22,32 +25,28 @@ export default class RemoveTrivialParsersTransformer extends ParentChildTransfor if (parent instanceof AlternativeParser && child instanceof SuccessParser) { return parent.wrap(...parent.parsers.slice(0, index)) } - if ( - parent instanceof AlternativeParser && child instanceof FailureParser - || parent instanceof SequenceParser && child instanceof SuccessParser - ) { - const children = parent.unwrap() - children.splice(index, 1) - return parent.wrap(...children) - } if (parent instanceof SequenceParser && child instanceof FailureParser) { - return new FailureParser() + return child + } + if (parent instanceof TimesParser || parent instanceof CapturingGroupParser || parent instanceof NonCapturingGroupParser) { + return child } + return parent } /** * @protected - * @param {AlternativeParser[]> | SequenceParser[]>} parent + * @param {AlternativeParser[]> | SequenceParser[]> | TimesParser> | CapturingGroupParser | NonCapturingGroupParser} parent * @param {SuccessParser | FailureParser} child * @param {Number} index * @returns {Parser[]} */ doTransformChild(parent, child, index) { - if (parent instanceof AlternativeParser && child instanceof FailureParser) { - return [] - } - if (parent instanceof SequenceParser && child instanceof SuccessParser) { - return [] + if ( + parent instanceof AlternativeParser && child instanceof FailureParser + || parent instanceof SequenceParser && child instanceof SuccessParser + ) { + return [] // Remove the child from the list } } diff --git a/tests/transformers.spec.js b/tests/transformers.spec.js index ff894c2..7fbb6f8 100644 --- a/tests/transformers.spec.js +++ b/tests/transformers.spec.js @@ -431,6 +431,18 @@ test("Remove trivial parsers 4", ({ page }) => { ).toBeTruthy() }) +test("Remove trivial parsers 5", ({ page }) => { + const removeTrivialParsers = new RemoveTrivialParsersTransformer() + expect( + R.equals( + removeTrivialParsers.transform( + R.seq(R.number, R.grp(R.nonGrp(R.success()).map(f1).atLeast(20)).map(f3), R.str("a")) + ), + R.seq(R.number, R.str("a")) + ) + ).toBeTruthy() +}) + // test("Test 1", ({ page }) => { // expect(R.equals( // transformer.transform(