sifflet-lib-1.1: Sifflet/Language/Parser.hs
-- | A parser for Sifflet input values.
-- This is not a parser for all Sifflet expressions,
-- but just those that might be input in textual form
-- through the function call dialog that asks for the argument values.
-- So, it is limited (deliberately) to "data" types of expressions:
-- that is, Exprs using the constructors:
-- ELit
-- EList
-- That means excluding Exprs constructed with EUndefined,
-- ESymbol, EIf, and ECall.
module Sifflet.Language.Parser
(parseExpr
, parseValue
, parseLiteral
, parseTest
, parseSuccFail
, parseTypedInput2, parseTypedInputs2
, parseTypedInput3, parseTypedInputs3
, nothingBut
, expr, list
, value, typedValue
, bool, qchar, qstring, integer, double
, number
)
where
import Text.ParserCombinators.Parsec
import Data.Number.Sifflet
import Sifflet.Language.Expr
import Sifflet.Util
-- | Parse a Sifflet data literal (number, string, char, bool, or list),
-- returning an Expr
parseExpr :: String -> SuccFail Expr
parseExpr = parseSuccFail expr
-- | Parse a Sifflet literal expression and return its Value
parseValue :: String -> SuccFail Value
parseValue s =
-- take a shortcut here?
-- case parseExpr s of -- stringToExpr s of
-- Succ expr -> exprToValue expr
-- Fail errmsg -> Fail errmsg
parseLiteral s >>= exprToValue
parseLiteral :: String -> SuccFail Expr
parseLiteral s =
-- parseValue s >>= valueToLiteral
case parseExpr s of
Succ e -> if exprIsLiteral e
then Succ e
else Fail $
"parseLiteral: expr is non-literal" ++ show e
Fail errmsg -> Fail errmsg
parseSuccFail :: Parser a -> String -> SuccFail a
parseSuccFail p s =
case parse p "user input" s of
Left perr -> Fail (show perr)
Right v -> Succ v
-- | Try to parse an input value of a specific type
parseTypedInput2 :: (String, VpType) -> SuccFail Value
parseTypedInput2 (str, vartype) =
parseSuccFail (nothingBut (typedValue vartype)) str
-- | Try to parse input values of specific types
parseTypedInputs2 :: [String] -- ^ input strings
-> [VpType] -- ^ expected types
-> SuccFail [Value]
parseTypedInputs2 strs vartypes =
mapM parseTypedInput2 (zip strs vartypes)
-- | Try to parse an input value for a named variable of a specific type
parseTypedInput3 :: (String, String, VpType) -> SuccFail Value
parseTypedInput3 (s, varname, vartype) =
case parseSuccFail (nothingBut (typedValue vartype)) s of
Fail msg -> Fail ("For variable " ++ varname ++ ":\n" ++ msg)
Succ v -> Succ v
-- | Try to parse input values for named variables of specific types
parseTypedInputs3 :: [String] -- ^ inputs
-> [String] -- ^ variable names
-> [VpType] -- ^ variable types
-> SuccFail [Value]
parseTypedInputs3 strs varnames vartypes =
mapM parseTypedInput3 (zip3 strs varnames vartypes)
-- | Like expr, but consumes the entire input,
-- so there must not be any extraneous characters after the Expr.
input :: Parser Expr
input = nothingBut expr
-- | 'nothingBut p is like 'p', but consumes the entire input,
-- so there must be no extraneous characters (except space)
-- after whatever 'p' parses.
nothingBut :: Parser a -> Parser a
nothingBut p = (many space >> p) `prog1` (many space >> eof)
prog1 :: (Monad m) => m a -> m b -> m a
prog1 m1 m2 = m1 >>= (\ r -> m2 >> return r)
-- | Parse a Sifflet data expression -- actually only a literal
-- or a list of literals.
expr :: Parser Expr
expr = -- (try (list expr >>= return . EList)) <|>
(bool >>= return . EBool) <|>
(qchar >>= return . EChar) <|>
(qstring >>= return . EString) <|>
try (double >>= return . ENumber . Inexact) <|>
(integer >>= return . ENumber . Exact) <|>
(list expr >>= return . EList)
list :: Parser a -> Parser [a]
list element =
let sep = try (skipMany space >> char ',' >> skipMany space)
in (char '[' >> many space >> sepBy element sep)
`prog1`
(many space >> char ']')
-- do I need (...) above?
<?> "list" -- ???
-- | Parser for a Value of any type (any VpType),
-- except that we cannot parse as VpTypeVar or VpTypeFunction.
value :: Parser Value
value = (bool >>= return . VBool) <|>
(qchar >>= return .VChar) <|>
(qstring >>= return . VString) <|>
try (double >>= return . VNumber . Inexact) <|>
(integer >>= return . VNumber . Exact) <|>
(list value >>= return . VList)
-- | Parser for a value with a specific VpType expected.
-- Again, we cannot do this for VpTypeVar (why not?)
-- or VpTypeFunction
typedValue :: VpType -> Parser Value
typedValue t =
(case t of
VpTypeBool -> bool >>= return . VBool
VpTypeChar -> qchar >>= return . VChar
VpTypeString -> qstring >>= return . VString
VpTypeNum -> number >>= return . VNumber
VpTypeList e -> list (typedValue e) >>= return . VList
VpTypeVar _ -> value -- can't check, so just accept anything
VpTypeFunction _ _ ->
error "typedValue: not implemented for VpTypeFunction"
)
<?> typeName t
-- | A name for the type, for use in parser error reporting
typeName :: VpType -> String
typeName t =
case t of
VpTypeBool -> "boolean" -- "boolean (True or False)"
VpTypeChar -> "character" -- "character (in single quotes)"
VpTypeNum -> "number"
VpTypeString -> "string" -- "string (in double quotes)"
VpTypeList e -> "list" ++ -- "list (in brackets)" ++
case e of
VpTypeVar _ -> ""
_ -> " of " ++ typeName e
VpTypeVar _ -> "anything" -- could be more specific!
VpTypeFunction _ _ -> "function" -- ???
bool :: Parser Bool
bool = (try (string "True" >> return True) <|>
(string "False" >> return False))
<?> typeName VpTypeBool
-- quoted character 'c'
qchar :: Parser Char
qchar =
let sq = '\'' -- single quote character
in (((char sq <?> "opening single quote") >>
(try escapedChar <|> noneOf [sq]))
`prog1`
(char sq <?> "closing single quote")
)
<?> typeName VpTypeChar
-- quoted string "c..."
qstring :: Parser String
qstring =
let dq = '\"' -- double quote character
in (char dq >>
many (escapedChar <|> noneOf [dq] <?> ""))
`prog1`
(char dq <?> "close of quotation")
-- Do I need (...) above?
<?> typeName VpTypeString
-- escapedChar recognizes the following escape sequences:
-- \t = tab
-- \n = newline
-- \r = carriage return
-- \\ = backslash
-- Anything else that begins with \ is an error.
escapedChar :: Parser Char
escapedChar =
let bs = '\\' -- backslash character
in char bs >>
(oneOf "ntr\\" <?> "n, t, r, or \\ to follow \\") >>=
(\ c ->
return (case c of
'n' -> '\n'
't' -> '\t'
'r' -> '\r'
'\\' -> '\\'
_ -> error "escapedChar: c MUST be n, t, r, or \\"
)
)
data Sign = Minus | Plus
-- Integer ::= (+|-)? digit+
integer :: Parser Integer -- sign, digits
integer = do { s <- optSign;
u <- unsignedInteger;
return (applySign s u)
}
<?> "integer"
unsignedInteger :: Parser Integer
unsignedInteger = many1 digit >>= return . read
-- An optional + or - defaulting to +
optSign :: Parser Sign -- 1: negative; 0: non-negative
optSign = try ( char '-' >> return Minus ) <|>
try ( char '+' >> return Plus ) <|>
return Plus
applySign :: (Num n) => Sign -> n -> n
applySign s x =
case s of
Minus -> (- x)
Plus -> x
-- A double (float) may begin with a sign (+ or -) and must contain
-- a decimal point along with at least one digit before and/or after
-- the decimal point.
-- So there are three cases:
-- [sign] digits '.' digits
-- [sign] digits '.'
-- [sign] '.' digits
double :: Parser Double
-- Double FAILS if there is a decimal point.
-- It succeeds in the following cases:
double =
let digits1 = many1 digit
point = char '.'
-- wpf: whole-part point fraction-part
wpf = do { dd <- digits1;
dd' <- point >> digits1;
return (dd, dd')
}
-- wp: whole-part point
wp = do { dd <- digits1 `prog1` point;
return (dd, "0")
}
-- pf: point fraction-part
pf = do { dd' <- point >> digits1;
return ("0", dd')
}
-- optional trailing exponent notation e.g. e-4
scale = do { i <- oneOf "eE" >> integer;
return (10 ** fromIntegral i)
}
<|> return 1
in do { sign <- optSign
; (whole, frac) <- (try wpf <|>
try wp <|>
try pf)
; m <- scale;
; let w = read (whole ++ ".0") -- whole part as number
f = read ("0." ++ frac) -- frac part as number
; return (m * applySign sign (w + f))
}
<?> "real number"
-- A number is a Sifflet Number, which is exact unless it contains
-- a decimal point.
-- To avoid consuming "123" from "123." and interpreting it as an exact
-- number, we MUST try to parse double before integer.
number :: Parser Number
number = (try (double >>= return . Inexact) <|>
(integer >>= return . Exact))
<?> typeName VpTypeNum