-
-
Save xplosunn/477e21e43bc444b9b9d2916f20f95521 to your computer and use it in GitHub Desktop.
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
| package peter_parser | |
| import tenecs.boolean.and | |
| import tenecs.boolean.not | |
| import tenecs.compare.eq | |
| import tenecs.list.appendAll | |
| import tenecs.string.join | |
| import tenecs.string.startsWith | |
| import tenecs.string.stripPrefix | |
| import tenecs.test.UnitTest | |
| import tenecs.test.UnitTestSuite | |
| struct Parser<In, T>( | |
| parse: (input: In) ~> ParseSuccess<In, T> | ParseError | |
| ) | |
| struct ParseSuccess<In, T>(parsed: T, remaining: In) | |
| struct ParseError(message: String) | |
| andThen := <In, A, B, C>(parserA: Parser<In, A>, parserB: Parser<In, B>, combine: (A, B) ~> C): Parser<In, C> => { | |
| Parser<In, C>((input: In) => { | |
| successA :? ParseError = parserA.parse(input) | |
| successB :? ParseError = parserB.parse(successA.remaining) | |
| ParseSuccess<In, C>(combine(successA.parsed, successB.parsed), successB.remaining) | |
| }) | |
| } | |
| ignoreAndThen := <In, A, B>(parserA: Parser<In, A>, parserB: Parser<In, B>): Parser<In, B> => { | |
| andThen<In, A, B, B>(parserA, parserB, (_, b) => { b }) | |
| } | |
| andThenIgnore := <In, A, B>(parserA: Parser<In, A>, parserB: Parser<In, B>): Parser<In, A> => { | |
| andThen<In, A, B, A>(parserA, parserB, (a, _) => { a }) | |
| } | |
| _ := UnitTestSuite("andThen, ignoreAndThen, andThenIgnore", (registry) => { | |
| booleanParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith("true") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix("true")) | |
| } else if input->startsWith("false") { | |
| ParseSuccess<String, Boolean>(false, input->stripPrefix("false")) | |
| } else { | |
| ParseError("Could not parse Boolean") | |
| } | |
| }) | |
| commaParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith(",") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix(",")) | |
| } else { | |
| ParseError("Could not parse comma") | |
| } | |
| }) | |
| booleanAndParser := booleanParser->andThenIgnore(commaParser)->andThen(booleanParser, (boolA, boolB) => { | |
| boolA->and(() => { boolB }) | |
| }) | |
| booleanLeftParser := booleanParser->andThenIgnore(commaParser)->andThenIgnore(booleanParser) | |
| booleanRightParser := booleanParser->andThenIgnore(commaParser)->ignoreAndThen(booleanParser) | |
| registry.test("fail to parse 'true'", (testkit): Void => { | |
| toParse := "true" | |
| testkit.assert.equal(ParseError("Could not parse comma"), booleanAndParser.parse(toParse)) | |
| testkit.assert.equal(ParseError("Could not parse comma"), booleanLeftParser.parse(toParse)) | |
| testkit.assert.equal(ParseError("Could not parse comma"), booleanRightParser.parse(toParse)) | |
| }) | |
| registry.test("fail to parse 'true,'", (testkit): Void => { | |
| toParse := "true," | |
| testkit.assert.equal(ParseError("Could not parse Boolean"), booleanAndParser.parse(toParse)) | |
| testkit.assert.equal(ParseError("Could not parse Boolean"), booleanLeftParser.parse(toParse)) | |
| testkit.assert.equal(ParseError("Could not parse Boolean"), booleanRightParser.parse(toParse)) | |
| }) | |
| registry.test("parse 'true,true'", (testkit): Void => { | |
| toParse := "true,true" | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(true, ""), booleanAndParser.parse(toParse)) | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(true, ""), booleanLeftParser.parse(toParse)) | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(true, ""), booleanRightParser.parse(toParse)) | |
| }) | |
| registry.test("parse 'false,true'", (testkit): Void => { | |
| toParse := "false,true" | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(false, ""), booleanAndParser.parse(toParse)) | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(false, ""), booleanLeftParser.parse(toParse)) | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(true, ""), booleanRightParser.parse(toParse)) | |
| }) | |
| registry.test("parse 'true,false'", (testkit): Void => { | |
| toParse := "true,false" | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(false, ""), booleanAndParser.parse(toParse)) | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(true, ""), booleanLeftParser.parse(toParse)) | |
| testkit.assert.equal(ParseSuccess<String, Boolean>(false, ""), booleanRightParser.parse(toParse)) | |
| }) | |
| }) | |
| widen := <In, A, B>(parser: Parser<In, A>): Parser<In, A | B> => { | |
| Parser<In, A | B>((input: In) => { | |
| success :? ParseError = parser.parse(input) | |
| ParseSuccess<In, A | B>(success.parsed, success.remaining) | |
| }) | |
| } | |
| _ := UnitTestSuite("widen", (registry) => { | |
| nullParser := Parser<String, Void>((input) => { | |
| if input->startsWith("null") { | |
| ParseSuccess<String, Void>(null, input->stripPrefix("null")) | |
| } else { | |
| ParseError("Could not parse null") | |
| } | |
| }) | |
| parserUnderTest: Parser<String, Void | Boolean> = nullParser->widen<String, Void, Boolean>() | |
| registry.test("fail to parse null 'b'", (testkit): Void => { | |
| parseError := ParseError("Could not parse null") | |
| testkit.assert.equal(parseError, parserUnderTest.parse("t")) | |
| }) | |
| registry.test("parse 'null'", (testkit): Void => { | |
| testkit.assert.equal(ParseSuccess<String, Void | Boolean>(null, ""), parserUnderTest.parse("null")) | |
| }) | |
| registry.test("parse 'null,'", (testkit): Void => { | |
| testkit.assert.equal(ParseSuccess<String, Void | Boolean>(null, ","), parserUnderTest.parse("null,")) | |
| }) | |
| }) | |
| or := <In, A, B>(parserA: Parser<In, A>, parserB: Parser<In, B>): Parser<In, A | B> => { | |
| Parser<In, A | B>((input: In) => { | |
| errA: ParseError ?= parserA->widen<In, A, B>().parse(input) | |
| errB: ParseError ?= parserB->widen<In, B, A>().parse(input) | |
| ParseError("Could not parse due to errors: ["->join(errA.message)->join(", ")->join(errB.message)->join("]")) | |
| }) | |
| } | |
| _ := UnitTestSuite("or", (registry) => { | |
| booleanParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith("true") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix("true")) | |
| } else if input->startsWith("false") { | |
| ParseSuccess<String, Boolean>(false, input->stripPrefix("false")) | |
| } else { | |
| ParseError("Could not parse Boolean") | |
| } | |
| }) | |
| nullParser := Parser<String, Void>((input) => { | |
| if input->startsWith("null") { | |
| ParseSuccess<String, Void>(null, input->stripPrefix("null")) | |
| } else { | |
| ParseError("Could not parse null") | |
| } | |
| }) | |
| nullOrBooleanParser := booleanParser->or(nullParser) | |
| registry.test("fail to parse boolean or null 'b'", (testkit): Void => { | |
| parseError := ParseError("Could not parse due to errors: [Could not parse Boolean, Could not parse null]") | |
| testkit.assert.equal(parseError, nullOrBooleanParser.parse("t")) | |
| }) | |
| registry.test("parse boolean or null 'true'", (testkit): Void => { | |
| testkit.assert.equal(ParseSuccess<String, Boolean | Void>(true, ""), nullOrBooleanParser.parse("true")) | |
| }) | |
| registry.test("parse boolean or null 'false'", (testkit): Void => { | |
| testkit.assert.equal(ParseSuccess<String, Boolean | Void>(false, ""), nullOrBooleanParser.parse("false")) | |
| }) | |
| registry.test("parse boolean or null 'null'", (testkit): Void => { | |
| testkit.assert.equal(ParseSuccess<String, Boolean | Void>(null, ""), nullOrBooleanParser.parse("null")) | |
| }) | |
| registry.test("parse boolean or null 'null,'", (testkit): Void => { | |
| testkit.assert.equal(ParseSuccess<String, Boolean | Void>(null, ","), nullOrBooleanParser.parse("null,")) | |
| }) | |
| }) | |
| zeroOrMoreTimes := <In, T>(parser: Parser<In, T>): Parser<In, List<T>> => { | |
| Parser<In, List<T>>((input: In) => { | |
| when parser.parse(input) { | |
| is ParseError => { | |
| ParseSuccess<In, List<T>>([], input) | |
| } | |
| other nextResult => { | |
| remainingResults :? ParseError = zeroOrMoreTimes(parser).parse(nextResult.remaining) | |
| results := [nextResult.parsed] | |
| ParseSuccess<In, List<T>>(results->appendAll(remainingResults.parsed), remainingResults.remaining) | |
| } | |
| } | |
| }) | |
| } | |
| _ := UnitTestSuite("zeroOrMoreTimes", (registry) => { | |
| commaParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith(",") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix(",")) | |
| } else { | |
| ParseError("Could not parse comma") | |
| } | |
| }) | |
| registry.test("parse zero commas", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, List<Boolean>>(<Boolean>[], "abc"), | |
| commaParser->zeroOrMoreTimes().parse("abc") | |
| ) | |
| }) | |
| registry.test("parse one comma", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, List<Boolean>>([true], "abc"), | |
| commaParser->zeroOrMoreTimes().parse(",abc") | |
| ) | |
| }) | |
| registry.test("parse multiple commas", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, List<Boolean>>([true, true, true], "abc"), | |
| commaParser->zeroOrMoreTimes().parse(",,,abc") | |
| ) | |
| }) | |
| }) | |
| oneOrMoreTimes := <In, T>(parser: Parser<In, T>): Parser<In, List<T>> => { | |
| parser->andThen(zeroOrMoreTimes(parser), (head, tail) => { [head]->appendAll(tail) }) | |
| } | |
| _ := UnitTestSuite("oneOrMoreTimes", (registry) => { | |
| commaParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith(",") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix(",")) | |
| } else { | |
| ParseError("Could not parse comma") | |
| } | |
| }) | |
| registry.test("fail to parse zero commas", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseError("Could not parse comma"), | |
| commaParser->oneOrMoreTimes().parse("abc") | |
| ) | |
| }) | |
| registry.test("parse one comma", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, List<Boolean>>([true], "abc"), | |
| commaParser->oneOrMoreTimes().parse(",abc") | |
| ) | |
| }) | |
| registry.test("parse multiple commas", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, List<Boolean>>([true, true, true], "abc"), | |
| commaParser->oneOrMoreTimes().parse(",,,abc") | |
| ) | |
| }) | |
| }) | |
| optional := <In, T>(parser: Parser<In, T>): Parser<In, T | Void> => { | |
| Parser<In, T | Void>((input: In) => { | |
| when parser.parse(input) { | |
| is ParseError => { | |
| ParseSuccess<In, T | Void>(null, input) | |
| } | |
| other success => { | |
| ParseSuccess<In, T | Void>(success.parsed, success.remaining) | |
| } | |
| } | |
| }) | |
| } | |
| _ := UnitTestSuite("optional", (registry) => { | |
| booleanParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith("true") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix("true")) | |
| } else if input->startsWith("false") { | |
| ParseSuccess<String, Boolean>(false, input->stripPrefix("false")) | |
| } else { | |
| ParseError("Could not parse Boolean") | |
| } | |
| }) | |
| optionalBooleanParser := booleanParser->optional() | |
| registry.test("parse non-boolean returns null", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, Boolean | Void>(null, "abc"), | |
| optionalBooleanParser.parse("abc") | |
| ) | |
| }) | |
| registry.test("parse true returns true", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, Boolean | Void>(true, ","), | |
| optionalBooleanParser.parse("true,") | |
| ) | |
| }) | |
| registry.test("parse false returns false", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, Boolean | Void>(false, ","), | |
| optionalBooleanParser.parse("false,") | |
| ) | |
| }) | |
| }) | |
| map := <In, A, B>(parser: Parser<In, A>, transform: (A) ~> B): Parser<In, B> => { | |
| Parser<In, B>((input: In) => { | |
| success :? ParseError = parser.parse(input) | |
| ParseSuccess<In, B>(transform(success.parsed), success.remaining) | |
| }) | |
| } | |
| _ := UnitTestSuite("map", (registry) => { | |
| booleanParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith("true") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix("true")) | |
| } else if input->startsWith("false") { | |
| ParseSuccess<String, Boolean>(false, input->stripPrefix("false")) | |
| } else { | |
| ParseError("Could not parse Boolean") | |
| } | |
| }) | |
| notParser := booleanParser->map((b) => { not(b) }) | |
| registry.test("fail to parse boolean", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseError("Could not parse Boolean"), | |
| notParser.parse("abc") | |
| ) | |
| }) | |
| registry.test("parse true returns false", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, Boolean>(false, ""), | |
| notParser.parse("true") | |
| ) | |
| }) | |
| registry.test("parse false returns true", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, Boolean>(true, ","), | |
| notParser.parse("false,") | |
| ) | |
| }) | |
| }) | |
| separatedBy := <In, T, Sep>(parser: Parser<In, T>, separator: Parser<In, Sep>): Parser<In, List<T>> => { | |
| parser->andThen( | |
| separator->ignoreAndThen(parser)->zeroOrMoreTimes(), | |
| (head, tail) => { [head]->appendAll(tail) } | |
| ) | |
| } | |
| _ := UnitTestSuite("separatedBy", (registry) => { | |
| booleanParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith("true") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix("true")) | |
| } else if input->startsWith("false") { | |
| ParseSuccess<String, Boolean>(false, input->stripPrefix("false")) | |
| } else { | |
| ParseError("Could not parse Boolean") | |
| } | |
| }) | |
| commaParser := Parser<String, Boolean>((input) => { | |
| if input->startsWith(",") { | |
| ParseSuccess<String, Boolean>(true, input->stripPrefix(",")) | |
| } else { | |
| ParseError("Could not parse comma") | |
| } | |
| }) | |
| endOfInputParser := Parser<String, Void>((input) => { | |
| if input->eq("") { | |
| ParseSuccess<String, Void>(null, input) | |
| } else { | |
| ParseError("Could not parse end of input, remaining: "->join(input)) | |
| } | |
| }) | |
| commaSeparatedBooleans := booleanParser->separatedBy(commaParser)->andThenIgnore(endOfInputParser) | |
| registry.test("fail to parse empty string", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseError("Could not parse Boolean"), | |
| commaSeparatedBooleans.parse("") | |
| ) | |
| }) | |
| registry.test("parse single boolean", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, List<Boolean>>([true], ""), | |
| commaSeparatedBooleans.parse("true") | |
| ) | |
| }) | |
| registry.test("parse multiple booleans", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseSuccess<String, List<Boolean>>([true, false, true], ""), | |
| commaSeparatedBooleans.parse("true,false,true") | |
| ) | |
| }) | |
| registry.test("fail on trailing separator", (testkit): Void => { | |
| testkit.assert.equal( | |
| ParseError("Could not parse end of input, remaining: ,"), | |
| commaSeparatedBooleans.parse("true,false,") | |
| ) | |
| }) | |
| }) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment