Created
August 9, 2016 13:27
-
-
Save abuseofnotation/6f964714ebd5e36939786596fa39ed3e to your computer and use it in GitHub Desktop.
Simple parser combinators implementation in JS
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
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