packages feed

caml-parser-0.1.0.0: src/CamlParser/Lexer/Lexer.hs

module CamlParser.Lexer.Lexer where

import Control.Monad (void)
import Data.Char (chr, isLetter, isDigit, digitToInt)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.Void (Void)
import Text.Megaparsec hiding (Token, token)
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import CamlParser.Lexer.Token
import CamlParser.Syntax.Location

type Lexer = Parsec Void String

spaceConsumer :: Lexer ()
spaceConsumer = L.space
  (void (some (oneOf " \t\n\r\f\v")))
  (L.skipLineComment "--" <|> L.skipBlockCommentNested "(*" "*)")
  empty

lexeme :: Lexer a -> Lexer a
lexeme = L.lexeme spaceConsumer

symbol :: String -> Lexer String
symbol = L.symbol spaceConsumer

keywordTableCore :: Map String Token
keywordTableCore = Map.fromList
  [ ("and", TokAnd), ("as", TokAs), ("begin", TokBegin)
  , ("do", TokDo), ("done", TokDone), ("downto", TokDownto)
  , ("else", TokElse), ("end", TokEnd), ("exception", TokException)
  , ("for", TokFor), ("fun", TokFun), ("function", TokFunction)
  , ("if", TokIf), ("in", TokIn), ("let", TokLet)
  , ("match", TokMatch), ("mutable", TokMutable), ("not", TokNot)
  , ("of", TokOf), ("or", TokOr), ("prefix", TokPref)
  , ("rec", TokRec), ("then", TokThen), ("to", TokTo)
  , ("try", TokTry), ("type", TokType), ("value", TokValue)
  , ("when", TokWhen), ("where", TokWhere), ("while", TokWhile)
  , ("with", TokWith)
  , ("mod", TokInfix 3 "mod"), ("quo", TokInfix 3 "quo")
  , ("land", TokInfix 3 "land"), ("lor", TokInfix 2 "lor")
  , ("lxor", TokInfix 2 "lxor"), ("lsl", TokInfix 4 "lsl")
  , ("lsr", TokInfix 4 "lsr"), ("asr", TokInfix 4 "asr")
  ]

tokenP :: Map String Token -> Lexer (Located Token)
tokenP kwTable = located $ choice
  [ stringLiteral
  , charLiteral
  , try floatLiteral
  , intLiteral
  , operatorOrSymbol
  , identifier kwTable
  ]

located :: Lexer a -> Lexer (Located a)
located p = do
  pos1 <- getSourcePos
  v <- p
  pos2 <- getSourcePos
  return $ Located v (Location pos1 pos2)

identifier :: Map String Token -> Lexer Token
identifier kwTable = lexeme $ try $ do
  c <- letterChar <|> char '_'
  cs <- many (alphaNumChar <|> char '_' <|> char '\'')
  let s = c:cs
  return $ fromMaybe (TokIdent s) (Map.lookup s kwTable)

intLiteral :: Lexer Token
intLiteral = lexeme $ TokInt <$> choice
  [ try $ string "0x" *> L.hexadecimal
  , try $ string "0o" *> L.octal
  , try $ string "0b" *> L.binary
  , L.decimal
  ]

floatLiteral :: Lexer Token
floatLiteral = lexeme $ TokFloat <$> choice
  [ try L.float
  , do n <- L.decimal
       _ <- char '.'
       frac <- many digitChar
       exp <- optional $ try $ do
         _ <- oneOf "eE"
         sign <- optional (oneOf "+-")
         e <- L.decimal
         return $ case sign of
           Just '-' -> negate (fromIntegral e)
           _        -> fromIntegral e
       let base = fromIntegral n + readFrac frac
           mult = case exp of
             Just e  -> 10 ** e
             Nothing -> 1.0
       return $ base * mult
  ]
  where
    readFrac "" = 0.0
    readFrac ds = read ('0':'.':ds) / 10 ^^ length ds

