packages feed

baskell-0.1: src/Lexer.hs

{-------------------------------------------------------------------------------

        Copyright:              Bernie Pope 2004

        Module:                 Lexer

        Description:            Lexical analysis for baskell's parser.

        Primary Authors:        Bernie Pope

-------------------------------------------------------------------------------}

{-
    This file is part of baskell.

    baskell is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    baskell is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with baskell; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-}

module Lexer
   ( Token (..)
   , Symbol (..)
   , lexer
   )
where

import Data.Char
   ( isDigit
   , isAlpha
   , isPrint
   , isLower
   )

import Text.ParserCombinators.Parsec.Pos
   ( newPos
   , setSourceColumn
   , incSourceColumn
   , incSourceLine
   )

import Text.ParserCombinators.Parsec
   ( SourcePos )

--------------------------------------------------------------------------------

newtype Token = Token (SourcePos, Symbol)

instance Show Token where
   show (Token (pos, symbol)) = show symbol

data Symbol
   = Word String
   | SingleQuoted String
   | Num Int
   | Equals
   | LeftRoundBracket
   | RightRoundBracket
   | LeftSquareBracket
   | RightSquareBracket
   | Comma
   | Colon
   | DoubleColon
   | SemiColon
   | BackSlash
   | Exclamation
   | Dash
   | GreaterThan
   | Bad String
   deriving (Eq)

-- slighlty pretty printing of Symbols via Show. The
-- prettiness is due to the fact that the Parsec parser
-- uses show to print Symbols as part of error messages
instance Show Symbol where
   show (Word s)           = "word: " ++ s
   show (SingleQuoted s)   = "single quoted string: " ++ s
   show (Num n)            = "number: " ++ show n
   show Equals             = "equals sign: '='"
   show LeftRoundBracket   = "bracket: '('"
   show RightRoundBracket  = "bracket: ')'"
   show LeftSquareBracket  = "bracket: '['"
   show RightSquareBracket = "bracket: ']'"
   show Comma              = "comma: ','"
   show Colon              = "colon: ':'"
   show DoubleColon        = "double colon: '::'"
   show SemiColon          = "semi-colon: ';'"
   show BackSlash          = "backslash: '\'"
   show Exclamation        = "exclamation sign: '!'"
   show Dash               = "dash '-'"
   show GreaterThan        = "greater than sign: '>'"
   show (Bad str)          = str

-- turn a stream of characters into a stream of tokens
lexer :: String -> String -> [Token]
lexer filename input
   = lexWork (newPos filename 1 1) input

lexWork :: SourcePos -> String -> [Token]
lexWork pos [] = []
lexWork pos (x:xs)
   | x == '='  = simpleToken Equals nextCol
   | x == '('  = simpleToken LeftRoundBracket nextCol
   | x == ')'  = simpleToken RightRoundBracket nextCol
   | x == '['  = simpleToken LeftSquareBracket nextCol
   | x == ']'  = simpleToken RightSquareBracket nextCol
   | x == ','  = simpleToken Comma nextCol
   | x == '\n' = lexWork (nextLine pos) xs
   | x == ':'  = lexColon pos xs
   | x == ';'  = simpleToken SemiColon nextCol
   | x == '\\' = simpleToken BackSlash nextCol
   | x == '!'  = simpleToken Exclamation nextCol
   | x == '-'  = simpleToken Dash nextCol
   | x == '>'  = simpleToken GreaterThan nextCol
   -- source location does not need to be accurate within a comment
   | x == '#'  = lexWork pos (dropWhile (/= '\n') xs)
   | isWhiteSpace x = let nextPos = incSourceColumn pos 1
                      in lexWork nextPos xs
   | isDigit x = let (num, rest) = span isDigit (x:xs)
                     nextPos = incSourceColumn pos (length num)
                 in Token (pos, Num $ read num) : lexWork nextPos rest
   | isAlpha x
        = let (restWord, rest) = span isWordChar xs
              word = x:restWord
              nextPos = incSourceColumn pos (length word)
          in Token (pos, Word word) : lexWork nextPos rest
   -- quoted strings need special care - escaped quotes can appear
   -- within the string, and the string might not be terminated
   -- by a quote in the case of a lexical error
   | x == '\''
        = let (thisString, rest) = lexTailQuotedString xs
              nextPos = incSourceColumn pos (length thisString + 2)
          in if null rest
                then [Token (pos, Bad $ "ill-quoted input: " ++ x:thisString)]
                else Token (pos, SingleQuoted thisString) : lexWork nextPos (tail rest)
   | otherwise = simpleToken (Bad $ "symbol: " ++ show x) nextCol
   where
   simpleToken :: Symbol -> (SourcePos -> SourcePos) -> [Token]
   simpleToken tok srcPosUpdate
      = Token (pos, tok) : lexWork (srcPosUpdate pos) xs
   isWhiteSpace :: Char -> Bool
   isWhiteSpace c = c `elem` " \t\r\f\v\xA0"
   isWordChar :: Char -> Bool
   isWordChar c = isAlpha c || isDigit c

nextLine :: SourcePos -> SourcePos
nextLine pos = incSourceLine (setSourceColumn pos 1) 1

nextCol :: SourcePos -> SourcePos
nextCol pos = incSourceColumn pos 1

lexColon :: SourcePos -> String -> [Token]
lexColon pos [] = [Token (pos, Colon)]
lexColon pos (':':rest) =
   Token (pos, DoubleColon) : lexWork (incSourceColumn pos 2) rest
lexColon pos (other:rest) =
   Token (pos, Colon) : lexWork (nextCol pos) (other:rest)

-- lex the rest of a string following the first quote mark
-- must skip escaped quotes, and escaped backslashes
-- include the final quote mark in the result
lexTailQuotedString :: String -> (String, String)
lexTailQuotedString [] = ([], [])
lexTailQuotedString str@('\'':xs) = ([], str)
-- a backslash then a quote is escaped
lexTailQuotedString ('\\':'\'':xs)
   = let (string, rest) = lexTailQuotedString xs in ('\'':string, rest)
-- a backslash then a backslash is an escaped backslash
lexTailQuotedString ('\\':'\\':xs)
   = let (string, rest) = lexTailQuotedString xs in ('\\':'\\':string, rest)
lexTailQuotedString (x:xs)
   = let (string, rest) = lexTailQuotedString xs in (x:string, rest)