From c58ef5da3538e0936a3dac9e37d1e6fb3531c200 Mon Sep 17 00:00:00 2001 From: andre-dietrich Date: Wed, 28 Feb 2018 18:15:19 +0100 Subject: [PATCH 01/78] Update elm-package.json --- elm-package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm-package.json b/elm-package.json index 18e676f..e1f8b3d 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,7 +1,7 @@ { "version": "2.0.0", - "summary": "A parser combinator library", - "repository": "https://github.com/elm-community/parser-combinators.git", + "summary": "A fork of the elm-community/parser-combinator, with function modifyStream.", + "repository": "https://github.com/andre-dietrich/parser-combinators.git", "license": "BSD3", "source-directories": [ "examples", From 402eea1423ee237b889d23ae3e9fd8d736cef59b Mon Sep 17 00:00:00 2001 From: andre-dietrich Date: Wed, 28 Feb 2018 18:19:28 +0100 Subject: [PATCH 02/78] Update Combine.elm --- src/Combine.elm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Combine.elm b/src/Combine.elm index 42aff92..130340d 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -22,6 +22,7 @@ module Combine , currentSourceLine , currentLine , currentColumn + , modifyStream , map , mapError , andThen @@ -511,6 +512,14 @@ currentColumn = currentLocation >> .column +{-| Modify the parser's InputStream. +-} +modifyStream : (String -> String) -> Parser s () +modifyStream f = + Parser <| + \state stream -> + app (succeed ()) state { stream | input = f stream.input } + -- Transformers -- ------------ From 20c7fc3c25a9dd5065ffc852d407e80a69a033cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Andr=C3=A9=20Dietrich?= Date: Wed, 28 Feb 2018 19:07:30 +0100 Subject: [PATCH 03/78] modifyState added --- elm-package.json | 2 +- src/Combine.elm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/elm-package.json b/elm-package.json index e1f8b3d..f42cc4f 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,5 +1,5 @@ { - "version": "2.0.0", + "version": "1.0.0", "summary": "A fork of the elm-community/parser-combinator, with function modifyStream.", "repository": "https://github.com/andre-dietrich/parser-combinators.git", "license": "BSD3", diff --git a/src/Combine.elm b/src/Combine.elm index 130340d..2f2d0f2 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -126,7 +126,7 @@ into concrete Elm values. ### State Combinators -@docs withState, putState, modifyState, withLocation, withLine, withColumn, currentLocation, currentSourceLine, currentLine, currentColumn +@docs withState, putState, modifyState, withLocation, withLine, withColumn, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream -} From 01d960bcd99e7c82a0abca7607196685c83d052a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 6 Nov 2018 17:27:54 +0100 Subject: [PATCH 04/78] updated test for string/macro injection --- tests/Parsers.elm | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/Parsers.elm b/tests/Parsers.elm index 1c37c1c..f798373 100644 --- a/tests/Parsers.elm +++ b/tests/Parsers.elm @@ -1,8 +1,9 @@ -module Parsers exposing (..) +module Parsers exposing (calcSuite, manyTillSuite, sepEndBy1Suite, sepEndBySuite, sequenceSuite, streamModification, successful) import Calc exposing (calc) import Combine exposing (..) import Combine.Char exposing (..) +import Combine.Num exposing (int) import Expect import String import Test exposing (Test, describe, test) @@ -124,3 +125,42 @@ sequenceSuite = (parse (sequence [ string "a", string "b", string "c" ]) "abd") (Err ( (), { data = "abd", input = "d", position = 2 }, [ "expected \"c\"" ] )) ] + + +streamModification : Test +streamModification = + describe "stream modification tests" + [ test "simple injection" <| + \() -> + Expect.equal + (parse + (sequence + [ string "a" + , string "b" + , string "c" + , modifyStream ((++) "def") *> string "d" + , string "e" + , string "f" + ] + ) + "abc" + ) + (Ok ( (), { data = "abc", input = "", position = 6 }, [ "a", "b", "c", "d", "e", "f" ] )) + , test "statefull injection" <| + \() -> + Expect.equal + (parse + (sequence + [ string "a" + , (int >>= (\i -> modifyStream ((++) (String.repeat i "X")))) *> string "X" + , string "X" + , string "X" + , string "X" + , string "X" + , string "b" + ] + ) + "a5b" + ) + (Ok ( (), { data = "a5b", input = "", position = 8 }, [ "a", "X", "X", "X", "X", "X", "b" ] )) + ] From cf67948d737dc18601c0515c9acc87bdc8fa8140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 6 Nov 2018 17:30:45 +0100 Subject: [PATCH 05/78] changed elm-package back to original --- elm-package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/elm-package.json b/elm-package.json index f42cc4f..18e676f 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,7 +1,7 @@ { - "version": "1.0.0", - "summary": "A fork of the elm-community/parser-combinator, with function modifyStream.", - "repository": "https://github.com/andre-dietrich/parser-combinators.git", + "version": "2.0.0", + "summary": "A parser combinator library", + "repository": "https://github.com/elm-community/parser-combinators.git", "license": "BSD3", "source-directories": [ "examples", From b9388e7b22a813ddbf96c1411945882d407477eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 10:14:52 +0100 Subject: [PATCH 06/78] new/old test --- tests/Parsers.elm | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/tests/Parsers.elm b/tests/Parsers.elm index f798373..1c37c1c 100644 --- a/tests/Parsers.elm +++ b/tests/Parsers.elm @@ -1,9 +1,8 @@ -module Parsers exposing (calcSuite, manyTillSuite, sepEndBy1Suite, sepEndBySuite, sequenceSuite, streamModification, successful) +module Parsers exposing (..) import Calc exposing (calc) import Combine exposing (..) import Combine.Char exposing (..) -import Combine.Num exposing (int) import Expect import String import Test exposing (Test, describe, test) @@ -125,42 +124,3 @@ sequenceSuite = (parse (sequence [ string "a", string "b", string "c" ]) "abd") (Err ( (), { data = "abd", input = "d", position = 2 }, [ "expected \"c\"" ] )) ] - - -streamModification : Test -streamModification = - describe "stream modification tests" - [ test "simple injection" <| - \() -> - Expect.equal - (parse - (sequence - [ string "a" - , string "b" - , string "c" - , modifyStream ((++) "def") *> string "d" - , string "e" - , string "f" - ] - ) - "abc" - ) - (Ok ( (), { data = "abc", input = "", position = 6 }, [ "a", "b", "c", "d", "e", "f" ] )) - , test "statefull injection" <| - \() -> - Expect.equal - (parse - (sequence - [ string "a" - , (int >>= (\i -> modifyStream ((++) (String.repeat i "X")))) *> string "X" - , string "X" - , string "X" - , string "X" - , string "X" - , string "b" - ] - ) - "a5b" - ) - (Ok ( (), { data = "a5b", input = "", position = 8 }, [ "a", "X", "X", "X", "X", "X", "b" ] )) - ] From 9684d4b2e30a029ef6c22c176d21f3300e6319ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 11:01:26 +0100 Subject: [PATCH 07/78] new updated parsers ... removed all defined operators --- src/Combine.elm | 420 ++++++++++++++----------------------------- src/Combine/Char.elm | 50 +++--- src/Combine/Num.elm | 34 ++-- 3 files changed, 172 insertions(+), 332 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 2f2d0f2..41e605c 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1,73 +1,13 @@ -module Combine - exposing - ( Parser - , InputStream - , ParseLocation - , ParseContext - , ParseResult - , ParseError - , ParseOk - , primitive - , app - , lazy - , parse - , runParser - , withState - , putState - , modifyState - , withLocation - , withLine - , withColumn - , currentLocation - , currentSourceLine - , currentLine - , currentColumn - , modifyStream - , map - , mapError - , andThen - , andMap - , sequence - , fail - , succeed - , string - , regex - , end - , whitespace - , whitespace1 - , lookAhead - , while - , or - , choice - , optional - , maybe - , many - , many1 - , manyTill - , sepBy - , sepBy1 - , sepEndBy - , sepEndBy1 - , skip - , skipMany - , skipMany1 - , chainl - , chainr - , count - , between - , parens - , braces - , brackets - , () - , (>>=) - , (<$>) - , (<$) - , ($>) - , (<*>) - , (<*) - , (*>) - , (<|>) - ) +module Combine exposing + ( Parser, InputStream, ParseLocation, ParseContext, ParseResult, ParseError, ParseOk + , parse, runParser + , primitive, app, lazy + , fail, succeed, string, regex, end, whitespace, whitespace1 + , map, onsuccess, mapError, error + , andThen, andMap, sequence + , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets + , withState, putState, modifyState, withLocation, withLine, withColumn, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream + ) {-| This library provides facilities for parsing structured text data into concrete Elm values. @@ -111,17 +51,17 @@ into concrete Elm values. ### Transforming Parsers -@docs map, (<$>), (<$), ($>), mapError, () +@docs map, onsuccess, mapError, error ### Chaining Parsers -@docs andThen, (>>=), andMap, (<*>), (<*), (*>), sequence +@docs andThen, andMap, sequence ### Parser Combinators -@docs lookAhead, while, or, (<|>), choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets +@docs lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets ### State Combinators @@ -257,7 +197,7 @@ Here's how you would implement a greedy version of `manyTill` using ( estate, estream, Err ms ) -> ( estate, estream, Err ms ) in - primitive <| accumulate [] + primitive <| accumulate [] -} app : Parser state res -> state -> InputStream -> ParseContext state res @@ -312,7 +252,7 @@ parse p = -- Parse an int, then increment the state and return the parsed -- int. It's important that we try to parse the int _first_ -- since modifying the state will always succeed. - int <* modifyState ((+) 1) + int |> ignore (modifyState ((+) 1)) ints : Parse Int (List Int) ints = @@ -355,10 +295,10 @@ function to avoid "bad-recursion" errors. | EList (List E) name : Parser s String - name = whitespace *> regex "[a-zA-Z]+" <* whitespace + name = whitespace |> keep (regex "[a-zA-Z]+") |> ignore whitespace term : Parser s Expression - term = ETerm <$> name + term = andMap name ETerm list : Parser s Expression list = @@ -366,7 +306,8 @@ function to avoid "bad-recursion" errors. -- helper is itself a function so we avoid the case where the -- value `list` tries to apply itself in its definition. helper () = - EList <$> between (string "(") (string ")") (many (term <|> list)) + between (string "(") (string ")") (many (or term list)) + |> andMap EList in -- lazy defers calling helper until it's actually needed. lazy helper @@ -481,14 +422,16 @@ currentLocation stream = lengthPlusNL = length + 1 in - if position == length then - ParseLocation line currentLine position - else if position > length then - find (position - lengthPlusNL) (currentLine + 1) rest - else - ParseLocation line currentLine position + if position == length then + ParseLocation line currentLine position + + else if position > length then + find (position - lengthPlusNL) (currentLine + 1) rest + + else + ParseLocation line currentLine position in - find stream.position 0 (String.split "\n" stream.data) + find stream.position 0 (String.split "\n" stream.data) {-| Get the current source line in the input stream. @@ -521,6 +464,7 @@ modifyStream f = app (succeed ()) state { stream | input = f stream.input } + -- Transformers -- ------------ @@ -608,7 +552,7 @@ andThen f p = sum = int |> map (+) - |> andMap (plus *> int) + |> andMap (plus |> keep int) parse sum "1+2" -- Ok 3 @@ -616,7 +560,7 @@ andThen f p = -} andMap : Parser s a -> Parser s (a -> b) -> Parser s b andMap rp lp = - lp >>= flip map rp + lp |> andThen (flip map rp) {-| Run a list of parsers in sequence, accumulating the results. The @@ -647,9 +591,9 @@ sequence parsers = ( estate, estream, Err ms ) -> ( estate, estream, Err ms ) in - Parser <| - \state stream -> - accumulate [] parsers state stream + Parser <| + \state stream -> + accumulate [] parsers state stream @@ -714,7 +658,8 @@ string s = pos = stream.position + len in - ( state, { stream | input = rem, position = pos }, Ok s ) + ( state, { stream | input = rem, position = pos }, Ok s ) + else ( state, stream, Err [ "expected " ++ toString s ] ) @@ -735,27 +680,28 @@ regex pat = pattern = if String.startsWith "^" pat then pat + else "^" ++ pat in - Parser <| - \state stream -> - case Regex.find (Regex.AtMost 1) (Regex.regex pattern) stream.input of - [ match ] -> - let - len = - String.length match.match + Parser <| + \state stream -> + case Regex.find (Regex.AtMost 1) (Regex.regex pattern) stream.input of + [ match ] -> + let + len = + String.length match.match - rem = - String.dropLeft len stream.input + rem = + String.dropLeft len stream.input - pos = - stream.position + len - in - ( state, { stream | input = rem, position = pos }, Ok match.match ) + pos = + stream.position + len + in + ( state, { stream | input = rem, position = pos }, Ok match.match ) - _ -> - ( state, stream, Err [ "expected input matching Regexp /" ++ pattern ++ "/" ] ) + _ -> + ( state, stream, Err [ "expected input matching Regexp /" ++ pattern ++ "/" ] ) {-| Consume input while the predicate matches. @@ -778,20 +724,21 @@ while pred = pos = stream.position + 1 in - accumulate (acc ++ c) state { stream | input = rest, position = pos } + accumulate (acc ++ c) state { stream | input = rest, position = pos } + else ( state, stream, acc ) Nothing -> ( state, stream, acc ) in - Parser <| - \state stream -> - let - ( rstate, rstream, res ) = - accumulate "" state stream - in - ( rstate, rstream, Ok res ) + Parser <| + \state stream -> + let + ( rstate, rstream, res ) = + accumulate "" state stream + in + ( rstate, rstream, Ok res ) {-| Fail when the input is not empty. @@ -809,6 +756,7 @@ end = \state stream -> if stream.input == "" then ( state, stream, Ok () ) + else ( state, stream, Err [ "expected end of input" ] ) @@ -884,7 +832,7 @@ choice xs = -} optional : a -> Parser s a -> Parser s a optional res p = - p <|> succeed res + succeed res |> or p {-| Wrap the return value into a `Maybe`. Returns `Nothing` on failure. @@ -928,19 +876,20 @@ many p = ( rstate, rstream, Ok res ) -> if stream == rstream then ( rstate, rstream, List.reverse acc ) + else accumulate (res :: acc) rstate rstream _ -> ( state, stream, List.reverse acc ) in - Parser <| - \state stream -> - let - ( rstate, rstream, res ) = - accumulate [] state stream - in - ( rstate, rstream, Ok res ) + Parser <| + \state stream -> + let + ( rstate, rstream, res ) = + accumulate [] state stream + in + ( rstate, rstream, Ok res ) {-| Parse at least one result. @@ -954,13 +903,13 @@ many p = -} many1 : Parser s a -> Parser s (List a) many1 p = - (::) <$> p <*> many p + p |> map (::) |> andMap (many p) {-| Apply the first parser zero or more times until second parser succeeds. On success, the list of the first parser's results is returned. - string "") + string "")) -} manyTill : Parser s a -> Parser s end -> Parser s (List a) @@ -979,7 +928,7 @@ manyTill p end = _ -> ( estate, estream, Err ms ) in - Parser (accumulate []) + Parser (accumulate []) {-| Parser zero or more occurences of one parser separated by another. @@ -996,14 +945,14 @@ manyTill p end = -} sepBy : Parser s x -> Parser s a -> Parser s (List a) sepBy sep p = - sepBy1 sep p <|> succeed [] + or (sepBy1 sep p) (succeed []) {-| Parse one or more occurences of one parser separated by another. -} sepBy1 : Parser s x -> Parser s a -> Parser s (List a) sepBy1 sep p = - (::) <$> p <*> many (sep *> p) + map (::) p |> andMap (many (sep |> keep p)) {-| Parse zero or more occurences of one parser separated and @@ -1015,7 +964,7 @@ optionally ended by another. -} sepEndBy : Parser s x -> Parser s a -> Parser s (List a) sepEndBy sep p = - sepEndBy1 sep p <|> succeed [] + or (sepEndBy1 sep p) (succeed []) {-| Parse one or more occurences of one parser separated and @@ -1033,28 +982,28 @@ optionally ended by another. -} sepEndBy1 : Parser s x -> Parser s a -> Parser s (List a) sepEndBy1 sep p = - sepBy1 sep p <* maybe sep + sepBy1 sep p |> ignore (maybe sep) {-| Apply a parser and skip its result. -} skip : Parser s x -> Parser s () skip p = - () <$ p + p |> onsuccess () {-| Apply a parser and skip its result many times. -} skipMany : Parser s x -> Parser s () skipMany p = - () <$ many (skip p) + many (skip p) |> onsuccess () {-| Apply a parser and skip its result at least once. -} skipMany1 : Parser s x -> Parser s () skipMany1 p = - () <$ many1 (skip p) + many1 (skip p) |> onsuccess () {-| Parse one or more occurences of `p` separated by `op`, recursively @@ -1065,16 +1014,17 @@ chainl : Parser s (a -> a -> a) -> Parser s a -> Parser s a chainl op p = let accumulate x = - (op - |> andThen - (\f -> - p - |> andThen (\y -> accumulate (f x y)) + succeed x + |> or + (op + |> andThen + (\f -> + p + |> andThen (\y -> accumulate (f x y)) + ) ) - ) - <|> succeed x in - andThen accumulate p + andThen accumulate p {-| Similar to `chainl` but functions of `op` are applied in @@ -1085,17 +1035,18 @@ chainr : Parser s (a -> a -> a) -> Parser s a -> Parser s a chainr op p = let accumulate x = - (op - |> andThen - (\f -> - p - |> andThen accumulate - |> andThen (\y -> succeed (f x y)) + succeed x + |> or + (op + |> andThen + (\f -> + p + |> andThen accumulate + |> andThen (\y -> succeed (f x y)) + ) ) - ) - <|> succeed x in - andThen accumulate p + andThen accumulate p {-| Parse `n` occurences of `p`. @@ -1106,10 +1057,11 @@ count n p = accumulate x acc = if x <= 0 then succeed (List.reverse acc) + else andThen (\res -> accumulate (x - 1) (res :: acc)) p in - accumulate n [] + accumulate n [] {-| Parse something between two other parsers. @@ -1120,12 +1072,12 @@ The parser is equivalent to the parser - string "(" *> string "a" <* string ")" + string "(" |> keep (string "a") |> ignore (string ")") -} between : Parser s l -> Parser s r -> Parser s a -> Parser s a between lp rp p = - lp *> p <* rp + lp |> keep p |> ignore rp {-| Parse something between parentheses. @@ -1151,30 +1103,30 @@ brackets = {-| Parse zero or more whitespace characters. - parse (whitespace *> string "hello") "hello" + parse (whitespace |> keep (string "hello")) "hello" -- Ok "hello" - parse (whitespace *> string "hello") " hello" + parse (whitespace |> keep (string "hello")) " hello" -- Ok "hello" -} whitespace : Parser s String whitespace = - regex "[ \t\x0D\n]*" "whitespace" + regex "\\s*" |> error "whitespace" {-| Parse one or more whitespace characters. - parse (whitespace1 *> string "hello") "hello" + parse (whitespace1 |> keep (string "hello")) "hello" -- Err ["whitespace"] - parse (whitespace1 *> string "hello") " hello" + parse (whitespace1 |> keep (string "hello")) " hello" -- Ok "hello" -} whitespace1 : Parser s String whitespace1 = - regex "[ \t\x0D\n]+" "whitespace" + regex "\\s+" |> error "whitespace" @@ -1185,102 +1137,27 @@ whitespace1 = {-| Variant of `mapError` that replaces the Parser's error with a List of a single string. - parse (string "a" "gimme an 'a'") "b" + parse (string "a" |> error "gimme an 'a'") "b" -- Err ["gimme an 'a'"] -} -() : Parser s a -> String -> Parser s a -() p m = +error : String -> Parser s a -> Parser s a +error m p = mapError (always [ m ]) p -{-| Infix version of `andThen`. - - import Combine.Num exposing (int) - - choosy : Parser s String - choosy = - let - createParser n = - if n % 2 == 0 then - string " is even" - else - string " is odd" - in - int >>= createParser - - parse choosy "1 is odd" - -- Ok " is odd" - - parse choosy "2 is even" - -- Ok " is even" - - parse choosy "1 is even" - -- Err ["expected \" is odd\""] - --} -(>>=) : Parser s a -> (a -> Parser s b) -> Parser s b -(>>=) = - flip andThen - - -{-| Infix version of `map`. - - parse (toString <$> int) "42" - -- Ok "42" - - parse (toString <$> int) "abc" - -- Err ["expected an integer"] - --} -(<$>) : (a -> b) -> Parser s a -> Parser s b -(<$>) = - map - - -{-| Run a parser and return the value on the left on success. - - parse (True <$ string "true") "true" - -- Ok True - - parse (True <$ string "true") "false" - -- Err ["expected \"true\""] - --} -(<$) : a -> Parser s x -> Parser s a -(<$) res = - map (always res) - - {-| Run a parser and return the value on the right on success. - parse (string "true" $> True) "true" + parse (string "true" |> onsuccess True) "true" -- Ok True - parse (string "true" $> True) "false" + parse (string "true" |> onsuccess True) "false" -- Err ["expected \"true\""] -} -($>) : Parser s x -> a -> Parser s a -($>) = - flip (<$) - - -{-| Infix version of `andMap`. - - add : Int -> Int -> Int - add = (+) - - plus : Parser s String - plus = string "+" - - parse (add <$> int <*> (plus *> int)) "1+1" - -- Ok 2 - --} -(<*>) : Parser s (a -> b) -> Parser s a -> Parser s b -(<*>) = - flip andMap +onsuccess : a -> Parser s x -> Parser s a +onsuccess res = + map (always res) {-| Join two parsers, ignoring the result of the one on the right. @@ -1288,17 +1165,17 @@ of a single string. unsuffix : Parser s String unsuffix = regex "[a-z]" - <* regex "[!?]" + |> keep (regex "[!?]") parse unsuffix "a!" -- Ok "a" -} -(<*) : Parser s a -> Parser s x -> Parser s a -(<*) lp rp = - lp - |> map always - |> andMap rp +keep : Parser s a -> Parser s x -> Parser s a +keep p1 p2 = + p2 + |> map (flip always) + |> andMap p1 {-| Join two parsers, ignoring the result of the one on the left. @@ -1306,50 +1183,15 @@ of a single string. unprefix : Parser s String unprefix = string ">" - *> while ((==) ' ') - *> while ((/=) ' ') + |> ignore (while ((==) ' ')) + |> ignore (while ((/=) ' ')) parse unprefix "> a" -- Ok "a" -} -(*>) : Parser s x -> Parser s a -> Parser s a -(*>) lp rp = - lp - |> map (flip always) - |> andMap rp - - -{-| Synonym for `or`. --} -(<|>) : Parser s a -> Parser s a -> Parser s a -(<|>) = - or - - - --- Fixities - - -infixl 1 >>= - - -infixr 1 <|> - - -infixl 4 <$> - - -infixl 4 <$ - - -infixl 4 $> - - -infixl 4 <*> - - -infixl 4 <* - - -infixl 4 *> +ignore : Parser s x -> Parser s a -> Parser s a +ignore p1 p2 = + p2 + |> map always + |> andMap p1 diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index 038537e..2b3af29 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -7,12 +7,15 @@ everything that you can do with this module by using `Combine.regex`, `Combine.string` or `Combine.primitive` and, in general, those will be much faster. + # Parsers + @docs satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit + -} import Char -import Combine exposing (Parser, primitive, regex, (), (<$), (<|>)) +import Combine exposing (Parser, error, onsuccess, or, primitive, regex) import String @@ -33,16 +36,17 @@ satisfy pred = message = "could not satisfy predicate" in - case String.uncons stream.input of - Just ( h, rest ) -> - if pred h then - ( state, { stream | input = rest, position = stream.position + 1 }, Ok h ) - else - ( state, stream, Err [ message ] ) - - Nothing -> + case String.uncons stream.input of + Just ( h, rest ) -> + if pred h then + ( state, { stream | input = rest, position = stream.position + 1 }, Ok h ) + + else ( state, stream, Err [ message ] ) + Nothing -> + ( state, stream, Err [ message ] ) + {-| Parse an exact character match. @@ -55,7 +59,7 @@ satisfy pred = -} char : Char -> Parser s Char char c = - satisfy ((==) c) ("expected " ++ toString c) + satisfy ((==) c) |> error ("expected " ++ toString c) {-| Parse any character. @@ -69,7 +73,7 @@ char c = -} anyChar : Parser s Char anyChar = - satisfy (always True) "expected any character" + satisfy (always True) |> error "expected any character" {-| Parse a character from the given list. @@ -83,7 +87,7 @@ anyChar = -} oneOf : List Char -> Parser s Char oneOf cs = - satisfy (flip List.member cs) ("expected one of " ++ toString cs) + satisfy (flip List.member cs) |> error ("expected one of " ++ toString cs) {-| Parse a character that is not in the given list. @@ -97,74 +101,74 @@ oneOf cs = -} noneOf : List Char -> Parser s Char noneOf cs = - satisfy (not << flip List.member cs) ("expected none of " ++ toString cs) + satisfy (not << flip List.member cs) |> error ("expected none of " ++ toString cs) {-| Parse a space character. -} space : Parser s Char space = - satisfy ((==) ' ') "expected space" + satisfy ((==) ' ') |> error "expected space" {-| Parse a `\t` character. -} tab : Parser s Char tab = - satisfy ((==) '\t') "expected tab" + satisfy ((==) '\t') |> error "expected tab" {-| Parse a `\n` character. -} newline : Parser s Char newline = - satisfy ((==) '\n') "expected newline" + satisfy ((==) '\n') |> error "expected newline" {-| Parse a `\r\n` sequence, returning a `\n` character. -} crlf : Parser s Char crlf = - '\n' <$ regex "\x0D\n" "expected crlf" + regex "\\r\\n" |> onsuccess '\n' |> error "expected crlf" {-| Parse an end of line character or sequence, returning a `\n` character. -} eol : Parser s Char eol = - newline <|> crlf + or newline crlf {-| Parse any lowercase character. -} lower : Parser s Char lower = - satisfy Char.isLower "expected a lowercase character" + satisfy Char.isLower |> error "expected a lowercase character" {-| Parse any uppercase character. -} upper : Parser s Char upper = - satisfy Char.isUpper "expected an uppercase character" + satisfy Char.isUpper |> error "expected an uppercase character" {-| Parse any base 10 digit. -} digit : Parser s Char digit = - satisfy Char.isDigit "expected a digit" + satisfy Char.isDigit |> error "expected a digit" {-| Parse any base 8 digit. -} octDigit : Parser s Char octDigit = - satisfy Char.isOctDigit "expected an octal digit" + satisfy Char.isOctDigit |> error "expected an octal digit" {-| Parse any base 16 digit. -} hexDigit : Parser s Char hexDigit = - satisfy Char.isHexDigit "expected a hexadecimal digit" + satisfy Char.isHexDigit |> error "expected a hexadecimal digit" diff --git a/src/Combine/Num.elm b/src/Combine/Num.elm index 2fe6a3f..8929acc 100644 --- a/src/Combine/Num.elm +++ b/src/Combine/Num.elm @@ -1,15 +1,12 @@ -module Combine.Num - exposing - ( sign - , digit - , int - , float - ) +module Combine.Num exposing (sign, digit, int, float) {-| This module contains Parsers specific to parsing numbers. + # Parsers + @docs sign, digit, int, float + -} import Char @@ -44,10 +41,9 @@ for negative numbers. sign : Parser s Int sign = optional 1 - (choice - [ 1 <$ string "+" - , -1 <$ string "-" - ] + (or + (string "+" |> onsuccess 1) + (string "-" |> onsuccess -1) ) @@ -59,24 +55,22 @@ digit = toDigit c = Char.toCode c - Char.toCode '0' in - toDigit <$> Combine.Char.digit "expected a digit" + map toDigit Combine.Char.digit |> error "expected a digit" {-| Parse an integer. -} int : Parser s Int int = - (*) - <$> sign - <*> (toInt <$> regex "(0|[1-9][0-9]*)") - "expected an integer" + map (*) sign + |> andMap (regex "(0|[1-9][0-9]*)" |> map toInt) + |> error "expected an integer" {-| Parse a float. -} float : Parser s Float float = - ((*) << Basics.toFloat) - <$> sign - <*> (toFloat <$> regex "(0|[1-9][0-9]*)(\\.[0-9]+)") - "expected a float" + map ((*) << Basics.toFloat) sign + |> andMap (regex "(0|[1-9][0-9]*)(\\.[0-9]+)" |> map toFloat) + |> error "expected a float" From 64a716b65c17da84834f19d677db4209cf2aab5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 11:35:10 +0100 Subject: [PATCH 08/78] updated docs --- src/Combine.elm | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 41e605c..d18dd35 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -5,7 +5,7 @@ module Combine exposing , fail, succeed, string, regex, end, whitespace, whitespace1 , map, onsuccess, mapError, error , andThen, andMap, sequence - , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets + , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore , withState, putState, modifyState, withLocation, withLine, withColumn, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream ) @@ -61,7 +61,7 @@ into concrete Elm values. ### Parser Combinators -@docs lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets +@docs lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, onsuccess, keep, ignore ### State Combinators @@ -1129,11 +1129,6 @@ whitespace1 = regex "\\s+" |> error "whitespace" - --- Infix operators --- --------------- - - {-| Variant of `mapError` that replaces the Parser's error with a List of a single string. From 1df794e0cd7ef78ca05ca8d4bcb37e6b42078daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 11:52:25 +0100 Subject: [PATCH 09/78] changed error to onerror --- src/Combine.elm | 14 +++++++------- src/Combine/Char.elm | 28 ++++++++++++++-------------- src/Combine/Num.elm | 6 +++--- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index d18dd35..f04ad15 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -3,7 +3,7 @@ module Combine exposing , parse, runParser , primitive, app, lazy , fail, succeed, string, regex, end, whitespace, whitespace1 - , map, onsuccess, mapError, error + , map, onsuccess, mapError, onerror , andThen, andMap, sequence , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore , withState, putState, modifyState, withLocation, withLine, withColumn, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream @@ -51,7 +51,7 @@ into concrete Elm values. ### Transforming Parsers -@docs map, onsuccess, mapError, error +@docs map, onsuccess, mapError, onerror ### Chaining Parsers @@ -1112,7 +1112,7 @@ brackets = -} whitespace : Parser s String whitespace = - regex "\\s*" |> error "whitespace" + regex "\\s*" |> onerror "whitespace" {-| Parse one or more whitespace characters. @@ -1126,18 +1126,18 @@ whitespace = -} whitespace1 : Parser s String whitespace1 = - regex "\\s+" |> error "whitespace" + regex "\\s+" |> onerror "whitespace" {-| Variant of `mapError` that replaces the Parser's error with a List of a single string. - parse (string "a" |> error "gimme an 'a'") "b" + parse (string "a" |> onerror "gimme an 'a'") "b" -- Err ["gimme an 'a'"] -} -error : String -> Parser s a -> Parser s a -error m p = +onerror : String -> Parser s a -> Parser s a +onerror m p = mapError (always [ m ]) p diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index 2b3af29..8076454 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -15,7 +15,7 @@ much faster. -} import Char -import Combine exposing (Parser, error, onsuccess, or, primitive, regex) +import Combine exposing (Parser, onerror, onsuccess, or, primitive, regex) import String @@ -59,7 +59,7 @@ satisfy pred = -} char : Char -> Parser s Char char c = - satisfy ((==) c) |> error ("expected " ++ toString c) + satisfy ((==) c) |> onerror ("expected " ++ toString c) {-| Parse any character. @@ -73,7 +73,7 @@ char c = -} anyChar : Parser s Char anyChar = - satisfy (always True) |> error "expected any character" + satisfy (always True) |> onerror "expected any character" {-| Parse a character from the given list. @@ -87,7 +87,7 @@ anyChar = -} oneOf : List Char -> Parser s Char oneOf cs = - satisfy (flip List.member cs) |> error ("expected one of " ++ toString cs) + satisfy (flip List.member cs) |> onerror ("expected one of " ++ toString cs) {-| Parse a character that is not in the given list. @@ -101,35 +101,35 @@ oneOf cs = -} noneOf : List Char -> Parser s Char noneOf cs = - satisfy (not << flip List.member cs) |> error ("expected none of " ++ toString cs) + satisfy (not << flip List.member cs) |> onerror ("expected none of " ++ toString cs) {-| Parse a space character. -} space : Parser s Char space = - satisfy ((==) ' ') |> error "expected space" + satisfy ((==) ' ') |> onerror "expected space" {-| Parse a `\t` character. -} tab : Parser s Char tab = - satisfy ((==) '\t') |> error "expected tab" + satisfy ((==) '\t') |> onerror "expected tab" {-| Parse a `\n` character. -} newline : Parser s Char newline = - satisfy ((==) '\n') |> error "expected newline" + satisfy ((==) '\n') |> onerror "expected newline" {-| Parse a `\r\n` sequence, returning a `\n` character. -} crlf : Parser s Char crlf = - regex "\\r\\n" |> onsuccess '\n' |> error "expected crlf" + regex "\\r\\n" |> onsuccess '\n' |> onerror "expected crlf" {-| Parse an end of line character or sequence, returning a `\n` character. @@ -143,32 +143,32 @@ eol = -} lower : Parser s Char lower = - satisfy Char.isLower |> error "expected a lowercase character" + satisfy Char.isLower |> onerror "expected a lowercase character" {-| Parse any uppercase character. -} upper : Parser s Char upper = - satisfy Char.isUpper |> error "expected an uppercase character" + satisfy Char.isUpper |> onerror "expected an uppercase character" {-| Parse any base 10 digit. -} digit : Parser s Char digit = - satisfy Char.isDigit |> error "expected a digit" + satisfy Char.isDigit |> onerror "expected a digit" {-| Parse any base 8 digit. -} octDigit : Parser s Char octDigit = - satisfy Char.isOctDigit |> error "expected an octal digit" + satisfy Char.isOctDigit |> onerror "expected an octal digit" {-| Parse any base 16 digit. -} hexDigit : Parser s Char hexDigit = - satisfy Char.isHexDigit |> error "expected a hexadecimal digit" + satisfy Char.isHexDigit |> onerror "expected a hexadecimal digit" diff --git a/src/Combine/Num.elm b/src/Combine/Num.elm index 8929acc..6a2ef71 100644 --- a/src/Combine/Num.elm +++ b/src/Combine/Num.elm @@ -55,7 +55,7 @@ digit = toDigit c = Char.toCode c - Char.toCode '0' in - map toDigit Combine.Char.digit |> error "expected a digit" + map toDigit Combine.Char.digit |> onerror "expected a digit" {-| Parse an integer. @@ -64,7 +64,7 @@ int : Parser s Int int = map (*) sign |> andMap (regex "(0|[1-9][0-9]*)" |> map toInt) - |> error "expected an integer" + |> onerror "expected an integer" {-| Parse a float. @@ -73,4 +73,4 @@ float : Parser s Float float = map ((*) << Basics.toFloat) sign |> andMap (regex "(0|[1-9][0-9]*)(\\.[0-9]+)" |> map toFloat) - |> error "expected a float" + |> onerror "expected a float" From 81d5bef7dc3669ca7eb85fbd8a2a4612ae4d6c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 11:53:01 +0100 Subject: [PATCH 10/78] Calc example seems to work --- examples/Calc.elm | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/Calc.elm b/examples/Calc.elm index 992137c..1fd4aa6 100644 --- a/examples/Calc.elm +++ b/examples/Calc.elm @@ -3,6 +3,7 @@ module Calc exposing (calc) {-| An example parser that computes arithmetic expressions. @docs calc + -} import Combine exposing (..) @@ -12,16 +13,16 @@ import Combine.Num exposing (int) addop : Parser s (Int -> Int -> Int) addop = choice - [ (+) <$ string "+" - , (-) <$ string "-" + [ string "+" |> onsuccess (+) + , string "-" |> onsuccess (-) ] mulop : Parser s (Int -> Int -> Int) mulop = choice - [ (*) <$ string "*" - , (//) <$ string "/" + [ string "*" |> onsuccess (*) + , string "/" |> onsuccess (//) ] @@ -31,7 +32,7 @@ expr = go () = chainl addop term in - lazy go + lazy go term : Parser s Int @@ -40,19 +41,21 @@ term = go () = chainl mulop factor in - lazy go + lazy go factor : Parser s Int factor = - whitespace *> (parens expr <|> int) <* whitespace + whitespace + |> keep (or (parens expr) int) + |> ignore whitespace {-| Compute the result of an expression. -} calc : String -> Result String Int calc s = - case parse (expr <* end) s of + case parse (expr |> ignore end) s of Ok ( _, _, n ) -> Ok n From 069060be6b3476396913db38d665c68ca4d8a7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 12:00:15 +0100 Subject: [PATCH 11/78] ported Scheme example to new version parser --- examples/Scheme.elm | 140 ++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 71 deletions(-) diff --git a/examples/Scheme.elm b/examples/Scheme.elm index 5bffd78..5f54e5d 100644 --- a/examples/Scheme.elm +++ b/examples/Scheme.elm @@ -1,4 +1,4 @@ -module Scheme exposing (..) +module Scheme exposing (E(..), bool, char, comment, expr, float, formatError, identifier, int, list, parse, program, quasiquote, quote, str, unquote, unquoteSplice, vector) import Combine exposing (..) import Combine.Char exposing (anyChar) @@ -24,37 +24,28 @@ type E comment : Parser s E comment = - EComment - <$> regex ";[^\n]+" - "comment" + regex ";[^\n]+" |> map EComment |> onerror "comment" bool : Parser s E bool = let boolLiteral = - choice - [ True <$ string "#t" - , False <$ string "#f" - ] + or + (string "#t" |> onsuccess True) + (string "#f" |> onsuccess False) in - EBool - <$> boolLiteral - "boolean literal" + map EBool boolLiteral |> onerror "boolean literal" int : Parser s E int = - EInt - <$> Combine.Num.int - "integer literal" + map EInt Combine.Num.int |> onerror "integer literal" float : Parser s E float = - EFloat - <$> Combine.Num.float - "float literal" + map EFloat Combine.Num.float |> onerror "float literal" char : Parser s E @@ -62,22 +53,22 @@ char = let charLiteral = string "#\\" - *> choice - [ ' ' <$ string "space" - , '\n' <$ string "newline" - , anyChar - ] + |> keep + (choice + [ string "space" |> onsuccess ' ' + , string "newline" |> onsuccess '\n' + , anyChar + ] + ) in - EChar - <$> charLiteral - "character literal" + map EChar charLiteral |> onerror "character literal" str : Parser s E str = - EString - <$> regex "\"(\\\"|[^\"])+\"" - "string literal" + regex "\"(\\\"|[^\"])+\"" + |> map EString + |> onerror "string literal" identifier : Parser s E @@ -110,49 +101,53 @@ identifier = identifierRe = initialRe ++ subsequentRe in - EIdentifier <$> regex identifierRe "identifier" + regex identifierRe |> map EIdentifier |> onerror "identifier" list : Parser s E list = - EList - <$> parens (many expr) - "list" + many expr |> parens |> map EList |> onerror "list" vector : Parser s E vector = - EVector - <$> (string "#(" *> many expr <* string ")") - "vector" + string "#(" + |> keep (many expr) + |> ignore (string ")") + |> map EVector + |> onerror "vector" quote : Parser s E quote = - EQuote - <$> (string "'" *> expr) - "quoted expression" + string "'" + |> keep expr + |> map EQuote + |> onerror "quoted expression" quasiquote : Parser s E quasiquote = - EQuasiquote - <$> (string "`" *> expr) - "quasiquoted expression" + string "`" + |> keep expr + |> map EQuasiquote + |> onerror "quasiquoted expression" unquote : Parser s E unquote = - EUnquote - <$> (string "," *> expr) - "unquoted expression" + string "," + |> keep expr + |> map EUnquote + |> onerror "unquoted expression" unquoteSplice : Parser s E unquoteSplice = - EUnquoteSplice - <$> (string ",@" *> expr) - "spliced expression" + string ",@" + |> keep expr + |> map EUnquoteSplice + |> onerror "spliced expression" expr : Parser s E @@ -161,22 +156,25 @@ expr = \() -> let parsers = - [ bool - , float - , int - , char - , str - , identifier - , list - , vector - , quote - , quasiquote - , unquote - , unquoteSplice - , comment - ] + choice + [ bool + , float + , int + , char + , str + , identifier + , list + , vector + , quote + , quasiquote + , unquote + , unquoteSplice + , comment + ] in - whitespace *> choice parsers <* whitespace + whitespace + |> keep parsers + |> ignore whitespace program : Parser s (List E) @@ -205,15 +203,15 @@ formatError ms stream = padding = location.column + separatorOffset + 2 in - "Parse error around line:\n\n" - ++ toString location.line - ++ separator - ++ location.source - ++ "\n" - ++ String.padLeft padding ' ' "^" - ++ "\nI expected one of the following:\n" - ++ expectationSeparator - ++ String.join expectationSeparator ms + "Parse error around line:\n\n" + ++ toString location.line + ++ separator + ++ location.source + ++ "\n" + ++ String.padLeft padding ' ' "^" + ++ "\nI expected one of the following:\n" + ++ expectationSeparator + ++ String.join expectationSeparator ms parse : String -> Result String (List E) From 8d62279ac67eb41aea2b9400b8a7d38e529a8d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 12:52:45 +0100 Subject: [PATCH 12/78] Python lifted to newer verstion --- examples/Python.elm | 324 +++++++++++++++++++++++++------------------- 1 file changed, 184 insertions(+), 140 deletions(-) diff --git a/examples/Python.elm b/examples/Python.elm index bbc9cd2..0d4e4cf 100644 --- a/examples/Python.elm +++ b/examples/Python.elm @@ -1,4 +1,4 @@ -module Python exposing (..) +module Python exposing (CompoundStatement(..), Expression(..), Indentation, Statement(..), addop, andExpr, andop, app, arithExpr, assertStmt, assignStmt, assignop, atom, attribute, block, blockStmt, bool, breakStmt, cmpExpr, cmpop, commaSep, comment, compoundStmt, continueStmt, dedent, delStmt, dict, dictSep, dropWhile, expr, exprList, exprStmt, factor, float, forStmt, formatError, funcStmt, globalStmt, identifier, importAs, importFromStmt, importStmt, indent, indentation, initIndentation, int, keyword, list, listSep, mulop, notExpr, orop, parse, passStmt, printStmt, program, raiseStmt, returnStmt, set, simpleStmt, spaces, stmt, str, term, test, token, tuple, whileStmt, whitespace, withStmt) import Combine exposing (..) import Combine.Char exposing (..) @@ -71,6 +71,7 @@ dropWhile p xs = x :: ys -> if p x then dropWhile p ys + else xs @@ -87,7 +88,7 @@ spaces = whitespace : Parser s String whitespace = - comment <|> spaces "whitespace" + or comment spaces |> onerror "whitespace" token : Parser s res -> Parser s res @@ -97,63 +98,66 @@ token = keyword : String -> Parser s String keyword s = - string s <* spaces + string s |> ignore spaces bool : Parser s Expression bool = - EBool - <$> choice - [ False <$ string "False" - , True <$ string "True" - ] - "boolean" + or (string "False" |> onsuccess False) (string "True" |> onsuccess True) + |> map EBool + |> onerror "boolean" int : Parser s Expression int = - EInt <$> Combine.Num.int "integer" + map EInt Combine.Num.int |> onerror "integer" float : Parser s Expression float = - EFloat <$> Combine.Num.float "float" + map EFloat Combine.Num.float |> onerror "float" str : Parser s Expression str = - EString - <$> choice - [ string "'" *> regex "(\\\\'|[^'\n])*" <* string "'" - , string "\"" *> regex "(\\\\\"|[^\"\n])*" <* string "\"" - ] - "string" + or + (string "'" + |> keep (regex "(\\\\'|[^'\n])*") + |> ignore (string "'") + ) + (string "\"" + |> keep (regex "(\\\\\"|[^\"\n])*") + |> ignore (string "\"") + ) + |> map EString + |> onerror "string" identifier : Parser s Expression identifier = - EIdentifier <$> regex "[_a-zA-Z][_a-zA-Z0-9]*" "identifier" + regex "[_a-zA-Z][_a-zA-Z0-9]*" + |> map EIdentifier + |> onerror "identifier" attribute : Parser s Expression attribute = lazy <| \() -> - EAttribute - <$> identifier - <* string "." - <*> choice [ attribute, identifier ] - "attribute" + map EAttribute identifier + |> ignore (string ".") + |> andMap (or attribute identifier) + |> onerror "attribute" app : Parser s Expression app = lazy <| \() -> - EApp - <$> choice [ attribute, identifier ] - <*> parens exprList - "function call" + or attribute identifier + |> map EApp + |> andMap (parens exprList) + |> onerror "function call" commaSep : Parser s String @@ -163,48 +167,55 @@ commaSep = dictSep : Parser s String dictSep = - regex ":[ \t\x0D\n]*" + regex ":[ \t\n]*" listSep : Parser s String listSep = - regex ",[ \t\x0D\n]*" + regex ",[ \t\n]*" list : Parser s Expression list = lazy <| \() -> - EList - <$> brackets (sepBy listSep expr) - "list" + brackets (sepBy listSep expr) + |> map EList + |> onerror "error list" tuple : Parser s Expression tuple = lazy <| \() -> - ETuple - <$> parens (sepBy listSep expr) - "tuple" + sepBy listSep expr + |> parens + |> map ETuple + |> onerror "tuple" dict : Parser s Expression dict = lazy <| \() -> - EDict - <$> brackets (sepBy listSep ((,) <$> expr <* dictSep <*> expr)) - "dictionary" + expr + |> ignore dictSep + |> map (,) + |> andMap expr + |> sepBy listSep + |> brackets + |> map EDict + |> onerror "dictionary" set : Parser s Expression set = lazy <| \() -> - ESet - <$> brackets (sepBy listSep expr) - "set" + sepBy listSep expr + |> brackets + |> map ESet + |> onerror "set" atom : Parser s Expression @@ -228,7 +239,7 @@ notExpr : Parser s Expression notExpr = lazy <| \() -> - (token <| ENot <$> (string "not" *> notExpr)) <|> cmpExpr + or (string "not" |> keep notExpr |> map ENot |> token) cmpExpr cmpExpr : Parser s Expression @@ -248,50 +259,48 @@ term = factor : Parser s Expression factor = - lazy (\() -> token (parens expr <|> app <|> atom)) + lazy (\() -> token (choice [ parens expr, app, atom ])) orop : Parser s (Expression -> Expression -> Expression) orop = - EOr <$ string "or" + string "or" |> onsuccess EOr andop : Parser s (Expression -> Expression -> Expression) andop = - EAnd <$ string "and" + string "and" |> onsuccess EAnd cmpop : Parser s (Expression -> Expression -> Expression) cmpop = - ECmp - <$> choice - [ string "<" - , string ">" - , string "==" - , string "!=" - , string ">=" - , string "<=" - , string "in" - , (++) <$> keyword "not" <*> string "in" - , string "is" - , (++) <$> keyword "is" <*> string "not" - ] + [ string "<" + , string ">" + , string "==" + , string "!=" + , string ">=" + , string "<=" + , string "in" + , keyword "not" |> map (++) |> andMap (string "in") + , string "is" + , keyword "is" |> map (++) |> andMap (string "not") + ] + |> choice + |> map ECmp addop : Parser s (Expression -> Expression -> Expression) addop = - choice - [ EAdd <$ string "+" - , ESub <$ string "-" - ] + or + (string "+" |> onsuccess EAdd) + (string "-" |> onsuccess ESub) mulop : Parser s (Expression -> Expression -> Expression) mulop = - choice - [ EMul <$ string "*" - , EDiv <$ string "/" - ] + or + (string "*" |> onsuccess EMul) + (string "/" |> onsuccess EDiv) exprList : Parser s (List Expression) @@ -301,87 +310,112 @@ exprList = exprStmt : Parser s Statement exprStmt = - SExpr <$> expr "expression" + map SExpr expr |> onerror "expression" printStmt : Parser s Statement printStmt = - SPrint <$> (keyword "print" *> exprList) "print statement" + keyword "print" + |> keep exprList + |> map SPrint + |> onerror "print statement" delStmt : Parser s Statement delStmt = - SDel <$> (keyword "del" *> exprList) "del statement" + keyword "del" + |> keep exprList + |> map SDel + |> onerror "del statement" passStmt : Parser s Statement passStmt = - SPass <$ keyword "pass" "pass statement" + keyword "pass" + |> onsuccess SPass + |> onerror "pass statement" breakStmt : Parser s Statement breakStmt = - SBreak <$ keyword "break" "break statement" + keyword "break" + |> onsuccess SBreak + |> onerror "break statement" continueStmt : Parser s Statement continueStmt = - SContinue <$ keyword "continue" "continue statement" + keyword "continue" + |> onsuccess SContinue + |> onerror "continue statement" returnStmt : Parser s Statement returnStmt = - SReturn <$> (keyword "return" *> maybe expr) "return statement" + keyword "return" + |> keep (maybe expr) + |> map SReturn + |> onerror "return statement" raiseStmt : Parser s Statement raiseStmt = - SRaise <$> (keyword "raise" *> maybe exprList) "raise statement" + keyword "raise" + |> keep (maybe exprList) + |> map SRaise + |> onerror "raise statement" importAs : Parser s (List ( Expression, Maybe Expression )) importAs = - sepBy commaSep <| - (,) - <$> choice [ attribute, identifier ] - <*> maybe (whitespace *> keyword "as" *> identifier) + or attribute identifier + |> map (,) + |> andMap (whitespace |> ignore (keyword "as") |> keep identifier |> maybe) + |> sepBy commaSep importStmt : Parser s Statement importStmt = - SImport - <$> (keyword "import" *> importAs) - "import statement" + keyword "import" + |> keep importAs + |> map SImport + |> onerror "import statement" importFromStmt : Parser s Statement importFromStmt = - SImportFrom - <$> (keyword "from" *> choice [ attribute, identifier ] <* spaces) - <*> (keyword "import" *> importAs) - "from statement" + keyword "from" + |> keep (or attribute identifier) + |> ignore spaces + |> map SImportFrom + |> ignore (keyword "import") + |> andMap importAs + |> onerror "from statement" globalStmt : Parser s Statement globalStmt = - SGlobal <$> (keyword "global" *> sepBy commaSep identifier) + keyword "global" + |> keep (sepBy commaSep identifier) + |> map SGlobal assertStmt : Parser s Statement assertStmt = - SAssert - <$> (keyword "assert" *> expr) - <*> maybe (commaSep *> expr) + keyword "assert" + |> keep expr + |> map SAssert + |> andMap (commaSep |> keep expr |> maybe) assignop : Parser s (Expression -> Expression -> Expression) assignop = - EAssign <$ token (string "=") + token (string "=") |> onsuccess EAssign assignStmt : Parser s Statement assignStmt = - SAssign <$> chainr assignop expr + chainr assignop expr |> map SAssign indentation : Parser Indentation res -> Parser Indentation res @@ -398,14 +432,15 @@ indentation p = indent = String.length s in - if indent == current then - succeed () - else - fail ("expected " ++ toString current ++ " spaces of indentation") + if indent == current then + succeed () + + else + fail ("expected " ++ toString current ++ " spaces of indentation") in - spaces >>= validate + spaces |> andThen validate in - withState skipIndent *> p + withState skipIndent |> keep p indent : Parser Indentation () @@ -420,17 +455,18 @@ indent = indent = String.length s in - case stack of - [] -> - fail "negative indentation" - - current :: _ -> - if indent > current then - putState (indent :: stack) - else - fail "expected indentation" + case stack of + [] -> + fail "negative indentation" + + current :: _ -> + if indent > current then + putState (indent :: stack) + + else + fail "expected indentation" in - lookAhead <| spaces >>= push + spaces |> andThen push |> lookAhead dedent : Parser Indentation () @@ -445,14 +481,14 @@ dedent = rem = dropWhile ((/=) (String.length s)) stack in - case rem of - _ :: _ -> - putState rem + case rem of + _ :: _ -> + putState rem - _ -> - fail "unindent does not match any outer indentation level" + _ -> + fail "unindent does not match any outer indentation level" in - spaces >>= pop + spaces |> andThen pop block : Parser Indentation (List CompoundStatement) @@ -460,11 +496,11 @@ block = lazy <| \() -> string ":" - *> whitespace - *> eol - *> indent - *> many1 stmt - <* dedent + |> ignore whitespace + |> ignore eol + |> ignore indent + |> keep (many1 stmt) + |> ignore dedent blockStmt : Parser Indentation (List CompoundStatement -> CompoundStatement) -> Parser Indentation CompoundStatement @@ -499,33 +535,41 @@ simpleStmt = , exprStmt ] in - indentation (CSimple <$> sepBy (string ";" <* whitespace) stmt <* (() <$ eol <|> end)) + sepBy (string ";" |> ignore whitespace) stmt + |> ignore (or (skip eol) end) + |> map CSimple + |> indentation whileStmt : Parser s (List CompoundStatement -> CompoundStatement) whileStmt = - CWhile <$> (keyword "while" *> expr) + keyword "while" + |> keep expr + |> map CWhile forStmt : Parser s (List CompoundStatement -> CompoundStatement) forStmt = - CFor - <$> (keyword "for" *> identifier) - <*> (spaces *> keyword "in" *> expr) + keyword "for" + |> keep identifier + |> map CFor + |> andMap (spaces |> ignore (keyword "in") |> keep expr) withStmt : Parser s (List CompoundStatement -> CompoundStatement) withStmt = - CWith - <$> (keyword "with" *> expr) - <*> maybe (keyword "as" *> identifier) + keyword "with" + |> keep expr + |> map CWith + |> andMap (keyword "as" |> keep identifier |> maybe) funcStmt : Parser s (List CompoundStatement -> CompoundStatement) funcStmt = - CFunc - <$> (keyword "def" *> identifier) - <*> parens (sepBy commaSep identifier) + keyword "def" + |> keep identifier + |> map CFunc + |> andMap (parens <| sepBy commaSep identifier) compoundStmt : Parser Indentation CompoundStatement @@ -541,14 +585,14 @@ compoundStmt = , funcStmt ] in - choice parsers + choice parsers stmt : Parser Indentation CompoundStatement stmt = lazy <| \() -> - compoundStmt <|> simpleStmt + or compoundStmt simpleStmt program : Parser Indentation (List CompoundStatement) @@ -577,15 +621,15 @@ formatError ms stream = padding = location.column + separatorOffset + 2 in - "Parse error around line:\n\n" - ++ toString location.line - ++ separator - ++ location.source - ++ "\n" - ++ String.padLeft padding ' ' "^" - ++ "\nI expected one of the following:\n" - ++ expectationSeparator - ++ String.join expectationSeparator ms + "Parse error around line:\n\n" + ++ toString location.line + ++ separator + ++ location.source + ++ "\n" + ++ String.padLeft padding ' ' "^" + ++ "\nI expected one of the following:\n" + ++ expectationSeparator + ++ String.join expectationSeparator ms parse : String -> Result String (List CompoundStatement) From 05ebe8909fcbc08576d9176ea3431f44e42f7f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 12:55:32 +0100 Subject: [PATCH 13/78] updated tests --- tests/CurrentLocationTests.elm | 52 ++++++++++++++++++---------------- tests/Parsers.elm | 6 ++-- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/tests/CurrentLocationTests.elm b/tests/CurrentLocationTests.elm index 8254ec3..fba77cf 100644 --- a/tests/CurrentLocationTests.elm +++ b/tests/CurrentLocationTests.elm @@ -1,4 +1,4 @@ -module CurrentLocationTests exposing (..) +module CurrentLocationTests exposing (entryPoint, noNegativeValuesForColumn, noNegativeValuesForLine, specificLocationTests) import Combine exposing (..) import Combine.Char @@ -57,21 +57,22 @@ noNegativeValuesForColumn = c = if String.length s == 0 then 0 + else - (i % String.length s) + i % String.length s in - case - Combine.parse - (Combine.count c Combine.Char.anyChar - *> (Combine.withColumn Combine.succeed) - ) - s - of - Err _ -> - Expect.fail "Should always parse" + case + Combine.parse + (Combine.count c Combine.Char.anyChar + |> keep (Combine.withColumn Combine.succeed) + ) + s + of + Err _ -> + Expect.fail "Should always parse" - Ok ( _, _, v ) -> - Expect.greaterThan -1 v + Ok ( _, _, v ) -> + Expect.greaterThan -1 v noNegativeValuesForLine : Test @@ -82,18 +83,19 @@ noNegativeValuesForLine = c = if String.length s == 0 then 0 + else - (i % String.length s) + i % String.length s in - case - Combine.parse - (Combine.count c Combine.Char.anyChar - *> (Combine.withLine Combine.succeed) - ) - s - of - Err _ -> - Expect.fail "Should always parse" + case + Combine.parse + (Combine.count c Combine.Char.anyChar + |> keep (Combine.withLine Combine.succeed) + ) + s + of + Err _ -> + Expect.fail "Should always parse" - Ok ( _, _, v ) -> - Expect.greaterThan -1 v + Ok ( _, _, v ) -> + Expect.greaterThan -1 v diff --git a/tests/Parsers.elm b/tests/Parsers.elm index 1c37c1c..aad6363 100644 --- a/tests/Parsers.elm +++ b/tests/Parsers.elm @@ -1,4 +1,4 @@ -module Parsers exposing (..) +module Parsers exposing (calcSuite, manyTillSuite, sepEndBy1Suite, sepEndBySuite, sequenceSuite, successful) import Calc exposing (calc) import Combine exposing (..) @@ -44,10 +44,10 @@ manyTillSuite : Test manyTillSuite = let comment = - string "") + string "")) line = - manyTill anyChar (many space *> eol) + manyTill anyChar (many space |> keep eol) in describe "manyTill tests" [ successful "Example" comment "" [ ' ', 't', 'e', 's', 't', ' ' ] From e816eb46ca0e789e023919c7ed83e278df543fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 12:58:11 +0100 Subject: [PATCH 14/78] new version --- elm-package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm-package.json b/elm-package.json index 18e676f..764d899 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,7 +1,7 @@ { "version": "2.0.0", - "summary": "A parser combinator library", - "repository": "https://github.com/elm-community/parser-combinators.git", + "summary": "A port of the elm-community-parsercombinator library to elm 0.19", + "repository": "https://github.com/andre-dietrich/parser-combinators.git", "license": "BSD3", "source-directories": [ "examples", From 5aa57d85ac6ed220f71d57d060200f7a51d190dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 12:59:53 +0100 Subject: [PATCH 15/78] changed version number --- elm-package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm-package.json b/elm-package.json index 764d899..41980da 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,5 +1,5 @@ { - "version": "2.0.0", + "version": "1.0.1", "summary": "A port of the elm-community-parsercombinator library to elm 0.19", "repository": "https://github.com/andre-dietrich/parser-combinators.git", "license": "BSD3", From 6b55eeb85dc552e057b42863bce0fc4a763f8e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 13:00:58 +0100 Subject: [PATCH 16/78] updated to new operator less version --- elm-package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm-package.json b/elm-package.json index 41980da..764d899 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,5 +1,5 @@ { - "version": "1.0.1", + "version": "2.0.0", "summary": "A port of the elm-community-parsercombinator library to elm 0.19", "repository": "https://github.com/andre-dietrich/parser-combinators.git", "license": "BSD3", From f3c92e5ca767f9e9854cbbcca95bcdf42e59fd29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 13:33:11 +0100 Subject: [PATCH 17/78] little regex optimizations --- src/Combine/Char.elm | 2 +- src/Combine/Num.elm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index 8076454..2b2afb1 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -129,7 +129,7 @@ newline = -} crlf : Parser s Char crlf = - regex "\\r\\n" |> onsuccess '\n' |> onerror "expected crlf" + string "\r\n" |> onsuccess '\n' |> onerror "expected crlf" {-| Parse an end of line character or sequence, returning a `\n` character. diff --git a/src/Combine/Num.elm b/src/Combine/Num.elm index 6a2ef71..9793bed 100644 --- a/src/Combine/Num.elm +++ b/src/Combine/Num.elm @@ -63,7 +63,7 @@ digit = int : Parser s Int int = map (*) sign - |> andMap (regex "(0|[1-9][0-9]*)" |> map toInt) + |> andMap (regex "(?:0|[1-9]\\d*)" |> map toInt) |> onerror "expected an integer" @@ -72,5 +72,5 @@ int = float : Parser s Float float = map ((*) << Basics.toFloat) sign - |> andMap (regex "(0|[1-9][0-9]*)(\\.[0-9]+)" |> map toFloat) + |> andMap (regex "(?:0|[1-9]\\d*)\\.\\d+" |> map toFloat) |> onerror "expected a float" From 06c6a603c11a28ffc5a1fdcad9bba71f3963d983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 13:41:41 +0100 Subject: [PATCH 18/78] added missing string function --- src/Combine/Char.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index 2b2afb1..241bc79 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -15,7 +15,7 @@ much faster. -} import Char -import Combine exposing (Parser, onerror, onsuccess, or, primitive, regex) +import Combine exposing (Parser, onerror, onsuccess, or, primitive, regex, string) import String From 0b16bff4c4c73799565054c78c26c48bc2a214c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 20 Dec 2018 13:43:16 +0100 Subject: [PATCH 19/78] version bump --- elm-package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm-package.json b/elm-package.json index 764d899..8d1fe5b 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,5 +1,5 @@ { - "version": "2.0.0", + "version": "2.0.1", "summary": "A port of the elm-community-parsercombinator library to elm 0.19", "repository": "https://github.com/andre-dietrich/parser-combinators.git", "license": "BSD3", From db2958c086a05d35e6ac7bb914d59950f78642cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 8 Jan 2019 22:50:52 +0100 Subject: [PATCH 20/78] updated to elm 0.19 --- CHANGELOG.md | 13 +++++++++---- elm-package.json | 20 -------------------- elm.json | 19 +++++++++++++++++++ src/Combine.elm | 34 ++++++++++++++++++++-------------- src/Combine/Char.elm | 19 +++++++++++++++---- src/Combine/Num.elm | 42 +++++++++++++++++++----------------------- 6 files changed, 82 insertions(+), 65 deletions(-) delete mode 100644 elm-package.json create mode 100644 elm.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8bb2d..55c6c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,13 @@ ## Version 2.0.0 (2017-12-05) -No API change, but a major change. The internal implementation how locations (line/columns) are working has changed. -Lines were previously 1-based, and columns sometimes had negative values. This is changed into zero-based lines and columns can never have negative values anymore. +No API change, but a major change. The internal implementation how locations +(line/columns) are working has changed. Lines were previously 1-based, and +columns sometimes had negative values. This is changed into zero-based lines and +columns can never have negative values anymore. -If your application/library did not rely on parse locations, the update is seamless. +If your application/library did not rely on parse locations, the update is +seamless. ## Version 1.0.0 (2017-02-09) @@ -13,7 +16,9 @@ If your application/library did not rely on parse locations, the update is seaml --- -> This repository is transferred from [Bogdanp/elm-combine](github.com/Bogdanp/elm-combine). The following changelog statements originate from that repository. +> This repository is transferred from +> [Bogdanp/elm-combine](github.com/Bogdanp/elm-combine). +> The following changelog statements originate from that repository. --- diff --git a/elm-package.json b/elm-package.json deleted file mode 100644 index 8d1fe5b..0000000 --- a/elm-package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "2.0.1", - "summary": "A port of the elm-community-parsercombinator library to elm 0.19", - "repository": "https://github.com/andre-dietrich/parser-combinators.git", - "license": "BSD3", - "source-directories": [ - "examples", - "src" - ], - "exposed-modules": [ - "Combine", - "Combine.Char", - "Combine.Num" - ], - "dependencies": { - "elm-lang/core": "5.0.0 <= v < 6.0.0", - "elm-lang/lazy": "2.0.0 <= v < 3.0.0" - }, - "elm-version": "0.18.0 <= v < 0.19.0" -} diff --git a/elm.json b/elm.json new file mode 100644 index 0000000..38e0d2a --- /dev/null +++ b/elm.json @@ -0,0 +1,19 @@ +{ + "type": "package", + "name": "andre-dietrich/parser-combinators", + "summary": "Port of the community parser combinator to elm 0.19", + "license": "BSD-3-Clause", + "version": "3.0.0", + "exposed-modules": [ + "Combine", + "Combine.Char", + "Combine.Num" + ], + "elm-version": "0.19.0 <= v < 0.20.0", + "dependencies": { + "elm/core": "1.0.1 <= v < 2.0.0", + "elm/regex": "1.0.0 <= v < 2.0.0", + "pilatch/flip": "1.0.0 <= v < 2.0.0" + }, + "test-dependencies": {} +} \ No newline at end of file diff --git a/src/Combine.elm b/src/Combine.elm index f04ad15..83cf867 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -70,7 +70,7 @@ into concrete Elm values. -} -import Lazy as L +import Flip exposing (flip) import Regex exposing (Regex) import String @@ -155,7 +155,10 @@ remaining `InputStream` and a `ParseResult res`. -} type Parser state res = Parser (ParseFn state res) - | RecursiveParser (L.Lazy (ParseFn state res)) + + + +--| RecursiveParser (L.Lazy (ParseFn state res)) {-| Construct a new primitive Parser. @@ -206,8 +209,10 @@ app p = Parser inner -> inner - RecursiveParser t -> - L.force t + + +-- RecursiveParser t -> +-- L.force t {-| Parse a string. See `runParser` if your parser needs to manage @@ -324,7 +329,8 @@ function to avoid "bad-recursion" errors. -} lazy : (() -> Parser s a) -> Parser s a lazy t = - RecursiveParser (L.lazy (\() -> app (t ()))) + -- RecursiveParser (L.lazy (\() -> app (t ()))) + succeed () |> andThen t {-| Transform both the result and error message of a parser. @@ -409,10 +415,10 @@ withColumn f = currentLocation : InputStream -> ParseLocation currentLocation stream = let - find position currentLine lines = + find position currentLine_ lines = case lines of [] -> - ParseLocation "" currentLine position + ParseLocation "" currentLine_ position line :: rest -> let @@ -423,13 +429,13 @@ currentLocation stream = length + 1 in if position == length then - ParseLocation line currentLine position + ParseLocation line currentLine_ position else if position > length then - find (position - lengthPlusNL) (currentLine + 1) rest + find (position - lengthPlusNL) (currentLine_ + 1) rest else - ParseLocation line currentLine position + ParseLocation line currentLine_ position in find stream.position 0 (String.split "\n" stream.data) @@ -661,7 +667,7 @@ string s = ( state, { stream | input = rem, position = pos }, Ok s ) else - ( state, stream, Err [ "expected " ++ toString s ] ) + ( state, stream, Err [ "expected " ++ s ] ) {-| Parse a Regex match. @@ -686,7 +692,7 @@ regex pat = in Parser <| \state stream -> - case Regex.find (Regex.AtMost 1) (Regex.regex pattern) stream.input of + case Regex.findAtMost 1 (Regex.fromString pattern |> Maybe.withDefault Regex.never) stream.input of [ match ] -> let len = @@ -913,10 +919,10 @@ succeeds. On success, the list of the first parser's results is returned. -} manyTill : Parser s a -> Parser s end -> Parser s (List a) -manyTill p end = +manyTill p end_ = let accumulate acc state stream = - case app end state stream of + case app end_ state stream of ( rstate, rstream, Ok _ ) -> ( rstate, rstream, Ok (List.reverse acc) ) diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index 241bc79..052a9f0 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -16,6 +16,7 @@ much faster. import Char import Combine exposing (Parser, onerror, onsuccess, or, primitive, regex, string) +import Flip exposing (flip) import String @@ -59,7 +60,16 @@ satisfy pred = -} char : Char -> Parser s Char char c = - satisfy ((==) c) |> onerror ("expected " ++ toString c) + satisfy ((==) c) |> onerror ("expected " ++ String.fromChar c) + + +charList : List Char -> String +charList chars = + chars + |> List.map (\c -> "'" ++ String.fromChar c ++ "'") + |> List.intersperse ", " + |> String.concat + |> (\str -> "[" ++ str ++ "]") {-| Parse any character. @@ -87,7 +97,8 @@ anyChar = -} oneOf : List Char -> Parser s Char oneOf cs = - satisfy (flip List.member cs) |> onerror ("expected one of " ++ toString cs) + satisfy (flip List.member cs) + |> onerror ("expected one of " ++ charList cs) {-| Parse a character that is not in the given list. @@ -101,7 +112,7 @@ oneOf cs = -} noneOf : List Char -> Parser s Char noneOf cs = - satisfy (not << flip List.member cs) |> onerror ("expected none of " ++ toString cs) + satisfy (not << flip List.member cs) |> onerror ("expected none of " ++ charList cs) {-| Parse a space character. @@ -129,7 +140,7 @@ newline = -} crlf : Parser s Char crlf = - string "\r\n" |> onsuccess '\n' |> onerror "expected crlf" + string "\u{000D}\n" |> onsuccess '\n' |> onerror "expected crlf" {-| Parse an end of line character or sequence, returning a `\n` character. diff --git a/src/Combine/Num.elm b/src/Combine/Num.elm index 9793bed..792dabf 100644 --- a/src/Combine/Num.elm +++ b/src/Combine/Num.elm @@ -15,24 +15,18 @@ import Combine.Char import String -unwrap : (String -> Result x res) -> String -> res -unwrap f s = - case f s of - Ok res -> - res - Err m -> - Debug.crash ("impossible state in Combine.Num.unwrap: " ++ toString m) +{- + unwrap : (String -> Maybe res) -> String -> res + unwrap f s = + case f s of + Just res -> + onsuccess res + Nothing -> + onerror "impossible state in Combine.Num.unwrap: " -toInt : String -> Int -toInt = - unwrap String.toInt - - -toFloat : String -> Float -toFloat = - unwrap String.toFloat +-} {-| Parse a numeric sign, returning `1` for positive numbers and `-1` @@ -60,17 +54,19 @@ digit = {-| Parse an integer. -} -int : Parser s Int +int : Parser s (Maybe Int) int = - map (*) sign - |> andMap (regex "(?:0|[1-9]\\d*)" |> map toInt) - |> onerror "expected an integer" + regex "-?(?:0|[1-9]\\d*)" + |> map String.toInt + + + +--|> onerror "expected an integer" {-| Parse a float. -} -float : Parser s Float +float : Parser s (Maybe Float) float = - map ((*) << Basics.toFloat) sign - |> andMap (regex "(?:0|[1-9]\\d*)\\.\\d+" |> map toFloat) - |> onerror "expected a float" + regex "-?(?:0|[1-9]\\d*)\\.\\d+" + |> map String.toFloat From 8f75d0c33a7b1e8b9041febc329b21a26998196c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 8 Jan 2019 22:52:53 +0100 Subject: [PATCH 21/78] removed obsolete onsuccess --- src/Combine.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Combine.elm b/src/Combine.elm index 83cf867..09827c8 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -61,7 +61,7 @@ into concrete Elm values. ### Parser Combinators -@docs lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, onsuccess, keep, ignore +@docs lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore ### State Combinators From 65b18d232c7e9f524aaadaa3d8d5da162ef03b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 8 Jan 2019 22:54:20 +0100 Subject: [PATCH 22/78] version changed back to 1.0.0 --- elm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm.json b/elm.json index 38e0d2a..052c1bb 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.0.0", + "version": "1.0.0", "exposed-modules": [ "Combine", "Combine.Char", @@ -16,4 +16,4 @@ "pilatch/flip": "1.0.0 <= v < 2.0.0" }, "test-dependencies": {} -} \ No newline at end of file +} From e993735aebe126c11b3614158e0ac06f4f60290f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 8 Jan 2019 22:56:08 +0100 Subject: [PATCH 23/78] version changed back to 1.0.1 --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index 052c1bb..b7db55d 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "1.0.0", + "version": "1.0.1", "exposed-modules": [ "Combine", "Combine.Char", From 180b7415d185520fe699c6db7439f1089f47da69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 8 Jan 2019 22:58:27 +0100 Subject: [PATCH 24/78] version bump --- elm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm.json b/elm.json index b7db55d..c13781e 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "1.0.1", + "version": "2.0.0", "exposed-modules": [ "Combine", "Combine.Char", @@ -16,4 +16,4 @@ "pilatch/flip": "1.0.0 <= v < 2.0.0" }, "test-dependencies": {} -} +} \ No newline at end of file From cca469b6094d646407471f236c1a9e5fae278cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 8 Jan 2019 23:00:24 +0100 Subject: [PATCH 25/78] new tag --- elm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm.json b/elm.json index c13781e..ce71c4a 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "2.0.0", + "version": "3.0.0", "exposed-modules": [ "Combine", "Combine.Char", @@ -16,4 +16,4 @@ "pilatch/flip": "1.0.0 <= v < 2.0.0" }, "test-dependencies": {} -} \ No newline at end of file +} From acc766c116c2bebf111e45f4f3cc11cbdcae37c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 8 Jan 2019 23:10:05 +0100 Subject: [PATCH 26/78] new new version 2.0.0 --- elm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm.json b/elm.json index ce71c4a..c13781e 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.0.0", + "version": "2.0.0", "exposed-modules": [ "Combine", "Combine.Char", @@ -16,4 +16,4 @@ "pilatch/flip": "1.0.0 <= v < 2.0.0" }, "test-dependencies": {} -} +} \ No newline at end of file From 071b2dac1d1d3b356222b4660abb7c7b1cbd038d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 9 Jan 2019 09:42:05 +0100 Subject: [PATCH 27/78] updated Combine.Num and lazy doc --- .gitignore | 1 + elm.json | 5 ++++- examples/Calc.elm | 14 ++++++++++++- examples/Python.elm | 2 +- examples/elm.json | 27 +++++++++++++++++++++++++ src/Combine.elm | 49 ++++++++------------------------------------- src/Combine/Num.elm | 35 ++++++++++++++------------------ 7 files changed, 69 insertions(+), 64 deletions(-) create mode 100644 examples/elm.json diff --git a/.gitignore b/.gitignore index 94ab26f..70c3fca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /elm-stuff elm.js +examples/elm-stuff diff --git a/elm.json b/elm.json index c13781e..ac52142 100644 --- a/elm.json +++ b/elm.json @@ -4,6 +4,9 @@ "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", "version": "2.0.0", + "source-directories": [ + "src" + ], "exposed-modules": [ "Combine", "Combine.Char", @@ -16,4 +19,4 @@ "pilatch/flip": "1.0.0 <= v < 2.0.0" }, "test-dependencies": {} -} \ No newline at end of file +} diff --git a/examples/Calc.elm b/examples/Calc.elm index 1fd4aa6..02c734a 100644 --- a/examples/Calc.elm +++ b/examples/Calc.elm @@ -60,4 +60,16 @@ calc s = Ok n Err ( _, stream, ms ) -> - Err ("parse error: " ++ toString ms ++ ", " ++ toString stream) + Err + ("parse error: " + ++ "[ \"" + ++ (ms |> List.intersperse "\", \"" |> String.concat) + ++ "\" ] , " + ++ "{ data: " + ++ stream.data + ++ ", input: " + ++ stream.input + ++ ", position: " + ++ String.fromInt stream.position + ++ " }" + ) diff --git a/examples/Python.elm b/examples/Python.elm index 0d4e4cf..980d73a 100644 --- a/examples/Python.elm +++ b/examples/Python.elm @@ -622,7 +622,7 @@ formatError ms stream = location.column + separatorOffset + 2 in "Parse error around line:\n\n" - ++ toString location.line + ++ String.fromInt location.line ++ separator ++ location.source ++ "\n" diff --git a/examples/elm.json b/examples/elm.json new file mode 100644 index 0000000..68104c6 --- /dev/null +++ b/examples/elm.json @@ -0,0 +1,27 @@ +{ + "type": "application", + "source-directories": [ + "." + ], + "elm-version": "0.19.0", + "dependencies": { + "direct": { + "andre-dietrich/parser-combinators": "2.0.0", + "elm/browser": "1.0.1", + "elm/core": "1.0.2", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.1.2", + "elm/regex": "1.0.0", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2", + "pilatch/flip": "1.0.0" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} \ No newline at end of file diff --git a/src/Combine.elm b/src/Combine.elm index 09827c8..26bf999 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -204,15 +204,8 @@ Here's how you would implement a greedy version of `manyTill` using -} app : Parser state res -> state -> InputStream -> ParseContext state res -app p = - case p of - Parser inner -> - inner - - - --- RecursiveParser t -> --- L.force t +app (Parser inner) = + inner {-| Parse a string. See `runParser` if your parser needs to manage @@ -292,39 +285,13 @@ runParser p st s = Err ( state, stream, ms ) -{-| Defer running a parser until it's actually required. Use this -function to avoid "bad-recursion" errors. - - type Expression - = ETerm String - | EList (List E) - - name : Parser s String - name = whitespace |> keep (regex "[a-zA-Z]+") |> ignore whitespace - - term : Parser s Expression - term = andMap name ETerm - - list : Parser s Expression - list = - let - -- helper is itself a function so we avoid the case where the - -- value `list` tries to apply itself in its definition. - helper () = - between (string "(") (string ")") (many (or term list)) - |> andMap EList - in - -- lazy defers calling helper until it's actually needed. - lazy helper - - parse list "" - -- Err ["expected \"(\""] - - parse list "()" - -- Ok (EList []) +{-| Unfortunatelly this is not a real lazy function anymore, since this +functionality is not accessable anymore by ordinary developers. Use this +function only to avoid "bad-recursion" errors or use the following example +snippet in your code to circumvent this problem: - parse list "(a (b c))" - -- Ok (EList [ETerm "a", EList [ETerm "b", ETerm "c"]]) + recursion x = + \() -> recursion x -} lazy : (() -> Parser s a) -> Parser s a diff --git a/src/Combine/Num.elm b/src/Combine/Num.elm index 792dabf..a8ba57b 100644 --- a/src/Combine/Num.elm +++ b/src/Combine/Num.elm @@ -15,20 +15,6 @@ import Combine.Char import String - -{- - unwrap : (String -> Maybe res) -> String -> res - unwrap f s = - case f s of - Just res -> - onsuccess res - - Nothing -> - onerror "impossible state in Combine.Num.unwrap: " - --} - - {-| Parse a numeric sign, returning `1` for positive numbers and `-1` for negative numbers. -} @@ -54,19 +40,28 @@ digit = {-| Parse an integer. -} -int : Parser s (Maybe Int) +int : Parser s Int int = regex "-?(?:0|[1-9]\\d*)" |> map String.toInt - - - ---|> onerror "expected an integer" + |> andThen unwrap + |> onerror "expected an float" {-| Parse a float. -} -float : Parser s (Maybe Float) +float : Parser s Float float = regex "-?(?:0|[1-9]\\d*)\\.\\d+" |> map String.toFloat + |> andThen unwrap + |> onerror "expected an float" + + +unwrap value = + case value of + Just v -> + succeed v + + Nothing -> + fail "impossible state in Combine.Num.unwrap" From bf2337da38443e808174687517465356691f691e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 9 Jan 2019 10:11:29 +0100 Subject: [PATCH 28/78] version bump --- elm.json | 7 ++----- examples/Calc.elm | 12 ++++++++++++ examples/elm.json | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/elm.json b/elm.json index ac52142..38e0d2a 100644 --- a/elm.json +++ b/elm.json @@ -3,10 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "2.0.0", - "source-directories": [ - "src" - ], + "version": "3.0.0", "exposed-modules": [ "Combine", "Combine.Char", @@ -19,4 +16,4 @@ "pilatch/flip": "1.0.0 <= v < 2.0.0" }, "test-dependencies": {} -} +} \ No newline at end of file diff --git a/examples/Calc.elm b/examples/Calc.elm index 02c734a..69ca98e 100644 --- a/examples/Calc.elm +++ b/examples/Calc.elm @@ -2,6 +2,12 @@ module Calc exposing (calc) {-| An example parser that computes arithmetic expressions. +To run this example, simply enter the examples and: + +1. run `elm repl` +2. type in `import Calc exposing (calc)` +3. try out some expressions like `calc "22*2+(3+18)"` + @docs calc -} @@ -52,6 +58,12 @@ factor = {-| Compute the result of an expression. + + import Calc exposing (calc) + + calc "123+1" + -- 123 + -} calc : String -> Result String Int calc s = diff --git a/examples/elm.json b/examples/elm.json index 68104c6..9f4fe89 100644 --- a/examples/elm.json +++ b/examples/elm.json @@ -6,7 +6,7 @@ "elm-version": "0.19.0", "dependencies": { "direct": { - "andre-dietrich/parser-combinators": "2.0.0", + "andre-dietrich/parser-combinators": "3.0.0", "elm/browser": "1.0.1", "elm/core": "1.0.2", "elm/html": "1.0.0" @@ -24,4 +24,4 @@ "direct": {}, "indirect": {} } -} \ No newline at end of file +} From a34be365599866eac4561664dff2c65bbd8f983f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 9 Jan 2019 10:38:35 +0100 Subject: [PATCH 29/78] updated examples --- examples/Calc.elm | 7 ++++-- examples/Python.elm | 56 ++++++++++++++++++++++++++++++++++++--------- examples/Scheme.elm | 49 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 15 deletions(-) diff --git a/examples/Calc.elm b/examples/Calc.elm index 69ca98e..7c7da3d 100644 --- a/examples/Calc.elm +++ b/examples/Calc.elm @@ -61,8 +61,11 @@ factor = import Calc exposing (calc) - calc "123+1" - -- 123 + calc "22*2+(3+18)" + -- Ok 65 + + calc "33 ** 12" + -- Err ("parse error: [ \"expected end of input\" ] , { data: 33 ** 12, input: ** 12, position: 3 }") -} calc : String -> Result String Int diff --git a/examples/Python.elm b/examples/Python.elm index 980d73a..85e10a9 100644 --- a/examples/Python.elm +++ b/examples/Python.elm @@ -1,4 +1,19 @@ -module Python exposing (CompoundStatement(..), Expression(..), Indentation, Statement(..), addop, andExpr, andop, app, arithExpr, assertStmt, assignStmt, assignop, atom, attribute, block, blockStmt, bool, breakStmt, cmpExpr, cmpop, commaSep, comment, compoundStmt, continueStmt, dedent, delStmt, dict, dictSep, dropWhile, expr, exprList, exprStmt, factor, float, forStmt, formatError, funcStmt, globalStmt, identifier, importAs, importFromStmt, importStmt, indent, indentation, initIndentation, int, keyword, list, listSep, mulop, notExpr, orop, parse, passStmt, printStmt, program, raiseStmt, returnStmt, set, simpleStmt, spaces, stmt, str, term, test, token, tuple, whileStmt, whitespace, withStmt) +module Python exposing + ( parse, test + , CompoundStatement(..), Expression(..), Indentation, Statement(..), addop, andExpr, andop, app, arithExpr, assertStmt, assignStmt, assignop, atom, attribute, block, blockStmt, bool, breakStmt, cmpExpr, cmpop, commaSep, comment, compoundStmt, continueStmt, dedent, delStmt, dict, dictSep, dropWhile, expr, exprList, exprStmt, factor, float, forStmt, formatError, funcStmt, globalStmt, identifier, importAs, importFromStmt, importStmt, indent, indentation, initIndentation, int, keyword, list, listSep, mulop, notExpr, orop, passStmt, printStmt, program, raiseStmt, returnStmt, set, simpleStmt, spaces, stmt, str, term, token, tuple, whileStmt, whitespace, withStmt + ) + +{-| An example parser for the Python programming language. + +To run this example, simply enter the examples and: + +1. run `elm repl` +2. type in `import Python` +3. try it out with `Python.test` or `Python.parse "a = 12 * 33"` + +@docs parse, test + +-} import Combine exposing (..) import Combine.Char exposing (..) @@ -200,7 +215,7 @@ dict = \() -> expr |> ignore dictSep - |> map (,) + |> map Tuple.pair |> andMap expr |> sepBy listSep |> brackets @@ -369,7 +384,7 @@ raiseStmt = importAs : Parser s (List ( Expression, Maybe Expression )) importAs = or attribute identifier - |> map (,) + |> map Tuple.pair |> andMap (whitespace |> ignore (keyword "as") |> keep identifier |> maybe) |> sepBy commaSep @@ -429,14 +444,14 @@ indentation p = validate s = let - indent = + indent_ = String.length s in - if indent == current then + if indent_ == current then succeed () else - fail ("expected " ++ toString current ++ " spaces of indentation") + fail ("expected " ++ String.fromInt current ++ " spaces of indentation") in spaces |> andThen validate in @@ -452,7 +467,7 @@ indent = withState <| \stack -> let - indent = + indent_ = String.length s in case stack of @@ -460,8 +475,8 @@ indent = fail "negative indentation" current :: _ -> - if indent > current then - putState (indent :: stack) + if indent_ > current then + putState (indent_ :: stack) else fail "expected indentation" @@ -518,7 +533,7 @@ simpleStmt = lazy <| \() -> let - stmt = + stmt_ = choice [ assertStmt , globalStmt @@ -535,7 +550,7 @@ simpleStmt = , exprStmt ] in - sepBy (string ";" |> ignore whitespace) stmt + sepBy (string ";" |> ignore whitespace) stmt_ |> ignore (or (skip eol) end) |> map CSimple |> indentation @@ -632,6 +647,17 @@ formatError ms stream = ++ String.join expectationSeparator ms +{-| Parse simple Python-code ... + + import Python exposing (parse) + + parse "a = 33 * 12" + -- Ok [CSimple [SAssign (EAssign (EIdentifier "a") (EMul (EInt 33) (EInt 12)))]] + + parse " = 33 * test(22)" + -- Err ("Parse error around line:\n\n0| = 33 * test(22)\n ^\nI expected one of the following:\n\n * expected end of input") + +-} parse : String -> Result String (List CompoundStatement) parse s = case Combine.runParser program initIndentation s of @@ -642,6 +668,14 @@ parse s = Err <| formatError ms stream +{-| Run the following example ... + + import Python exposing (test) + + test + -- Ok [CSimple [SImport [(EIdentifier "os",Nothing)]],CSimple [],CSimple [SAssign (EAssign (EIdentifier "a") (EAssign (EIdentifier "b") (EInt 1)))],CSimple [],CFunc (EIdentifier "rel") [EIdentifier "p"] [CSimple [SReturn (Just (EApp (EAttribute (EIdentifier "os") (EAttribute (EIdentifier "path") (EIdentifier "join"))) [EApp (EAttribute (EIdentifier "os") (EAttribute (EIdentifier "path") (EIdentifier "dirname"))) [EIdentifier "__file__"],EIdentifier "p"]))]],CSimple [],CFunc (EIdentifier "f") [EIdentifier "a",EIdentifier "b"] [CSimple [SReturn (Just (EAdd (EIdentifier "a") (EIdentifier "b")))]],CSimple [],CWith (EApp (EIdentifier "open") [EApp (EIdentifier "rel") [EString "Python.elm"]]) (Just (EIdentifier "f")) [CFor (EIdentifier "line") (EIdentifier "f") [CSimple [SPrint [EIdentifier "f"]]]]] + +-} test : Result String (List CompoundStatement) test = parse """import os diff --git a/examples/Scheme.elm b/examples/Scheme.elm index 5f54e5d..dcd277a 100644 --- a/examples/Scheme.elm +++ b/examples/Scheme.elm @@ -1,4 +1,19 @@ -module Scheme exposing (E(..), bool, char, comment, expr, float, formatError, identifier, int, list, parse, program, quasiquote, quote, str, unquote, unquoteSplice, vector) +module Scheme exposing + ( parse, test + , E(..), bool, char, comment, expr, float, formatError, identifier, int, list, program, quasiquote, quote, str, unquote, unquoteSplice, vector + ) + +{-| An example parser for the Scheme programming language. + +To run this example, simply enter the examples and: + +1. run `elm repl` +2. type in `import Scheme` +3. try it out with `Scheme.test` or `Scheme.parse "(* 12 12 13)"` + +@docs parse, test + +-} import Combine exposing (..) import Combine.Char exposing (anyChar) @@ -204,7 +219,7 @@ formatError ms stream = location.column + separatorOffset + 2 in "Parse error around line:\n\n" - ++ toString location.line + ++ String.fromInt location.line ++ separator ++ location.source ++ "\n" @@ -214,6 +229,18 @@ formatError ms stream = ++ String.join expectationSeparator ms +{-| Parse simple Scheme-code ... + + import Scheme exposing (parse) + + parse "(* 12 12 13)" + -- Ok [EList [EIdentifier "*",EInt 12,EInt 12,EInt 13]] + + + parse "(* 12 12 13" + -- Err ("Parse error around line:\n\n0|> (* 12 12 13\n ^\nI expected one of the following:\n\n * expected end of input") + +-} parse : String -> Result String (List E) parse s = case Combine.parse program s of @@ -222,3 +249,21 @@ parse s = Err ( _, stream, ms ) -> Err <| formatError ms stream + + +{-| Run the following example ... + + import Scheme exposing (test) + + test + -- Ok [EList [EIdentifier "define",EList [EIdentifier "derivative",EIdentifier "f",EIdentifier "dx"],EList [EIdentifier "lambda",EList [EIdentifier "x"],EList [EIdentifier "/",EList [EIdentifier "-",EList [EIdentifier "f",EList [EIdentifier "+",EIdentifier "x",EIdentifier "dx"]],EList [EIdentifier "f",EIdentifier "x"]],EIdentifier "dx"]]]] + +-} +test : Result String (List E) +test = + parse """ + (define (derivative f dx) + (lambda (x) + (/ (- (f (+ x dx)) (f x)) + dx))) + """ From 3c78b8534cdb520422b5a60a964056bc780dd1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 9 Jan 2019 10:40:21 +0100 Subject: [PATCH 30/78] updated version --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index 38e0d2a..283c595 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.0.0", + "version": "3.0.1", "exposed-modules": [ "Combine", "Combine.Char", From 67945649f5ad8f164c76fd0428d5bbd178e16910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 9 Jan 2019 14:06:50 +0100 Subject: [PATCH 31/78] test updated and CHANGELOG updated --- CHANGELOG.md | 29 ++++++++++++++++++++++ elm.json | 10 ++++++-- src/Combine.elm | 2 +- tests/.gitignore | 3 --- tests/CurrentLocationTests.elm | 4 +-- tests/Parsers.elm | 45 +++++++++++++++++++--------------- tests/elm-package.json | 20 --------------- 7 files changed, 65 insertions(+), 48 deletions(-) delete mode 100644 tests/.gitignore delete mode 100644 tests/elm-package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 55c6c00..e4acac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## Version 3.0.2 (2019-01-09) + +I hope, it is possible to transfer this back to the community repo. This is a +simple port of the community/parser-combinators package to elm 0.19. +Unfortunatelly all operators had to be removed or in other words replaced. And +lazyness is not supported anymore, these things can only be used by native elm +modules :.(. + +**Basic changes:** + +* `*>` was replaced by the new function `ignore` +* `<*` was replaced by the function `keep` +* `$>` is now function `onsuccess` +* while `` is represented by the function `onerror` +* `lazy` can still be used, but in the manner, that it was replaced by the + function: + + ``` elm + lazy : (() -> Parser s a) -> Parser s a + lazy t = + succeed () |> andThen t + ``` + + which tries to circumvent the bad-recursion problem + +* Examples and test have been updated and a new the new function `modifyStream` + was added, which I personally use to inject code/macros at compile-time. +* Some minor optimization of regular expression ... + ## Version 2.0.0 (2017-12-05) No API change, but a major change. The internal implementation how locations diff --git a/elm.json b/elm.json index 283c595..7fbd1c1 100644 --- a/elm.json +++ b/elm.json @@ -4,6 +4,10 @@ "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", "version": "3.0.1", + "source-directories": [ + "src", + "examples" + ], "exposed-modules": [ "Combine", "Combine.Char", @@ -15,5 +19,7 @@ "elm/regex": "1.0.0 <= v < 2.0.0", "pilatch/flip": "1.0.0 <= v < 2.0.0" }, - "test-dependencies": {} -} \ No newline at end of file + "test-dependencies": { + "elm-explorations/test": "1.2.0 <= v < 2.0.0" + } +} diff --git a/src/Combine.elm b/src/Combine.elm index 26bf999..5dfc34c 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -634,7 +634,7 @@ string s = ( state, { stream | input = rem, position = pos }, Ok s ) else - ( state, stream, Err [ "expected " ++ s ] ) + ( state, stream, Err [ "expected \"" ++ s ++ "\"" ] ) {-| Parse a Regex match. diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index b176e88..0000000 --- a/tests/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/elm-stuff -elm.js -index.html \ No newline at end of file diff --git a/tests/CurrentLocationTests.elm b/tests/CurrentLocationTests.elm index fba77cf..a323095 100644 --- a/tests/CurrentLocationTests.elm +++ b/tests/CurrentLocationTests.elm @@ -59,7 +59,7 @@ noNegativeValuesForColumn = 0 else - i % String.length s + modBy (String.length s) i in case Combine.parse @@ -85,7 +85,7 @@ noNegativeValuesForLine = 0 else - i % String.length s + modBy (String.length s) i in case Combine.parse diff --git a/tests/Parsers.elm b/tests/Parsers.elm index aad6363..3eebc35 100644 --- a/tests/Parsers.elm +++ b/tests/Parsers.elm @@ -1,6 +1,7 @@ -module Parsers exposing (calcSuite, manyTillSuite, sepEndBy1Suite, sepEndBySuite, sequenceSuite, successful) +module Parsers exposing (manyTillSuite, sepEndBy1Suite, sepEndBySuite, sequenceSuite, successful) + +--import Calc exposing (calc) -import Calc exposing (calc) import Combine exposing (..) import Combine.Char exposing (..) import Expect @@ -20,24 +21,28 @@ successful desc p s r = Expect.fail <| String.join ", " ms -calcSuite : Test -calcSuite = - let - equiv s x () = - Expect.equal (calc s) (Ok x) - in - describe "calc example tests" - [ test "Atoms" (equiv "1" 1) - , test "Atoms 2" (equiv "-1" -1) - , test "Parenthesized atoms" (equiv "(1)" 1) - , test "Addition" (equiv "1 + 1" 2) - , test "Subtraction" (equiv "1 - 1" 0) - , test "Multiplication" (equiv "1 * 1" 1) - , test "Division" (equiv "1 / 1" 1) - , test "Precedence 1" (equiv "1 + 2 * 3" 7) - , test "Precedence 2" (equiv "1 + 2 * 3 * 2" 13) - , test "Parenthesized precedence" (equiv "(1 + 2) * 3 * 2" 18) - ] + +{- + calcSuite : Test + calcSuite = + let + equiv s x () = + Expect.equal (calc s) (Ok x) + in + describe "calc example tests" + [ test "Atoms" (equiv "1" 1) + , test "Atoms 2" (equiv "-1" -1) + , test "Parenthesized atoms" (equiv "(1)" 1) + , test "Addition" (equiv "1 + 1" 2) + , test "Subtraction" (equiv "1 - 1" 0) + , test "Multiplication" (equiv "1 * 1" 1) + , test "Division" (equiv "1 / 1" 1) + , test "Precedence 1" (equiv "1 + 2 * 3" 7) + , test "Precedence 2" (equiv "1 + 2 * 3 * 2" 13) + , test "Parenthesized precedence" (equiv "(1 + 2) * 3 * 2" 18) + ] + +-} manyTillSuite : Test diff --git a/tests/elm-package.json b/tests/elm-package.json deleted file mode 100644 index 3fc7480..0000000 --- a/tests/elm-package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "1.0.0", - "summary": "parser-combinators test suite", - "repository": "https://github.com/elm-community/parser-combinators.git", - "license": "BSD3", - "source-directories": [".", "../examples", "../src"], - "exposed-modules": [ - "Calc", - "Combine", - "Combine.Char", - "Combine.Infix", - "Combine.Num" - ], - "dependencies": { - "elm-community/elm-test": "4.0.0 <= v < 5.0.0", - "elm-lang/core": "5.0.0 <= v < 6.0.0", - "elm-lang/lazy": "2.0.0 <= v < 3.0.0" - }, - "elm-version": "0.18.0 <= v < 0.19.0" -} From 25f2a781d0a1fd3653bc2f58f0202e244757ba7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 9 Jan 2019 14:07:26 +0100 Subject: [PATCH 32/78] version bump --- elm.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/elm.json b/elm.json index 7fbd1c1..b7ca505 100644 --- a/elm.json +++ b/elm.json @@ -3,11 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.0.1", - "source-directories": [ - "src", - "examples" - ], + "version": "3.0.2", "exposed-modules": [ "Combine", "Combine.Char", @@ -22,4 +18,4 @@ "test-dependencies": { "elm-explorations/test": "1.2.0 <= v < 2.0.0" } -} +} \ No newline at end of file From ec7d33983bb858d5b004b33e61fb11934bd5afd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 29 Jan 2019 23:11:24 +0100 Subject: [PATCH 33/78] little optimizations --- src/Combine.elm | 2 +- src/Combine/Char.elm | 2 +- src/Combine/Num.elm | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 5dfc34c..bacea23 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -71,7 +71,7 @@ into concrete Elm values. -} import Flip exposing (flip) -import Regex exposing (Regex) +import Regex import String diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index 052a9f0..aa8baeb 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -15,7 +15,7 @@ much faster. -} import Char -import Combine exposing (Parser, onerror, onsuccess, or, primitive, regex, string) +import Combine exposing (Parser, onerror, onsuccess, or, primitive, string) import Flip exposing (flip) import String diff --git a/src/Combine/Num.elm b/src/Combine/Num.elm index a8ba57b..dfa1f14 100644 --- a/src/Combine/Num.elm +++ b/src/Combine/Num.elm @@ -10,7 +10,7 @@ module Combine.Num exposing (sign, digit, int, float) -} import Char -import Combine exposing (..) +import Combine exposing (Parser, andThen, fail, map, onerror, onsuccess, optional, or, regex, string, succeed) import Combine.Char import String @@ -58,6 +58,7 @@ float = |> onerror "expected an float" +unwrap : Maybe v -> Parser s v unwrap value = case value of Just v -> From cfc8dfe87610c59eee076f0c2414ddb6f2901199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 29 Jan 2019 23:12:30 +0100 Subject: [PATCH 34/78] version bumb --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index b7ca505..a16ed89 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.0.2", + "version": "3.0.3", "exposed-modules": [ "Combine", "Combine.Char", From da46c88835851a359083504a7bbdd301855d266d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 7 Feb 2019 13:13:43 +0100 Subject: [PATCH 35/78] new regex functions added --- src/Combine.elm | 62 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index bacea23..eef5219 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -2,7 +2,7 @@ module Combine exposing ( Parser, InputStream, ParseLocation, ParseContext, ParseResult, ParseError, ParseOk , parse, runParser , primitive, app, lazy - , fail, succeed, string, regex, end, whitespace, whitespace1 + , fail, succeed, string, regex, regexWith, regexWithSub, end, whitespace, whitespace1 , map, onsuccess, mapError, onerror , andThen, andMap, sequence , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore @@ -43,7 +43,7 @@ into concrete Elm values. ## Parsers -@docs fail, succeed, string, regex, end, whitespace, whitespace1 +@docs fail, succeed, string, regex, regexWith, regexWithSub, end, whitespace, whitespace1 ## Combinators @@ -648,7 +648,59 @@ every pattern unless one already exists. -} regex : String -> Parser s String -regex pat = +regex = + regexRun Regex.fromString .match + + +{-| Parse a Regex match. + +Since, Regex now also has support for more parameters, this option was +included into this package. Call `regexWith` with two additional parameters: +`caseInsensitive` and `multiline`, which allow you to tweak your expression. +The rest is as follows. Regular expressions must match from the beginning +of the input and their subgroups are ignored. A `^` is added implicitly to +the beginning of every pattern unless one already exists. + + parse (regexWith True False "a+") "aaaAAaAab" + -- Ok "aaaAAaAa" + +-} +regexWith : Bool -> Bool -> String -> Parser s String +regexWith caseInsensitive multiline = + regexRun + (Regex.fromStringWith + { caseInsensitive = caseInsensitive + , multiline = multiline + } + ) + .match + + +{-| Parse a Regex match. + +Similar to `regexWith`, but a tuple is returned, with a list of additional +submatches. +The rest is as follows. Regular expressions must match from the beginning +of the input and their subgroups are ignored. A `^` is added implicitly to +the beginning of every pattern unless one already exists. + + parse (regexWithSub True False "a+") "aaaAAaAab" + -- Ok ("aaaAAaAa", []) + +-} +regexWithSub : Bool -> Bool -> String -> Parser s ( String, List (Maybe String) ) +regexWithSub caseInsensitive multiline = + regexRun + (Regex.fromStringWith + { caseInsensitive = caseInsensitive + , multiline = multiline + } + ) + (\m -> ( m.match, m.submatches )) + + +regexRun : (String -> Maybe Regex.Regex) -> (Regex.Match -> out) -> String -> Parser s out +regexRun input output pat = let pattern = if String.startsWith "^" pat then @@ -659,7 +711,7 @@ regex pat = in Parser <| \state stream -> - case Regex.findAtMost 1 (Regex.fromString pattern |> Maybe.withDefault Regex.never) stream.input of + case Regex.findAtMost 1 (input pattern |> Maybe.withDefault Regex.never) stream.input of [ match ] -> let len = @@ -671,7 +723,7 @@ regex pat = pos = stream.position + len in - ( state, { stream | input = rem, position = pos }, Ok match.match ) + ( state, { stream | input = rem, position = pos }, Ok (output match) ) _ -> ( state, stream, Err [ "expected input matching Regexp /" ++ pattern ++ "/" ] ) From 8b4b9b2cd4863cfcc38de23f5a82ad9ed5c0bd6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 7 Feb 2019 13:15:34 +0100 Subject: [PATCH 36/78] new regex functions added --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index a16ed89..bc68738 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.0.3", + "version": "3.1.0", "exposed-modules": [ "Combine", "Combine.Char", From 0e2b27615b997d54c35cc7114eb1bbbc86ec6eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 7 Feb 2019 13:36:33 +0100 Subject: [PATCH 37/78] added regexSub --- src/Combine.elm | 64 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index eef5219..7d7fd10 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -2,7 +2,7 @@ module Combine exposing ( Parser, InputStream, ParseLocation, ParseContext, ParseResult, ParseError, ParseOk , parse, runParser , primitive, app, lazy - , fail, succeed, string, regex, regexWith, regexWithSub, end, whitespace, whitespace1 + , fail, succeed, string, regex, regexSub, regexWith, regexWithSub, end, whitespace, whitespace1 , map, onsuccess, mapError, onerror , andThen, andMap, sequence , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore @@ -43,7 +43,7 @@ into concrete Elm values. ## Parsers -@docs fail, succeed, string, regex, regexWith, regexWithSub, end, whitespace, whitespace1 +@docs fail, succeed, string, regex, regexSub, regexWith, regexWithSub, end, whitespace, whitespace1 ## Combinators @@ -649,7 +649,23 @@ every pattern unless one already exists. -} regex : String -> Parser s String regex = - regexRun Regex.fromString .match + regexer Regex.fromString .match >> Parser + + +{-| Parse a Regex match. + +Same as regex, but returns also submatches as the second parameter in +the result tuple. + + parse (regexSub "a+") "aaaaab" + -- Ok ("aaaaa", []) + +-} +regexSub : String -> Parser s ( String, List (Maybe String) ) +regexSub = + regexer Regex.fromString + (\m -> ( m.match, m.submatches )) + >> Parser {-| Parse a Regex match. @@ -667,13 +683,14 @@ the beginning of every pattern unless one already exists. -} regexWith : Bool -> Bool -> String -> Parser s String regexWith caseInsensitive multiline = - regexRun + regexer (Regex.fromStringWith { caseInsensitive = caseInsensitive , multiline = multiline } ) .match + >> Parser {-| Parse a Regex match. @@ -690,17 +707,22 @@ the beginning of every pattern unless one already exists. -} regexWithSub : Bool -> Bool -> String -> Parser s ( String, List (Maybe String) ) regexWithSub caseInsensitive multiline = - regexRun + regexer (Regex.fromStringWith { caseInsensitive = caseInsensitive , multiline = multiline } ) (\m -> ( m.match, m.submatches )) + >> Parser -regexRun : (String -> Maybe Regex.Regex) -> (Regex.Match -> out) -> String -> Parser s out -regexRun input output pat = +regexer : + (String -> Maybe Regex.Regex) + -> (Regex.Match -> res) + -> String + -> (state -> InputStream -> ( state, InputStream, ParseResult res )) +regexer input output pat state stream = let pattern = if String.startsWith "^" pat then @@ -709,24 +731,22 @@ regexRun input output pat = else "^" ++ pat in - Parser <| - \state stream -> - case Regex.findAtMost 1 (input pattern |> Maybe.withDefault Regex.never) stream.input of - [ match ] -> - let - len = - String.length match.match + case Regex.findAtMost 1 (input pattern |> Maybe.withDefault Regex.never) stream.input of + [ match ] -> + let + len = + String.length match.match - rem = - String.dropLeft len stream.input + rem = + String.dropLeft len stream.input - pos = - stream.position + len - in - ( state, { stream | input = rem, position = pos }, Ok (output match) ) + pos = + stream.position + len + in + ( state, { stream | input = rem, position = pos }, Ok (output match) ) - _ -> - ( state, stream, Err [ "expected input matching Regexp /" ++ pattern ++ "/" ] ) + _ -> + ( state, stream, Err [ "expected input matching Regexp /" ++ pattern ++ "/" ] ) {-| Consume input while the predicate matches. From 8e772b37b86706472c48c71e6957646346c25d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Sat, 30 Mar 2019 12:18:07 +0100 Subject: [PATCH 38/78] updated .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2a2acc0..3bbbd40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ install: make && make install; cd ..; fi - - npm install -g elm elm-test@0.18.7 + - npm install -g elm elm-test script: - $TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-test From 8bc1a3c90f31b44dd23350cb5fde7d85ce9980c9 Mon Sep 17 00:00:00 2001 From: Nick Boultbee Date: Fri, 12 Apr 2019 09:26:40 +0100 Subject: [PATCH 39/78] Improve some parser failure messages * Most importantly: `Int` was failing _with expected an Float_ * Also, _expected whitespace_ for `\s*` was perhaps misleading, so changed to _expected optional whitespace_ (even though this will probably never fail :grin:) * Others fixed for better consistency / English --- src/Combine.elm | 2 +- src/Combine/Char.elm | 8 ++++---- src/Combine/Num.elm | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 7d7fd10..56869d3 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1157,7 +1157,7 @@ brackets = -} whitespace : Parser s String whitespace = - regex "\\s*" |> onerror "whitespace" + regex "\\s*" |> onerror "optional whitespace" {-| Parse one or more whitespace characters. diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index aa8baeb..c347a71 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -119,28 +119,28 @@ noneOf cs = -} space : Parser s Char space = - satisfy ((==) ' ') |> onerror "expected space" + satisfy ((==) ' ') |> onerror "expected a space" {-| Parse a `\t` character. -} tab : Parser s Char tab = - satisfy ((==) '\t') |> onerror "expected tab" + satisfy ((==) '\t') |> onerror "expected a tab" {-| Parse a `\n` character. -} newline : Parser s Char newline = - satisfy ((==) '\n') |> onerror "expected newline" + satisfy ((==) '\n') |> onerror "expected a newline" {-| Parse a `\r\n` sequence, returning a `\n` character. -} crlf : Parser s Char crlf = - string "\u{000D}\n" |> onsuccess '\n' |> onerror "expected crlf" + string "\u{000D}\n" |> onsuccess '\n' |> onerror "expected CRLF" {-| Parse an end of line character or sequence, returning a `\n` character. diff --git a/src/Combine/Num.elm b/src/Combine/Num.elm index dfa1f14..d1fb271 100644 --- a/src/Combine/Num.elm +++ b/src/Combine/Num.elm @@ -45,7 +45,7 @@ int = regex "-?(?:0|[1-9]\\d*)" |> map String.toInt |> andThen unwrap - |> onerror "expected an float" + |> onerror "expected an int" {-| Parse a float. @@ -55,7 +55,7 @@ float = regex "-?(?:0|[1-9]\\d*)\\.\\d+" |> map String.toFloat |> andThen unwrap - |> onerror "expected an float" + |> onerror "expected a float" unwrap : Maybe v -> Parser s v From 651e15093030754367f77b4d42da5ec8f7f44e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 12 Apr 2019 10:32:57 +0200 Subject: [PATCH 40/78] version bump patch --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index bc68738..597ffb0 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.1.0", + "version": "3.1.1", "exposed-modules": [ "Combine", "Combine.Char", From 97a57cdc768473a6bb4da136b8fd2877eab57817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 27 Sep 2019 19:02:35 +0200 Subject: [PATCH 41/78] added a function and an example to ease the debugging with parser-combinators --- examples/Debugger.elm | 64 +++++++++++++++++++++++++++++++++++++++++++ examples/elm.json | 14 +++++----- src/Combine.elm | 20 ++++++++++++-- 3 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 examples/Debugger.elm diff --git a/examples/Debugger.elm b/examples/Debugger.elm new file mode 100644 index 0000000..4fd0bd6 --- /dev/null +++ b/examples/Debugger.elm @@ -0,0 +1,64 @@ +module Debugger exposing (runDebug) + +{-| An example parser defines a simple debugger, so that you are aware of the +current rule and location information ... state can be added accordingly... + +To run this example, simply enter the examples and: + +1. run `elm repl` +2. type in `import Debugger exposing (runDebug)` +3. try out some expressions like `runDebug "1234\nabcdef\nABCD"` + +@docs calc + +-} + +import Combine exposing (..) + + +debug : String -> Parser s a -> Parser s a +debug log p = + withLine + (\x -> + withColumn + (\y -> + withSourceLine + (\s -> + let + output = + Debug.log log ( x, y, s ) + in + p + ) + ) + ) + + +runDebug : String -> Result String String +runDebug s = + case + parse + ((regex "(.|\n)" + |> debug "regex" + ) + |> many + ) + s + of + Ok ( _, _, n ) -> + Ok <| String.concat n + + Err ( _, stream, ms ) -> + Err + ("parse error: " + ++ "[ \"" + ++ (ms |> List.intersperse "\", \"" |> String.concat) + ++ "\" ] , " + ++ "{ data: " + ++ stream.data + ++ ", input: " + ++ stream.input + ++ ", position: " + ++ String.fromInt stream.position + ++ " }" + ) diff --git a/examples/elm.json b/examples/elm.json index 9f4fe89..70c2caf 100644 --- a/examples/elm.json +++ b/examples/elm.json @@ -1,27 +1,27 @@ { "type": "application", "source-directories": [ - "." + ".", + "../src" ], "elm-version": "0.19.0", "dependencies": { "direct": { - "andre-dietrich/parser-combinators": "3.0.0", "elm/browser": "1.0.1", "elm/core": "1.0.2", - "elm/html": "1.0.0" + "elm/html": "1.0.0", + "elm/regex": "1.0.0", + "pilatch/flip": "1.0.0" }, "indirect": { "elm/json": "1.1.2", - "elm/regex": "1.0.0", "elm/time": "1.0.0", "elm/url": "1.0.0", - "elm/virtual-dom": "1.0.2", - "pilatch/flip": "1.0.0" + "elm/virtual-dom": "1.0.2" } }, "test-dependencies": { "direct": {}, "indirect": {} } -} +} \ No newline at end of file diff --git a/src/Combine.elm b/src/Combine.elm index 56869d3..80d542d 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -6,7 +6,7 @@ module Combine exposing , map, onsuccess, mapError, onerror , andThen, andMap, sequence , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore - , withState, putState, modifyState, withLocation, withLine, withColumn, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream + , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream ) {-| This library provides facilities for parsing structured text data @@ -66,7 +66,7 @@ into concrete Elm values. ### State Combinators -@docs withState, putState, modifyState, withLocation, withLine, withColumn, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream +@docs withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream -} @@ -377,6 +377,22 @@ withColumn f = app (f <| currentColumn stream) state stream +{-| Get the current InputStream and pipe it into a parser, +only for debugging purposes ... +-} +withSourceLine : (String -> Parser s a) -> Parser s a +withSourceLine f = + Parser <| + \state stream -> + app + (currentSourceLine stream + |> String.dropLeft (currentColumn stream) + |> f + ) + state + stream + + {-| Get the current `(line, column)` in the input stream. -} currentLocation : InputStream -> ParseLocation From cd93fcb4f248d071ee851f3de323810a3b7908c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 27 Sep 2019 20:43:27 +0200 Subject: [PATCH 42/78] updated debug function --- elm.json | 2 +- examples/Debugger.elm | 14 +++++++++++--- src/Combine.elm | 8 +------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/elm.json b/elm.json index 597ffb0..1367c2a 100644 --- a/elm.json +++ b/elm.json @@ -18,4 +18,4 @@ "test-dependencies": { "elm-explorations/test": "1.2.0 <= v < 2.0.0" } -} \ No newline at end of file +} diff --git a/examples/Debugger.elm b/examples/Debugger.elm index 4fd0bd6..cd91215 100644 --- a/examples/Debugger.elm +++ b/examples/Debugger.elm @@ -19,14 +19,22 @@ import Combine exposing (..) debug : String -> Parser s a -> Parser s a debug log p = withLine - (\x -> + (\y -> withColumn - (\y -> + (\x -> withSourceLine (\s -> let output = - Debug.log log ( x, y, s ) + Debug.log log + ( x + , y + , String.slice 0 x s + ++ "[" + ++ String.slice x (x + 1) s + ++ "]" + ++ String.slice (x + 1) -1 s + ) in p ) diff --git a/src/Combine.elm b/src/Combine.elm index 80d542d..9a79045 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -384,13 +384,7 @@ withSourceLine : (String -> Parser s a) -> Parser s a withSourceLine f = Parser <| \state stream -> - app - (currentSourceLine stream - |> String.dropLeft (currentColumn stream) - |> f - ) - state - stream + app (f <| currentSourceLine stream) state stream {-| Get the current `(line, column)` in the input stream. From 88780fcb0fcb7fc4d2050e9269e6b53cf724fce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 27 Sep 2019 20:44:08 +0200 Subject: [PATCH 43/78] version bump --- elm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm.json b/elm.json index 1367c2a..d9c3d93 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.1.1", + "version": "3.2.0", "exposed-modules": [ "Combine", "Combine.Char", @@ -18,4 +18,4 @@ "test-dependencies": { "elm-explorations/test": "1.2.0 <= v < 2.0.0" } -} +} \ No newline at end of file From b848c854666dfa7770e7cc72d7ab582a64153725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Tue, 10 Mar 2020 12:15:56 +0100 Subject: [PATCH 44/78] added a string stream output function --- examples/elm.json | 6 ++---- src/Combine.elm | 11 +++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/elm.json b/examples/elm.json index 70c2caf..de50a63 100644 --- a/examples/elm.json +++ b/examples/elm.json @@ -4,12 +4,10 @@ ".", "../src" ], - "elm-version": "0.19.0", + "elm-version": "0.19.1", "dependencies": { "direct": { - "elm/browser": "1.0.1", "elm/core": "1.0.2", - "elm/html": "1.0.0", "elm/regex": "1.0.0", "pilatch/flip": "1.0.0" }, @@ -24,4 +22,4 @@ "direct": {}, "indirect": {} } -} \ No newline at end of file +} diff --git a/src/Combine.elm b/src/Combine.elm index 9a79045..88594d7 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -6,7 +6,7 @@ module Combine exposing , map, onsuccess, mapError, onerror , andThen, andMap, sequence , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore - , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream + , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyStream ) {-| This library provides facilities for parsing structured text data @@ -66,7 +66,7 @@ into concrete Elm values. ### State Combinators -@docs withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, modifyStream +@docs withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyStream -} @@ -438,6 +438,13 @@ currentColumn = currentLocation >> .column +{-| Get the current string stream. That might be useful for applying memorization. +-} +currentStream : InputStream -> String +currentStream = + .input + + {-| Modify the parser's InputStream. -} modifyStream : (String -> String) -> Parser s () From 30a77ce9f7e71d585d0f6170ab765eca9d7e4802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 13 Mar 2020 09:44:22 +0100 Subject: [PATCH 45/78] updated convenience functions --- src/Combine.elm | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 88594d7..f88865d 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -6,7 +6,7 @@ module Combine exposing , map, onsuccess, mapError, onerror , andThen, andMap, sequence , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore - , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyStream + , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyInput, modifyPosition ) {-| This library provides facilities for parsing structured text data @@ -66,7 +66,7 @@ into concrete Elm values. ### State Combinators -@docs withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyStream +@docs withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyInput, modifyPosition -} @@ -445,15 +445,24 @@ currentStream = .input -{-| Modify the parser's InputStream. +{-| Modify the parser's InputStream input (String). -} -modifyStream : (String -> String) -> Parser s () -modifyStream f = +modifyInput : (String -> String) -> Parser s () +modifyInput f = Parser <| \state stream -> app (succeed ()) state { stream | input = f stream.input } +{-| Modify the parser's InputStream position (Int). +-} +modifyPosition : (Int -> Int) -> Parser s () +modifyPosition f = + Parser <| + \state stream -> + app (succeed ()) state { stream | position = f stream.position } + + -- Transformers -- ------------ From 0aa9e0d1846a2c3929c0d41ee1f6764a0cdfe93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 13 Mar 2020 09:45:26 +0100 Subject: [PATCH 46/78] version bump --- elm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm.json b/elm.json index d9c3d93..2b03877 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "3.2.0", + "version": "4.0.0", "exposed-modules": [ "Combine", "Combine.Char", @@ -18,4 +18,4 @@ "test-dependencies": { "elm-explorations/test": "1.2.0 <= v < 2.0.0" } -} \ No newline at end of file +} From 8cc2c79ca287218cb9f51f94cd77c79ef4b55541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 18 Jun 2021 21:44:44 +0200 Subject: [PATCH 47/78] Adds many1Till & elm-review suggestions --- examples/elm.json | 4 ++-- src/Combine.elm | 24 ++++++++++++++++++++++-- tests/CurrentLocationTests.elm | 15 +++++++++++---- tests/Parsers.elm | 22 +++++++++++++++++++--- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/examples/elm.json b/examples/elm.json index de50a63..0ef2aa8 100644 --- a/examples/elm.json +++ b/examples/elm.json @@ -7,12 +7,12 @@ "elm-version": "0.19.1", "dependencies": { "direct": { - "elm/core": "1.0.2", + "elm/core": "1.0.5", "elm/regex": "1.0.0", "pilatch/flip": "1.0.0" }, "indirect": { - "elm/json": "1.1.2", + "elm/json": "1.1.3", "elm/time": "1.0.0", "elm/url": "1.0.0", "elm/virtual-dom": "1.0.2" diff --git a/src/Combine.elm b/src/Combine.elm index f88865d..951c5a2 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -5,7 +5,7 @@ module Combine exposing , fail, succeed, string, regex, regexSub, regexWith, regexWithSub, end, whitespace, whitespace1 , map, onsuccess, mapError, onerror , andThen, andMap, sequence - , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore + , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyInput, modifyPosition ) @@ -61,7 +61,7 @@ into concrete Elm values. ### Parser Combinators -@docs lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore +@docs lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore ### State Combinators @@ -1002,6 +1002,26 @@ manyTill p end_ = Parser (accumulate []) +{-| Apply the first parser one or more times until second parser +succeeds. On success, the list of the first parser's results is returned. + + string "")) + +-} +many1Till : Parser s a -> Parser s end -> Parser s (List a) +many1Till p = + manyTill p + >> andThen + (\result -> + case result of + [] -> + fail "not enough results" + + _ -> + succeed result + ) + + {-| Parser zero or more occurences of one parser separated by another. parse (sepBy (string ",") (string "a")) "b" diff --git a/tests/CurrentLocationTests.elm b/tests/CurrentLocationTests.elm index a323095..c414bde 100644 --- a/tests/CurrentLocationTests.elm +++ b/tests/CurrentLocationTests.elm @@ -1,10 +1,17 @@ module CurrentLocationTests exposing (entryPoint, noNegativeValuesForColumn, noNegativeValuesForLine, specificLocationTests) -import Combine exposing (..) +import Combine import Combine.Char import Expect import Fuzz -import Test exposing (..) +import Test + exposing + ( Test + , describe + , fuzz + , fuzz2 + , test + ) entryPoint : Test @@ -64,7 +71,7 @@ noNegativeValuesForColumn = case Combine.parse (Combine.count c Combine.Char.anyChar - |> keep (Combine.withColumn Combine.succeed) + |> Combine.keep (Combine.withColumn Combine.succeed) ) s of @@ -90,7 +97,7 @@ noNegativeValuesForLine = case Combine.parse (Combine.count c Combine.Char.anyChar - |> keep (Combine.withLine Combine.succeed) + |> Combine.keep (Combine.withLine Combine.succeed) ) s of diff --git a/tests/Parsers.elm b/tests/Parsers.elm index 3eebc35..7e18a0c 100644 --- a/tests/Parsers.elm +++ b/tests/Parsers.elm @@ -1,9 +1,25 @@ -module Parsers exposing (manyTillSuite, sepEndBy1Suite, sepEndBySuite, sequenceSuite, successful) +module Parsers exposing (manyTillSuite, sepEndBy1Suite, sepEndBySuite, sequenceSuite) --import Calc exposing (calc) -import Combine exposing (..) -import Combine.Char exposing (..) +import Combine + exposing + ( Parser + , keep + , many + , manyTill + , parse + , sepEndBy + , sepEndBy1 + , sequence + , string + ) +import Combine.Char + exposing + ( anyChar + , eol + , space + ) import Expect import String import Test exposing (Test, describe, test) From ab4f16219c7b05cc5d5b3fb8c6b6df96c04f43b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 21 Jun 2021 13:53:40 +0200 Subject: [PATCH 48/78] Version bump to 4.1.0 --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index 2b03877..88393d4 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "4.0.0", + "version": "4.1.0", "exposed-modules": [ "Combine", "Combine.Char", From c0c6ecbf061a1e3c96117b90fcaddf949e159560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Sun, 20 Oct 2024 23:53:27 +0200 Subject: [PATCH 49/78] improve(Num): little faster functions --- src/Combine/Num.elm | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/Combine/Num.elm b/src/Combine/Num.elm index d1fb271..7a46d8a 100644 --- a/src/Combine/Num.elm +++ b/src/Combine/Num.elm @@ -17,6 +17,13 @@ import String {-| Parse a numeric sign, returning `1` for positive numbers and `-1` for negative numbers. + + parse sign "+" == Ok 1 + + parse sign "-" == Ok -1 + + parse sign "a" == Err [ "expected a sign" ] + -} sign : Parser s Int sign = @@ -28,33 +35,49 @@ sign = {-| Parse a digit. + + parse digit "1" == Ok 1 + + parse digit "a" == Err [ "expected a digit" ] + -} digit : Parser s Int digit = - let - toDigit c = - Char.toCode c - Char.toCode '0' - in - map toDigit Combine.Char.digit |> onerror "expected a digit" + Combine.Char.digit + -- 48 is the ASCII code for '0' + |> map (\c -> Char.toCode c - 48) + |> onerror "expected a digit" {-| Parse an integer. + + parse int "123" == Ok 123 + + parse int "-123" == Ok -123 + + parse int "abc" == Err [ "expected an int" ] + -} int : Parser s Int int = regex "-?(?:0|[1-9]\\d*)" - |> map String.toInt - |> andThen unwrap + |> andThen (String.toInt >> unwrap) |> onerror "expected an int" {-| Parse a float. + + parse float "123.456" == Ok 123.456 + + parse float "-123.456" == Ok -123.456 + + parse float "abc" == Err [ "expected a float" ] + -} float : Parser s Float float = regex "-?(?:0|[1-9]\\d*)\\.\\d+" - |> map String.toFloat - |> andThen unwrap + |> andThen (String.toFloat >> unwrap) |> onerror "expected a float" From 3c732749b43b691d6fff9efb6dd440e94111550b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 21 Oct 2024 13:47:34 +0200 Subject: [PATCH 50/78] docs: Correct typos in Conbine-Parsers --- src/Combine.elm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 951c5a2..8490d14 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -285,8 +285,8 @@ runParser p st s = Err ( state, stream, ms ) -{-| Unfortunatelly this is not a real lazy function anymore, since this -functionality is not accessable anymore by ordinary developers. Use this +{-| Unfortunately this is not a real lazy function anymore, since this +functionality is not accessible anymore by ordinary developers. Use this function only to avoid "bad-recursion" errors or use the following example snippet in your code to circumvent this problem: @@ -1022,7 +1022,7 @@ many1Till p = ) -{-| Parser zero or more occurences of one parser separated by another. +{-| Parser zero or more occurrences of one parser separated by another. parse (sepBy (string ",") (string "a")) "b" -- Ok [] @@ -1039,14 +1039,14 @@ sepBy sep p = or (sepBy1 sep p) (succeed []) -{-| Parse one or more occurences of one parser separated by another. +{-| Parse one or more occurrences of one parser separated by another. -} sepBy1 : Parser s x -> Parser s a -> Parser s (List a) sepBy1 sep p = map (::) p |> andMap (many (sep |> keep p)) -{-| Parse zero or more occurences of one parser separated and +{-| Parse zero or more occurrences of one parser separated and optionally ended by another. parse (sepEndBy (string ",") (string "a")) "a,a,a," @@ -1058,7 +1058,7 @@ sepEndBy sep p = or (sepEndBy1 sep p) (succeed []) -{-| Parse one or more occurences of one parser separated and +{-| Parse one or more occurrences of one parser separated and optionally ended by another. parse (sepEndBy1 (string ",") (string "a")) "" @@ -1097,7 +1097,7 @@ skipMany1 p = many1 (skip p) |> onsuccess () -{-| Parse one or more occurences of `p` separated by `op`, recursively +{-| Parse one or more occurrences of `p` separated by `op`, recursively apply all functions returned by `op` to the values returned by `p`. See the `examples/Calc.elm` file for an example. -} @@ -1140,7 +1140,7 @@ chainr op p = andThen accumulate p -{-| Parse `n` occurences of `p`. +{-| Parse `n` occurrences of `p`. -} count : Int -> Parser s a -> Parser s (List a) count n p = From 2ce83252697d7ffaf2afb581b881d2ffac4e43c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 23 Oct 2024 15:39:11 +0200 Subject: [PATCH 51/78] feat: Add more convenience functions to Char - alpha: checks for Char.isAlpha - alphaNum: checks for Cahr.isAlphaNum --- src/Combine.elm | 215 +++++++++++++++++++++++++++++++++++++++++-- src/Combine/Char.elm | 116 +++++++++++++++++++++-- 2 files changed, 319 insertions(+), 12 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 8490d14..076f646 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -324,6 +324,15 @@ bimap fok ferr p = {-| Get the parser's state and pipe it into a parser. + + let + parser = + string "a" + |> andThen (\_ -> withState (\state -> succeed state)) + in + parse parser "a" + -- Ok () + -} withState : (s -> Parser s a) -> Parser s a withState f = @@ -333,6 +342,15 @@ withState f = {-| Replace the parser's state. + + let + parser = + string "a" + |> andThen (\_ -> putState 42) + in + parse parser "a" + -- Ok () + -} putState : s -> Parser s () putState state = @@ -342,6 +360,15 @@ putState state = {-| Modify the parser's state. + + let + parser = + string "a" + |> andThen (\_ -> modifyState ((+) 1)) + in + parse parser "a" + -- Ok () + -} modifyState : (s -> s) -> Parser s () modifyState f = @@ -351,6 +378,15 @@ modifyState f = {-| Get the current position in the input stream and pipe it into a parser. + + let + parser = + string "a" + |> andThen (\_ -> withLocation (\loc -> succeed loc)) + in + parse parser "a" + -- Ok { source = "a", line = 0, column = 1 } + -} withLocation : (ParseLocation -> Parser s a) -> Parser s a withLocation f = @@ -360,6 +396,15 @@ withLocation f = {-| Get the current line and pipe it into a parser. + + let + parser = + string "a" + |> andThen (\_ -> withLine (\line -> succeed line)) + in + parse parser "a" + -- Ok 0 + -} withLine : (Int -> Parser s a) -> Parser s a withLine f = @@ -369,6 +414,15 @@ withLine f = {-| Get the current column and pipe it into a parser. + + let + parser = + string "a" + |> andThen (\_ -> withColumn (\column -> succeed column)) + in + parse parser "a" + -- Ok 1 + -} withColumn : (Int -> Parser s a) -> Parser s a withColumn f = @@ -379,6 +433,15 @@ withColumn f = {-| Get the current InputStream and pipe it into a parser, only for debugging purposes ... + + let + parser = + string "a" + |> andThen (\_ -> withSourceLine (\line -> succeed line)) + in + parse parser "a" + -- Ok "a" + -} withSourceLine : (String -> Parser s a) -> Parser s a withSourceLine f = @@ -388,6 +451,15 @@ withSourceLine f = {-| Get the current `(line, column)` in the input stream. + + let + parser = + string "a" + |> andThen (\_ -> withLocation (\loc -> succeed loc)) + in + parse parser "a" + -- Ok { source = "a", line = 0, column = 1 } + -} currentLocation : InputStream -> ParseLocation currentLocation stream = @@ -418,6 +490,15 @@ currentLocation stream = {-| Get the current source line in the input stream. + + let + parser = + string "a" + |> andThen (\_ -> modifyInput String.toUpper) + in + parse parser "a" + -- Ok () + -} currentSourceLine : InputStream -> String currentSourceLine = @@ -425,6 +506,15 @@ currentSourceLine = {-| Get the current line in the input stream. + + let + parser = + string "a" + |> andThen (\_ -> modifyPosition ((+) 1)) + in + parse parser "a" + -- Ok () + -} currentLine : InputStream -> Int currentLine = @@ -432,6 +522,15 @@ currentLine = {-| Get the current column in the input stream. + + let + parser = + string "a" + |> andThen (\_ -> modifyPosition ((+) 1)) + in + parse parser "a" + -- Ok () + -} currentColumn : InputStream -> Int currentColumn = @@ -439,6 +538,15 @@ currentColumn = {-| Get the current string stream. That might be useful for applying memorization. + + let + parser = + string "a" + |> andThen (\_ -> modifyInput String.toUpper) + in + parse parser "a" + -- Ok () + -} currentStream : InputStream -> String currentStream = @@ -446,6 +554,15 @@ currentStream = {-| Modify the parser's InputStream input (String). + + let + parser = + string "a" + |> andThen (\_ -> modifyInput String.toUpper) + in + parse parser "a" + -- Ok () + -} modifyInput : (String -> String) -> Parser s () modifyInput f = @@ -455,6 +572,15 @@ modifyInput f = {-| Modify the parser's InputStream position (Int). + + let + parser = + string "a" + |> andThen (\_ -> modifyPosition ((+) 1)) + in + parse parser "a" + -- Ok () + -} modifyPosition : (Int -> Int) -> Parser s () modifyPosition f = @@ -703,12 +829,12 @@ The rest is as follows. Regular expressions must match from the beginning of the input and their subgroups are ignored. A `^` is added implicitly to the beginning of every pattern unless one already exists. - parse (regexWith True False "a+") "aaaAAaAab" + parse (regexWith { caseInsensitive = True, multiline = False} "a+") "aaaAAaAab" -- Ok "aaaAAaAa" -} -regexWith : Bool -> Bool -> String -> Parser s String -regexWith caseInsensitive multiline = +regexWith : { caseInsensitive : Bool, multiline : Bool } -> String -> Parser s String +regexWith { caseInsensitive, multiline } = regexer (Regex.fromStringWith { caseInsensitive = caseInsensitive @@ -727,12 +853,12 @@ The rest is as follows. Regular expressions must match from the beginning of the input and their subgroups are ignored. A `^` is added implicitly to the beginning of every pattern unless one already exists. - parse (regexWithSub True False "a+") "aaaAAaAab" + parse (regexWithSub { caseInsensitive = True, multiline = False } "a+") "aaaAAaAab" -- Ok ("aaaAAaAa", []) -} -regexWithSub : Bool -> Bool -> String -> Parser s ( String, List (Maybe String) ) -regexWithSub caseInsensitive multiline = +regexWithSub : { caseInsensitive : Bool, multiline : Bool } -> String -> Parser s ( String, List (Maybe String) ) +regexWithSub { caseInsensitive, multiline } = regexer (Regex.fromStringWith { caseInsensitive = caseInsensitive @@ -833,6 +959,13 @@ end = {-| Apply a parser without consuming any input on success. + + parse (whitespace1 |> keep (string "a")) " a" + -- Ok "a" + + parse (whitespace1 |> keep (string "a")) "a" + -- Err ["expected whitespace"] + -} lookAhead : Parser s a -> Parser s a lookAhead p = @@ -1040,6 +1173,16 @@ sepBy sep p = {-| Parse one or more occurrences of one parser separated by another. + + parse (sepBy1 (string ",") (string "a")) "" + -- Err ["expected \"a\""] + + parse (sepBy1 (string ",") (string "a")) "a" + -- Ok ["a"] + + parse (sepBy1 (string ",") (string "a")) "a," + -- Ok ["a"] + -} sepBy1 : Parser s x -> Parser s a -> Parser s (List a) sepBy1 sep p = @@ -1077,6 +1220,13 @@ sepEndBy1 sep p = {-| Apply a parser and skip its result. + + parse (skip (string "a")) "a" + -- Ok () + + parse (skip (string "a")) "b" + -- Err ["expected \"a\""] + -} skip : Parser s x -> Parser s () skip p = @@ -1084,6 +1234,13 @@ skip p = {-| Apply a parser and skip its result many times. + + parse (skipMany (string "a")) "aaa" + -- Ok () + + parse (skipMany (string "a")) "" + -- Ok () + -} skipMany : Parser s x -> Parser s () skipMany p = @@ -1091,6 +1248,13 @@ skipMany p = {-| Apply a parser and skip its result at least once. + + parse (skipMany1 (string "a")) "a" + -- Ok () + + parse (skipMany1 (string "a")) "" + -- Err ["expected \"a\""] + -} skipMany1 : Parser s x -> Parser s () skipMany1 p = @@ -1100,6 +1264,13 @@ skipMany1 p = {-| Parse one or more occurrences of `p` separated by `op`, recursively apply all functions returned by `op` to the values returned by `p`. See the `examples/Calc.elm` file for an example. + + parse (chainl (string "+") (int)) "1+2+3" + -- Ok 6 + + parse (chainl (string "+") (int)) "1+2+" + -- Err ["expected an integer"] + -} chainl : Parser s (a -> a -> a) -> Parser s a -> Parser s a chainl op p = @@ -1121,6 +1292,10 @@ chainl op p = {-| Similar to `chainl` but functions of `op` are applied in right-associative order to the values of `p`. See the `examples/Python.elm` file for a usage example. + + parse (chainr (string "a" |> keep (string "+")) (string "a")) "a+a+a" + -- Ok "a" + -} chainr : Parser s (a -> a -> a) -> Parser s a -> Parser s a chainr op p = @@ -1141,6 +1316,13 @@ chainr op p = {-| Parse `n` occurrences of `p`. + + parse (count 3 (string "a")) "aaa" + -- Ok ["a", "a", "a"] + + parse (count 3 (string "a")) "aa" + -- Err ["expected \"a\""] + -} count : Int -> Parser s a -> Parser s (List a) count n p = @@ -1172,6 +1354,13 @@ between lp rp p = {-| Parse something between parentheses. + + parse (parens (string "hello")) "(hello)" + -- Ok "hello" + + parse (parens (string "hello")) "(world)" + -- Err ["expected \"hello\""] + -} parens : Parser s a -> Parser s a parens = @@ -1179,6 +1368,13 @@ parens = {-| Parse something between braces `{}`. + + parse (braces (string "hello")) "{hello}" + -- Ok "hello" + + parse (braces (string "hello")) "{world}" + -- Err ["expected \"hello\""] + -} braces : Parser s a -> Parser s a braces = @@ -1186,6 +1382,13 @@ braces = {-| Parse something between square brackets `[]`. + + parse (brackets (string "hello")) "[hello]" + -- Ok "hello" + + parse (brackets (string "hello")) "[world]" + -- Err ["expected \"hello\""] + -} brackets : Parser s a -> Parser s a brackets = diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index c347a71..4987d13 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -1,4 +1,4 @@ -module Combine.Char exposing (satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit) +module Combine.Char exposing (satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit, alpha, alphaNum) {-| This module contains `Char`-specific Parsers. @@ -10,7 +10,7 @@ much faster. # Parsers -@docs satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit +@docs satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit, alpha, alphaNum -} @@ -51,11 +51,19 @@ satisfy pred = {-| Parse an exact character match. - parse (char 'a') "a" == - -- Ok 'a' + parse (char 'a') "a" --> Ok 'a' + + parse (char 'a') "b" --> Err ["expected 'a'"] + + -- You can write the expected result on the next line, + + add 41 1 + --> 42 - parse (char 'a') "b" == - -- Err ["expected 'a'"] + -- You can write the expected result on the next line, + + add 41 1 + --> 42 -} char : Char -> Parser s Char @@ -116,6 +124,11 @@ noneOf cs = {-| Parse a space character. + + parse space " " == Ok ' ' + + parse space "a" == Err [ "expected a space" ] + -} space : Parser s Char space = @@ -123,6 +136,11 @@ space = {-| Parse a `\t` character. + + parse tab "\t" == Ok '\t' + + parse tab "a" == Err [ "expected a tab" ] + -} tab : Parser s Char tab = @@ -130,6 +148,11 @@ tab = {-| Parse a `\n` character. + + parse newline "\n" == Ok '\n' + + parse newline "a" == Err [ "expected a newline" ] + -} newline : Parser s Char newline = @@ -137,6 +160,13 @@ newline = {-| Parse a `\r\n` sequence, returning a `\n` character. + + parse crlf "\u{000D}\n" == Ok '\n' + + parse crlf "\n" == Err [ "expected CRLF" ] + + parse crlf "\u{000D}" == Err [ "expected CRLF" ] + -} crlf : Parser s Char crlf = @@ -144,6 +174,15 @@ crlf = {-| Parse an end of line character or sequence, returning a `\n` character. + + parse eol "\n" == Ok '\n' + + parse eol "\u{000D}\n" == Ok '\n' + + parse eol "\u{000D}" == Ok '\n' + + parse eol "a" == Err [ "expected an end of line character" ] + -} eol : Parser s Char eol = @@ -151,6 +190,11 @@ eol = {-| Parse any lowercase character. + + parse lower "a" == Ok 'a' + + parse lower "A" == Err [ "expected a lowercase character" ] + -} lower : Parser s Char lower = @@ -158,6 +202,11 @@ lower = {-| Parse any uppercase character. + + parse upper "A" == Ok 'A' + + parse upper "a" == Err [ "expected an uppercase character" ] + -} upper : Parser s Char upper = @@ -165,6 +214,13 @@ upper = {-| Parse any base 10 digit. + + parse digit "0" == Ok '0' + + parse digit "9" == Ok '9' + + parse digit "a" == Err [ "expected a digit" ] + -} digit : Parser s Char digit = @@ -172,6 +228,13 @@ digit = {-| Parse any base 8 digit. + + parse octDigit "0" == Ok '0' + + parse octDigit "7" == Ok '7' + + parse octDigit "8" == Err [ "expected an octal digit" ] + -} octDigit : Parser s Char octDigit = @@ -179,7 +242,48 @@ octDigit = {-| Parse any base 16 digit. + + parse hexDigit "0" == Ok '0' + + parse hexDigit "7" == Ok '7' + + parse hexDigit "a" == Ok 'a' + + parse hexDigit "f" == Ok 'f' + + parse hexDigit "g" == Err [ "expected a hexadecimal digit" ] + -} hexDigit : Parser s Char hexDigit = satisfy Char.isHexDigit |> onerror "expected a hexadecimal digit" + + +{-| Parse any alphabetic character. + + parse alpha "a" == Ok 'a' + + parse alpha "A" == Ok 'A' + + parse alpha "0" == Err [ "expected an alphabetic character" ] + +-} +alpha : Parser s Char +alpha = + satisfy Char.isAlpha |> onerror "expected an alphabetic character" + + +{-| Parse any alphanumeric character. + + parse alphaNum "a" == Ok 'a' + + parse alphaNum "A" == Ok 'A' + + parse alphaNum "0" == Ok '0' + + parse alphaNum "-" == Err [ "expected an alphanumeric character" ] + +-} +alphaNum : Parser s Char +alphaNum = + satisfy Char.isAlphaNum |> onerror "expected an alphanumeric character" From 8c2723d030a979472d1a1fdc38459f515eb52429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 23 Oct 2024 17:46:46 +0200 Subject: [PATCH 52/78] new convenience functions for Combine --- src/Combine.elm | 204 ++++++++++++++++++++++++++---------------------- 1 file changed, 111 insertions(+), 93 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 076f646..c883c48 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -6,7 +6,7 @@ module Combine exposing , map, onsuccess, mapError, onerror , andThen, andMap, sequence , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore - , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyInput, modifyPosition + , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, modifyInput, putInput, modifyPosition, putPosition ) {-| This library provides facilities for parsing structured text data @@ -66,7 +66,7 @@ into concrete Elm values. ### State Combinators -@docs withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, currentLocation, currentSourceLine, currentLine, currentColumn, currentStream, modifyInput, modifyPosition +@docs withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, modifyInput, putInput, modifyPosition, putPosition -} @@ -328,7 +328,7 @@ bimap fok ferr p = let parser = string "a" - |> andThen (\_ -> withState (\state -> succeed state)) + |> keep (withState (\state -> succeed state)) in parse parser "a" -- Ok () @@ -382,7 +382,7 @@ modifyState f = let parser = string "a" - |> andThen (\_ -> withLocation (\loc -> succeed loc)) + |> keep (withLocation succeed) in parse parser "a" -- Ok { source = "a", line = 0, column = 1 } @@ -399,11 +399,11 @@ withLocation f = let parser = - string "a" - |> andThen (\_ -> withLine (\line -> succeed line)) + string "a\n\n" + |> keep (withLine succeed) in - parse parser "a" - -- Ok 0 + parse parser "a\n\n" + -- Ok 2 -} withLine : (Int -> Parser s a) -> Parser s a @@ -417,11 +417,11 @@ withLine f = let parser = - string "a" - |> andThen (\_ -> withColumn (\column -> succeed column)) + string "aaa" + |> keep (withColumn succeed) in - parse parser "a" - -- Ok 1 + parse parser "aaa" + -- Ok 3 -} withColumn : (Int -> Parser s a) -> Parser s a @@ -436,11 +436,11 @@ only for debugging purposes ... let parser = - string "a" - |> andThen (\_ -> withSourceLine (\line -> succeed line)) + string "a" + |> keep (withSourceLine succeed) in - parse parser "a" - -- Ok "a" + parse parser "abc" + -- Ok "bc" -} withSourceLine : (String -> Parser s a) -> Parser s a @@ -451,15 +451,6 @@ withSourceLine f = {-| Get the current `(line, column)` in the input stream. - - let - parser = - string "a" - |> andThen (\_ -> withLocation (\loc -> succeed loc)) - in - parse parser "a" - -- Ok { source = "a", line = 0, column = 1 } - -} currentLocation : InputStream -> ParseLocation currentLocation stream = @@ -490,15 +481,6 @@ currentLocation stream = {-| Get the current source line in the input stream. - - let - parser = - string "a" - |> andThen (\_ -> modifyInput String.toUpper) - in - parse parser "a" - -- Ok () - -} currentSourceLine : InputStream -> String currentSourceLine = @@ -506,15 +488,6 @@ currentSourceLine = {-| Get the current line in the input stream. - - let - parser = - string "a" - |> andThen (\_ -> modifyPosition ((+) 1)) - in - parse parser "a" - -- Ok () - -} currentLine : InputStream -> Int currentLine = @@ -522,71 +495,71 @@ currentLine = {-| Get the current column in the input stream. - - let - parser = - string "a" - |> andThen (\_ -> modifyPosition ((+) 1)) - in - parse parser "a" - -- Ok () - -} currentColumn : InputStream -> Int currentColumn = currentLocation >> .column -{-| Get the current string stream. That might be useful for applying memorization. +{-| Modify the parser's current InputStream input (String). - let - parser = - string "a" - |> andThen (\_ -> modifyInput String.toUpper) - in - parse parser "a" - -- Ok () + parse (modifyInput String.toUpper + |> keep (many (string "A"))) "aaa" + -- Ok ["A","A","A"] -} -currentStream : InputStream -> String -currentStream = - .input +modifyInput : (String -> String) -> Parser s () +modifyInput f = + Parser <| + \state stream -> + app (succeed ()) state { stream | input = f stream.input } + +{-| Replace the remaining input with a new string. -{-| Modify the parser's InputStream input (String). + parse ( string "a" + |> ignore (putInput "AAA") + |> keep (many (string "A"))) "aaa" + -- Ok ["A","A","A"] + +-} +putInput : String -> Parser s () +putInput i = + modifyInput (always i) + + +{-| Modify the parser's InputStream position (Int). let parser = string "a" - |> andThen (\_ -> modifyInput String.toUpper) + |> ignore (modifyPosition ((+) 1000)) in parse parser "a" - -- Ok () + -- Ok ((),{ data = "a", input = "", position = 1001 },"a") -} -modifyInput : (String -> String) -> Parser s () -modifyInput f = +modifyPosition : (Int -> Int) -> Parser s () +modifyPosition f = Parser <| \state stream -> - app (succeed ()) state { stream | input = f stream.input } + app (succeed ()) state { stream | position = f stream.position } -{-| Modify the parser's InputStream position (Int). +{-| Replace the parser position. let parser = string "a" - |> andThen (\_ -> modifyPosition ((+) 1)) + |> ignore (putPosition 1000) in parse parser "a" - -- Ok () + -- Ok ((),{ data = "a", input = "", position = 1000 },"a") -} -modifyPosition : (Int -> Int) -> Parser s () -modifyPosition f = - Parser <| - \state stream -> - app (succeed ()) state { stream | position = f stream.position } +putPosition : Int -> Parser s () +putPosition i = + modifyPosition (always i) @@ -798,6 +771,11 @@ every pattern unless one already exists. parse (regex "a+") "aaaaab" -- Ok "aaaaa" + parse (regex "a+") "Aaaaab" + -- Err ["expected input matching Regexp /^a+/"] + +Use `regexWith` for more options on allowing case-insensitive or multiline. + -} regex : String -> Parser s String regex = @@ -809,8 +787,11 @@ regex = Same as regex, but returns also submatches as the second parameter in the result tuple. - parse (regexSub "a+") "aaaaab" - -- Ok ("aaaaa", []) + parse (regexSub "(a+)(b+)") "aaaaab" + -- Ok ("aaaaab",[Just "aaaaa",Just "b"]) + + parse (regexSub "(?:a+)(b+)") "aaaaab" + -- Ok ("aaaaab",[Just "b"]) -} regexSub : String -> Parser s ( String, List (Maybe String) ) @@ -829,8 +810,11 @@ The rest is as follows. Regular expressions must match from the beginning of the input and their subgroups are ignored. A `^` is added implicitly to the beginning of every pattern unless one already exists. - parse (regexWith { caseInsensitive = True, multiline = False} "a+") "aaaAAaAab" - -- Ok "aaaAAaAa" + parse (regexWith { caseInsensitive = True, multiline = False} "a+") "AaaAAaAab" + -- Ok "AaaAAaAa" + + parse (regexWith { caseInsensitive = False, multiline = False} "a+") "AaaAAaAab" + -- Err ["expected input matching Regexp /^a+/"] -} regexWith : { caseInsensitive : Bool, multiline : Bool } -> String -> Parser s String @@ -853,9 +837,12 @@ The rest is as follows. Regular expressions must match from the beginning of the input and their subgroups are ignored. A `^` is added implicitly to the beginning of every pattern unless one already exists. - parse (regexWithSub { caseInsensitive = True, multiline = False } "a+") "aaaAAaAab" + parse (regexWithSub { caseInsensitive = True, multiline = False } "a+") "AaaAAaAab" -- Ok ("aaaAAaAa", []) + parse (regexWithSub { caseInsensitive = False, multiline = False } "a+") "AaaAAaAab" + -- Err ["expected input matching Regexp /^a+/"] + -} regexWithSub : { caseInsensitive : Bool, multiline : Bool } -> String -> Parser s ( String, List (Maybe String) ) regexWithSub { caseInsensitive, multiline } = @@ -960,11 +947,14 @@ end = {-| Apply a parser without consuming any input on success. - parse (whitespace1 |> keep (string "a")) " a" + parse (lookAhead (string "a") |> keep (string "a")) "a" -- Ok "a" - parse (whitespace1 |> keep (string "a")) "a" - -- Err ["expected whitespace"] + parse (lookAhead (string "a") |> keep (string "b")) "a" + -- Err ["expected \"b\""] + + parse (lookAhead (string "a") |> keep (string "b")) "b" + -- Err ["expected \"a\""] -} lookAhead : Parser s a -> Parser s a @@ -1113,7 +1103,10 @@ many1 p = {-| Apply the first parser zero or more times until second parser succeeds. On success, the list of the first parser's results is returned. - string "")) + parse ( string "") + |> map String.fromList ) + -- Ok "foo bar" -} manyTill : Parser s a -> Parser s end -> Parser s (List a) @@ -1138,7 +1131,10 @@ manyTill p end_ = {-| Apply the first parser one or more times until second parser succeeds. On success, the list of the first parser's results is returned. - string "")) + parse ( string "") + |> map String.fromList ) + -- Ok "foo bar" -} many1Till : Parser s a -> Parser s end -> Parser s (List a) @@ -1265,11 +1261,18 @@ skipMany1 p = apply all functions returned by `op` to the values returned by `p`. See the `examples/Calc.elm` file for an example. - parse (chainl (string "+") (int)) "1+2+3" + let + addop = + choice + [ string "+" |> onsuccess (+) + , string "-" |> onsuccess (-) + ] + in + parse (chainl addop int) "1+2+3" -- Ok 6 - parse (chainl (string "+") (int)) "1+2+" - -- Err ["expected an integer"] + parse (chainl addop int) "1+2+3-X" + -- Ok 6 -} chainl : Parser s (a -> a -> a) -> Parser s a -> Parser s a @@ -1293,8 +1296,19 @@ chainl op p = right-associative order to the values of `p`. See the `examples/Python.elm` file for a usage example. - parse (chainr (string "a" |> keep (string "+")) (string "a")) "a+a+a" - -- Ok "a" + let + addop = + choice + [ string "+" |> onsuccess (+) + , string "-" |> onsuccess (-) + ] + in + + parse (chainr addop int) "1-2-3" -- 1 - (2 - 3) + -- Ok 2 + + parse (chainl addop int) "1-2-3" -- 1 - 2 - 3 + -- Ok (-4) -} chainr : Parser s (a -> a -> a) -> Parser s a -> Parser s a @@ -1323,6 +1337,9 @@ chainr op p = parse (count 3 (string "a")) "aa" -- Err ["expected \"a\""] + parse (count 3 (string "a")) "aaaaa" + -- Ok ["a", "a", "a"] + -} count : Int -> Parser s a -> Parser s (List a) count n p = @@ -1341,7 +1358,8 @@ count n p = The parser - between (string "(") (string ")") (string "a") + parse (between (string "(") (string ")") (string "a")) "(a)" + -- Ok "a" is equivalent to the parser From 48d1c6f886c521769d77a6368da3649479c849dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Wed, 23 Oct 2024 17:52:23 +0200 Subject: [PATCH 53/78] Version bump to 5.0.0 --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index 88393d4..c9da4d6 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "4.1.0", + "version": "5.0.0", "exposed-modules": [ "Combine", "Combine.Char", From a875911c18c9b5792199307b91b5f45125add1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 24 Oct 2024 08:55:58 +0200 Subject: [PATCH 54/78] docs: Improved examples and links --- src/Combine.elm | 144 +++++++++++++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 56 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index c883c48..56f05fb 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -2,10 +2,11 @@ module Combine exposing ( Parser, InputStream, ParseLocation, ParseContext, ParseResult, ParseError, ParseOk , parse, runParser , primitive, app, lazy - , fail, succeed, string, regex, regexSub, regexWith, regexWithSub, end, whitespace, whitespace1 + , fail, succeed, string, end, whitespace, whitespace1 + , regex, regexSub, regexWith, regexWithSub , map, onsuccess, mapError, onerror , andThen, andMap, sequence - , lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore + , keep, ignore, lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, modifyInput, putInput, modifyPosition, putPosition ) @@ -43,7 +44,12 @@ into concrete Elm values. ## Parsers -@docs fail, succeed, string, regex, regexSub, regexWith, regexWithSub, end, whitespace, whitespace1 +@docs fail, succeed, string, end, whitespace, whitespace1 + + +### Regular Expressions + +@docs regex, regexSub, regexWith, regexWithSub ## Combinators @@ -61,7 +67,7 @@ into concrete Elm values. ### Parser Combinators -@docs lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets, keep, ignore +@docs keep, ignore, lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets ### State Combinators @@ -167,7 +173,7 @@ If you find yourself reaching for this function often consider opening a [Github issue][issues] with the library to have your custom Parsers included in the standard distribution. -[issues]: https://github.com/elm-community/parser-combinators/issues +[issues]: https://github.com/andre-dietrich/parser-combinators/issues -} primitive : (state -> InputStream -> ParseContext state res) -> Parser state res @@ -326,12 +332,12 @@ bimap fok ferr p = {-| Get the parser's state and pipe it into a parser. let - parser = - string "a" - |> keep (withState (\state -> succeed state)) + parser = + string "a" + |> keep (withState (\state -> succeed state)) in - parse parser "a" - -- Ok () + parse parser "a" + -- Ok () -} withState : (s -> Parser s a) -> Parser s a @@ -344,12 +350,12 @@ withState f = {-| Replace the parser's state. let - parser = - string "a" - |> andThen (\_ -> putState 42) + parser = + string "a" + |> andThen (\_ -> putState 42) in - parse parser "a" - -- Ok () + parse parser "a" + -- Ok () -} putState : s -> Parser s () @@ -362,12 +368,12 @@ putState state = {-| Modify the parser's state. let - parser = - string "a" - |> andThen (\_ -> modifyState ((+) 1)) + parser = + string "a" + |> andThen (\_ -> modifyState ((+) 1)) in - parse parser "a" - -- Ok () + parse parser "a" + -- Ok () -} modifyState : (s -> s) -> Parser s () @@ -380,12 +386,12 @@ modifyState f = {-| Get the current position in the input stream and pipe it into a parser. let - parser = - string "a" - |> keep (withLocation succeed) + parser = + string "a" + |> keep (withLocation succeed) in - parse parser "a" - -- Ok { source = "a", line = 0, column = 1 } + parse parser "a" + -- Ok { source = "a", line = 0, column = 1 } -} withLocation : (ParseLocation -> Parser s a) -> Parser s a @@ -398,12 +404,12 @@ withLocation f = {-| Get the current line and pipe it into a parser. let - parser = - string "a\n\n" - |> keep (withLine succeed) + parser = + string "a\n\n" + |> keep (withLine succeed) in - parse parser "a\n\n" - -- Ok 2 + parse parser "a\n\n" + -- Ok 2 -} withLine : (Int -> Parser s a) -> Parser s a @@ -416,12 +422,12 @@ withLine f = {-| Get the current column and pipe it into a parser. let - parser = - string "aaa" - |> keep (withColumn succeed) + parser = + string "aaa" + |> keep (withColumn succeed) in - parse parser "aaa" - -- Ok 3 + parse parser "aaa" + -- Ok 3 -} withColumn : (Int -> Parser s a) -> Parser s a @@ -435,12 +441,12 @@ withColumn f = only for debugging purposes ... let - parser = + parser = string "a" |> keep (withSourceLine succeed) in - parse parser "abc" - -- Ok "bc" + parse parser "abc" + -- Ok "bc" -} withSourceLine : (String -> Parser s a) -> Parser s a @@ -503,8 +509,11 @@ currentColumn = {-| Modify the parser's current InputStream input (String). - parse (modifyInput String.toUpper - |> keep (many (string "A"))) "aaa" + parse + (modifyInput String.toUpper + |> keep (many (string "A")) + ) + "aaa" -- Ok ["A","A","A"] -} @@ -517,9 +526,12 @@ modifyInput f = {-| Replace the remaining input with a new string. - parse ( string "a" + parse + (string "a" |> ignore (putInput "AAA") - |> keep (many (string "A"))) "aaa" + |> keep (many (string "A")) + ) + "aaa" -- Ok ["A","A","A"] -} @@ -531,12 +543,12 @@ putInput i = {-| Modify the parser's InputStream position (Int). let - parser = - string "a" - |> ignore (modifyPosition ((+) 1000)) + parser = + string "a" + |> ignore (modifyPosition ((+) 1000)) in - parse parser "a" - -- Ok ((),{ data = "a", input = "", position = 1001 },"a") + parse parser "a" + -- Ok ((),{ data = "a", input = "", position = 1001 },"a") -} modifyPosition : (Int -> Int) -> Parser s () @@ -549,12 +561,12 @@ modifyPosition f = {-| Replace the parser position. let - parser = - string "a" - |> ignore (putPosition 1000) + parser = + string "a" + |> ignore (putPosition 1000) in - parse parser "a" - -- Ok ((),{ data = "a", input = "", position = 1000 },"a") + parse parser "a" + -- Ok ((),{ data = "a", input = "", position = 1000 },"a") -} putPosition : Int -> Parser s () @@ -810,10 +822,20 @@ The rest is as follows. Regular expressions must match from the beginning of the input and their subgroups are ignored. A `^` is added implicitly to the beginning of every pattern unless one already exists. - parse (regexWith { caseInsensitive = True, multiline = False} "a+") "AaaAAaAab" + parse + (regexWith + { caseInsensitive = True, multiline = False } + "a+" + ) + "AaaAAaAab" -- Ok "AaaAAaAa" - parse (regexWith { caseInsensitive = False, multiline = False} "a+") "AaaAAaAab" + parse + (regexWith + { caseInsensitive = False, multiline = False } + "a+" + ) + "AaaAAaAab" -- Err ["expected input matching Regexp /^a+/"] -} @@ -837,10 +859,20 @@ The rest is as follows. Regular expressions must match from the beginning of the input and their subgroups are ignored. A `^` is added implicitly to the beginning of every pattern unless one already exists. - parse (regexWithSub { caseInsensitive = True, multiline = False } "a+") "AaaAAaAab" + parse + (regexWithSub + { caseInsensitive = True, multiline = False } + "a+" + ) + "AaaAAaAab" -- Ok ("aaaAAaAa", []) - parse (regexWithSub { caseInsensitive = False, multiline = False } "a+") "AaaAAaAab" + parse + (regexWithSub + { caseInsensitive = False, multiline = False } + "a+" + ) + "AaaAAaAab" -- Err ["expected input matching Regexp /^a+/"] -} From fdbe7296dd1c7b47d0a555603347b64d13838ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 24 Oct 2024 08:56:54 +0200 Subject: [PATCH 55/78] Version bump to 5.0.1 --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index c9da4d6..49ad9d8 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "5.0.0", + "version": "5.0.1", "exposed-modules": [ "Combine", "Combine.Char", From c0237c8f57b1daa9c6d3079e818ad71bab069b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 24 Oct 2024 09:18:25 +0200 Subject: [PATCH 56/78] add missing helper functions --- src/Combine.elm | 82 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 56f05fb..dfb6648 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -8,6 +8,7 @@ module Combine exposing , andThen, andMap, sequence , keep, ignore, lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, modifyInput, putInput, modifyPosition, putPosition + , currentLocation, currentSourceLine, currentLine, currentColumn, currentStream ) {-| This library provides facilities for parsing structured text data @@ -74,6 +75,11 @@ into concrete Elm values. @docs withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, modifyInput, putInput, modifyPosition, putPosition + +### Miscellaneous + +@docs currentLocation, currentSourceLine, currentLine, currentColumn, currentStream + -} import Flip exposing (flip) @@ -253,9 +259,10 @@ parse p = statefulInt : Parse Int Int statefulInt = - -- Parse an int, then increment the state and return the parsed - -- int. It's important that we try to parse the int _first_ - -- since modifying the state will always succeed. + -- Parse an int, then increment the state and return + -- the parsed int. It's important that we try to parse + -- the int _first_ since modifying the state will + -- always succeed. int |> ignore (modifyState ((+) 1)) ints : Parse Int (List Int) @@ -507,6 +514,13 @@ currentColumn = currentLocation >> .column +{-| Get the current string stream. That might be useful for applying memorization. +-} +currentStream : InputStream -> String +currentStream = + .input + + {-| Modify the parser's current InputStream input (String). parse @@ -1135,9 +1149,14 @@ many1 p = {-| Apply the first parser zero or more times until second parser succeeds. On success, the list of the first parser's results is returned. - parse ( string "") - |> map String.fromList ) + parse + (string "") + |> map String.fromList + ) + ) + "" -- Ok "foo bar" -} @@ -1163,9 +1182,14 @@ manyTill p end_ = {-| Apply the first parser one or more times until second parser succeeds. On success, the list of the first parser's results is returned. - parse ( string "") - |> map String.fromList ) + parse + (string "") + |> map String.fromList + ) + ) + "" -- Ok "foo bar" -} @@ -1390,12 +1414,20 @@ count n p = The parser - parse (between (string "(") (string ")") (string "a")) "(a)" + parse + (between + (string "(") + (string ")") + (string "a") + ) + "(a)" -- Ok "a" is equivalent to the parser - string "(" |> keep (string "a") |> ignore (string ")") + string "(" + |> keep (string "a") + |> ignore (string ")") -} between : Parser s l -> Parser s r -> Parser s a -> Parser s a @@ -1447,10 +1479,18 @@ brackets = {-| Parse zero or more whitespace characters. - parse (whitespace |> keep (string "hello")) "hello" + parse + (whitespace + |> keep (string "hello") + ) + "hello" -- Ok "hello" - parse (whitespace |> keep (string "hello")) " hello" + parse + (whitespace + |> keep (string "hello") + ) + " hello" -- Ok "hello" -} @@ -1461,11 +1501,19 @@ whitespace = {-| Parse one or more whitespace characters. - parse (whitespace1 |> keep (string "hello")) "hello" - -- Err ["whitespace"] + parse + (whitespace1 + |> keep (string "hello") + ) + "hello" + -- Err ["whitespace"] - parse (whitespace1 |> keep (string "hello")) " hello" - -- Ok "hello" + parse + (whitespace1 + |> keep (string "hello") + ) + " hello" + -- Ok "hello" -} whitespace1 : Parser s String From 7e2ac8285262f9eb95fbc8d17e38e078bd6969ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 24 Oct 2024 09:19:00 +0200 Subject: [PATCH 57/78] Version bump to 5.1.0 --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index 49ad9d8..4b3ecea 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "5.0.1", + "version": "5.1.0", "exposed-modules": [ "Combine", "Combine.Char", From 7c80de8ceb46b98b36184a0c259b31b9de00cb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 10:59:03 +0100 Subject: [PATCH 58/78] improve: manyTill now fix infinite loops --- src/Combine.elm | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index dfb6648..16440d9 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1171,10 +1171,18 @@ manyTill p end_ = ( estate, estream, Err ms ) -> case app p state stream of ( rstate, rstream, Ok res ) -> - accumulate (res :: acc) rstate rstream + if stream.position == rstream.position then + ( estate, estream, Err [ "manyTill: parser succeeded without consuming input" ] ) - _ -> - ( estate, estream, Err ms ) + else + accumulate (res :: acc) rstate rstream + + ( pstate, pstream, Err _ ) -> + if pstream.input == "" then + ( estate, estream, Err [ "manyTill: reached end of input without finding end parser" ] ) + + else + ( estate, estream, Err ms ) in Parser (accumulate []) From 81ad9b2397b7fe09a4328ac487f8912fe7c0316d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:05:42 +0100 Subject: [PATCH 59/78] improve: choice function improved with better performance --- src/Combine.elm | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Combine.elm b/src/Combine.elm index 16440d9..d0b29ef 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1055,7 +1055,21 @@ or lp rp = -} choice : List (Parser s a) -> Parser s a choice xs = - List.foldr or emptyErr xs + let + tryParsers parsers state stream errors = + case parsers of + [] -> + ( state, stream, Err (List.reverse errors) ) + + p :: rest -> + case app p state stream of + ( _, _, Ok _ ) as res -> + res + + ( _, _, Err ms ) -> + tryParsers rest state stream (List.foldl (::) errors ms) + in + Parser <| \state stream -> tryParsers xs state stream [] {-| Return a default value when the given parser fails. From ed336081b171f7493099f294fab9da2fd199d08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:06:20 +0100 Subject: [PATCH 60/78] improve: chainl/r now with infinite loop protection --- src/Combine.elm | 76 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index d0b29ef..465a826 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1356,18 +1356,31 @@ the `examples/Calc.elm` file for an example. chainl : Parser s (a -> a -> a) -> Parser s a -> Parser s a chainl op p = let - accumulate x = - succeed x - |> or - (op - |> andThen - (\f -> - p - |> andThen (\y -> accumulate (f x y)) - ) - ) + accumulate x state stream = + case app op state stream of + ( opstate, opstream, Ok f ) -> + if stream.position == opstream.position then + ( opstate, opstream, Ok x ) + + else + case app p opstate opstream of + ( pstate, pstream, Ok y ) -> + accumulate (f x y) pstate pstream + + ( estate, estream, Err ms ) -> + ( estate, estream, Err ms ) + + ( _, _, Err _ ) -> + ( state, stream, Ok x ) in - andThen accumulate p + Parser <| + \state stream -> + case app p state stream of + ( pstate, pstream, Ok x ) -> + accumulate x pstate pstream + + ( estate, estream, Err ms ) -> + ( estate, estream, Err ms ) {-| Similar to `chainl` but functions of `op` are applied in @@ -1392,19 +1405,36 @@ right-associative order to the values of `p`. See the chainr : Parser s (a -> a -> a) -> Parser s a -> Parser s a chainr op p = let - accumulate x = - succeed x - |> or - (op - |> andThen - (\f -> - p - |> andThen accumulate - |> andThen (\y -> succeed (f x y)) - ) - ) + accumulate x state stream = + case app op state stream of + ( opstate, opstream, Ok f ) -> + if stream.position == opstream.position then + ( opstate, opstream, Ok x ) + + else + case app p opstate opstream of + ( pstate, pstream, Ok y ) -> + case accumulate y pstate pstream of + ( rstate, rstream, Ok z ) -> + ( rstate, rstream, Ok (f x z) ) + + err -> + err + + ( estate, estream, Err ms ) -> + ( estate, estream, Err ms ) + + ( _, _, Err _ ) -> + ( state, stream, Ok x ) in - andThen accumulate p + Parser <| + \state stream -> + case app p state stream of + ( pstate, pstream, Ok x ) -> + accumulate x pstate pstream + + ( estate, estream, Err ms ) -> + ( estate, estream, Err ms ) {-| Parse `n` occurrences of `p`. From 58dc7b634b80cbc8caaaa9d49f94331aa8011e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:08:39 +0100 Subject: [PATCH 61/78] improve: speed of while function --- src/Combine.elm | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 465a826..5b52daa 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -948,25 +948,22 @@ while pred = Just ( h, rest ) -> if pred h then let - c = - String.cons h "" - pos = stream.position + 1 in - accumulate (acc ++ c) state { stream | input = rest, position = pos } + accumulate (h :: acc) state { stream | input = rest, position = pos } else - ( state, stream, acc ) + ( state, stream, String.fromList (List.reverse acc) ) Nothing -> - ( state, stream, acc ) + ( state, stream, String.fromList (List.reverse acc) ) in Parser <| \state stream -> let ( rstate, rstream, res ) = - accumulate "" state stream + accumulate [] state stream in ( rstate, rstream, Ok res ) From 7d5cf1b0f8ccb3848d16ecb2e7ade32fa1a2548a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:19:36 +0100 Subject: [PATCH 62/78] improve: add caching to regex function --- src/Combine.elm | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 5b52daa..2b7d134 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -907,7 +907,7 @@ regexer : -> (Regex.Match -> res) -> String -> (state -> InputStream -> ( state, InputStream, ParseResult res )) -regexer input output pat state stream = +regexer input output pat = let pattern = if String.startsWith "^" pat then @@ -915,23 +915,27 @@ regexer input output pat state stream = else "^" ++ pat + + compiledRegex = + input pattern |> Maybe.withDefault Regex.never in - case Regex.findAtMost 1 (input pattern |> Maybe.withDefault Regex.never) stream.input of - [ match ] -> - let - len = - String.length match.match + \state stream -> + case Regex.findAtMost 1 compiledRegex stream.input of + [ match ] -> + let + len = + String.length match.match - rem = - String.dropLeft len stream.input + rem = + String.dropLeft len stream.input - pos = - stream.position + len - in - ( state, { stream | input = rem, position = pos }, Ok (output match) ) + pos = + stream.position + len + in + ( state, { stream | input = rem, position = pos }, Ok (output match) ) - _ -> - ( state, stream, Err [ "expected input matching Regexp /" ++ pattern ++ "/" ] ) + _ -> + ( state, stream, Err [ "expected input matching Regexp /" ++ pattern ++ "/" ] ) {-| Consume input while the predicate matches. From b9fc52679099c7b6de8fd93b5d2b888baf70de0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:20:16 +0100 Subject: [PATCH 63/78] fix: manyTill consuming recognition --- src/Combine.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Combine.elm b/src/Combine.elm index 2b7d134..788274f 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1186,7 +1186,7 @@ manyTill p end_ = ( estate, estream, Err ms ) -> case app p state stream of ( rstate, rstream, Ok res ) -> - if stream.position == rstream.position then + if stream == rstream then ( estate, estream, Err [ "manyTill: parser succeeded without consuming input" ] ) else From 3a07e3a4a393f6f68260af3e4de74a6b4132c0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:22:01 +0100 Subject: [PATCH 64/78] fix: chainl consuming recognition --- src/Combine.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 788274f..10fc743 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1360,7 +1360,7 @@ chainl op p = accumulate x state stream = case app op state stream of ( opstate, opstream, Ok f ) -> - if stream.position == opstream.position then + if stream == opstream then ( opstate, opstream, Ok x ) else @@ -1409,7 +1409,7 @@ chainr op p = accumulate x state stream = case app op state stream of ( opstate, opstream, Ok f ) -> - if stream.position == opstream.position then + if stream == opstream then ( opstate, opstream, Ok x ) else From e8543bf931dc6318dbee6ef6ef04db8387f4131a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:25:58 +0100 Subject: [PATCH 65/78] micro-optimization for lazy and or functions --- src/Combine.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 10fc743..85c3e3c 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -310,7 +310,7 @@ snippet in your code to circumvent this problem: lazy : (() -> Parser s a) -> Parser s a lazy t = -- RecursiveParser (L.lazy (\() -> app (t ()))) - succeed () |> andThen t + Parser <| \state stream -> app (t ()) state stream {-| Transform both the result and error message of a parser. @@ -1042,7 +1042,7 @@ or lp rp = res ( _, _, Err rms ) -> - ( state, stream, Err (lms ++ rms) ) + ( state, stream, Err (List.foldl (::) lms rms |> List.reverse) ) {-| Choose between a list of parsers. From c71486262409b0cbd3986bf845c16d3ca7f7bb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:46:44 +0100 Subject: [PATCH 66/78] improve: charList --- src/Combine/Char.elm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index 4987d13..0e272f0 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -73,11 +73,9 @@ char c = charList : List Char -> String charList chars = - chars - |> List.map (\c -> "'" ++ String.fromChar c ++ "'") - |> List.intersperse ", " - |> String.concat - |> (\str -> "[" ++ str ++ "]") + "[" + ++ String.join ", " (List.map (\c -> "'" ++ String.fromChar c ++ "'") chars) + ++ "]" {-| Parse any character. From 2a1c29d3b35459090baa36463818e312003755c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Mon, 26 Jan 2026 11:47:44 +0100 Subject: [PATCH 67/78] Version bump to 5.1.1 --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index 4b3ecea..3d36bbb 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "5.1.0", + "version": "5.1.1", "exposed-modules": [ "Combine", "Combine.Char", From 1aff84cacfea11379fe89f45fe510eeb1586b940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 19 Feb 2026 09:58:43 +0100 Subject: [PATCH 68/78] Updated infinite loop protection --- src/Combine.elm | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 85c3e3c..d5adde4 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1129,7 +1129,7 @@ many p = accumulate acc state stream = case app p state stream of ( rstate, rstream, Ok res ) -> - if stream == rstream then + if stream.input == rstream.input then ( rstate, rstream, List.reverse acc ) else @@ -1186,7 +1186,7 @@ manyTill p end_ = ( estate, estream, Err ms ) -> case app p state stream of ( rstate, rstream, Ok res ) -> - if stream == rstream then + if stream.input == rstream.input then ( estate, estream, Err [ "manyTill: parser succeeded without consuming input" ] ) else @@ -1360,13 +1360,17 @@ chainl op p = accumulate x state stream = case app op state stream of ( opstate, opstream, Ok f ) -> - if stream == opstream then + if stream.input == opstream.input then ( opstate, opstream, Ok x ) else case app p opstate opstream of ( pstate, pstream, Ok y ) -> - accumulate (f x y) pstate pstream + if opstream.input == pstream.input then + ( pstate, pstream, Ok x ) + + else + accumulate (f x y) pstate pstream ( estate, estream, Err ms ) -> ( estate, estream, Err ms ) @@ -1409,18 +1413,22 @@ chainr op p = accumulate x state stream = case app op state stream of ( opstate, opstream, Ok f ) -> - if stream == opstream then + if stream.input == opstream.input then ( opstate, opstream, Ok x ) else case app p opstate opstream of ( pstate, pstream, Ok y ) -> - case accumulate y pstate pstream of - ( rstate, rstream, Ok z ) -> - ( rstate, rstream, Ok (f x z) ) + if opstream.input == pstream.input then + ( pstate, pstream, Ok x ) + + else + case accumulate y pstate pstream of + ( rstate, rstream, Ok z ) -> + ( rstate, rstream, Ok (f x z) ) - err -> - err + err -> + err ( estate, estream, Err ms ) -> ( estate, estream, Err ms ) From a395f2732401778ac6d1c1768d2899537363d96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Thu, 19 Feb 2026 10:07:14 +0100 Subject: [PATCH 69/78] Version bump to 5.1.2 --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index 3d36bbb..47afd5d 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "5.1.1", + "version": "5.1.2", "exposed-modules": [ "Combine", "Combine.Char", From 293783fdc274775e8bccc55dfcc027e71198fbe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 13:56:06 +0100 Subject: [PATCH 70/78] Improve: Lazy parser with infinite loop protection --- elm.json | 2 +- src/Combine.elm | 33 +++++++++++++++++++++++++++++---- tests/CurrentLocationTests.elm | 12 ++++++------ tests/Parsers.elm | 16 ++++++++-------- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/elm.json b/elm.json index 47afd5d..ef0c7e5 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "5.1.2", + "version": "6.0.0", "exposed-modules": [ "Combine", "Combine.Char", diff --git a/src/Combine.elm b/src/Combine.elm index d5adde4..3401cf6 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -92,18 +92,20 @@ import String - `data` is the initial input provided by the user - `input` is the remainder after running a parse - `position` is the starting position of `input` in `data` after a parse + - `lazyDepth` tracks consecutive lazy calls without input consumption (for infinite loop detection) -} type alias InputStream = { data : String , input : String , position : Int + , lazyDepth : Int } initStream : String -> InputStream initStream s = - InputStream s s 0 + InputStream s s 0 0 {-| A record representing the current parse location in an InputStream. @@ -306,11 +308,34 @@ snippet in your code to circumvent this problem: recursion x = \() -> recursion x +This function also includes infinite loop detection by tracking the depth +of consecutive lazy calls without input consumption. + -} -lazy : (() -> Parser s a) -> Parser s a -lazy t = +lazy : Maybe Int -> (() -> Parser s a) -> Parser s a +lazy lazyDepth t = -- RecursiveParser (L.lazy (\() -> app (t ()))) - Parser <| \state stream -> app (t ()) state stream + Parser <| + \state stream -> + let + depth = + stream.lazyDepth + in + if depth > Maybe.withDefault 1000 lazyDepth then + ( state, stream, Err [ "infinite loop detected: lazy recursion depth exceeded" ] ) + + else + case app (t ()) state { stream | lazyDepth = depth + 1 } of + ( rstate, rstream, Ok res ) -> + -- Reset depth if we consumed input, otherwise keep incrementing + if stream.input == rstream.input then + ( rstate, { rstream | lazyDepth = depth + 1 }, Ok res ) + + else + ( rstate, { rstream | lazyDepth = 0 }, Ok res ) + + ( estate, estream, Err ms ) -> + ( estate, { estream | lazyDepth = 0 }, Err ms ) {-| Transform both the result and error message of a parser. diff --git a/tests/CurrentLocationTests.elm b/tests/CurrentLocationTests.elm index c414bde..35e300e 100644 --- a/tests/CurrentLocationTests.elm +++ b/tests/CurrentLocationTests.elm @@ -19,22 +19,22 @@ entryPoint = describe "entry point" [ test "column should be zero with empty input" <| \() -> - { data = "", input = "", position = 0 } + { data = "", input = "", position = 0, lazyDepth = 0 } |> Combine.currentColumn |> Expect.equal 0 , test "column should be zero with two lines" <| \() -> - { data = "\n", input = "\n", position = 0 } + { data = "\n", input = "\n", position = 0, lazyDepth = 0 } |> Combine.currentColumn |> Expect.equal 0 , fuzz Fuzz.string "column should be zero" <| \s -> - { data = s, input = s, position = 0 } + { data = s, input = s, position = 0, lazyDepth = 0 } |> Combine.currentColumn |> Expect.equal 0 , fuzz Fuzz.string "line should be zero" <| \s -> - { data = s, input = s, position = 0 } + { data = s, input = s, position = 0, lazyDepth = 0 } |> Combine.currentLine |> Expect.equal 0 ] @@ -45,12 +45,12 @@ specificLocationTests = describe "specific locations" [ test "should not skip to next line on eol" <| \() -> - { data = "x\ny", input = "x\ny", position = 1 } + { data = "x\ny", input = "x\ny", position = 1, lazyDepth = 0 } |> Combine.currentLocation |> Expect.equal { source = "x", line = 0, column = 1 } , test "should skip to next line on eol + 1" <| \() -> - { data = "x\ny", input = "x\ny", position = 2 } + { data = "x\ny", input = "x\ny", position = 2, lazyDepth = 0 } |> Combine.currentLocation |> Expect.equal { source = "y", line = 1, column = 0 } ] diff --git a/tests/Parsers.elm b/tests/Parsers.elm index 7e18a0c..634aaba 100644 --- a/tests/Parsers.elm +++ b/tests/Parsers.elm @@ -102,22 +102,22 @@ sepEndBy1Suite = \() -> Expect.equal (parse commaSep "a,a,a") - (Ok ( (), { data = "a,a,a", input = "", position = 5 }, [ "a", "a", "a" ] )) + (Ok ( (), { data = "a,a,a", input = "", position = 5, lazyDepth = 0 }, [ "a", "a", "a" ] )) , test "sepEndBy1 2" <| \() -> Expect.equal (parse commaSep "b") - (Err ( (), { data = "b", input = "b", position = 0 }, [ "expected \"a\"" ] )) + (Err ( (), { data = "b", input = "b", position = 0, lazyDepth = 0 }, [ "expected \"a\"" ] )) , test "sepEndBy1 3" <| \() -> Expect.equal (parse commaSep "a,a,a,") - (Ok ( (), { data = "a,a,a,", input = "", position = 6 }, [ "a", "a", "a" ] )) + (Ok ( (), { data = "a,a,a,", input = "", position = 6, lazyDepth = 0 }, [ "a", "a", "a" ] )) , test "sepEndBy1 4" <| \() -> Expect.equal (parse commaSep "a,a,b") - (Ok ( (), { data = "a,a,b", input = "b", position = 4 }, [ "a", "a" ] )) + (Ok ( (), { data = "a,a,b", input = "b", position = 4, lazyDepth = 0 }, [ "a", "a" ] )) ] @@ -128,20 +128,20 @@ sequenceSuite = \() -> Expect.equal (parse (sequence []) "a") - (Ok ( (), { data = "a", input = "a", position = 0 }, [] )) + (Ok ( (), { data = "a", input = "a", position = 0, lazyDepth = 0 }, [] )) , test "one parser" <| \() -> Expect.equal (parse (sequence [ many <| string "a" ]) "aaaab") - (Ok ( (), { data = "aaaab", input = "b", position = 4 }, [ [ "a", "a", "a", "a" ] ] )) + (Ok ( (), { data = "aaaab", input = "b", position = 4, lazyDepth = 0 }, [ [ "a", "a", "a", "a" ] ] )) , test "many parsers" <| \() -> Expect.equal (parse (sequence [ string "a", string "b", string "c" ]) "abc") - (Ok ( (), { data = "abc", input = "", position = 3 }, [ "a", "b", "c" ] )) + (Ok ( (), { data = "abc", input = "", position = 3, lazyDepth = 0 }, [ "a", "b", "c" ] )) , test "many parsers failure" <| \() -> Expect.equal (parse (sequence [ string "a", string "b", string "c" ]) "abd") - (Err ( (), { data = "abd", input = "d", position = 2 }, [ "expected \"c\"" ] )) + (Err ( (), { data = "abd", input = "d", position = 2, lazyDepth = 0 }, [ "expected \"c\"" ] )) ] From aec605ed941d73a098415ee2e45856e2a3719297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 18:01:47 +0100 Subject: [PATCH 71/78] feat: Separated trackedLazy parser --- src/Combine.elm | 85 +++++++++++++++++++++++++--------- tests/CurrentLocationTests.elm | 13 +++--- tests/Parsers.elm | 17 +++---- 3 files changed, 79 insertions(+), 36 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 3401cf6..ca6d812 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1,7 +1,7 @@ module Combine exposing ( Parser, InputStream, ParseLocation, ParseContext, ParseResult, ParseError, ParseOk , parse, runParser - , primitive, app, lazy + , primitive, app, lazy, trackedLazy , fail, succeed, string, end, whitespace, whitespace1 , regex, regexSub, regexWith, regexWithSub , map, onsuccess, mapError, onerror @@ -40,7 +40,7 @@ into concrete Elm values. ## Constructing Parsers -@docs primitive, app, lazy +@docs primitive, app, lazy, trackedLazy ## Parsers @@ -82,6 +82,7 @@ into concrete Elm values. -} +import Dict exposing (Dict) import Flip exposing (flip) import Regex import String @@ -93,19 +94,20 @@ import String - `input` is the remainder after running a parse - `position` is the starting position of `input` in `data` after a parse - `lazyDepth` tracks consecutive lazy calls without input consumption (for infinite loop detection) + - `lazyTracking` tracks depth per unique lazy parser ID (for trackedLazy) -} type alias InputStream = { data : String , input : String , position : Int - , lazyDepth : Int + , lazyTracking : Dict String Int } initStream : String -> InputStream initStream s = - InputStream s s 0 0 + InputStream s s 0 Dict.empty {-| A record representing the current parse location in an InputStream. @@ -304,38 +306,77 @@ runParser p st s = functionality is not accessible anymore by ordinary developers. Use this function only to avoid "bad-recursion" errors or use the following example snippet in your code to circumvent this problem: +recursion x = +() -> recursion x +-} +lazy : (() -> Parser s a) -> Parser s a +lazy t = + -- RecursiveParser (L.lazy (\() -> app (t ()))) + Parser <| \state stream -> app (t ()) state stream + + +{-| A more granular version of lazy that tracks recursion depth per unique parser ID. +This allows different lazy parsers to have independent depth tracking and custom limits. - recursion x = - \() -> recursion x +Use this when you want fine-grained control over infinite loop detection: -This function also includes infinite loop detection by tracking the depth -of consecutive lazy calls without input consumption. + myRecursiveParser : Parser s Expr + myRecursiveParser = + trackedLazy "expr-parser" 50 <| + \() -> + choice + [ string "atom" + , myRecursiveParser -- Each ID has its own counter + ] + + parse myRecursiveParser "atom" + -- Ok "atom" + +Parameters: + + - `id`: A unique identifier for this lazy parser (e.g., "json-object", "expression") + - `maxDepth`: Maximum recursion depth before triggering infinite loop detection + - `thunk`: The parser to evaluate lazily -} -lazy : Maybe Int -> (() -> Parser s a) -> Parser s a -lazy lazyDepth t = - -- RecursiveParser (L.lazy (\() -> app (t ()))) +trackedLazy : String -> Int -> (() -> Parser s a) -> Parser s a +trackedLazy id maxDepth t = Parser <| \state stream -> let - depth = - stream.lazyDepth + currentDepth = + Dict.get id stream.lazyTracking |> Maybe.withDefault 0 in - if depth > Maybe.withDefault 1000 lazyDepth then - ( state, stream, Err [ "infinite loop detected: lazy recursion depth exceeded" ] ) + if currentDepth > maxDepth then + ( state + , stream + , Err [ "infinite loop detected: lazy parser '" ++ id ++ "' exceeded depth limit of " ++ String.fromInt maxDepth ] + ) else - case app (t ()) state { stream | lazyDepth = depth + 1 } of + let + updatedTracking = + Dict.insert id (currentDepth + 1) stream.lazyTracking + + streamWithTracking = + { stream | lazyTracking = updatedTracking } + in + case app (t ()) state streamWithTracking of ( rstate, rstream, Ok res ) -> - -- Reset depth if we consumed input, otherwise keep incrementing - if stream.input == rstream.input then - ( rstate, { rstream | lazyDepth = depth + 1 }, Ok res ) + -- Reset this parser's depth if input was consumed + let + finalTracking = + if stream.input == rstream.input then + rstream.lazyTracking - else - ( rstate, { rstream | lazyDepth = 0 }, Ok res ) + else + Dict.remove id rstream.lazyTracking + in + ( rstate, { rstream | lazyTracking = finalTracking }, Ok res ) ( estate, estream, Err ms ) -> - ( estate, { estream | lazyDepth = 0 }, Err ms ) + -- Reset this parser's depth on error + ( estate, { estream | lazyTracking = Dict.remove id estream.lazyTracking }, Err ms ) {-| Transform both the result and error message of a parser. diff --git a/tests/CurrentLocationTests.elm b/tests/CurrentLocationTests.elm index 35e300e..2eb4d76 100644 --- a/tests/CurrentLocationTests.elm +++ b/tests/CurrentLocationTests.elm @@ -2,6 +2,7 @@ module CurrentLocationTests exposing (entryPoint, noNegativeValuesForColumn, noN import Combine import Combine.Char +import Dict import Expect import Fuzz import Test @@ -19,22 +20,22 @@ entryPoint = describe "entry point" [ test "column should be zero with empty input" <| \() -> - { data = "", input = "", position = 0, lazyDepth = 0 } + { data = "", input = "", position = 0, lazyTracking = Dict.empty } |> Combine.currentColumn |> Expect.equal 0 , test "column should be zero with two lines" <| \() -> - { data = "\n", input = "\n", position = 0, lazyDepth = 0 } + { data = "\n", input = "\n", position = 0, lazyTracking = Dict.empty } |> Combine.currentColumn |> Expect.equal 0 , fuzz Fuzz.string "column should be zero" <| \s -> - { data = s, input = s, position = 0, lazyDepth = 0 } + { data = s, input = s, position = 0, lazyTracking = Dict.empty } |> Combine.currentColumn |> Expect.equal 0 , fuzz Fuzz.string "line should be zero" <| \s -> - { data = s, input = s, position = 0, lazyDepth = 0 } + { data = s, input = s, position = 0, lazyTracking = Dict.empty } |> Combine.currentLine |> Expect.equal 0 ] @@ -45,12 +46,12 @@ specificLocationTests = describe "specific locations" [ test "should not skip to next line on eol" <| \() -> - { data = "x\ny", input = "x\ny", position = 1, lazyDepth = 0 } + { data = "x\ny", input = "x\ny", position = 1, lazyTracking = Dict.empty } |> Combine.currentLocation |> Expect.equal { source = "x", line = 0, column = 1 } , test "should skip to next line on eol + 1" <| \() -> - { data = "x\ny", input = "x\ny", position = 2, lazyDepth = 0 } + { data = "x\ny", input = "x\ny", position = 2, lazyTracking = Dict.empty } |> Combine.currentLocation |> Expect.equal { source = "y", line = 1, column = 0 } ] diff --git a/tests/Parsers.elm b/tests/Parsers.elm index 634aaba..bcda447 100644 --- a/tests/Parsers.elm +++ b/tests/Parsers.elm @@ -20,6 +20,7 @@ import Combine.Char , eol , space ) +import Dict import Expect import String import Test exposing (Test, describe, test) @@ -102,22 +103,22 @@ sepEndBy1Suite = \() -> Expect.equal (parse commaSep "a,a,a") - (Ok ( (), { data = "a,a,a", input = "", position = 5, lazyDepth = 0 }, [ "a", "a", "a" ] )) + (Ok ( (), { data = "a,a,a", input = "", position = 5, lazyTracking = Dict.empty }, [ "a", "a", "a" ] )) , test "sepEndBy1 2" <| \() -> Expect.equal (parse commaSep "b") - (Err ( (), { data = "b", input = "b", position = 0, lazyDepth = 0 }, [ "expected \"a\"" ] )) + (Err ( (), { data = "b", input = "b", position = 0, lazyTracking = Dict.empty }, [ "expected \"a\"" ] )) , test "sepEndBy1 3" <| \() -> Expect.equal (parse commaSep "a,a,a,") - (Ok ( (), { data = "a,a,a,", input = "", position = 6, lazyDepth = 0 }, [ "a", "a", "a" ] )) + (Ok ( (), { data = "a,a,a,", input = "", position = 6, lazyTracking = Dict.empty }, [ "a", "a", "a" ] )) , test "sepEndBy1 4" <| \() -> Expect.equal (parse commaSep "a,a,b") - (Ok ( (), { data = "a,a,b", input = "b", position = 4, lazyDepth = 0 }, [ "a", "a" ] )) + (Ok ( (), { data = "a,a,b", input = "b", position = 4, lazyTracking = Dict.empty }, [ "a", "a" ] )) ] @@ -128,20 +129,20 @@ sequenceSuite = \() -> Expect.equal (parse (sequence []) "a") - (Ok ( (), { data = "a", input = "a", position = 0, lazyDepth = 0 }, [] )) + (Ok ( (), { data = "a", input = "a", position = 0, lazyTracking = Dict.empty }, [] )) , test "one parser" <| \() -> Expect.equal (parse (sequence [ many <| string "a" ]) "aaaab") - (Ok ( (), { data = "aaaab", input = "b", position = 4, lazyDepth = 0 }, [ [ "a", "a", "a", "a" ] ] )) + (Ok ( (), { data = "aaaab", input = "b", position = 4, lazyTracking = Dict.empty }, [ [ "a", "a", "a", "a" ] ] )) , test "many parsers" <| \() -> Expect.equal (parse (sequence [ string "a", string "b", string "c" ]) "abc") - (Ok ( (), { data = "abc", input = "", position = 3, lazyDepth = 0 }, [ "a", "b", "c" ] )) + (Ok ( (), { data = "abc", input = "", position = 3, lazyTracking = Dict.empty }, [ "a", "b", "c" ] )) , test "many parsers failure" <| \() -> Expect.equal (parse (sequence [ string "a", string "b", string "c" ]) "abd") - (Err ( (), { data = "abd", input = "d", position = 2, lazyDepth = 0 }, [ "expected \"c\"" ] )) + (Err ( (), { data = "abd", input = "d", position = 2, lazyTracking = Dict.empty }, [ "expected \"c\"" ] )) ] From e4d21792567443b76822356fe794619904fe96ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 18:11:46 +0100 Subject: [PATCH 72/78] more parser functions added --- src/Combine.elm | 97 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index ca6d812..7bbdca4 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -6,7 +6,7 @@ module Combine exposing , regex, regexSub, regexWith, regexWithSub , map, onsuccess, mapError, onerror , andThen, andMap, sequence - , keep, ignore, lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets + , keep, ignore, lookAhead, notFollowedBy, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, skipUntil, upTo, chainl, chainr, count, between, parens, braces, brackets , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, modifyInput, putInput, modifyPosition, putPosition , currentLocation, currentSourceLine, currentLine, currentColumn, currentStream ) @@ -68,7 +68,7 @@ into concrete Elm values. ### Parser Combinators -@docs keep, ignore, lookAhead, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets +@docs keep, ignore, lookAhead, notFollowedBy, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, skipUntil, upTo, chainl, chainr, count, between, parens, braces, brackets ### State Combinators @@ -1082,6 +1082,35 @@ lookAhead p = err +{-| Succeed if the given parser fails, without consuming any input. +Useful for implementing keyword parsers that don't match prefixes. + + import Combine.Char exposing (alphaNum) + + keyword : String -> Parser s String + keyword kw = + string kw + |> ignore (notFollowedBy alphaNum) + + parse (keyword "if") "if x" + -- Ok "if" + + parse (keyword "if") "iffy" + -- Err ["unexpected alphanumeric character"] + +-} +notFollowedBy : Parser s a -> Parser s () +notFollowedBy p = + Parser <| + \state stream -> + case app p state stream of + ( _, _, Ok _ ) -> + ( state, stream, Err [ "unexpected input" ] ) + + ( _, _, Err _ ) -> + ( state, stream, Ok () ) + + {-| Choose between two parsers. parse (or (string "a") (string "b")) "a" @@ -1402,6 +1431,70 @@ skipMany1 p = many1 (skip p) |> onsuccess () +{-| Skip input until the given parser succeeds. +This is similar to `manyTill`, but more efficient as it doesn't +accumulate results. + + parse + (skipUntil (string "-->") |> keep (string "-->")) + "some text here-->" + -- Ok "-->" + +-} +skipUntil : Parser s end -> Parser s () +skipUntil end_ = + let + accumulate state stream = + case app end_ state stream of + ( rstate, rstream, Ok _ ) -> + ( rstate, rstream, Ok () ) + + ( estate, estream, Err _ ) -> + case String.uncons stream.input of + Just ( _, rest ) -> + accumulate state { stream | input = rest, position = stream.position + 1 } + + Nothing -> + ( estate, estream, Err [ "skipUntil: reached end of input without finding end parser" ] ) + in + Parser accumulate + + +{-| Parse at most `n` occurrences of a parser. +Similar to `many`, but with an upper limit. + + parse (upTo 3 (string "a")) "aaaaa" + -- Ok ["a", "a", "a"] + + parse (upTo 3 (string "a")) "aa" + -- Ok ["a", "a"] + + parse (upTo 3 (string "a")) "b" + -- Ok [] + +-} +upTo : Int -> Parser s a -> Parser s (List a) +upTo n p = + let + accumulate remaining acc state stream = + if remaining <= 0 then + ( state, stream, Ok (List.reverse acc) ) + + else + case app p state stream of + ( rstate, rstream, Ok res ) -> + if stream.input == rstream.input then + ( rstate, rstream, Ok (List.reverse acc) ) + + else + accumulate (remaining - 1) (res :: acc) rstate rstream + + _ -> + ( state, stream, Ok (List.reverse acc) ) + in + Parser <| \state stream -> accumulate n [] state stream + + {-| Parse one or more occurrences of `p` separated by `op`, recursively apply all functions returned by `op` to the values returned by `p`. See the `examples/Calc.elm` file for an example. From 89d4ca3c01387e92f272f2fe53a7735bb64f0c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 18:16:24 +0100 Subject: [PATCH 73/78] feat: more typical helper functions --- src/Combine.elm | 65 ++++++++++++++++++++++++++++++++++++++++++-- src/Combine/Char.elm | 29 ++++++++++++++++++-- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 7bbdca4..0c42bf5 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -6,7 +6,7 @@ module Combine exposing , regex, regexSub, regexWith, regexWithSub , map, onsuccess, mapError, onerror , andThen, andMap, sequence - , keep, ignore, lookAhead, notFollowedBy, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, skipUntil, upTo, chainl, chainr, count, between, parens, braces, brackets + , keep, ignore, lookAhead, notFollowedBy, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, skipUntil, skipWhile, atLeast, upTo, chainl, chainr, count, between, parens, braces, brackets , withState, putState, modifyState, withLocation, withLine, withColumn, withSourceLine, modifyInput, putInput, modifyPosition, putPosition , currentLocation, currentSourceLine, currentLine, currentColumn, currentStream ) @@ -68,7 +68,7 @@ into concrete Elm values. ### Parser Combinators -@docs keep, ignore, lookAhead, notFollowedBy, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, skipUntil, upTo, chainl, chainr, count, between, parens, braces, brackets +@docs keep, ignore, lookAhead, notFollowedBy, while, or, choice, optional, maybe, many, many1, manyTill, many1Till, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, skipUntil, skipWhile, atLeast, upTo, chainl, chainr, count, between, parens, braces, brackets ### State Combinators @@ -1460,6 +1460,62 @@ skipUntil end_ = Parser accumulate +{-| Skip characters while the predicate holds. +More efficient than `skipMany (satisfy pred)` as it doesn't build a list. + + import Combine.Char exposing (space) + + parse + (skipWhile ((==) ' ') |> keep (string "hello")) + " hello" + -- Ok "hello" + + parse (skipWhile Char.isDigit) "123abc" + -- Ok () + +-} +skipWhile : (Char -> Bool) -> Parser s () +skipWhile pred = + Parser <| + \state stream -> + let + skipChars input pos = + case String.uncons input of + Just ( c, rest ) -> + if pred c then + skipChars rest (pos + 1) + + else + ( input, pos ) + + Nothing -> + ( input, pos ) + + ( remainingInput, newPos ) = + skipChars stream.input stream.position + in + ( state, { stream | input = remainingInput, position = newPos }, Ok () ) + + +{-| Parse at least `n` occurrences of a parser. +Complements `upTo` for full bounded repetition control. + + parse (atLeast 2 (string "a")) "aaa" + -- Ok ["a", "a", "a"] + + parse (atLeast 2 (string "a")) "a" + -- Err ["expected \"a\""] + + parse (atLeast 0 (string "a")) "b" + -- Ok [] + +-} +atLeast : Int -> Parser s a -> Parser s (List a) +atLeast n p = + count n p + |> andThen (\initial -> many p |> map (\rest -> initial ++ rest)) + + {-| Parse at most `n` occurrences of a parser. Similar to `many`, but with an upper limit. @@ -1472,6 +1528,11 @@ Similar to `many`, but with an upper limit. parse (upTo 3 (string "a")) "b" -- Ok [] +Combine with `atLeast` for bounded repetition: + + between2And4 p = + atLeast 2 p |> andThen (\_ -> upTo 4 p) + -} upTo : Int -> Parser s a -> Parser s (List a) upTo n p = diff --git a/src/Combine/Char.elm b/src/Combine/Char.elm index 0e272f0..2c2166a 100644 --- a/src/Combine/Char.elm +++ b/src/Combine/Char.elm @@ -1,4 +1,4 @@ -module Combine.Char exposing (satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit, alpha, alphaNum) +module Combine.Char exposing (satisfy, char, anyChar, peekChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit, alpha, alphaNum) {-| This module contains `Char`-specific Parsers. @@ -10,7 +10,7 @@ much faster. # Parsers -@docs satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit, alpha, alphaNum +@docs satisfy, char, anyChar, peekChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit, alpha, alphaNum -} @@ -92,6 +92,31 @@ anyChar = satisfy (always True) |> onerror "expected any character" +{-| Peek at the next character without consuming any input. +Returns `Nothing` if at end of input. + + parse peekChar "abc" == + -- Ok (Just 'a') + + parse (peekChar |> Combine.ignore (char 'a')) "abc" == + -- Ok (Just 'a') + + parse peekChar "" == + -- Ok Nothing + +-} +peekChar : Parser s (Maybe Char) +peekChar = + primitive <| + \state stream -> + case String.uncons stream.input of + Just ( c, _ ) -> + ( state, stream, Ok (Just c) ) + + Nothing -> + ( state, stream, Ok Nothing ) + + {-| Parse a character from the given list. parse (oneOf ['a', 'b']) "a" == From 997c1546948aa480eab087a7ce68de5199590d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 18:17:16 +0100 Subject: [PATCH 74/78] feat: more typical helper functions --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index ef0c7e5..c5ee29a 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "6.0.0", + "version": "7.0.0", "exposed-modules": [ "Combine", "Combine.Char", From aee36be7ee90b2aac18a62d0c1abc70efc1b1ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 18:25:55 +0100 Subject: [PATCH 75/78] fix: lazy with tracking --- src/Combine.elm | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 0c42bf5..186407b 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -347,7 +347,7 @@ trackedLazy id maxDepth t = currentDepth = Dict.get id stream.lazyTracking |> Maybe.withDefault 0 in - if currentDepth > maxDepth then + if currentDepth >= maxDepth then ( state , stream , Err [ "infinite loop detected: lazy parser '" ++ id ++ "' exceeded depth limit of " ++ String.fromInt maxDepth ] @@ -363,20 +363,28 @@ trackedLazy id maxDepth t = in case app (t ()) state streamWithTracking of ( rstate, rstream, Ok res ) -> - -- Reset this parser's depth if input was consumed + -- Always restore depth to what it was before this call let finalTracking = - if stream.input == rstream.input then - rstream.lazyTracking + if currentDepth == 0 then + Dict.remove id rstream.lazyTracking else - Dict.remove id rstream.lazyTracking + Dict.insert id currentDepth rstream.lazyTracking in ( rstate, { rstream | lazyTracking = finalTracking }, Ok res ) ( estate, estream, Err ms ) -> - -- Reset this parser's depth on error - ( estate, { estream | lazyTracking = Dict.remove id estream.lazyTracking }, Err ms ) + -- Restore depth on error too + let + finalTracking = + if currentDepth == 0 then + Dict.remove id estream.lazyTracking + + else + Dict.insert id currentDepth estream.lazyTracking + in + ( estate, { estream | lazyTracking = finalTracking }, Err ms ) {-| Transform both the result and error message of a parser. From bf5595d41a79c18e9dc0ee60de707c9e1781b7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 18:26:17 +0100 Subject: [PATCH 76/78] version bump --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index c5ee29a..68e9a19 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "7.0.0", + "version": "7.0.1", "exposed-modules": [ "Combine", "Combine.Char", From b399d39b6e0000371fb23c347c7379ad2141ffa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 19:17:12 +0100 Subject: [PATCH 77/78] fix: eol detection in manyTill --- src/Combine.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Combine.elm b/src/Combine.elm index 186407b..96a6865 100644 --- a/src/Combine.elm +++ b/src/Combine.elm @@ -1295,8 +1295,8 @@ manyTill p end_ = else accumulate (res :: acc) rstate rstream - ( pstate, pstream, Err _ ) -> - if pstream.input == "" then + ( _, _, Err _ ) -> + if stream.input == "" then ( estate, estream, Err [ "manyTill: reached end of input without finding end parser" ] ) else From c75f42424a15cd0a082e799b0458fa2b4acc5c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Fri, 20 Feb 2026 19:17:32 +0100 Subject: [PATCH 78/78] version bump --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index 68e9a19..394b748 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "andre-dietrich/parser-combinators", "summary": "Port of the community parser combinator to elm 0.19", "license": "BSD-3-Clause", - "version": "7.0.1", + "version": "7.0.2", "exposed-modules": [ "Combine", "Combine.Char",