davidcavazos/parser/1.2.0

NOTE: the latest version is 7.0.0

Renamed to lambda-phi/parser. It is published under that name, and new versions come out there.

Versions published as davidcavazos/parser still install under that name. 2 versions of 12 come from the new location (see their notes). 10 versions could not be recovered and cannot be installed.

Unavailable. Nobody can download this version any more, with elm or with the Lamdera compiler: github.com/lambda-phi/parser exists but has no tag 1.2.0; Software Heritage has not archived the repo; no fork with tag 1.2.0 found (60 same-name repos checked); no cached copy. Use another version.

Details

Parser

Intuitive and easy to use parser library.

Basic usage

type alias Parser a =
State -> Result Error ( a, State )

A Parser is defined as a function that takes an input state, including an input text, and returns either an Error, or a value with the next state.

type alias Error =
{ message : String
, input : String
, row : Int
, col : Int
, context : List Context
}

Contains the description of an error. This includes an error message, the position, and the context stack.

parse : String -> Parser a -> Result Error a

Parse an input text, and get either an Error or the parsed value as a result.

parse "abc" letter --> Ok 'a'


letter
    |> parse "123"
    |> Result.mapError .message
--> Err "expected a letter [a-zA-Z], but got '1' instead"
into : String -> Parser a -> State -> Result Error ( a, State )

Starts a parsing pipeline to parse into a data type.

You should also specify a name for the context, this helps to give better error messages.

import Parser.Common exposing (number)


type alias Point =
    { x : Float
    , y : Float
    }


point : Parser Point
point =
    into "Point"
        (succeed Point
            |> drop (char '(')
            |> take number
            |> drop (char ',')
            |> take number
            |> drop (char ')')
        )


parse "(12,34)" point --> Ok {x = 12, y = 34}
take : Parser a -> Parser (a -> b) -> Parser b

Takes a parsed value and feeds it to the return value of the parser.

drop : Parser a -> Parser b -> Parser b

Ignores a parsed value, but it still must match to continue.

Matching characters

anyChar : State -> Result Error ( Char, State )

Matches any single character.

ℹ️ Equivalent regular expression: .

parse "abc" anyChar --> Ok 'a'


anyChar
    |> parse ""
    |> Result.mapError .message
--> Err "expected a character, but reached the end of the input text"
char : Char -> Parser Char

Matches a specific single character. This is case sensitive.

parse "abc" (char 'a') --> Ok 'a'


char 'a'
    |> parse "ABC"
    |> Result.mapError .message
--> Err "expected the character 'a', but got 'A' instead"
charNoCase : Char -> Parser Char

Matches a specific single character. This is case insensitive.

parse "abc" (charNoCase 'a') --> Ok 'a'


parse "ABC" (charNoCase 'a') --> Ok 'A'


charNoCase 'a'
    |> parse "123"
    |> Result.mapError .message
--> Err "expected the character 'a' or 'A', but got '1' instead"
digit : Parser Char

Matches a digit character.

ℹ️ Equivalent regular expression: [0-9] or \d

parse "123" digit --> Ok '1'


digit
    |> parse "abc"
    |> Result.mapError .message
--> Err "expected a digit [0-9], but got 'a' instead"
digits : Parser String

Matches one or more digit.

ℹ️ Equivalent regular expression: [0-9]+ or \d+

parse "123abc" digits --> Ok "123"


digits
    |> parse "abc123"
    |> Result.mapError .message
--> Err "expected a digit [0-9], but got 'a' instead"
letter : Parser Char

Matches a letter character. This is case insensitive.

ℹ️ Equivalent regular expression: [a-zA-Z]

parse "abc" letter --> Ok 'a'


parse "ABC" letter --> Ok 'A'


letter
    |> parse "123"
    |> Result.mapError .message
--> Err "expected a letter [a-zA-Z], but got '1' instead"
letters : Parser String

Matches one or more letter. This is case insensitive.

ℹ️ Equivalent regular expression: [a-zA-Z]+

parse "abc123" letters --> Ok "abc"