stringLiteral :: Lexer Token
stringLiteral = lexeme $ TokString <$> (char '"' *> many stringChar <* char '"')
  where
    stringChar = escapeSeq <|> satisfy (/= '"')
    escapeSeq = char '\\' *> choice
      [ '\n' <$ char 'n', '\r' <$ char 'r', '\t' <$ char 't'
      , '\b' <$ char 'b', '\\' <$ char '\\', '"' <$ char '"'
      , chr . read <$> count 3 digitChar
      ]

charLiteral :: Lexer Token
charLiteral = lexeme $ TokChar <$> between (char '`') (char '`') (escapeSeq <|> anySingleBut '`')
  where
    escapeSeq = char '\\' *> choice
      [ '\n' <$ char 'n', '\r' <$ char 'r', '\t' <$ char 't'
      , '\b' <$ char 'b', '\\' <$ char '\\', '`' <$ char '`'
      , chr . read <$> count 3 digitChar
      ]

operatorOrSymbol :: Lexer Token
operatorOrSymbol = lexeme $ choice $ map try
  [ TokMinusGreater <$ string "->"
  , TokDotDot <$ string ".."
  , TokDotLParen <$ string ".("
  , TokDotLBracket <$ string ".["
  , TokColonColon <$ string "::"
  , TokColonEqual <$ string ":="
  , TokSemiSemi <$ string ";;"
  , TokLessMinus <$ string "<-"
  , TokEqualEqual <$ string "=="
  , TokUnderUnder <$ string "__"
  , TokBarRBracket <$ string "|]"
  , TokLBracketBar <$ string "[|"
  , TokLBracketLess <$ string "[<"
  , TokGreaterRBracket <$ string ">]"
  , TokAmpersandAmpersand <$ string "&&"
  , TokBarBar <$ string "||"
  , TokEqual <$ string "="
  , TokSharp <$ string "#"
  , TokAmpersand <$ string "&"
  , TokQuote <$ string "'"
  , TokLParen <$ string "("
  , TokRParen <$ string ")"
  , TokStar <$ try (string "*" <* notFollowedBy camlSymbolChar)
  , TokComma <$ string ","
  , TokDot <$ string "."
  , TokColon <$ string ":"
  , TokSemi <$ string ";"
  , TokLBracket <$ string "["
  , TokRBracket <$ string "]"
  , TokUnderscore <$ try (string "_" <* notFollowedBy (alphaNumChar <|> char '_' <|> char '\''))
  , TokLBrace <$ string "{"
  , TokBar <$ string "|"
  , TokRBrace <$ string "}"
  , subtractiveOp
  , prefixOp
  , infixOp
  ]

subtractiveOp :: Lexer Token
subtractiveOp = choice [ TokSubtractive "-." <$ string "-.", TokSubtractive "-" <$ string "-" ]

prefixOp :: Lexer Token
prefixOp = TokPrefix <$> ((:) <$> oneOf "!?" <*> many camlSymbolChar)

infixOp :: Lexer Token
infixOp = choice
  [ TokInfix 0 <$> ((:) <$> oneOf "=<>|&~$" <*> many camlSymbolChar)
  , TokInfix 1 <$> ((:) <$> oneOf "@^" <*> many camlSymbolChar)
  , TokInfix 2 <$> ((:) <$> oneOf "+-" <*> many camlSymbolChar)
  , TokInfix 4 <$> (string "**" <> many camlSymbolChar)
  , TokInfix 3 <$> ((:) <$> oneOf "*/%" <*> many camlSymbolChar)
  ]

camlSymbolChar :: Lexer Char
camlSymbolChar = oneOf "!$%&*+-./:<=>?@^|~"

lexer :: Map String Token -> Lexer [Located Token]
lexer kwTable = spaceConsumer *> many (tokenP kwTable) <* eof

mkKeywordTable :: [(String, String)] -> Map String Token
mkKeywordTable pairs = Map.union keywordTableCore (Map.fromList [(kw, TokKeywordExt pid kw) | (pid, kw) <- pairs])