Last active
November 21, 2024 18:04
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A parser for things | |
// Is a function from strings | |
// To lists of pairs | |
// Of things and strings | |
// (c) Graham Hutton | |
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