packages feed

funcons-intgen-0.2.0.1: src/Parsing/Lexer.hs

module Parsing.Lexer (Parsing.Lexer.lexer, mylexer) where

import GLL.Combinators (Token(..),SubsumesToken(..))

import CCO.Component

import Data.Char (isDigit, isAlpha, isUpper, isLower, isAlphaNum, isSpace)
import Text.Regex.Applicative
import Text.Read (readEither)

lexer :: Component String [Token]
lexer = component (return . mylexer)
--lexer = component (((\x -> trace (show x) (return x))) . mylexer)

mylexer = sSpecs

-- Do not forget that states can be created that do not perform
-- longest match by default. Could help to disambiguate atoms for example.
lState :: String -> Bool -> RE Char t -> (t -> String -> [Token]) -> 
                  (t -> Maybe Token) -> String -> [Token]
lState _ _ _ _ _ [] = []
lState stateName discardLayout myTokens mySelector adder s =
    let re | discardLayout = Just <$> myTokens <|> ws
           | otherwise     = Just <$> myTokens
        ws =      (Nothing <$ some (psym isSpace))
              <|> (Nothing <$ string "//" <* many (psym ((/=) '\n')))
              <|> (Nothing <$ string "#" <* many (psym ((/=) '\n')))
    in case findLongestPrefix re s of
        Just (Just tok, rest)   -> (maybe id (:) (adder tok)) (mySelector tok rest)
        Just (Nothing,rest)     -> lState stateName discardLayout myTokens mySelector adder rest
        Nothing                 -> error ("lexical error at " ++ stateName ++ ": " ++ show (take 10 s))


sSpecs = lState "SPECS" True (lCommentStart <|> lTokens) sel Just
  where sel tok = case tok of  Keyword "/*"           -> sComment
                               Keyword "/*HS-IMPORTS" -> sComment
                               Keyword "Section"      -> sSectionNum
                               Keyword "Subsection"   -> sSectionNum
                               _                      -> sSpecs

sSectionNum = lState "SECTION-NUM" True (optional lSectNum) sel id
  where sel tok = sSection 

sSection = lState "SECTION" False lTitleWords (const sSpecs) Just

sComment = lState "COMMENT" False 
              (lHS_import <|> lTick <|> lCommentEnd <|> lCommentPart) sel Just
  where sel tok = case tok of Token "TICK" _  -> sTickIn 
                              Keyword "*/"    -> sSpecs
                              _               -> sComment
        lHS_import = Keyword <$> string "HS-IMPORTS"

sTickIn = lState "TICK-IN" True (lTick <|> lTokens) sel Just
  where sel tok = case tok of Token "TICK" _  -> sTickIn
                              _               -> sSpecInTick

sTickOut = lState "TICK-OUT" False (lTick <|> lCommentPart) sel Just
  where sel tok = case tok of Token "TICK" _  -> sTickOut
                              _               -> sComment

sSpecInTick = lState "SPEC-IN-TICK" True (lTick <|> lTokens) sel Just
  where sel tok = case tok of Token "TICK" _  -> sTickOut
                              _               -> sSpecInTick


lTokens :: SubsumesToken t => RE Char t
lTokens =
        lCharacters
    <|> lKeywords
    <|> charsToInt  <$> optional (sym '-') <*> some (psym isDigit)
    <|> upcast . IDLit . Just <$> lName 
    <|> upcast . AltIDLit . Just <$> lVar 
--    <|> upcast . CharLit . Just <$> lCharLit
    <|> upcast . StringLit . Just <$> lStringLit
    <|> lMore
    where
            charsToInt Nothing n = upcast (IntLit (Just (read n)))
            charsToInt (Just _) n = upcast (IntLit (Just (-(read n))))

            lChar c = upcast (Char c) <$ sym c
            lCharacters = foldr ((<|>) . lChar) empty cbs_characters

            lKeyword k  = upcast (Keyword k) <$ string k
            lKeywords = foldr ((<|>) . lKeyword) empty cbs_keywords

            lMore = foldr ((<|>) . uncurry lToken) empty myTokens 
            lToken t re = upcast . Token t . Just <$> re

            lStringLit = toString <$ sym '\"' <*> many strChar <* sym '\"'
             where strChar =  sym '\\' *> sym '\"'
                              <|> psym ((/=) '\"')
                   toString inner = case readEither ("\"" ++ inner ++ "\"") of
                      Left _  -> inner
                      Right v -> v 

            lCharLit = id <$ sym '\'' <*> charChar <* sym '\''
              where charChar = sym '\\' *> sym '\''
                                <|> psym ((/=) '\'')

cbs_characters = "~():,*+?!|[]{}.@=<>-_&^"

cbs_keywords =  [ "Alias"
                , "Assert"
                , "Auxiliary"
                , "Built-in"
                , "Datatype"    
                , "Entity"                
                , "Funcon"
                , "Hidden"
                , "Language"
                , "Lexis"
                , "Meta-variables"
                , "Otherwise" 
                , "Rule"
                , "SDF"
                , "Semantics"
                , "Syntax"
                , "Type"
                , "Variables"
                , "...", ">:", "<:", "=>", "|->", "==", "~>" 
                , "=/=", "|-", "--", "->", "::=", "[[", "]]"]

lCommentStart = Keyword "/*" <$ sym '/' <* sym '*'
            <|> Keyword "/*HS-IMPORTS" <$ string "/*HS-IMPORTS"
lCommentEnd   = Keyword "*/" <$ sym '*' <* sym '/'

myTokens =  [("ATOM", lAtom), ("FILE", lFile), ("BAR", lBar)
            ,("ORDINAL", lOrdinal), ("DQUOTE", lDQuote) ]

lDQuote = "\\\"" <$ sym '\\' <* sym '\"'

lBar = "----" <$ sym '-' <* sym '-' <* sym '-' <* sym '-' <* many (sym '-')

lName = (:) <$> psym isLower <*> many (psym (\c -> isAlphaNum c || c == '-'))

lCommentPart :: SubsumesToken t => RE Char t
lCommentPart = 
      upcast (Char '*') <$ sym '*'
  <|> upcast (Char '@') <$ sym '@'
  <|> upcast <$ sym '@' <*> lSectNum
  <|> upcast . Token "ORDINARY" . Just <$> lOrdinary
                

lOrdinary = some $ psym (\c -> c /= '*' && c /= '`' && c /= '@')

--TODO atoms are not being lexed correctly
-- for example '\' | '`' should be two atoms separated by a char '|'
-- this is an interesting example of ambiguity
--
-- ambiguity: '\' '\' , one atom '\' ' followed by \', or two atoms '\'
--
-- Lexis
--    capitalized-characters ::= '\' | '`' 
lAtom = sym '\'' *> ((concat <$> few atom_char) <|> lBackslash <|> escaped) <* sym '\''
  where atom_char = (:[]) <$> psym (not . invC)
-- TODO how to allow escaped single quotes in atoms (see ambiguities above)
--                      <|> escaped 
         where invC c = c == '\'' || c == '\t' || c == '\n' || c == '\r'  
 --                           || c == '|' {- temporary fix -}

        escaped = (\a b -> [a,b]) <$> sym '\\' <*> sym '\''
lVar = (\c xs ys zs -> c:xs++ys++zs) <$> psym isUpper <*> many (psym isAlpha) 
            <*> stem_rest <*> rest
  where stem_rest = (++) <$> (concat <$> many ((:) <$> sym '-' <*> some (psym isAlpha))) 
                      <*> stem_suffix
        stem_suffix = maybe [] id <$> optional ((\a b -> [a,b]) <$> sym '-' <*> psym isDigit)
        rest = (++) <$> suffix <*> (maybe [] id <$> (optional postfix))
         where suffix = (++) <$> many (psym isDigit) <*> many (sym '\'')
               postfix = (:[]) <$> (sym '*' <|> sym '+' <|> sym '?')

lFile = ((++)) . concat <$> many part_sep <*> part
 where part = some (psym isAlphaNum)
       part_sep = (\a b -> a ++ [b]) <$> part <*> (sym '-' <|> sym '_')

lSectNum :: RE Char Token
lSectNum =    Token "SECT-NUM" . Just <$> lSect1Num 
          <|> Token "SECT-NUM" . Just <$> lSect2Num 
          <|> Token "SECT-NUM" . Just <$> lSect3Num 
          <|> Token "SECT-NUM" . Just <$> lSect4Num 

-- ambiguity MISC lexes both as lSect1Num, but should be interpreted as lTitleWords
lSect1Num = lOrdinal
lSect2Num = (\x y z -> x++[y]++z) <$> lOrdinal <*> sym '.' <*> lOrdinal
lSect3Num = (\x1 x2 x3 x4 x5 -> x1++[x2]++x3++[x4]++x5) <$> 
              lOrdinal <*> sym '.' <*> lOrdinal <*> sym '.' <*> lOrdinal 
lSect4Num = (\x1 x2 x3 x4 x5 x6 x7 -> x1++[x2]++x3++[x4]++x5++[x6]++x7) <$> 
              lOrdinal <*> sym '.' <*> lOrdinal <*> sym '.' <*> lOrdinal 
                       <*> sym '.' <*> lOrdinal
lOrdinal = some (psym isDigit) {- CAUSES AMBIGUITY, IS IT USED? <|> (:[]) <$> psym isAlpha-}

lTitleWords :: RE Char Token
lTitleWords = Token "TITLE" . Just <$> many (psym ((/=) '\n')) <* sym '\n' 

lTick :: SubsumesToken t => RE Char t
lTick = upcast (Token "TICK" (Just "`")) <$ sym '`'

-- | Escaped backslash
lBackslash = "\\" <$ sym '\\' <* sym '\\'

concatMany :: RE Char String -> RE Char String
concatMany p = concat <$> many p 

concatSome p = concat <$> some p

{-
notFollowedBy :: RE c s1 -> RE c s2 -> RE c s1
notFollowedBy p q = do
  -- apply the regex p
  (res, matched) <- withMatched p
  case match q of --then see if the regex q matches
    Just _  -> -- if it does we need to insert `matched` back into the input string
               empty
    Nothing -> return res -- otherwise just return the result
-}