parse "ABC123" letters --> Ok "ABC"


letters
    |> parse "123abc"
    |> Result.mapError .message
--> Err "expected a letter [a-zA-Z], but got '1' instead"
lowercase : Parser Char

Matches a lowercase letter character. This is case sensitive.

ℹ️ Equivalent regular expression: [a-z]

parse "abc" lowercase --> Ok 'a'


lowercase
    |> parse "ABC"
    |> Result.mapError .message
--> Err "expected a lowercase letter [a-z], but got 'A' instead"
uppercase : Parser Char

Matches an uppercase letter character. This is case sensitive.

ℹ️ Equivalent regular expression: [A-Z]

parse "ABC" uppercase --> Ok 'A'


uppercase
    |> parse "abc"
    |> Result.mapError .message
--> Err "expected an uppercase letter [A-Z], but got 'a' instead"
alphanumeric : Parser Char

Matches a letter or digit character. This is case insensitive.

ℹ️ Equivalent regular expression: [a-zA-Z0-9]

parse "abc" alphanumeric --> Ok 'a'


parse "ABC" alphanumeric --> Ok 'A'


parse "123" alphanumeric --> Ok '1'


alphanumeric
    |> parse "_abc"
    |> Result.mapError .message
--> Err "expected a letter or digit [a-zA-Z0-9], but got '_' instead"
space : Parser Char

Matches a blank space character. A blank space can be a regular space ' ', tab '\t', new line '\n', carriage return '\r', form feed '\f', or vertical tab '\v'.

ℹ️ Equivalent regular expression: [ \t\n\r\f\v] or \s

parse "    abc" space --> Ok ' '


parse "\t abc" space --> Ok '\t'


space
    |> parse "abc"
    |> Result.mapError .message
--> Err "expected a blank space character, but got 'a' instead"
spaces : Parser String

Matches zero or more spaces.

ℹ️ Equivalent regular expression: [ \t\n\r\f\v]* or \s*

parse "    abc" spaces --> Ok "    "


parse "\t abc" spaces --> Ok "\t "


parse "abc" spaces --> Ok ""

Text operations

text : String -> Parser String

Matches a specific text string. This is case sensitive.

parse "abcdef" (text "abc") --> Ok "abc"


text "abc"
    |> parse "ABCDEF"
    |> Result.mapError .message
--> Err "expected the character 'a', but got 'A' instead"
textNoCase : String -> Parser String

Matches a specific text string. This is case insensitive.

parse "abcdef" (textNoCase "abc") --> Ok "abc"


parse "ABCDEF" (textNoCase "abc") --> Ok "ABC"


textNoCase "abc"
    |> parse "123"
    |> Result.mapError .message
--> Err "expected the character 'a' or 'A', but got '1' instead"
textOf : Parser (List Char) -> Parser String

Gets a string from a list of characters.

textOf (oneOrMore letter) -- letters
    |> parse "abc123" --> Ok "abc"
line : Parser String

Gets a line from the input text, delimited by '\n'.

parse "abc\ndef" line --> Ok "abc"

List operations

sequence : List (Parser a) -> State -> Result Error ( List a, State )

Matches a sequence of parsers in order, and gets the result as a List.

sequence [ char '_', letter, digit ]
    |> parse "_A5" --> Ok [ '_', 'A', '5' ]
concat : List (Parser (List a)) -> Parser (List a)

Concatenates the parsed values from all the parsers into a single list with all the values.

concat
    [ oneOrMore digit       -- [ '3' ]
    , zeroOrOne (char '.')  -- [ '.' ]
    , zeroOrMore digit      -- [ '1', '4' ]
    ]
    |> parse "3.14"
--> Ok [ '3', '.', '1', '4' ]
oneOf : List (Parser a) -> Parser a

Returns the value of the first parser that matches. It tries to match the parsers in order.

If none of the parsers match, it keeps the error message from the last parser. To give a more descriptive error message, you can use withError.

ℹ️ Equivalent regular expression: |

oneOf [ char '_', letter ]
    |> parse "_abc"
--> Ok '_'


oneOf [ char '_', letter ]
    |> parse "abc"
--> Ok 'a'


oneOf [ char '_', letter ]
    |> parse "123"
    |> Result.mapError .message
--> Err "expected a letter [a-zA-Z], but got '1' instead"
maybe : Parser a -> Parser (Maybe a)

Parses an optional value and returns it as a Maybe.

ℹ️ Equivalent regular expression: ?

parse "abc" (maybe letter) --> Ok (Just 'a')


parse "_abc" (maybe letter) --> Ok Nothing
zeroOrOne : Parser a -> Parser (List a)

Parses an optional value and returns it as a List.

ℹ️ Equivalent regular expression: ?

parse "abc" (zeroOrOne letter) --> Ok [ 'a' ]


parse "_abc" (zeroOrOne letter) --> Ok []
zeroOrMore : Parser a -> Parser (List a)

Parses zero or more values and returns them as a List.

ℹ️ Equivalent regular expression: *

parse "abc" (zeroOrMore letter) --> Ok [ 'a', 'b', 'c' ]


parse "_abc" (zeroOrMore letter) --> Ok []
oneOrMore : Parser a -> Parser (List a)

Parses one or more values and returns them as a List.

ℹ️ Equivalent regular expression: +

parse "abc" (oneOrMore letter) --> Ok [ 'a', 'b', 'c' ]


oneOrMore letter
    |> parse "_abc"
    |> Result.mapError .message
--> Err "expected a letter [a-zA-Z], but got '_' instead"
repeat : Int -> Parser a -> Parser (List a)

Parses a value exactly a number of times and returns them as a List.

ℹ️ Equivalent regular expression: {n}

parse "abcdef" (repeat 3 letter) --> Ok [ 'a', 'b', 'c' ]


repeat 3 letter
    |> parse "ab_def"
    |> Result.mapError .message
--> Err "expected a letter [a-zA-Z], but got '_' instead"
atLeast : Int -> Parser a -> Parser (List a)

Parses a value at least a number of times and returns them as a List.

ℹ️ Equivalent regular expression: {min,}

parse "abcdef" (atLeast 3 letter) --> Ok [ 'a', 'b', 'c', 'd', 'e', 'f' ]


atLeast 3 letter
    |> parse "ab_def"
    |> Result.mapError .message
--> Err "expected a letter [a-zA-Z], but got '_' instead"
atMost : Int -> Parser a -> Parser (List a)

Parses a value at most a number of times and returns them as a List.

ℹ️ Equivalent regular expression: {0,max}

parse "abcdef" (atMost 3 letter) --> Ok [ 'a', 'b', 'c' ]


parse "ab_def" (atMost 3 letter) --> Ok [ 'a', 'b' ]


parse "_bcdef" (atMost 3 letter) --> Ok []
between : Int -> Int -> Parser a -> Parser (List a)

Parses a value between a range of times and returns them as a List.

ℹ️ Equivalent regular expression: {min,max}

parse "abcdef" (between 2 3 letter) --> Ok [ 'a', 'b', 'c' ]


parse "ab_def" (between 2 3 letter) --> Ok [ 'a', 'b' ]


between 2 3 letter
    |> parse "a_cdef"
    |> Result.mapError .message
--> Err "expected a letter [a-zA-Z], but got '_' instead"
until : Parser stop -> Parser a -> Parser (List a)

Parses a values repeatedly until a delimiter parser matches. The delimiter marks the end of the sequence, but is not consumed.

If the delimiter is not found, it parses until it stops matching or until the end of the input text.

letter
    |> until (char 'd')
    |> parse "abcdef"
--> Ok [ 'a', 'b', 'c' ]


letter
    |> until (char 'd')
    |> parse "abc123"
--> Ok [ 'a', 'b', 'c' ]


letter
    |> until (char 'd')
    |> parse "abc"
--> Ok [ 'a', 'b', 'c' ]


