Implement comparisons in preprocessor expressions.

This brings Tock's conditional compilation facilities up to par with occ21.
This commit is contained in:
Adam Sampson 2008-02-29 00:23:49 +00:00
parent a5fd73130a
commit c8b6be34e1
2 changed files with 87 additions and 12 deletions

View File

@ -24,6 +24,7 @@ import Control.Monad.State
import Data.List import Data.List
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Data.Set as Set import qualified Data.Set as Set
import Numeric
import System.IO import System.IO
import Text.ParserCombinators.Parsec import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Language (haskellDef) import Text.ParserCombinators.Parsec.Language (haskellDef)
@ -236,10 +237,14 @@ handleIf m [condition]
type PreprocParser = GenParser Char (Map.Map String PreprocDef) type PreprocParser = GenParser Char (Map.Map String PreprocDef)
--{{{ lexer --{{{ lexer
reservedOps :: [String]
reservedOps = ["=", "<>", "<", "<=", ">", ">="]
ppLexer :: P.TokenParser (Map.Map String PreprocDef) ppLexer :: P.TokenParser (Map.Map String PreprocDef)
ppLexer = P.makeTokenParser (haskellDef ppLexer = P.makeTokenParser (haskellDef
{ P.identStart = letter <|> digit { P.identStart = letter <|> digit
, P.identLetter = letter <|> digit <|> char '.' , P.identLetter = letter <|> digit <|> char '.'
, P.reservedOpNames = reservedOps
}) })
lexeme :: PreprocParser a -> PreprocParser a lexeme :: PreprocParser a -> PreprocParser a
@ -256,15 +261,21 @@ parens = P.parens ppLexer
symbol :: String -> PreprocParser String symbol :: String -> PreprocParser String
symbol = P.symbol ppLexer symbol = P.symbol ppLexer
reservedOp :: String -> PreprocParser ()
reservedOp = P.reservedOp ppLexer
--}}} --}}}
tryVX :: PreprocParser a -> PreprocParser b -> PreprocParser a tryVX :: PreprocParser a -> PreprocParser b -> PreprocParser a
tryVX a b = try (do { av <- a; b; return av }) tryVX a b = try (do { av <- a; b; return av })
tryVV :: PreprocParser a -> PreprocParser b -> PreprocParser (a, b)
tryVV a b = try (do { av <- a; bv <- b; return (av, bv) })
literal :: PreprocParser PreprocDef literal :: PreprocParser PreprocDef
literal literal
= do { ds <- lexeme $ many1 digit; return $ PreprocInt ds } = (lexeme $ do { ds <- many1 digit; return $ PreprocInt ds })
<|> do { char '"'; s <- manyTill anyChar $ char '"'; return $ PreprocString s } <|> (lexeme $ do { char '"'; s <- manyTill anyChar $ char '"'; return $ PreprocString s })
<?> "preprocessor literal" <?> "preprocessor literal"
defineDirective :: PreprocParser (String, PreprocDef) defineDirective :: PreprocParser (String, PreprocDef)
@ -279,25 +290,68 @@ defineDirective
defined :: PreprocParser Bool defined :: PreprocParser Bool
defined defined
= do symbol "DEFINED" = do symbol "DEFINED"
i <- parens identifier var <- parens identifier
definitions <- getState definitions <- getState
return $ Map.member i definitions return $ Map.member var definitions
operand :: PreprocParser Bool simpleExpression :: PreprocParser Bool
operand simpleExpression
= do { try $ symbol "NOT"; e <- expression; return $ not e } = do { try $ symbol "NOT"; e <- expression; return $ not e }
<|> do { try $ symbol "TRUE"; return True } <|> do { try $ symbol "TRUE"; return True }
<|> do { try $ symbol "FALSE"; return False } <|> do { try $ symbol "FALSE"; return False }
<|> defined <|> defined
<|> parens expression <|> parens expression
<?> "preprocessor simple expression"
operand :: PreprocParser PreprocDef
operand
= literal
<|> do var <- identifier
definitions <- getState
case Map.lookup var definitions of
Nothing -> fail $ var ++ " is not defined"
Just PreprocNothing -> fail $ var ++ " is defined, but has no value"
Just value -> return value
<?> "preprocessor operand" <?> "preprocessor operand"
comparisonOp :: PreprocParser String
comparisonOp
= choice [do { try $ reservedOp op; return op } | op <- reservedOps]
<?> "preprocessor comparison operator"
-- | Apply a comparison operator to two values, checking the types are
-- appropriate.
applyComparison :: String -> PreprocDef -> PreprocDef -> PreprocParser Bool
applyComparison op (PreprocString l) (PreprocString r)
= case op of
"=" -> return $ l == r
"<>" -> return $ l /= r
_ -> fail "Invalid operator for string comparison"
applyComparison op (PreprocInt l) (PreprocInt r)
= do lv <- getInt l
rv <- getInt r
case op of
"=" -> return $ lv == rv
"<>" -> return $ lv /= rv
"<" -> return $ lv < rv
"<=" -> return $ lv <= rv
">" -> return $ lv > rv
">=" -> return $ lv >= rv
where
getInt :: String -> PreprocParser Int
getInt s
= case readDec s of
[(v, "")] -> return v
_ -> fail $ "Bad integer literal: " ++ s
applyComparison _ _ _ = fail "Invalid types for comparison"
expression :: PreprocParser Bool expression :: PreprocParser Bool
expression expression
= do { l <- tryVX operand (symbol "AND"); r <- operand; return $ l && r } = do { l <- tryVX simpleExpression (symbol "AND"); r <- simpleExpression; return $ l && r }
<|> do { l <- tryVX operand (symbol "OR"); r <- operand; return $ l || r } <|> do { l <- tryVX simpleExpression (symbol "OR"); r <- simpleExpression; return $ l || r }
<|> operand <|> do { (l, op) <- tryVV operand comparisonOp; r <- operand; applyComparison op l r }
<?> "preprocessor expression" <|> simpleExpression
<?> "preprocessor complex expression"
-- | Match a 'PreprocParser' production. -- | Match a 'PreprocParser' production.
runPreprocParser :: Meta -> PreprocParser a -> String -> PassM a runPreprocParser :: Meta -> PreprocParser a -> String -> PassM a

