packages feed

haskeem-0.7.4: Parser.hs

{- Copyright 2008 Uwe Hollerbach <uh@alumni.caltech.edu>
Portions of this were derived from Jonathan Tang's haskell
tutorial "Write yourself a scheme in 48 hours" and are thus
Copyright Jonathan Tang
(but I can't easily tell anymore who originally wrote what)

This file is part of haskeem.
haskeem 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.

haskeem 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 haskeem; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

$Id: parser.hs,v 1.14 2009-06-27 20:31:52 uwe Exp $ -}

module Parser (readExpr, readExprList, readNumber) where
import Prelude
import Data.Char
import Data.Ratio
import Text.ParserCombinators.Parsec as TPCP hiding (spaces)
import Control.Monad.Error as CME
import qualified Data.IntMap as DIM

import LispData

-- Parsers for the various kinds of LispVal

-- "#!/some/path/to/executable" at the top of the file, to enable
-- scheme "shell" scripts: for the rest of the program, it's a comment

hashbang :: Parser Char
hashbang =
  do char '#'
     char '!'
     many (noneOf "\r\n")
     return ' '

-- semicolon to end-of-line, the oldest style of lisp comment

comment :: Parser Char
comment = 
  do char ';'
     many (noneOf "\r\n")
     return ' '

spaces :: Parser ()
spaces = skipMany1 (comment <|> space)

-- This is not quite R6RS-compliant: R6RS allows '.'

symbol :: Parser Char
symbol = oneOf "!$%&*+-/:<=>?@^_~"

-- This is a small extension to R6RS

controlChar :: Parser Char
controlChar =
  do char '^'
     c <- oneOf (['A' .. 'Z'] ++ "[\\]^_")
     return (chr (ord c + 1 - ord 'A'))

namedChar :: Parser Char
namedChar =
  do name <- (string "alarm"
          <|> string "backspace"
          <|> string "delete"
          <|> string "esc"
          <|> string "linefeed"
          <|> (TPCP.try (string "newline"))
          <|> string "nul"
          <|> string "page"
          <|> string "return"
          <|> string "space"
          <|> string "tab"
          <|> string "vtab")
     case name of
          "nul"       -> return (chr 0)
          "alarm"     -> return (chr 7)
          "backspace" -> return (chr 8)
          "tab"       -> return '\t'
          "linefeed"  -> return '\n'
          "newline"   -> return '\n'
          "vtab"      -> return (chr 11)
          "page"      -> return (chr 12)
          "return"    -> return '\r'
          "esc"       -> return (chr 27)
          "space"     -> return ' '
          "delete"    -> return (chr 127)

parseChar :: Parser LispVal
parseChar =
  do char '#'
     char '\\'
     c <- (TPCP.try controlChar) <|> (TPCP.try namedChar) <|> anyChar
     return (Char c)

-- This is not quite R6RS-compliant: R6RS requires a hex escape spec,
-- and it forbids the "otherwise" clause below. oh well... later maybe

escChar :: Parser Char
escChar =
  do char '\\'
     c <- anyChar
     return (case c of
             'a' -> chr 7
             'b' -> chr 8
             't' -> '\t'
             'n' -> '\n'
             'v' -> chr 11
             'f' -> chr 12
             'r' -> '\r'
             _ -> c)

parseBool :: Parser LispVal
parseBool =
  do char '#'
     v <- oneOf "tTfF"
     return (case v of
            't' -> lispTrue
            'T' -> lispTrue
            'f' -> lispFalse
            'F' -> lispFalse)

parseString :: Parser LispVal
parseString =
  do char '"'
     x <- many (escChar <|> (noneOf "\""))
     char '"'
     return (String x)

parseSymbol :: Parser LispVal
parseSymbol =
  do first <- letter <|> symbol
     rest <- many (letter <|> digit <|> symbol)
     return (Symbol (first:rest))

readBaseInt :: Integer -> String -> Integer
readBaseInt b s = foldl ma 0 s
                  where ma v1 v2 = b*v1 + (toInteger (digitToInt v2))

readBaseFrac :: Integer -> String -> Double
readBaseFrac _ [] = 0.0
readBaseFrac b s = r * foldr ma 0 s where
                   r = 1.0/(fromInteger b)
                   ma v1 v2 = fromIntegral (digitToInt v1) + r*v2

parseHdr :: Parser (Char, Integer)
parseHdr =
  do b <- option 'd' (char '#' >> oneOf "bodxBODX")
     s <- option '+' (oneOf "+-")
     let base = (case b of
                 'b' -> 2
                 'B' -> 2
                 'o' -> 8
                 'O' -> 8
                 'd' -> 10
                 'D' -> 10
                 'x' -> 16
                 'X' -> 16)
     return (s, base)

baseDigits :: Integer -> String
baseDigits 2  = "01"
baseDigits 8  = "01234567"
baseDigits 10 = "0123456789"
baseDigits 16 = "0123456789abcdefABCDEF"

int :: String
int = "int"

-- The fact that this parser can deal with floating-point numbers
-- in bases 2, 8, and 16 as well as 10 is an extension of R6RS.

-- Parse first alternative for floating-point numbers: \d+(\.\d*)?

parseF1 :: Integer -> Parser (String,String)
parseF1 b =
  do ip <- many1 (oneOf (baseDigits b))
     fp <- option int (char '.' >> many (oneOf (baseDigits b)))
     return (ip,fp)

-- Parse second alternative for floating-point numbers: \.\d+

parseF2 :: Integer -> Parser (String,String)
parseF2 b =
  do char '.'
     fp <- many1 (oneOf (baseDigits b))
     return ("0",fp)

-- Parse the exponent

parseExp :: Integer -> Parser Integer
parseExp b =
  do if b == 16 then (oneOf "xX") else (oneOf "eExX")
     s <- option '+' (oneOf "+-")
     num <- many1 (oneOf (baseDigits b))
     let e = readBaseInt b num
     return (if s == '-' then (-e) else e)

powi :: Integer -> Integer -> Integer
powi b e | e == 0    = 1
         | e < 0     = error "negative exponent in powi"
         | even e    = powi (b*b) (e `quot` 2)
         | otherwise = b * (powi b (e - 1))

pow :: Integer -> Integer -> Double
pow b e =
  if e >= 0 then fromInteger (powi b e) else recip (fromInteger (powi b (-e)))

-- Parse an integer or a floating-point number. This parser will return
-- numbers written as aaaEbb (with no decimal point) as integers, if the
-- exponent bb is non-negative.

parseIntOrFlt :: Parser LispVal
parseIntOrFlt =
  do (s, b) <- parseHdr
     (ip, fp) <- (parseF1 b) <|> (parseF2 b)
     e <- option 0 (parseExp b)
     let fpi = if fp == int then "0" else fp
         vf = (pow b e) * (fromInteger (readBaseInt b ip) + readBaseFrac b fpi)
         vi = (powi b e) * (readBaseInt b ip)
     if (fp == int && e >= 0)
        then return (IntNumber (if s == '-' then (-vi) else vi))
        else return (FltNumber (if s == '-' then (-vf) else vf))

-- Parse a rational number written as numerator/denominator. This parser
-- accepts and understands rational infinity, both positive and negative,
-- and rational not-a-number: +infinity is written as 1/0, -infinity as
-- -1/0, and not-a-number as 0/0. That's an incompatible extension of R6RS.

parseRat :: Parser LispVal
parseRat =
  do (s, b) <- parseHdr
     nstr <- many1 (oneOf (baseDigits b))
     char '/'
     dstr <- many1 (oneOf (baseDigits b))
     let num = readBaseInt b nstr
         den = readBaseInt b dstr
         ns = if s == '-' then (-num) else num
         val = if den /= 0
                  then ns % den
                  else if ns > 0
                       then myRatPInf
                       else if ns < 0
                            then myRatNInf
                            else myRatNaN
     if (denominator val) == 1
        then return (IntNumber (numerator val))
        else return (RatNumber val)

-- Parse a couple of special floating-point numbers mandated by R6RS

parseNaNInf :: Parser LispVal
parseNaNInf =
  do val <- (TPCP.try (string "+nan.0"))
        <|> (TPCP.try (string "-nan.0"))
        <|> (TPCP.try (string "+inf.0"))
        <|> (TPCP.try (string "-inf.0"))
     case val of
          "+nan.0"    -> return (FltNumber myFltNaN)
          "-nan.0"    -> return (FltNumber myFltNaN)
          "+inf.0"    -> return (FltNumber myFltPInf)
          "-inf.0"    -> return (FltNumber myFltNInf)

parseNumber :: Parser LispVal
parseNumber = (TPCP.try parseNaNInf) <|> (TPCP.try parseRat) <|> parseIntOrFlt

-- Parsers for the abbreviations for the various kinds of quoting entities:
--	'<datum>   =>  (quote <datum>)
--	`<datum>   =>  (quasiquote <datum>)
--	,<datum>   =>  (unquote <datum>)
--	,@<datum>  =>  (unquote-splicing <datum>)

parseQQ :: Parser LispVal
parseQQ =
  do char '`'
     x <- parseExpr
     return (List [Symbol "quasiquote", x])

parseQ :: Parser LispVal
parseQ =
  do char '\''
     x <- parseExpr
     return (List [Symbol "quote", x])

parseUQ :: Parser LispVal
parseUQ =
  do char ','
     x <- parseExpr
     return (List [Symbol "unquote", x])

parseUQS :: Parser LispVal
parseUQS =
  do char ','
     char '@'
     x <- parseExpr
     return (List [Symbol "unquote-splicing", x])

parseQuoted :: Parser LispVal
parseQuoted = (TPCP.try parseUQS)
          <|> (TPCP.try parseUQ)
          <|> (TPCP.try parseQQ)
          <|> parseQ

-- Parser for a dotted-list or a regular list. Due to the representation of
-- scheme lists as haskell lists rather than as dotted-pairs, it's slightly
-- tricky to get the case of (a . (b . (c . ()))) and similar forms to come
-- out right; however, that is explicitly described as exactly identical to
-- the list (a b c) according to the RnRS standard, so it has to be treated
-- correctly.

parseDottedList :: Parser LispVal
parseDottedList =
  do char '('
     skipMany space
     hd <- sepEndBy parseExpr spaces
     tl <- option (List []) (TPCP.try (char '.' >> spaces >> parseExpr))
     skipMany space
     char ')'
     if isl tl
        then return (List (hd ++ (unpl tl)))
        else if isdl tl
                then return (DottedList (hd ++ (unpdlh tl)) (unpdlt tl))
                else return (DottedList hd tl)
  where isl (List ((Symbol sym):_)) =
          if sym == "unquote" || sym == "unquote-splicing"
             then False
             else True
        isl (List _) = True
        isl _ = False
        unpl (List l) = l
        isdl (DottedList _ _) = True
        isdl _ = False
        unpdlh (DottedList h _) = h
        unpdlt (DottedList _ t) = t

-- Parser for a vector: this is similar to a list (but not a dotted-list),
-- except that R6RS says access times are generally faster than for lists.
-- It would seem that haskell Arrays would be the natural way to go, but
-- the documentation for those is... well, crappy. Data.IntMap is much
-- better documented, and pretty close to what we want. Access times aren't
-- O(1), but they are O(min(n,W)), where n is the size of the vector and
-- W is the size in bits of a machine word: either 32 or 64 usually. This
-- is due to the implementation of Data.IntMap: internally, it's a PATRICIA
-- tree. That should be fast enough for the moment; if it becomes an issue,
-- I can always change later. Data.IntMaps are extensible, so I could in
-- principle have extensible vectors, which would mean I'd not need to store
-- the length, but bounds-checked arrays seem like a nice feature to have;
-- I can add an explicit grow-vector routine, which as a result of the
-- extensibility of Data.IntMaps will be very easy to write.

parseVector :: Parser LispVal
parseVector =
  do char '#'
     char '('
     skipMany space
     vals <- sepBy parseExpr spaces
     skipMany space
     char ')'
     return (Vector (toInteger (length vals))
                    (DIM.fromAscList (addkey 0 vals)))
  where addkey _ [] = []
        addkey n (v:vs) = (n, v):(addkey (n+1) vs)

parseExpr :: Parser LispVal
parseExpr = parseString
        <|> (TPCP.try parseBool)
        <|> (TPCP.try parseChar)
        <|> (TPCP.try parseNumber)
        <|> (TPCP.try parseVector)
        <|> (TPCP.try parseSymbol)
        <|> parseQuoted
        <|> parseDottedList

readOrThrow :: Parser a -> String -> ThrowsError a
readOrThrow parser input =
    case parse parser "lisp" input of
         Left err -> throwError (Parser err)
         Right val -> return val

readExpr :: String -> ThrowsError LispVal
readExpr = readOrThrow parseExpr

readExprList :: String -> ThrowsError [LispVal]
readExprList =
  readOrThrow ((optional hashbang) >>
               (skipMany spaces) >>
               endBy parseExpr (spaces <|> eof))

-- Parser for just numbers, for internally converting strings to numbers;
-- it's just a little more lenient than only and exactly a number: allow
-- whitespace on either side, that doesn't harm anything and seems polite

parseJustNumber :: Parser LispVal
parseJustNumber =
  do skipMany space
     num <- parseNumber
     skipMany space
     eof
     return num

readNumber :: String -> ThrowsError LispVal
readNumber input =
    case parse parseJustNumber "number" input of
         Left _ -> return lispFalse
         Right val -> return val