packages feed

atomo-0.4: src/Atomo/Lexer/Base.hs

module Atomo.Lexer.Base where

import Control.Monad.Identity
import Data.Char
import Data.List (nub, sort)
import Text.Parsec
import qualified Text.Parsec.Token as P

import Atomo.Types (Value)


data LexerState =
    LexerState
        { lsInsideOf :: [(SourcePos, Char)]
        }

data Token
    = TokKeyword String
    | TokOptional String
    | TokOptionalFlag String
    | TokOperator String
    | TokMacroQuote String String [Char]
    | TokIdentifier String
    | TokParticle [String]
    | TokPrimitive Value
    | TokPunctuation Char
    | TokOpen Char
    | TokClose Char
    | TokReserved String
    | TokEnd
    deriving (Eq, Show)

data TaggedToken =
    TaggedToken
        { tToken :: Token
        , tLocation :: SourcePos
        }
    deriving (Eq, Show)

type Lexer = ParsecT String LexerState Identity


isOpLetter :: Char -> Bool
isOpLetter c = c `elem` "!@#%&*-./\\?:" || (c `notElem` "`" && isSymbol c)

isOperator :: String -> Bool
isOperator "" = False
isOperator cs = head cs `notElem` "@$~" && all isOpLetter cs

def :: P.GenLanguageDef String u Identity
def = P.LanguageDef
    { P.commentStart = "{-"
    , P.commentEnd = "-}"
    , P.commentLine = "--"
    , P.nestedComments = True
    , P.identStart = satisfy (\c -> c == '_' || isLetter c || (c `notElem` "&@$~:" && isOpLetter c))
    , P.identLetter = satisfy (\c -> c == '_' || isAlphaNum c || (c /= ':' && isOpLetter c))
    , P.opStart = satisfy (\c -> c `notElem` "@$~" && isOpLetter c)
    , P.opLetter = satisfy isOpLetter
    , P.reservedOpNames = [",", "|"]
    , P.reservedNames = ["operator", "macro", "for-macro", "this"]
    , P.caseSensitive = True
    }

eol :: ParsecT String u Identity ()
eol = newline >> return ()

anyIdent :: ParsecT String u Identity String
anyIdent = try $ do
    c <- P.identStart def
    cs <- many (P.identLetter def)
    if isOperator (c:cs)
        then unexpected "operator"
        else do

    return (c:cs)

ident :: ParsecT String u Identity String
ident = do
    name <- anyIdent
    if isReservedName name
        then unexpected ("reserved word " ++ show name)
        else return name

lexeme :: ParsecT String u Identity a -> ParsecT String u Identity a
lexeme l = do
    r <- l
    whiteSpace
    return r

symbol :: String -> ParsecT String u Identity String
symbol = lexeme . string

identifier :: ParsecT String u Identity String
identifier = lexeme ident

operator :: ParsecT String u Identity String
operator = try $ do
    c <- P.opStart def
    cs <- many (P.opLetter def)
    if (c:cs) `elem` P.reservedOpNames def
        then unexpected ("reserved operator " ++ show (c:cs))
        else return (c:cs)

reserved :: String -> ParsecT String u Identity ()
reserved n = try $ do
    string n
    notFollowedBy (P.identLetter def) <?> "end of " ++ show n

integer :: ParsecT String u Identity Integer
integer = do
    f <- sign
    n <- natural
    return (f n)

float :: ParsecT String u Identity Double
float = do
    f <- sign
    n <- floating
    return (f n)

natural :: ParsecT String u Identity Integer
natural = zeroNumber <|> decimal

stringLiteral :: ParsecT String u Identity String
stringLiteral = do
    str <-
        between
            (char '"')
            (char '"' <?> "end of string")
            (many stringChar)

    return (foldr (maybe id (:)) "" str)

charLiteral :: ParsecT String u Identity Char
charLiteral = do
    char '$'
    charEscape <|> charLetter
    <?> "literal character"

tagged :: ParsecT String u Identity Token -> ParsecT String u Identity TaggedToken
tagged p = do
    pos <- getPosition
    r <- p
    return (TaggedToken r pos)

withTag :: TaggedToken -> Token -> TaggedToken
withTag tt t = tt { tToken = t }

isReservedName :: String -> Bool
isReservedName name = isReserved reservedNames name
  where
    reservedNames = sort (P.reservedNames def)

isReserved :: [String] -> String -> Bool
isReserved names name
    = scan names
    where
        scan [] = False
        scan (r:rs) =
            case compare r name of
                LT  -> scan rs
                EQ  -> True
                GT  -> False


-----------------------------------------------------------------------------
-- Numeric ------------------------------------------------------------------
-----------------------------------------------------------------------------

decimal :: ParsecT String u Identity Integer
decimal = number 10 digit

number :: Integer -> ParsecT String u Identity Char -> ParsecT String u Identity Integer
number base baseDigit = do
    digits <- many1 baseDigit
    let n = foldl (\x d -> base * x + toInteger (digitToInt d)) 0 digits
    n `seq` (return n)

zeroNumber :: ParsecT String u Identity Integer
zeroNumber = do
    char '0'
    hexadecimal <|> octal <|> decimal <|> return 0
    <?> "zeroNumber"

hexadecimal :: ParsecT String u Identity Integer
hexadecimal = oneOf "xX" >> number 16 hexDigit

octal :: ParsecT String u Identity Integer
octal = oneOf "oO" >> number 8 octDigit

floating :: ParsecT String u Identity Double
floating = do
    n <- decimal
    fractExponent n

