packages feed

atomo-0.4: src/Atomo/Lexer.hs

{-# LANGUAGE TypeSynonymInstances #-}
module Atomo.Lexer where

import Control.Monad.State
import Data.Char (isAlpha)
import Text.Parsec

import Atomo.Lexer.Base
import Atomo.Lexer.Primitive


lToken :: Lexer Token
lToken = choice
    [ lKeyword
    , lReserved
    , lOptional
    , lOptionalFlag
    , lOperator
    , lParticle
    , lPrimitive
    , lMacroQuote
    , lPunctuation
    , lEnd
    ]

lReserved :: Lexer Token
lReserved = try $ do
    n <- anyIdent
    if isReservedName n
        then return (TokReserved n)
        else fail "not reserved"

lKeyword :: Lexer Token
lKeyword = try $ do
    n <- ident
    char ':'
    return (TokKeyword n)

lOptional :: Lexer Token
lOptional = try $ do
    char '&'
    n <- ident
    char ':'
    return (TokOptional n)

lOptionalFlag :: Lexer Token
lOptionalFlag = try $ do
    char '&'
    n <- ident
    notFollowedBy (char ':')
    return (TokOptionalFlag n)

lParticle :: Lexer Token
lParticle = try $ do
    char '@'
    ks <- choice
        [ many1 . try $ do
            n <- ident
            char ':'
            return n
        , fmap (:[]) operator
        ]
    return (TokParticle ks)

lOperator :: Lexer Token
lOperator = try $ do
    o <- operator
    whiteSpace1 <|> eof <|> (lookAhead (lEnd <|> lPunctuation) >> return ())
    return (TokOperator o)

lPrimitive :: Lexer Token
lPrimitive = liftM TokPrimitive $ choice
    [ lvCharacter
    , lvString
    , try lvRational
    , try lvDouble
    , try lvInteger
    , try lvBoolean
    ]

lPunctuation :: Lexer Token
lPunctuation = choice
    [ liftM TokPunctuation (oneOf "|`'~@")
    , liftM TokOpen (oneOf "([{")
    , liftM TokClose (oneOf ")]}")
    ]

lEnd :: Lexer Token
lEnd = do
    oneOf ",;"
    return TokEnd

-- | Parse an identifier, possibly followed by a macro-quote segment.
lMacroQuote :: Lexer Token
lMacroQuote = do
    name <- ident
    choice
        [ do
            str <- delimited "({[\"$|`'~@"
            flags <- many (satisfy isAlpha)
            return (TokMacroQuote name str flags)
        , return (TokIdentifier name)
        ]
  where
    delimited ds = do
        o <- oneOf ds
        cs <- many $ choice
            [ try $ do
                char '\\'
                oneOf [o, close o]
            , noneOf [o, close o]
            ]
        char (close o)
        return cs

    close '(' = ')'
    close '{' = '}'
    close '[' = ']'
    close '<' = '>'
    close x = x

lIdentifier :: Lexer Token
lIdentifier = liftM TokIdentifier ident

lexer :: Lexer [TaggedToken]
lexer = whiteSpace >> getPosition >>= subLexer

subLexer :: SourcePos -> Lexer [TaggedToken]
subLexer start = do
    ios <- fmap lsInsideOf getState

    liftM concat $ many $ do
        i <- getPosition
        os <- fmap lsInsideOf getState
        if length os < length ios || sourceColumn i < sourceColumn start
            then fail "finished sublexer"
            else segment i

-- A sequence of tokens with a given start indentation level.
segment :: SourcePos -> Lexer [TaggedToken]
segment i = do
    t <- tagged lToken
    whiteSpace
    choice
        [ eof >> return (if tToken t == TokEnd then [t] else [t, withTag t TokEnd])
        , do
            p <- getPosition
            case tToken t of
                TokOpen o -> do
                    modifyState $ \ls -> ls { lsInsideOf = (i, o) : lsInsideOf ls }
                    n <- subLexer i
                    p' <- getPosition
                    if chainContinue i p'
                        then choice
                            [ eof >> return (t : n)
                            , do
                                ts <- segment i
                                return (t : n ++ ts)
                            ]
                        else return (t:n)
                TokClose c -> do
                    os <- fmap lsInsideOf getState
                    if not (null os) && matchWrap (snd $ head os) c
                        then modifyState $ \ls -> ls { lsInsideOf = tail os }
                        else fail $ "unmatched " ++ [c]

                    if chainContinue (fst (head os)) p || (not (chainContinue (fst (head os)) i) && chainContinue i p)
                        then return [t]
                        else return [t, withTag t TokEnd]
                _ | chainContinue i p -> do
                    ts <- segment i
                    return (t:ts)
                TokEnd -> return [t]
                _ -> return [t, withTag t TokEnd]
        ]
  where
    chainContinue o n = or
        [ sourceLine o == sourceLine n
        , sourceColumn o < sourceColumn n
        ]

matchWrap :: Char -> Char -> Bool
matchWrap '(' ')' = True
matchWrap '{' '}' = True
matchWrap '[' ']' = True
matchWrap _ _ = False

fileLexer :: Lexer [TaggedToken]
fileLexer = do
    optional (string "#!" >> manyTill anyToken (eol <|> eof))
    lexer