module Pugs.Parser.Number (
parseNatOrRat,
naturalOrRat,
signedNaturalOrRat,
) where
import Pugs.Internals
import Pugs.Rule
import Text.ParserCombinators.Parsec.Char
parseNatOrRat :: String -> Either ParseError (Either Integer (Ratio Integer))
parseNatOrRat s = runParser signedNaturalOrRat () "" s
signedNaturalOrRat :: GenParser Char st (Either Integer (Ratio Integer))
signedNaturalOrRat = do
sig <- sign
if sig then naturalOrRat else do
num <- naturalOrRat
return $ case num of
Left i -> Left (-i)
Right d -> Right (-d)
naturalOrRat :: GenParser Char st (Either Integer (Ratio Integer))
naturalOrRat = (<?> "number") $ do
try (char '0' >> zeroNumRat)
<|> decimalRat
<|> fractRatOnly
where
zeroNumRat = do
n <- hexadecimal <|> decimal <|> octalBad <|> octal <|> binary
return (Left n)
<|> decimalRat
<|> fractRat 0
<|> return (Left 0)
decimalRat = do
n <- decimalLiteral
option (Left n) (try $ fractRat n)
fractRatOnly = do
fract <- try fraction
expo <- option (1%1) expo
return (Right $ fract * expo) -- Right is Rat
fractRat n = do
fract <- try fraction
expo <- option (1%1) expo
return (Right $ ((n % 1) + fract) * expo) -- Right is Rat
<|> do
expo <- expo
if expo < 1
then return (Right $ (n % 1) * expo)
else return (Right $ (n % 1) * expo)
fraction = do
char '.'
digit <- satisfy isDigit
digits <- many (satisfy isWordDigit) <?> "fraction"
return (digitsToRat $ filter (/= '_') (digit:digits))
<?> "fraction"
where
digitsToRat d = digitsNum d % (10 ^ length d)
digitsNum d = foldl (\x y -> x * 10 + (toInteger $ digitToInt y)) 0 d
isWordDigit x = (isDigit x || x == '_')
expo :: GenParser Char st Rational
expo = do
oneOf "eE"
f <- sign
e <- decimalLiteral <?> "exponent"
return (power (if f then e else -e))
<?> "exponent"
where
power e | e < 0 = 1 % (10^abs(e))
| otherwise = (10^e) % 1
decimalLiteral = number 10
hexadecimal = do{ char 'x'; number 16 }
decimal = do{ oneOf "_d"; number 10 }
octal = do{ char 'o'; number 8 }
octalBad = do{ many1 octDigit ; fail "0100 is not octal in perl6 any more, use 0o100 instead." }
binary = do{ char 'b'; number 2 }
number base = do
d <- baseDigit base
ds <- many (baseDigit base <|>
do { char '_'; lookAhead (baseDigit base); return '_' })
let n = foldl (\x d -> base*x + b36DigitToInteger d) 0 digits
digits = (d : filter (/= '_') ds)
seq n (return n)
where
baseDigit = baseDigitInt . fromIntegral
baseDigitInt b
| b <= 10 = oneOf $ take b ['0'..'9']
| b > 10 && b <= 36 = oneOf $ ['0'..'9']
++ take (b - 10) ['a'..'z']
++ take (b - 10) ['A'..'Z']
| otherwise = error "baseDigitInt: base too large"
b36DigitToInteger = toInteger . b36DigitToInt
b36DigitToInt c
| isDigit c = fromEnum c - fromEnum '0'
| c >= 'a' && c <= 'z' = fromEnum c - fromEnum 'a' + 10
| c >= 'A' && c <= 'Z' = fromEnum c - fromEnum 'A' + 10
| otherwise = error "b36DigitToInt: not a base 36 digit"
sign :: GenParser Char st Bool
sign = (char '-' >> return False)
<|> (char '+' >> return True)
<|> return True