ddc-core-0.4.3.1: DDC/Core/Lexer/Token/Literal.hs
module DDC.Core.Lexer.Token.Literal
( Literal (..)
, scanLiteral
, isLitName
, isLitStart
, isLitBody
, readLitInteger
, readLitNat
, readLitInt
, readLitSize
, readLitWordOfBits
, readLitFloatOfBits
, readBinary
, readHex)
where
import DDC.Core.Exp.Literal
import Text.Lexer.Inchworm.Char
import qualified Data.Char as Char
import qualified Data.List as List
import qualified Data.Text as Text
-- Literal --------------------------------------------------------------------
scanLiteral :: Scanner IO Location [Char] (Location, (Literal, Bool))
scanLiteral
= alts [ munchPred Nothing matchLiteral acceptLiteral
-- Character literals with Haskell style escape codes.
, do (loc, c) <- scanHaskellChar
alts [ do _ <- satisfies (\c' -> c' == '#')
return (loc, (LChar c, True))
, do return (loc, (LChar c, False)) ]
-- String literals with Haskell style escape codes.
, do (loc, str) <- scanHaskellString
alts [ do _ <- satisfies (\c -> c == '#')
return (loc, (LString (Text.pack str), True))
, do return (loc, (LString (Text.pack str), False))
]
]
-- | Match a literal character.
matchLiteral :: Int -> Char -> Bool
matchLiteral 0 c = isLitStart c
matchLiteral _ c = isLitBody c
-- | Accept a literal.
acceptLiteral :: String -> Maybe (Literal, Bool)
acceptLiteral str
| ('#' : str') <- reverse str
, Just lit <- acceptLit (reverse str')
= Just (lit, True)
| Just lit <- acceptLit str
= Just (lit, False)
| otherwise
= Nothing
where acceptLit str'
| Just i <- readLitNat str' = Just (LNat i)
| Just i <- readLitInt str' = Just (LInt i)
| Just i <- readLitSize str' = Just (LSize i)
| Just (u, b) <- readLitWordOfBits str' = Just (LWord u b)
| Just (f, b) <- readLitFloatOfBits str' = Just (LFloat f b)
| otherwise = Nothing
-------------------------------------------------------------------------------
-- | String is the name of a literal.
isLitName :: String -> Bool
isLitName str
= case str of
[] -> False
c : cs
| isLitStart c
, and (map isLitBody cs)
-> True
| otherwise
-> False
-- | Character can start a literal.
isLitStart :: Char -> Bool
isLitStart c
= Char.isDigit c
|| c == '-'
-- | Character can be part of a literal body.
isLitBody :: Char -> Bool
isLitBody c
= Char.isDigit c
|| c == 'b' || c == 'o' || c == 'x'
|| c == 'w' || c == 'f' || c == 'i' || c == 's'
|| c == '.'
|| c == '#'
|| c == '\''
-------------------------------------------------------------------------------
-- | Read a signed integer.
readLitInteger :: String -> Maybe Integer
readLitInteger [] = Nothing
readLitInteger str@(c:cs)
| '-' <- c
, all Char.isDigit cs
= Just $ read str
| all Char.isDigit cs
= Just $ read str
| otherwise
= Nothing
-- | Read an integer with an explicit format specifier like @1234i@.
readLitNat :: String -> Maybe Integer
readLitNat str1
| (ds, "") <- List.span Char.isDigit str1
, not $ null ds
= Just $ read ds
| otherwise
= Nothing
-- | Read an integer literal with an explicit format specifier like @1234i@.
readLitInt :: String -> Maybe Integer
readLitInt str1
| '-' : str2 <- str1
, (ds, "i") <- List.span Char.isDigit str2
, not $ null ds
= Just $ negate $ read ds
| (ds, "i") <- List.span Char.isDigit str1
, not $ null ds
= Just $ read ds
| otherwise
= Nothing
-- | Read an size literal with an explicit format specifier like @1234s@.
readLitSize :: String -> Maybe Integer
readLitSize str1
| '-' : str2 <- str1
, (ds, "s") <- List.span Char.isDigit str2
, not $ null ds
= Just $ negate $ read ds
| (ds, "s") <- List.span Char.isDigit str1
, not $ null ds
= Just $ read ds
| otherwise
= Nothing
-- | Read a word with an explicit format speficier.
readLitWordOfBits :: String -> Maybe (Integer, Int)
readLitWordOfBits str1
-- binary like 0b01001w32
| Just str2 <- List.stripPrefix "0b" str1
, (ds, str3) <- List.span (\c -> c == '0' || c == '1') str2
, not $ null ds
, Just str4 <- List.stripPrefix "w" str3
, (bs, "") <- List.span Char.isDigit str4
, not $ null bs
, bits <- read bs
, length ds <= bits
= Just (readBinary ds, bits)
-- hex like 0x0ffw32
| Just str2 <- List.stripPrefix "0x" str1
, (ds, str3) <- List.span (\c -> elem c ['0' .. '9']
|| elem c ['A' .. 'F']
|| elem c ['a' .. 'f']) str2
, not $ null ds
, Just str4 <- List.stripPrefix "w" str3
, (bs, "") <- List.span Char.isDigit str4
, not $ null bs
, bits <- read bs
, length ds <= bits
= Just (readHex ds, bits)
-- decimal like 1234w32
| (ds, str2) <- List.span Char.isDigit str1
, not $ null ds
, Just str3 <- List.stripPrefix "w" str2
, (bs, "") <- List.span Char.isDigit str3
, not $ null bs
= Just (read ds, read bs)
| otherwise
= Nothing
-- | Read a float literal with an explicit format specifier like @123.00f32#@.
readLitFloatOfBits :: String -> Maybe (Double, Int)
readLitFloatOfBits str1
| '-' : str2 <- str1
, Just (d, bs) <- readLitFloatOfBits str2
= Just (negate d, bs)
| (ds1, str2) <- List.span Char.isDigit str1
, not $ null ds1
, Just str3 <- List.stripPrefix "." str2
, (ds2, str4) <- List.span Char.isDigit str3
, not $ null ds2
, Just str5 <- List.stripPrefix "f" str4
, (bs, "") <- List.span Char.isDigit str5
, not $ null bs
= Just (read (ds1 ++ "." ++ ds2), read bs)
| otherwise
= Nothing
-- | Read a binary string as a number.
readBinary :: Num a => String -> a
readBinary digits
= List.foldl' (\acc b -> if b then 2 * acc + 1 else 2 * acc) 0
$ map (/= '0') digits
-- | Read a hex string as a number.
readHex :: (Enum a, Num a) => String -> a
readHex digits
= List.foldl' (\acc d -> let Just v = lookup d table
in 16 * acc + v) 0
$ digits
where table
= zip ['0' .. '9'] [0 .. 9]
++ zip ['a' .. 'f'] [10 .. 15]
++ zip ['A' .. 'F'] [10 .. 15]