Skip to content

Instantly share code, notes, and snippets.

@DKurilo
Created January 14, 2022 17:00
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 DKurilo/fc60545e4498bf57022cabbee40c2c90 to your computer and use it in GitHub Desktop.
Save DKurilo/fc60545e4498bf57022cabbee40c2c90 to your computer and use it in GitHub Desktop.
Monad prsing inspired parsing in JS / copy from codesandbox just not to lose it
const skip = (parser) => (text) => {
const parsed = parser(text);
if (parsed.length === 0) {
return parsed;
}
return [{ parsed: [], text: parsed[0].text }];
};
const chain = (f) => (parser1) => (parser2) => (text) => {
const parsed = parser1(text);
if (parsed.length === 0) {
return parsed;
}
const parsedNext = parser2(parsed[0].text);
return parsedNext.length === 0
? parsedNext
: [
{
parsed: f(parsed[0].parsed)(parsedNext[0].parsed),
text: parsedNext[0].text
}
];
};
const both = chain((x) => (y) => x.concat(y));
const bothsep = chain((x) => (y) => [x, y].flat());
const any = (parser1) => (parser2) => (text) => {
const parsed = parser1(text);
return parsed.length === 0 ? parser2(text) : parsed;
};
const parse = (parser) => (text) => {
const parsed = parser(text);
return parsed.length === 0 ? null : parsed[0].parsed;
};
const parserNothing = (text) => (text.length > 0 ? [{ parsed: [], text }] : []);
const parserItem = (text) =>
text.length > 0 ? [{ parsed: text[0], text: text.slice(1) }] : [];
const parserSat = (p) => (text) => {
const parsed = parserItem(text);
return parsed.length === 0 || !p(parsed[0].parsed) ? [] : parsed;
};
const parserChar = (c) => parserSat((x) => x === c);
const parserMany = (parser) => (text) =>
text.length === 0
? [{ parsed: [], text }]
: any(parserSome(parser))(parserNothing)(text);
const parserSome = (parser) => (text) =>
text.length === 0 ? [] : both(parser)(parserMany(parser))(text);
const parserWS = parserSat((x) => /\s/.test(x));
const parserSpace = parserSat((x) => /[ \t]/.test(x));
const parserNL = parserSat((x) => /[\r\n]/.test(x));
const parserSomeLine = both(parserMany(parserSat((x) => !/[\r\n]/.test(x))))(
parserNL
);
// const parserEmptyLine = both(parserMany(parserSpace))(parserNL);
const parserDigit = parserSat((x) => /[0-9]/.test(x));
const parserLetter = parserSat((x) => /[a-zA-Z]/.test(x));
const parserString = (s) => (text) => {
if (s.length === 0) {
return [{ parsed: [], text }];
}
const parsed = both(parserChar(s[0]))(parserString(s.slice(1)))(text);
if (parsed.length === 0) {
return parsed;
}
return [{ parsed: parsed[0].parsed, text: parsed[0].text }];
};
const parserNumber = (text) => {
const parsed = parserSome(parserDigit)(text);
if (parsed.length === 0) {
return parsed;
}
return [{ parsed: parseInt(parsed[0].parsed, 10), text: parsed[0].text }];
};
const parserSomeString = parserMany(any(parserLetter)(parserDigit));
const parserLabel = (text) => {
const parsed = bothsep(skip(parserChar("[")))(
bothsep(parserMany(parserLetter))(
skip(both(parserChar("]"))(parserMany(parserWS)))
)
)(text);
return parsed.length === 0
? parsed
: [{ parsed: parsed[0].parsed[0], text: parsed[0].text }];
};
const parserKey = (k) => (text) => {
const parsed = bothsep(skip(parserMany(parserSpace)))(
bothsep(skip(parserString(k)))(
bothsep(skip(both(parserChar(":"))(parserMany(parserSpace))))(
bothsep(parserNumber)(skip(parserSomeLine))
)
)
)(text);
return parsed.length === 0
? parsed
: [{ parsed: [{ [k]: parsed[0].parsed[0] }], text: parsed[0].text }];
};
const parserAnyKey = both(parserMany(parserSpace))(
both(parserSomeString)(
both(parserChar(":"))(
both(parserMany(parserSpace))(both(parserSomeString)(parserSomeLine))
)
)
);
const parserAmount = parserKey("amount");
const parserPrice = parserKey("price");
const parserComment = both(skip(parserMany(parserSpace)))(
both(skip(parserChar("#")))(parserSomeLine)
);
const parserSection = (text) => {
const parsed = bothsep(skip(parserMany(any(parserWS)(parserComment))))(
bothsep(parserLabel)(
parserMany(
any(any(parserAmount)(parserPrice))(
skip(any(any(parserComment)(parserAnyKey))(parserSome(parserWS)))
)
)
)
)(text);
return parsed.length === 0
? parsed
: [
{
parsed: [
Object.assign(
{ name: parsed[0].parsed[0] },
...parsed[0].parsed.slice(1)
)
],
text: parsed[0].text
}
];
};
const parserConfig = parserMany(parserSection);
const main = () => {
const text = `
# our products
[qwer]
price: 30
# something
something: other
amount: 5
[asdf]
price: 700
# somethingelse
somethingelse: other-31
amount: 1
#other products
[fdfd]
# one more comment
amount: 7
[vbnm]
amount: 42
price: 55
`;
console.log(JSON.stringify(parse(parserConfig)(text), null, " "));
};
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment