1-- This parser for Scheme numbers has been copied from Husk Scheme.2-- https://github.com/justinethier/husk-scheme/blob/a39da0b385597264d3e5e11c09a907eef3d3db42/hs-src/Language/Scheme/Parser.hs3--4-- TODO: Write a custom parser for Scheme numbers which is closer to the5-- R7RS formal specification provided in section 7.1 (Formal Syntax).6--7-- Copyright (c) 2010 Justin Ethier8--9-- Permission is hereby granted, free of charge, to any person obtaining a copy10-- of this software and associated documentation files (the "Software"), to deal11-- in the Software without restriction, including without limitation the rights12-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell13-- copies of the Software, and to permit persons to whom the Software is14-- furnished to do so, subject to the following conditions:15--16-- The above copyright notice and this permission notice shall be included in17-- all copies or substantial portions of the Software.18--19-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR20-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,21-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE22-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER23-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,24-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN25-- THE SOFTWARE.2627module SchemeDoc.Parser.Number (number) where2829import Data.Complex30import Data.Ratio31import Numeric32import SchemeDoc.Types33import Text.ParserCombinators.Parsec3435import qualified Data.Char as DC3637-- Parse an integer in octal notation, base 838parseOctalNumber :: Parser Sexp39parseOctalNumber = do40 _ <- try (string "#o")41 sign <- many (oneOf "-")42 num <- many1 (oneOf "01234567")43 case (length sign) of44 0 -> return $ Number $ fst $ head (Numeric.readOct num)45 1 -> return $ Number $ fromInteger $ (*) (-1) $ fst $ head (Numeric.readOct num)46 _ -> pzero4748-- Parse an integer in binary notation, base 249parseBinaryNumber :: Parser Sexp50parseBinaryNumber = do51 _ <- try (string "#b")52 sign <- many (oneOf "-")53 num <- many1 (oneOf "01")54 case (length sign) of55 0 -> return $ Number $ fst $ head (Numeric.readInt 2 (`elem` "01") DC.digitToInt num)56 1 -> return $ Number $ fromInteger $ (*) (-1) $ fst $ head (Numeric.readInt 2 (`elem` "01") DC.digitToInt num)57 _ -> pzero5859-- Parse an integer in hexadecimal notation, base 1660parseHexNumber :: Parser Sexp61parseHexNumber = do62 _ <- try (string "#x")63 sign <- many (oneOf "-")64 num <- many1 (digit <|> oneOf "abcdefABCDEF")65 case (length sign) of66 0 -> return $ Number $ fst $ head (Numeric.readHex num)67 1 -> return $ Number $ fromInteger $ (*) (-1) $ fst $ head (Numeric.readHex num)68 _ -> pzero6970-- Parser for Integer, base 1071parseDecimalNumber :: Parser Sexp72parseDecimalNumber = do73 _ <- try (many (string "#d"))74 sign <- many (oneOf "-")75 num <- many1 digit76 if (length sign) > 177 then pzero78 else return $ (Number . read) $ sign ++ num7980-- Parser for a base 10 Integer that will also81-- check to see if the number is followed by82-- an exponent (scientific notation). If so,83-- the integer is converted to a float of the84-- given magnitude.85parseDecimalNumberMaybeExponent :: Parser Sexp86parseDecimalNumberMaybeExponent = do87 num <- parseDecimalNumber88 parseNumberExponent num8990-- Parse an integer in any base91parseNumber :: Parser Sexp92parseNumber =93 parseDecimalNumberMaybeExponent94 <|> parseHexNumber95 <|> parseBinaryNumber96 <|> parseOctalNumber97 <?> "Unable to parse number"9899-- Parse a floating point number100parseRealNumber :: Parser Sexp101parseRealNumber = do102 sign <- many (oneOf "-+")103 num <- many digit104 _ <- char '.'105 frac <- many1 digit106 let dec =107 if not (null num)108 then num ++ "." ++ frac109 else "0." ++ frac110 f <- case (length sign) of111 0 -> return $ Float $ fst $ head (Numeric.readFloat dec)112 -- Bit of a hack, but need to support the + sign as well as the minus.113 1 ->114 if sign == "-"115 then return $ Float $ (*) (-1.0) $ fst $ head (Numeric.readFloat dec)116 else return $ Float $ fst $ head (Numeric.readFloat dec)117 _ -> pzero118 parseNumberExponent f119120-- Parse the exponent section of a floating point number121-- in scientific notation. Eg "e10" from "1.0e10"122parseNumberExponent :: Sexp -> Parser Sexp123parseNumberExponent n = do124 expnt <- many $ oneOf "Ee"125 case (length expnt) of126 0 -> return n127 1 -> do128 num <- try parseDecimalNumber129 case num of130 Number nexp -> buildResult n nexp131 _ -> pzero132 _ -> pzero133 where134 buildResult (Number num) nexp = return $ Float $ (fromIntegral num) * (10 ** (fromIntegral nexp))135 buildResult (Float num) nexp = return $ Float $ num * (10 ** (fromIntegral nexp))136 buildResult _ _ = pzero137138-- Parse a rational number139parseRationalNumber :: Parser Sexp140parseRationalNumber = do141 pnumerator <- parseDecimalNumber142 case pnumerator of143 Number n -> do144 _ <- char '/'145 sign <- many (oneOf "-")146 num <- many1 digit147 if (length sign) > 1148 then pzero149 else do150 let pdenominator = read $ sign ++ num151 if pdenominator == 0152 then return $ Number 0 -- TODO: Prevents a div-by-zero error, but not really correct either153 else return $ Rational $ n % pdenominator154 _ -> pzero155156-- Parse a complex number157parseComplexNumber :: Parser Sexp158parseComplexNumber = do159 lispreal <- (try parseRealNumber <|> try parseRationalNumber <|> parseDecimalNumber)160 let real = case lispreal of161 Number n -> fromInteger n162 Rational r -> fromRational r163 Float f -> f164 _ -> 0165 _ <- char '+'166 lispimag <- (try parseRealNumber <|> try parseRationalNumber <|> parseDecimalNumber)167 let imag = case lispimag of168 Number n -> fromInteger n169 Rational r -> fromRational r170 Float f -> f171 _ -> 0 -- Case should never be reached172 _ <- char 'i'173 return $ Complex $ real :+ imag174175-- Parse a number176number :: Parser Sexp177number =178 try parseComplexNumber179 <|> try parseRationalNumber180 <|> try parseRealNumber181 <|> try parseNumber