packages feed

camfort-0.905: src/Camfort/Specification/Hoare/Lexer.hs

{-# LANGUAGE TupleSections #-}
{-# OPTIONS_GHC -Wall #-}

module Camfort.Specification.Hoare.Lexer (lexer) where

import Data.Monoid (Alt(..))
import Data.Coerce
import qualified Data.Char as Char

import Control.Monad.State
import Control.Monad.Except

import Camfort.Specification.Hoare.Parser.Types


-- | Lex an invariant annotation.
lexer :: String -> HoareSpecParser [Token]
lexer [] = return []
lexer (' ' : xs) = lexer xs
lexer ('\t' : xs) = lexer xs
lexer xs
  | Just (tok, rest) <- lexSymbol xs
  = addToTokens tok rest
lexer ('"' : xs) = do
  (tok, rest) <- lexQuoted xs
  addToTokens tok rest
lexer xs = do
  mname <- lexName xs
  case mname of
    Just (tok, rest) -> addToTokens tok rest
    Nothing -> throwError (LexError xs)


addToTokens :: Token -> String -> HoareSpecParser [Token]
addToTokens tok rest = do
 tokens <- lexer rest
 return $ tok : tokens

lexSymbol :: String -> Maybe (Token, String)
lexSymbol xs =
  let symbols =
        [ ("static_assert", TStaticAssert)
        , ("decl_aux", TDeclAux)
        , ("invariant", TInvariant)
        , ("post", TPost)
        , ("pre", TPre)
        , ("seq", TSeq)
        , ("&", TAnd)
        , ("|", TOr)
        , ("<->", TEquiv)
        , ("->", TImpl)
        , ("!", TNot)
        , ("t", TTrue)
        , ("f", TFalse)
        , ("(", TLParen)
        , (")", TRParen)
        , ("::", TDColon)
        ]

      tryMatch (symbol, tok) = (tok,) <$> stripPrefix symbol xs

      firstMatch = getAlt . mconcat . coerce

  in firstMatch (tryMatch <$> symbols)


lexQuoted :: String -> HoareSpecParser (Token, String)
lexQuoted input = do
  let
    go :: String -> StateT String HoareSpecParser String
    go ('"' : xs) = return xs
    go [] = throwError UnmatchedQuote
    go (c : xs) = do
      modify (c :)
      go xs

  (rest, expr) <- runStateT (go input) []
  return (TQuoted (reverse expr), rest)


isNameStartChar :: Char -> Bool
isNameStartChar c = Char.isLetter c || c == '_'

isNameChar :: Char -> Bool
isNameChar c =
  Char.isLetter c ||
  Char.isNumber c ||
  c == '_'

lexName :: String -> HoareSpecParser (Maybe (Token, String))
lexName xs =
  let (nm, rest) = span isNameChar xs
  in case nm of
    (n1 : _) | isNameStartChar n1 -> return (Just (TName nm, rest))
    _ -> return Nothing


stripPrefix :: (Eq a) => [a] -> [a] -> Maybe [a]
stripPrefix (p : refix) (s : tring)
  | p == s = stripPrefix refix tring
  | otherwise = Nothing
stripPrefix [] string = Just string
stripPrefix _ [] = Nothing