packages feed

hoq-0.2: src/Syntax/Parser/Lexer.x

{
module Syntax.Parser.Lexer
    ( alexScanTokens
    , Token(..), tokGetPos
    , Parser, ParserErr
    , Layout(..)
    ) where

import Data.Word
import Data.Char(isAlpha,isSpace)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C
import Control.Monad.State

import TypeChecking.Monad.Warn
import Syntax
}

$alpha      = [a-zA-Z]
$digit      = [0-9]
$any        = [\x00-\x10ffff]
$operator   = [\~\!\@\#\$\%\^\&\*\-\+\=\|\?\<\>\,\.\/\:\;\[\]\{\}]
@ident      = ($alpha | \_) ($alpha | $digit | \' | \- | \_)*
@lcomm      = "--".*
@mcomm      = "{-" ([$any # \-] | \- [$any # \}])* "-}"
@skip       = $white | @lcomm | @mcomm
@newline    = \n @skip*
@with       = "with" @skip*
@of         = "of" @skip*
@import     = "import" $white+ @ident ("." @ident)*

:-

[$white # \n]+;
@lcomm;
@mcomm;

@import         { \_ s -> TokImport $ breaks '.' $
                    dropWhile (\c -> not (isAlpha c) && c /= '_') (drop 6 s)        }
"data"          { \_ _ -> TokData                                                   }
"case"          { \p _ -> TokCase p                                                 }
@of             { \_ _ -> TokOf 0                                                   }
@with           { \_ _ -> TokWith 0                                                 }
\\              { \p _ -> TokLam p                                                  }
\(              { \p _ -> TokLParen p                                               }
\:              { \_ _ -> TokColon                                                  }
\=              { \_ _ -> TokEquals                                                 }
\{              { \p _ -> TokLBrace p                                               }
\}              { \_ _ -> TokRBrace                                                 }
\;              { \_ _ -> TokSemicolon                                              }
\)              { \_ _ -> TokRParen                                                 }
\|              { \_ _ -> TokPipe                                                   }
\@              { \_ _ -> TokAt                                                     }
\`              { \_ _ -> TokApos                                                   }
"->"            { \_ _ -> TokArrow                                                  }
$operator+      { \p s -> TokOperator (PIdent p s)                                  }
$digit+         { \p s -> TokInteger (p, read s)                                    }
"infixl"        { \p _ -> TokInfix (p, InfixL)                                      }
"infixr"        { \p _ -> TokInfix (p, InfixR)                                      }
"infix"         { \p _ -> TokInfix (p, InfixNA)                                     }
@ident          { \p s -> TokIdent (PIdent p s)                                     }
@newline        { \_ _ -> TokNewLine                                                }

{
data Token
    = TokIdent !PIdent
    | TokOperator !PIdent
    | TokLam !Posn
    | TokLParen !Posn
    | TokImport ![String]
    | TokData
    | TokCase !Posn
    | TokOf !Int
    | TokColon
    | TokEquals
    | TokLBrace !Posn
    | TokRBrace
    | TokSemicolon
    | TokDot
    | TokRParen
    | TokPipe
    | TokAt
    | TokApos
    | TokArrow
    | TokWith !Int
    | TokNewLine
    | TokInfix !(Posn, Infix)
    | TokInteger !(Posn, Int)
    | TokEOF

tokGetPos :: Token -> Maybe Posn
tokGetPos (TokIdent (PIdent pos _)) = Just pos
tokGetPos (TokLam pos) = Just pos
tokGetPos (TokLParen pos) = Just pos
tokGetPos (TokOperator (PIdent pos _)) = Just pos
tokGetPos _ = Nothing

breaks :: Eq a => a -> [a] -> [[a]]
breaks a as = case break (== a) as of
    (as1, [])    -> [as1]
    (as1, _:as2) -> as1 : breaks a as2

type AlexInput = (Posn, B.ByteString)

data Layout = Layout Int | NoLayout deriving Eq

type ParserErr a = WarnT [(Posn, String)] (State [Layout]) a
type Parser a = AlexInput -> ParserErr a

alexScanTokens :: (Token -> Parser a) -> Parser a
alexScanTokens cont = go
  where
    go inp@(pos,str) = case alexScan inp 0 of
        AlexEOF             -> cont TokEOF inp
        AlexError inp'      -> do
            warn [(pos, "Lexer error")]
            (go . findAGoodSymbol . skippingTheBadOne) inp'
        AlexSkip  inp' _    -> go inp'
        AlexToken inp'@((_, c), _) len act -> case act pos $ C.unpack (B.take len str) of
            TokNewLine -> do
                layout:layouts <- lift get
                case layout of
                    NoLayout -> go inp'
                    Layout n -> case compare n c of
                        LT -> go inp'
                        EQ -> cont TokSemicolon inp'
                        GT -> do
                            lift (put layouts)
                            cont TokRBrace inp
            TokRBrace  -> do
                layout <- lift get
                if NoLayout `elem` layout
                then do
                    lift $ put (tail layout)
                    cont TokRBrace $ if head layout == NoLayout then inp' else inp
                else do
                    warn [(pos, "Misplaced '}'")]
                    go inp'
            TokWith{} -> cont (TokWith c) inp'
            TokOf{} -> cont (TokOf c) inp'
            tok -> cont tok inp'

findAGoodSymbol :: AlexInput -> AlexInput
findAGoodSymbol ((l, c), str) =
    let (f,s) = C.break (\c -> isAlpha c || isSpace c || c `elem` "~!@#$%^&*-+=|?<>,./:l[]{}()\\_") str
    in ((l, c + B.length f), s)

skippingTheBadOne :: AlexInput -> AlexInput
skippingTheBadOne inp@((l, c), str) = if B.null str then inp else ((l, c + 1), B.tail str)

alexGetByte :: AlexInput -> Maybe (Word8, AlexInput)
alexGetByte (pos, str) = fmap (\(h,t) -> (h, (alexMove pos $ C.head str, t))) (B.uncons str)

tabSize :: Int
tabSize = 4

alexMove :: Posn -> Char -> Posn
alexMove (l, c) '\t' = (l, ((c + tabSize - 1) `div` tabSize) * tabSize + 1)
alexMove (l, c) '\n' = (l + 1, 1)
alexMove (l, c) _    = (l , c + 1)

alexInputPrevChar :: AlexInput -> Char
alexInputPrevChar = error "alexInputPrevChar"
}