fractExponent :: Integer -> ParsecT String u Identity Double
fractExponent n = choice
    [ do
        fract <- fraction
        expo  <- option 1.0 exponent'
        return ((fromInteger n + fract) * expo)
    , do
        expo <- exponent'
        return (fromInteger n * expo)
    ]

fraction :: ParsecT String u Identity Double
fraction = do
    char '.'
    digits <- many1 digit <?> "fraction"
    return (foldr op 0.0 digits)
    <?> "fraction"
    where
    op d f  = (f + fromIntegral (digitToInt d)) / 10.0

exponent' :: ParsecT String u Identity Double
exponent' = do
    oneOf "eE"
    f <- sign
    e <- decimal <?> "exponent"
    return (power (f e))
    <?> "exponent"
    where
    power e  | e < 0      = 1.0 / power(-e)
            | otherwise  = fromInteger (10 ^ e)

sign :: Num a => ParsecT String u Identity (a -> a)
sign = choice
    [ char '-' >> return negate
    , char '+' >> return id
    , return id
    ]

-----------------------------------------------------------------------------
-- Whitespace & Comments ----------------------------------------------------
-----------------------------------------------------------------------------

whiteSpace :: ParsecT String u Identity ()
whiteSpace = do
    spacing
    skipMany (try $ spacing >> newline)
    spacing

whiteSpace1 :: ParsecT String u Identity ()
whiteSpace1 = (space <|> newline) >> whiteSpace

simpleSpace :: ParsecT String u Identity ()
simpleSpace = skipMany1 $ satisfy (`elem` " \t\f\v\xa0")

spacing :: ParsecT String u Identity ()
spacing = skipMany spacing1

spacing1 :: ParsecT String u Identity ()
spacing1 = choice
    [ simpleSpace
    , oneLineComment
    , multiLineComment
    ]
    <?> "whitespace or commend"

oneLineComment :: ParsecT String u Identity ()
oneLineComment = do
    try (string (P.commentLine def))
    skipMany (satisfy (/= '\n'))

multiLineComment :: ParsecT String u Identity ()
multiLineComment = do
    try (string (P.commentStart def))
    inComment

inComment :: ParsecT String u Identity ()
inComment = choice
    [ try (string (P.commentEnd def)) >> return ()
    , multiLineComment >> inComment
    , skipMany1 (noneOf startEnd) >> inComment
    , oneOf startEnd >> inComment
    ]
    <?> "end of comment"
  where
    startEnd = nub (P.commentEnd def ++ P.commentStart def)


-----------------------------------------------------------------------------
-- Character Escaping -------------------------------------------------------
-----------------------------------------------------------------------------

charEscape :: ParsecT String u Identity Char
charEscape = char '\\' >> escapeCode

charLetter :: ParsecT String u Identity Char
charLetter = satisfy (\c -> (c /= '\\') && (c > '\026'))

stringChar :: ParsecT String u Identity (Maybe Char)
stringChar = choice
    [ fmap Just stringLetter
    , stringEscape
    ]
    <?> "string character"

stringLetter :: ParsecT String u Identity Char
stringLetter = satisfy (`notElem` "\"\\")

stringEscape :: ParsecT String u Identity (Maybe Char)
stringEscape = char '\\' >> choice
    [ escapeGap >> return Nothing
    , escapeEmpty >> return Nothing
    , fmap Just escapeCode
    ]

escapeEmpty :: ParsecT String u Identity Char
escapeEmpty = char '&'

escapeGap :: ParsecT String u Identity Char
escapeGap = do
    many1 space
    char '\\' <?> "end of string gap"

escMap :: [(Char, Char)]
escMap = zip "abfnrtv\\\"" "\a\b\f\n\r\t\v\\\""

asciiMap :: [(String, Char)]
asciiMap = zip (ascii3codes ++ ascii2codes) (ascii3 ++ ascii2)

ascii2codes :: [String]
ascii2codes = [ "BS","HT","LF","VT","FF","CR","SO","SI","EM",
                "FS","GS","RS","US","SP" ]

ascii3codes :: [String]
ascii3codes = [ "NUL","SOH","STX","ETX","EOT","ENQ","ACK","BEL",
                "DLE","DC1","DC2","DC3","DC4","NAK","SYN","ETB",
                "CAN","SUB","ESC","DEL" ]

ascii2 :: [Char]
ascii2 = "\b\t\n\v\f\r\SO\SI\EM\FS\GS\RS\US "

ascii3 :: [Char]
ascii3 = "\NUL\SOH\STX\ETX\EOT\ENQ\ACK\a\DLE\DC1\DC2\DC3\DC4\NAK\SYN\ETB\CAN\SUB\ESC\DEL"

escapeCode :: ParsecT String u Identity Char
escapeCode = charEsc <|> charNum <|> charAscii <|> charControl
    <?> "escape code"

charControl :: ParsecT String u Identity Char
charControl = do
    char '^'
    code <- upper
    return (toEnum (fromEnum code - fromEnum 'A'))

charNum :: ParsecT String u Identity Char
charNum = do
    code <- choice
        [ decimal
        , char 'o' >> number 8 octDigit
        , char 'x' >> number 16 hexDigit
        ]

    return (toEnum (fromInteger code))

charEsc :: ParsecT String u Identity Char
charEsc = choice (map parseEsc escMap)
  where
    parseEsc (c, code) = char c >> return code

charAscii :: ParsecT String u Identity Char
charAscii = choice (map parseAscii asciiMap)
  where
    parseAscii (asc, code) = try (string asc >> return code)