Skip to content

Instantly share code, notes, and snippets.

@xplosunn
Created March 16, 2025 14:00
Show Gist options
  • Select an option

  • Save xplosunn/477e21e43bc444b9b9d2916f20f95521 to your computer and use it in GitHub Desktop.

Select an option

Save xplosunn/477e21e43bc444b9b9d2916f20f95521 to your computer and use it in GitHub Desktop.
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