packages feed

uu-cco-0.1.0.0: src/CCO/Tree/ATerm/Lexer.hs

-------------------------------------------------------------------------------
-- |
-- Module      :  CCO.Tree.ATerm.Lexer
-- Copyright   :  (c) 2008 Utrecht University
-- License     :  All rights reserved
--
-- Maintainer  :  stefan@cs.uu.nl
-- Stability   :  provisional
-- Portability :  portable
--
-- A 'Lexer' for 'ATerm's.
--
-------------------------------------------------------------------------------

module CCO.Tree.ATerm.Lexer (
    -- * Tokens
    Token      -- abstract, instance: Symbol

    -- * Lexer
  , lexer      -- :: Lexer Token

    -- * Token parsers
  , integer    -- :: Parser Token Integer
  , float      -- :: Parser Token Double
  , string     -- :: Parser Token String
  , con        -- :: Parser Token Con
  , spec       -- :: Char -> Parser Token Char
) where

import CCO.Lexing hiding (satisfy, string)
import CCO.Parsing          (Symbol (describe), Parser, satisfy, (<!>))
import CCO.Tree.ATerm       (Con)
import Control.Applicative
import Data.Char            (chr)
import Prelude hiding (fromInteger)

-------------------------------------------------------------------------------
-- Tokens
-------------------------------------------------------------------------------

-- | Type of ATerm tokens.
data Token
   = Integer { fromInteger  :: Integer }    -- ^ Integer literal.
   | Float   { fromFloat    :: Double  }    -- ^ Floating-point literal.
   | String  { fromString   :: String  }    -- ^ String literal.
   | Con     { fromCon      :: Con     }    -- ^ Constructor symbol.
   | Spec    { fromSpec     :: Char    }    -- ^ Special character.

instance Symbol Token where
  describe (Integer _) lexeme = "integer literal "        ++ lexeme
  describe (Float _)   lexeme = "floating-point literal " ++ lexeme
  describe (String _)  lexeme = "string literal "         ++ lexeme
  describe (Con _)     lexeme = "constructor symbol "     ++ lexeme
  describe (Spec _)    lexeme = show lexeme 

-- | Retrieves whether a 'Token' is an 'Integer'.
isInteger :: Token -> Bool
isInteger (Integer _) = True
isInteger _           = False

-- | Retrieves whether a 'Token' is a 'Float'.
isFloat :: Token -> Bool
isFloat (Float _) = True
isFloat _         = False

-- | Retrieves whether a 'Token' is a 'String'.
isString :: Token -> Bool
isString (String _) = True
isString _           = False

-- | Retrieves whether a 'Token' is a 'Con'.
isCon :: Token -> Bool
isCon (Con _) = True
isCon _       = False

-- | Retrieves whether a 'Token' is a 'Spec'.
isSpec :: Token -> Bool
isSpec (Spec _) = True
isSpec _        = False

-------------------------------------------------------------------------------
-- Lexer
-------------------------------------------------------------------------------

-- | A 'Lexer' that recognises (and ignores) whitespace.
layout_ :: Lexer Token
layout_ = ignore (some (char ' ' <|> char '\n' <|> char '\t'))

-- | A 'Lexer' that recognises 'Integer' tokens.
integer_ :: Lexer Token
integer_ = Integer <$> integerPart

-- | A 'Lexer' that recognises 'Float' tokens.
float_ :: Lexer Token
float_ = (\n f -> Float (f n)) <$> integerPart <*> floatPart

-- | A 'Lexer' that recognises 'String' tokens.
string_ :: Lexer Token
string_ =
  String <$ char '\"' <*>
  (many stringChar <* char '\"' <|> message "unterminated string literal")

-- | A 'Lexer' that recognises 'Con' tokens.
con_ :: Lexer Token
con_ = (\c cs -> Con (c : cs)) <$> hd <*> tl
  where
    hd = range ('a', 'z') <|> range ('A', 'Z')
    tl = many $ range ('a', 'z') <|> range ('A', 'Z') <|> range ('0', '9') <|>
                anyCharFrom "_*+-"

-- | A 'Lexer' that recognises 'Spec' tokens.
spec_ :: Lexer Token
spec_ = Spec <$> anyCharFrom "()[{]},"

-- | A 'Lexer' for ATerms.
lexer :: Lexer Token
lexer = layout_ <|> integer_ <|> float_ <|> string_ <|> con_ <|> spec_

-------------------------------------------------------------------------------
-- Lexing utilities
-------------------------------------------------------------------------------

-- | A 'Lexer' for characters that may appear inside a string literal.
stringChar :: Lexer Char
stringChar = range (' ', '!') <|> range ('#', '[') <|> range (']', '\DEL') <|>
             escChar

-- | A 'Lexer' that recognises escaped characters.
escChar :: Lexer Char
escChar = char '\\' *> esc
  where
    esc = char '\\' <|> char '\"' <|>
          '\n' <$ char 'n' <|> '\r' <$ char 'r' <|> '\t' <$ char 't' <|>
          (\x y z -> chr (64 * x + 8 * y + z)) <$>
            binDigit_ <*> octDigit_ <*> octDigit_

-- | A 'Lexer' that recognises a list of signs.
signs :: Lexer [Integer -> Integer]
signs = many (negate <$ char '-')

-- | A 'Lexer' that recognises a list of digits and tokenises it as an
-- 'Integer'.
digits :: Lexer Integer
digits = foldl (\n i -> 10 * n + toInteger i) 0 <$> some digit_

-- | A 'Lexer' that recognises the integer part of an integer or float literal.
integerPart :: Lexer Integer
integerPart = (\fs n -> foldr ($) n fs) <$> signs <*> digits

-- | A 'Lexer' that recognises the float and standard-form part of a float
-- literal.
floatPart :: Lexer (Integer -> Double)
floatPart =  (\flt f n -> f n flt) <$>
             (pure "" <|> ((:) <$> char '.' <*> some digit)) <*> sfPart

-- | A 'Lexer' that recognises the standard-form part of a float literal.
sfPart :: Lexer (Integer -> String -> Double)
sfPart =
  (\sf n flt-> read (show n ++ flt ++ sf)) <$>
  (pure "" <|> (\c n -> c : show n) <$> anyCharFrom ['e', 'E'] <*> integerPart)

-------------------------------------------------------------------------------
-- Token Parsers
-------------------------------------------------------------------------------

-- | A 'Parser' that recognises an integer literal.
integer :: Parser Token Integer
integer = fromInteger <$> satisfy isInteger <!> "literal"

-- | A 'Parser' that recognises a floating-point literal.
float :: Parser Token Double
float = fromFloat <$> satisfy isFloat <!> "literal"

-- | A 'Parser' that recognises a string literal.
string :: Parser Token String
string = fromString <$> satisfy isString <!> "literal"

-- | A 'Parser' that recognises a constructor symbol.
con :: Parser Token Con
con = fromCon <$> satisfy isCon <!> "constructor"

-- | A 'Parser' that recognises a specified special character.
spec :: Char -> Parser Token Char
spec c =
  fromSpec <$> satisfy (\tok -> isSpec tok && fromSpec tok == c) <!> show c