optimize Parser.Core

shaves 1 second off of the HTTP parser tests
This commit is contained in:
Folkert 2023-01-15 16:05:33 +01:00
parent 545e6bc989
commit c4e6dcff22
No known key found for this signature in database
GPG Key ID: 1F17F6FFD112B97C

View File

@ -88,14 +88,14 @@ parse = \parser, input, isParsingCompleted ->
## in a `oneOf` or `alt` have failed, to provide some more descriptive error message. ## in a `oneOf` or `alt` have failed, to provide some more descriptive error message.
fail : Str -> Parser * * fail : Str -> Parser * *
fail = \msg -> fail = \msg ->
buildPrimitiveParser \_input -> Err (ParsingFailure msg) @Parser \_input -> Err (ParsingFailure msg)
## Parser that will always produce the given `val`, without looking at the actual input. ## Parser that will always produce the given `val`, without looking at the actual input.
## This is useful as basic building block, especially in combination with ## This is useful as basic building block, especially in combination with
## `map` and `keep`. ## `map` and `keep`.
const : a -> Parser * a const : a -> Parser * a
const = \val -> const = \val ->
buildPrimitiveParser \input -> @Parser \input ->
Ok { val: val, input: input } Ok { val: val, input: input }
## Try the `first` parser and (only) if it fails, try the `second` parser as fallback. ## Try the `first` parser and (only) if it fails, try the `second` parser as fallback.
@ -135,13 +135,18 @@ alt = \first, second ->
## This is because the parameters to the function will be applied one-by-one as parsing continues. ## This is because the parameters to the function will be applied one-by-one as parsing continues.
keep : Parser input (a -> b), Parser input a -> Parser input b keep : Parser input (a -> b), Parser input a -> Parser input b
keep = \funParser, valParser -> keep = \funParser, valParser ->
combined = \input -> @Parser \input ->
{ val: funVal, input: rest } <- Result.try (parsePartial funParser input) when parsePartial funParser input is
parsePartial valParser rest Ok { val: funVal, input: rest } ->
|> Result.map \{ val: val, input: rest2 } -> when parsePartial valParser rest is
{ val: funVal val, input: rest2 } Ok { val: val, input: rest2 } ->
Ok { val: funVal val, input: rest2 }
buildPrimitiveParser combined Err e ->
Err e
Err e ->
Err e
## Skip over a parsed item as part of a pipeline ## Skip over a parsed item as part of a pipeline
## ##
@ -157,9 +162,18 @@ keep = \funParser, valParser ->
## ##
skip : Parser input kept, Parser input skipped -> Parser input kept skip : Parser input kept, Parser input skipped -> Parser input kept
skip = \kept, skipped -> skip = \kept, skipped ->
const (\k -> \_ -> k) @Parser \input ->
|> keep kept when parsePartial kept input is
|> keep skipped Ok step1 ->
when parsePartial skipped step1.input is
Ok step2 ->
Ok { val: step1.val, input: step2.input }
Err e ->
Err e
Err e ->
Err e
# Internal utility function. Not exposed to users, since usage is discouraged! # Internal utility function. Not exposed to users, since usage is discouraged!
# #
@ -171,14 +185,15 @@ skip = \kept, skipped ->
# than using `const` with `map` and/or `keep`. # than using `const` with `map` and/or `keep`.
# Consider using those functions first. # Consider using those functions first.
andThen : Parser input a, (a -> Parser input b) -> Parser input b andThen : Parser input a, (a -> Parser input b) -> Parser input b
andThen = \firstParser, buildNextParser -> andThen = \@Parser firstParser, buildNextParser ->
fun = \input -> @Parser \input ->
{ val: firstVal, input: rest } <- Result.try (parsePartial firstParser input) when firstParser input is
nextParser = buildNextParser firstVal Ok step ->
(@Parser nextParser) = buildNextParser step.val
nextParser step.input
parsePartial nextParser rest Err e ->
Err e
buildPrimitiveParser fun
## Try a list of parsers in turn, until one of them succeeds ## Try a list of parsers in turn, until one of them succeeds
oneOf : List (Parser input a) -> Parser input a oneOf : List (Parser input a) -> Parser input a
@ -188,17 +203,31 @@ oneOf = \parsers ->
## Transforms the result of parsing into something else, ## Transforms the result of parsing into something else,
## using the given transformation function. ## using the given transformation function.
map : Parser input a, (a -> b) -> Parser input b map : Parser input a, (a -> b) -> Parser input b
map = \simpleParser, transform -> map = \@Parser simpleParser, transform ->
const transform @Parser \input ->
|> keep simpleParser when simpleParser input is
Ok step ->
Ok { val: transform step.val, input: step.input }
Err e ->
Err e
## Transforms the result of parsing into something else, ## Transforms the result of parsing into something else,
## using the given two-parameter transformation function. ## using the given two-parameter transformation function.
map2 : Parser input a, Parser input b, (a, b -> c) -> Parser input c map2 : Parser input a, Parser input b, (a, b -> c) -> Parser input c
map2 = \parserA, parserB, transform -> map2 = \@Parser parserA, @Parser parserB, transform ->
const (\a -> \b -> transform a b) @Parser \input ->
|> keep parserA when parserA input is
|> keep parserB Ok step1 ->
when parserB step1.input is
Ok step2 ->
Ok { val: transform step1.val step2.val, input: step2.input }
Err e ->
Err e
Err e ->
Err e
## Transforms the result of parsing into something else, ## Transforms the result of parsing into something else,
## using the given three-parameter transformation function. ## using the given three-parameter transformation function.
@ -206,11 +235,24 @@ map2 = \parserA, parserB, transform ->
## If you need transformations with more inputs, ## If you need transformations with more inputs,
## take a look at `keep`. ## take a look at `keep`.
map3 : Parser input a, Parser input b, Parser input c, (a, b, c -> d) -> Parser input d map3 : Parser input a, Parser input b, Parser input c, (a, b, c -> d) -> Parser input d
map3 = \parserA, parserB, parserC, transform -> map3 = \@Parser parserA, @Parser parserB, @Parser parserC, transform ->
const (\a -> \b -> \c -> transform a b c) @Parser \input ->
|> keep parserA when parserA input is
|> keep parserB Ok step1 ->
|> keep parserC when parserB step1.input is
Ok step2 ->
when parserC step2.input is
Ok step3 ->
Ok { val: transform step1.val step2.val step3.val, input: step3.input }
Err e ->
Err e
Err e ->
Err e
Err e ->
Err e
# ^ And this could be repeated for as high as we want, of course. # ^ And this could be repeated for as high as we want, of course.
# Removes a layer of 'result' from running the parser. # Removes a layer of 'result' from running the parser.
@ -249,15 +291,15 @@ maybe = \parser ->
alt (parser |> map (\val -> Ok val)) (const (Err Nothing)) alt (parser |> map (\val -> Ok val)) (const (Err Nothing))
manyImpl : Parser input a, List a, input -> ParseResult input (List a) manyImpl : Parser input a, List a, input -> ParseResult input (List a)
manyImpl = \parser, vals, input -> manyImpl = \@Parser parser, vals, input ->
result = parsePartial parser input result = parser input
when result is when result is
Err _ -> Err _ ->
Ok { val: vals, input: input } Ok { val: vals, input: input }
Ok { val: val, input: inputRest } -> Ok { val: val, input: inputRest } ->
manyImpl parser (List.append vals val) inputRest manyImpl (@Parser parser) (List.append vals val) inputRest
## A parser which runs the element parser *zero* or more times on the input, ## A parser which runs the element parser *zero* or more times on the input,
## returning a list containing all the parsed elements. ## returning a list containing all the parsed elements.
@ -265,7 +307,7 @@ manyImpl = \parser, vals, input ->
## Also see `oneOrMore`. ## Also see `oneOrMore`.
many : Parser input a -> Parser input (List a) many : Parser input a -> Parser input (List a)
many = \parser -> many = \parser ->
buildPrimitiveParser \input -> @Parser \input ->
manyImpl parser [] input manyImpl parser [] input
## A parser which runs the element parser *one* or more times on the input, ## A parser which runs the element parser *one* or more times on the input,
@ -273,10 +315,14 @@ many = \parser ->
## ##
## Also see `many`. ## Also see `many`.
oneOrMore : Parser input a -> Parser input (List a) oneOrMore : Parser input a -> Parser input (List a)
oneOrMore = \parser -> oneOrMore = \@Parser parser ->
const (\val -> \vals -> List.prepend vals val) @Parser \input ->
|> keep parser when parser input is
|> keep (many parser) Ok step ->
manyImpl (@Parser parser) [step.val] step.input
Err e ->
Err e
## Runs a parser for an 'opening' delimiter, then your main parser, then the 'closing' delimiter, ## Runs a parser for an 'opening' delimiter, then your main parser, then the 'closing' delimiter,
## and only returns the result of your main parser. ## and only returns the result of your main parser.
@ -286,10 +332,7 @@ oneOrMore = \parser ->
## >>> betweenBraces = \parser -> parser |> between (scalar '[') (scalar ']') ## >>> betweenBraces = \parser -> parser |> between (scalar '[') (scalar ']')
between : Parser input a, Parser input open, Parser input close -> Parser input a between : Parser input a, Parser input open, Parser input close -> Parser input a
between = \parser, open, close -> between = \parser, open, close ->
const (\_ -> \val -> \_ -> val) map3 open parser close (\_, val, _ -> val)
|> keep open
|> keep parser
|> keep close
sepBy1 : Parser input a, Parser input sep -> Parser input (List a) sepBy1 : Parser input a, Parser input sep -> Parser input (List a)
sepBy1 = \parser, separator -> sepBy1 = \parser, separator ->