succeed (\str -> str)
    |> drop (char '<')
    |> take (textOf (letter |> until (char '>')))
    |> drop (char '>')
    |> parse "<abc>"
--> Ok "abc"
while : (List a -> a -> Bool) -> Parser a -> Parser (List a)

Parses values while a condition is still True. Once the condition is False, the sequence stops and the current value is not consumed.

If the condition never gets to False, it parses until it stops matching or until the end of the input text.

The condition function takes two inputs: a list of all the previously matched values, and the current value matched.

letter
    |> while (\_ value -> value /= 'd')
    |> parse "abcdef"
--> Ok [ 'a', 'b', 'c' ]


letter
    |> while (\values _ -> List.length values < 3)
    |> parse "abcdef"
--> Ok [ 'a', 'b', 'c' ]


letter
    |> while (\_ value -> value /= 'd')
    |> parse "abc123"
--> Ok [ 'a', 'b', 'c' ]


letter
    |> while (\_ value -> value /= 'd')
    |> parse "abc"
--> Ok [ 'a', 'b', 'c' ]


succeed (\str -> str)
    |> drop (char '<')
    |> take (textOf (letter |> while (\_ value -> value /= '>')))
    |> drop (char '>')
    |> parse "<abc>"
--> Ok "abc"

Chaining

succeed : a -> State -> Result Error ( a, State )

A parser that always succeeds with the given value.

parse "" (succeed "abc") --> Ok "abc"
fail : String -> State -> Result Error ( a, State )

A parser that always fails with the given error message.

fail "something went wrong :("
    |> parse ""
    |> Result.mapError .message
--> Err "something went wrong :("
andThen : (a -> Parser b) -> Parser a -> State -> Result Error ( b, State )

Parse one value andThen do something with that value, which results in another parser.

This can be used to validate the last thing we parsed, or transform the last value, or to use the last value for the next parser like a backreference.

textOf (oneOrMore anyChar)
    |> andThen
        (\chars ->
            case String.toInt chars of
                Just n -> succeed n
                Nothing -> fail "better use Parser.Common.int"
        )
    |> parse "123"
--> Ok 123


textOf (oneOrMore anyChar)
    |> andThen
        (\chars ->
            case String.toInt chars of
                Just n -> succeed n
                Nothing -> fail "better use Parser.Common.int"
        )
    |> parse "abc"
    |> Result.mapError .message
--> Err "better use Parser.Common.int"
andThen2 : (a -> b -> Parser c) -> Parser a -> Parser b -> Parser c

Parse two values andThen2 do something with those values, which results in another parser.

This can be used to validate the last things we parsed, or transform the last values, or to ignore eiher of the values, or to use the last values for the next parser like a backreference.

andThen2
    (\_ chars ->
        case String.toInt chars of
            Just n -> succeed n
            Nothing -> fail "failed to parse a number"
    )
    (char '=')
    digits
    |> parse "=123"
--> Ok 123


andThen2
    (\_ chars ->
        case String.toInt chars of
            Just n -> succeed n
            Nothing -> fail "failed to parse a number"
    )
    (char '=')
    digits
    |> parse "_123"
    |> Result.mapError .message
--> Err "expected the character '=', but got '_' instead"
andThenIgnore : Parser ignore -> Parser a -> Parser a

Parse and consume the next parser, but ignore its value.

This is useful when you want to match and advance the parser, but keep the previous value.

letters
    |> andThenIgnore (char '@')
    |> andThenIgnore letters
    |> andThenIgnore (text ".com")
    |> parse "[email protected]"
--> Ok "user"


-- To ignore the previous value, you can use a regular andThen
letters
    |> andThen (\_ -> char '@')
    |> andThen (\_ -> letters)
    |> andThenIgnore (text ".com")
    |> parse "[email protected]"
--> Ok "example"
orElse : Parser a -> Parser a -> State -> Result Error ( a, State )

If the previous parser failed, try a fallback parser.

letters
    |> orElse digits
    |> parse "abc"
--> Ok "abc"


letters
    |> orElse digits
    |> parse "123"