View File

@ -113,6 +113,19 @@ testIf = TestLabel "testIf" $ TestList
, testPPCond 1280 "(TRUE AND FALSE) OR (FALSE AND TRUE)" False , testPPCond 1280 "(TRUE AND FALSE) OR (FALSE AND TRUE)" False
, testPPCond 1290 "(TRUE OR FALSE) AND (FALSE OR TRUE)" True , testPPCond 1290 "(TRUE OR FALSE) AND (FALSE OR TRUE)" True
, testPPCond 1300 "NOT (FALSE AND TRUE)" True , testPPCond 1300 "NOT (FALSE AND TRUE)" True
, testPPCond 1310 "3 < 4" True
, testPPCond 1320 "3 > 4" False
, testPPCond 1330 "3 <> 4" True
, testPPCond 1340 "3 = 4" False
, testPPCond 1350 "4 <= 4" True
, testPPCond 1360 "3 <= 4" True
, testPPCond 1370 "4 >= 4" True
, testPPCond 1380 "5 >= 4" True
, testPPCond 1390 "\"foo\" = \"foo\"" True
, testPPCond 1400 "\"foo\" <> \"foo\"" False
, testPPCond 1410 "\"foo\" = \"bar\"" False
, testPPCond 1420 "\"foo\" <> \"bar\"" True
, testPPCond 1430 "((3 > 4) OR (42 = 24)) AND (1 <= 2)" False
-- Invalid conditionals -- Invalid conditionals
, testPPFail 1900 [tp "#IF you can keep your head when all about you...", eol] , testPPFail 1900 [tp "#IF you can keep your head when all about you...", eol]
@ -122,6 +135,8 @@ testIf = TestLabel "testIf" $ TestList
, testPPFail 1940 [tp "#IF (TRUE", eol, tp "#ENDIF", eol] , testPPFail 1940 [tp "#IF (TRUE", eol, tp "#ENDIF", eol]
, testPPFail 1950 [tp "#ELSE", eol] , testPPFail 1950 [tp "#ELSE", eol]
, testPPFail 1960 [tp "#ENDIF", eol] , testPPFail 1960 [tp "#ENDIF", eol]
, testPPFail 1970 [tp "#IF 3 = \"foo\"", eol, tp "#ENDIF", eol]
, testPPFail 1980 [tp "#IF \"foo\" > \"bar\"", eol, tp "#ENDIF", eol]
] ]
where where
ti = TokIdentifier ti = TokIdentifier
@ -148,9 +163,15 @@ testDefine = TestLabel "testDefine" $ TestList
, testPPCond 2140 "DEFINED (COMPILER.TOCK)" True , testPPCond 2140 "DEFINED (COMPILER.TOCK)" True
, testPPCond 2150 "NOT DEFINED (COMPILER.TOCK)" False , testPPCond 2150 "NOT DEFINED (COMPILER.TOCK)" False
-- Conditions involving macros
, testPPCondAfter 2200 [tp "#DEFINE FOO 42", eol] "FOO = 42" True
, testPPCondAfter 2210 [tp "#DEFINE FOO 42", eol] "FOO <> 42" False
, testPPCondAfter 2220 [tp "#DEFINE FOO \"bar\"", eol] "FOO = \"bar\"" True
, testPPCondAfter 2230 [tp "#DEFINE FOO \"baz\"", eol] "FOO = \"bar\"" False
-- Expansion -- Expansion
, testPP 2200 [tp "#DEFINE FOO \"bar\"", eol, hh, ti "FOO"] [TokStringLiteral "bar"] , testPP 2600 [tp "#DEFINE FOO \"bar\"", eol, hh, ti "FOO"] [TokStringLiteral "bar"]
, testPP 2210 [tp "#DEFINE FOO 1234", eol, hh, ti "FOO"] [TokIntLiteral "1234"] , testPP 2610 [tp "#DEFINE FOO 1234", eol, hh, ti "FOO"] [TokIntLiteral "1234"]
-- Invalid definitions -- Invalid definitions
, testPPFail 2900 [tp "#DEFINE FOO", eol, tp "#DEFINE FOO", eol] , testPPFail 2900 [tp "#DEFINE FOO", eol, tp "#DEFINE FOO", eol]