Skip to content

Instantly share code, notes, and snippets.

@nooga
Created August 17, 2021 12:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nooga/efc429d0948cd41f49239f4cf417760b to your computer and use it in GitHub Desktop.
Save nooga/efc429d0948cd41f49239f4cf417760b to your computer and use it in GitHub Desktop.
/*
* Parser combinator lib
*/
function is_error(x) {
return typeof x === 'string';
}
function parser(f) {
f.then = function(n, fc) {
let f = this;
return parser(i => {
let a = f(i);
if (is_error(a)) return a;
else {
let b = n(a[1]);
if (is_error(b)) return b;
else return fc? [fc(a[0],b[0]), b[1]] : ["" + a[0] + b[0], b[1]];
}
});
}.bind(f);
f._then = function(n) {
let f = this;
return parser(i => {
let a = f(i);
if (is_error(a)) return a;
else {
let b = n(a[1]);
if (is_error(b)) return b;
else return [b[0], b[1]];
}
});
}.bind(f);
f.then_ = function(n) {
let f = this;
return parser(i => {
let a = f(i);
if (is_error(a)) return a;
else {
let b = n(a[1]);
if (is_error(b)) return b;
else return [a[0], b[1]];
}
});
}.bind(f);
f.or = function(n) {
let f = this;
return parser(i => {
let a = f(i);
if (!is_error(a)) return a;
else return n(i);
});
}.bind(f);
f.pmap = function(n) {
let f = this;
return parser(i => {
let a = f(i);
if (!is_error(a)) return [n(a[0]), a[1]];
else return a;
});
}.bind(f);
f.named = function(n) {
let f = this;
this.nom = n;
return parser(i => {
let a = f(i);
if (!is_error(a)) return a;
else return "Expected " + f.nom;
});
}.bind(f);
f.many = function(fc, fi) {
let f = this;
return parser(i => {
let acc = fi || '';
while (true) {
let a = f(i);
if (is_error(a)) return [acc, i];
else {
if(!fc) acc += a[0];
else acc = fc(acc, a[0]);
i = a[1];
}
}
});
}.bind(f);
f.many1 = function(fc,fi) {
let f = this;
return parser(i => {
let a = f(i);
if(is_error(a)) return a;
let acc = fi ? fi(a[0]) : a[0];
i = a[1];
while (true) {
let a = f(i);
if (is_error(a)) return [acc, i];
else {
if(!fc) acc += a[0];
else acc = fc(acc, a[0]);
i = a[1];
}
}
});
}.bind(f);
return f;
}
function pseq(ps) {
return ps.reduce((a, p) => a.then(p));
}
function palt(ps) {
return ps.reduce((a, p) => a.or(p));
}
function pchar(c) {
return parser(i => {
if (!i) return 'unexpected end of input';
else if (c === i[0]) return [c, i.slice(1)];
else return "expected '" + c + "' but '" + i[0] + "' found";
});
}
function pstr(c) {
return pseq(c.split('').map(pchar)).named("'"+c+"'");
}
function ptok(t) {
return parser(i => {
if(!i) return 'unexpected end of input';
else if (t === i[0].tok) return [i[0], i.slice(1)];
else return "expected token " + t + " but " + i[0] + " found";
});
}
function prange(a, b) {
return parser(i => {
if (!i) return 'unexpected end of input';
else if (b >= i[0] && a <= i[0]) return [i[0], i.slice(1)];
else return "expected '" + a + "'..'" + b + "' but '" + i[0] + "' found";
});
}
function delimited(p, d) {
return p.pmap(v => [v])
.then(d._then(p, (a,b) => [a,b])
.many((acc,v) => acc.concat(v), []),
(a,b) => a.concat(b));
}
function delimitedAll(p, d) {
return p.pmap(v => [v])
.then(d.then(p, (a,b) => [a,b])
.many((acc,v) => acc.concat(v), []),
(a,b) => a.concat(b));
}
function dynamic(id) {
return parser(i => global[id](i));
}
module.exports = { ptok, delimited, delimitedAll, palt, dynamic, parser };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment