Skip to content

Commit a2b22e7

Browse files
committed
Improve String.Basic.number, String.Basic.intDecimal
1 parent c8e3fa3 commit a2b22e7

File tree

2 files changed

+71
-45
lines changed

2 files changed

+71
-45
lines changed

src/Parsing/String/Basic.purs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,20 @@ import Prelude
2222

2323
import Data.Array (elem, notElem)
2424
import Data.CodePoint.Unicode (isAlpha, isAlphaNum, isDecDigit, isHexDigit, isLower, isOctDigit, isSpace, isUpper)
25-
import Data.Either (Either(..))
25+
import Data.Either (Either(..), either)
2626
import Data.Int as Data.Int
2727
import Data.Maybe (Maybe(..))
2828
import Data.Number (infinity, nan)
2929
import Data.Number as Data.Number
3030
import Data.String (CodePoint, singleton, takeWhile)
3131
import Data.String.CodePoints (codePointFromChar)
3232
import Data.String.CodeUnits as SCU
33-
import Data.Tuple (Tuple(..), fst)
33+
import Data.Tuple (fst)
3434
import Parsing (ParserT, fail)
35-
import Parsing.Combinators (choice, skipMany, (<?>), (<~?>))
36-
import Parsing.String (consumeWith, match, satisfy, satisfyCodePoint)
35+
import Parsing.Combinators (choice, tryRethrow, (<?>), (<|>), (<~?>))
36+
import Parsing.String (consumeWith, match, regex, satisfy, satisfyCodePoint)
3737
import Parsing.String as Parser.String
38+
import Partial.Unsafe (unsafeCrashWith)
3839

3940
-- | Parse a digit. Matches any char that satisfies `Data.CodePoint.Unicode.isDecDigit`.
4041
digit :: forall m. ParserT String m Char
@@ -84,26 +85,24 @@ alphaNum = satisfyCP isAlphaNum <?> "letter or digit"
8485
-- | * `"NaN"`
8586
-- | * `"-Infinity"`
8687
number :: forall m. ParserT String m Number
87-
-- TODO because the JavaScript parseFloat function will successfully parse
88-
-- a Number up until it doesn't understand something and then return
89-
-- the partially parsed Number, this parser will sometimes consume more
90-
-- String that it actually parses. Example "1..3" will parse as 1.0.
91-
-- So this needs improvement.
9288
number =
9389
choice
94-
[ Parser.String.string "Infinity" *> pure infinity
95-
, Parser.String.string "+Infinity" *> pure infinity
96-
, Parser.String.string "-Infinity" *> pure (negate infinity)
97-
, Parser.String.string "NaN" *> pure nan
98-
, do
99-
Tuple section _ <- Parser.String.match do
100-
_ <- oneOf [ '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]
101-
skipMany $ oneOf [ 'e', 'E', '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]
90+
[ tryRethrow do
91+
section <- numberRegex
10292
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat
10393
case Data.Number.fromString section of
104-
Nothing -> fail $ "Could not parse Number " <> section
94+
Nothing -> fail $ "Bug in number parser"
10595
Just x -> pure x
106-
]
96+
, Parser.String.string "Infinity" *> pure infinity
97+
, Parser.String.string "+Infinity" *> pure infinity
98+
, Parser.String.string "-Infinity" *> pure (negate infinity)
99+
, Parser.String.string "NaN" *> pure nan
100+
] <|> fail "Expected Number"
101+
102+
numberRegex :: forall m. ParserT String m String
103+
numberRegex = either (\errm -> unsafeCrashWith errm) identity $ regex pattern mempty
104+
where
105+
pattern = "[+-]?[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]*(\\.[0-9]*))?"
107106

108107
-- | Parser based on the __Data.Int.fromString__ function.
109108
-- |
@@ -114,14 +113,17 @@ number =
114113
-- | * `"-3"`
115114
-- | * `"+300"`
116115
intDecimal :: forall m. ParserT String m Int
117-
intDecimal = do
118-
Tuple section _ <- Parser.String.match do
119-
_ <- oneOf [ '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]
120-
skipMany $ oneOf [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]
116+
intDecimal = tryRethrow do
117+
section <- intDecimalRegex <|> fail "Expected Int"
121118
case Data.Int.fromString section of
122-
Nothing -> fail $ "Could not parse Int " <> section
119+
Nothing -> fail $ "Bug in intDecimal parser"
123120
Just x -> pure x
124121

122+
intDecimalRegex :: forall m. ParserT String m String
123+
intDecimalRegex = either (\errm -> unsafeCrashWith errm) identity $ regex pattern mempty
124+
where
125+
pattern = "[+-]?[0-9]*"
126+
125127
-- | Helper function
126128
satisfyCP :: forall m. (CodePoint -> Boolean) -> ParserT String m Char
127129
satisfyCP p = Parser.String.satisfy (p <<< codePointFromChar)

test/Main.purs

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ import Control.Lazy (fix)
1212
import Control.Monad.State (State, modify, runState)
1313
import Data.Array (some, toUnfoldable)
1414
import Data.Array as Array
15-
import Data.Bifunctor (rmap)
16-
import Data.Either (Either(..), hush)
15+
import Data.Bifunctor (lmap, rmap)
16+
import Data.Either (Either(..), either, hush)
1717
import Data.Foldable (oneOf)
1818
import Data.List (List(..), fromFoldable, (:))
1919
import Data.List.NonEmpty (NonEmptyList(..), catMaybes, cons, cons')
2020
import Data.List.NonEmpty as NE
2121
import Data.Maybe (Maybe(..), fromJust)
2222
import Data.NonEmpty ((:|))
23-
import Data.Number (infinity, isNaN)
23+
import Data.Number (infinity, nan)
24+
import Data.Number as Data.Number
2425
import Data.String (toUpper)
2526
import Data.String.CodePoints as SCP
2627
import Data.String.CodeUnits (fromCharArray, singleton)
@@ -685,19 +686,52 @@ main = do
685686

686687
log "\nTESTS number\n"
687688

688-
parseTest "Infinity" infinity number
689-
parseTest "+Infinity" infinity number
690-
parseTest "-Infinity" (negate infinity) number
691-
parseErrorTestPosition number "+xxx" (mkPos 2)
689+
-- assert' "Number.fromString" $ Just infinity == Data.Number.fromString "Infinity"
690+
assertEqual' "number Infinity"
691+
{ actual: runParser "Infinity" number
692+
, expected: Right infinity
693+
}
694+
assertEqual' "number +Infinity"
695+
{ actual: runParser "+Infinity" number
696+
, expected: Right infinity
697+
}
698+
assertEqual' "number -Infinity"
699+
{ actual: runParser "-Infinity" number
700+
, expected: Right (negate infinity)
701+
}
702+
assertEqual' "number +xxx"
703+
{ actual: lmap parseErrorPosition $ runParser "+xxx" number
704+
, expected: Left $ Position { index: 0, line: 1, column: 1 }
705+
}
692706

693-
parseTest "-3.0E-1.0" (-0.3) number
707+
assertEqual' "number 1"
708+
{ actual: runParser "-3.0E-1.0" number
709+
, expected: Right (-0.3)
710+
}
694711

695712
-- test from issue #73
696-
parseTest "0.7531531167929774" 0.7531531167929774 number
713+
assertEqual' "number 2"
714+
{ actual: runParser "0.7531531167929774" number
715+
, expected: Right 0.7531531167929774
716+
}
697717

698718
-- test from issue #115
699-
parseTest "-6.0" (-6.0) number
700-
parseTest "+6.0" (6.0) number
719+
assertEqual' "number 3"
720+
{ actual: runParser "-6.0" number
721+
, expected: Right (-6.0)
722+
}
723+
assertEqual' "number 4"
724+
{ actual: runParser "+6.0" number
725+
, expected: Right (6.0)
726+
}
727+
728+
assert' "number NaN 1" $ either (\_ -> false) Data.Number.isNaN (runParser (show nan) number)
729+
assert' "number NaN 2" $ either (\_ -> false) Data.Number.isNaN (runParser "NaN" number)
730+
731+
assertEqual' "number 5"
732+
{ actual: runParser "1..3" number
733+
, expected: Right (1.0)
734+
}
701735

702736
log "\nTESTS Operator\n"
703737
-- test from issue #161
@@ -758,16 +792,6 @@ main = do
758792
"c"
759793
"Expected \"b\""
760794

761-
-- we can't test "NaN" with `parseTest` because nan doesn't compare equal
762-
case runParser "NaN" number of
763-
Right actual -> do
764-
assert' ("expected: NaN, actual: " <> show actual) (isNaN actual)
765-
logShow actual
766-
Left err -> assert' ("error: " <> show err) false
767-
768-
-- TODO This shows the current limitations of the number parser. Ideally this parse should fail.
769-
parseTest "1..3" 1.0 $ number <* eof
770-
771795
log "\nTESTS intDecimal\n"
772796
parseTest "-300" (-300) intDecimal
773797

0 commit comments

Comments
 (0)