--> Ok "123"


letters
    |> orElse digits
    |> parse "_"
    |> Result.mapError .message
--> Err "expected a digit [0-9], but got '_' instead"
withError : String -> Parser a -> State -> Result Error ( a, State )

If there is an error, this replaces the error message. This helps create more descriptive error messages instead of the more generic ones.

If you want to use the value that failed some validation in the error message, consider using a more relaxed parser and using andThen to do the validation. It's a little longer, but that way you get access to the potentially invalid parsed value.

letters
    |> withError "a name can only consist of letters"
    |> parse "123"
    |> Result.mapError .message
--> Err "a name can only consist of letters"


-- Alternatively, if you want even better error messages.
textOf (oneOrMore anyChar)
    |> andThen
        (\txt ->
            if String.all Char.isAlpha txt then
                succeed txt
            else
                fail
                    ( "a name can only consist of letters, got '"
                    ++ txt
                    ++ "'"
                    )
        )
    |> parse "123"
    |> Result.mapError .message
--> Err "a name can only consist of letters, got '123'"
end : Parser a -> State -> Result Error ( a, State )

Succeeds only if there are no more remaining characters in the input text. This does not consume any inputs.

ℹ️ Equivalent regular expression: $

letters
    |> end
    |> parse "abc"
--> Ok "abc"


letters
    |> end
    |> parse "abc123"
    |> Result.mapError .message
--> Err "expected the end of the input text, but 3 characters are still remaining"
followedBy : Parser lookahead -> Parser a -> State -> Result Error ( a, State )

Succeeds only if the input text is followed by a lookahead parser. This does not consume any inputs.

ℹ️ Equivalent regular expression: (?=...) (positive lookahead)

letters
    |> followedBy digit
    |> parse "abc123"
--> Ok "abc"


letters
    |> followedBy digit
    |> parse "abc@def"
    |> Result.mapError .message
--> Err "expected a digit [0-9], but got '@' instead"
notFollowedBy : Parser lookahead -> Parser a -> State -> Result Error ( a, State )

Succeeds only if the input text is not followed by a lookahead parser. This does not consume any inputs.

ℹ️ Equivalent regular expression: (?!...) (negative lookahead)

letters
    |> notFollowedBy digit
    |> parse "abc@def"
--> Ok "abc"


letters
    |> notFollowedBy digit
    |> parse "abc123"
    |> Result.mapError .message
--> Err "the input text was followed by an unexpected pattern"

Mapping

map : (a -> b) -> Parser a -> Parser b

Transform the result of a parser.

letters
    |> map String.toLower
    |> parse "ABC"
--> Ok "abc"
map2 : (a -> b -> c) -> Parser a -> Parser b -> Parser c

Transform the result of two parsers.

import Parser.Common exposing (int)


map2
    (\op x ->
        if op == '-' then
            -x
        else
            x
    )
    (oneOf [char '+', char '-'])
    int
    |> parse "-123"
--> Ok -123
map3 : (a -> b -> c -> d) -> Parser a -> Parser b -> Parser c -> Parser d

Transform the result of three parsers.

import Parser.Common exposing (int)


map3
    (\x op y ->
        if op == '+' then
            x + y
        else
            x - y
    )
    int
    (oneOf [char '+', char '-'])
    int
    |> parse "5+2"
--> Ok 7
(a -> b -> c -> d -> e)
-> Parser a
-> Parser b
-> Parser c
-> Parser d
-> Parser e

Transform the result of four parsers.

(a -> b -> c -> d -> e -> f)
-> Parser a
-> Parser b
-> Parser c
-> Parser d
-> Parser e
-> Parser f

Transform the result of five parsers.

mapList : (List a -> b) -> List (Parser a) -> Parser b

Transform the result of a list of parsers. Note that all parsers must be of the same type, unlike map2..map5.

mapList (String.join ",")
    [ text "ab"
    , text "cd"
    , letters
    ]
    |> parse "abcdefg"
--> Ok "ab,cd,efg"