Skip to content

Instantly share code, notes, and snippets.

@abuseofnotation
Created August 9, 2016 13:27
Show Gist options
  • Save abuseofnotation/6f964714ebd5e36939786596fa39ed3e to your computer and use it in GitHub Desktop.
Save abuseofnotation/6f964714ebd5e36939786596fa39ed3e to your computer and use it in GitHub Desktop.
Simple parser combinators implementation in JS
const { dropWhile } = require('lodash')
const parser = {
of: (value) => Parser((_) => ({value, string:''})),
chain(f) {
return Parser((prevString) => {
const { value, string, error} = this.parse(prevString)
return error === undefined ? f(value).parse(string) : {error, value}
})
},
andThen(next) {
return this.chain(() => next)
}
}
const Parser = (parse) => Object.assign(Object.create(parser), {parse})
Parser.of = parser.of
const fromRegex = (regex) => Parser((string) => {
const result = string.match(regex)
return result === null ? {error: `${string} does not match ${regex}`, string}
: {value: string.slice(0, result.index + 1), string: string.slice(result.index + 1)}
})
const colon = fromRegex(/^:/)
const letter = fromRegex(/^[a-z]/)
const number = fromRegex(/^[0-9]/)
const oneOf = (...parsers) => Parser((string) => {
const validParsers = dropWhile(parsers, (parser) => parser.parse(string).error !== undefined )
return validParsers.length !== 0 ? validParsers[0].parse(string) : {error: 'Unable to parse '+string}
})
const letterOrNumber = oneOf(letter, number)
console.assert(letterOrNumber.parse('a').value === 'a')
console.assert(letterOrNumber.parse('4').value === '4')
const many = (parser) => Parser((string) => {
var error
var result = parser.parse(string)
while (error === undefined) {
const newElement = parser.parse(result.string)
if (newElement.error !== undefined) {
error = newElement.error
} else {
result = {value: result.value.concat(newElement.value), string: newElement.string}
}
}
return result
})
const keyValPair = many(letter).chain((key) =>
colon
.andThen(many(letter)).chain((value) =>
Parser.of({key, value})))
const keyVal = keyValPair.parse('key:value').value
console.assert(keyVal.key === 'key')
console.assert(keyVal.value === 'value')
const alphanumeric = many(letterOrNumber)
console.assert(alphanumeric.parse('a1b2:aaa').value === 'a1b2')
console.assert(alphanumeric.parse('a1b2:aaa').string = ':aaa')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment