Commit 78f3db88 by Rupert Swarbrick Committed by Zachary Snow

Fail more gracefully on mismatched `if/`elsif/`else/`endif

Without this patch, a stray `endif caused sv2v to take the tail of an
empty list. Now we raise a lexical error.

To do this, the patch pulls the "push" and "pop" logic for the
preprocessor condition stack into separate helper
functions (pushCondStack and popCondStack). It also defines some
helper functions for manipulating Cond's.

The patch also adds a documentation comment explaining what the
different values of Code meant (because I had to squint to work it
out).
parent 5ad48494
...@@ -41,11 +41,30 @@ data PP = PP ...@@ -41,11 +41,30 @@ data PP = PP
-- keeps track of the state of an if-else cascade level -- keeps track of the state of an if-else cascade level
data Cond data Cond
= CurrentlyTrue = CurrentlyTrue -- an active if/elsif/else branch (condition is met)
| PreviouslyTrue | PreviouslyTrue -- an inactive else/elsif block due to an earlier if/elsif
| NeverTrue | NeverTrue -- an inactive if/elsif block; a subsequent else will be met
deriving (Eq, Show) deriving (Eq, Show)
-- update a Cond for an `else block, where this block is active if and only if
-- no previous block was active
elseCond :: Cond -> Cond
elseCond NeverTrue = CurrentlyTrue
elseCond _ = NeverTrue
-- generate a Cond for an `if/`elsif that is not part of a PreviouslyTrue chain
ifCond :: Bool -> Cond
ifCond True = CurrentlyTrue
ifCond False = NeverTrue
-- update a Cond for an `elsif block. The boolean argument is whether the
-- `elsif block's test is true.
elsifCond :: Bool -> Cond -> Cond
elsifCond defined c =
case c of
NeverTrue -> ifCond defined
_ -> PreviouslyTrue
-- preprocessor entrypoint -- preprocessor entrypoint
preprocess :: [String] -> Env -> FilePath -> IO (Either String ([(Char, Position)], Env)) preprocess :: [String] -> Env -> FilePath -> IO (Either String ([(Char, Position)], Env))
preprocess includePaths env path = do preprocess includePaths env path = do
...@@ -157,6 +176,19 @@ getBuffer = do ...@@ -157,6 +176,19 @@ getBuffer = do
p <- getPosition p <- getPosition
return (x, p) return (x, p)
-- Push a condition onto the top of the preprocessor condition stack
pushCondStack :: Cond -> PPS ()
pushCondStack c = getCondStack >>= setCondStack . (c :)
-- Pop the top from the preprocessor condition stack
popCondStack :: String -> PPS Cond
popCondStack directive = do
cs <- getCondStack
case cs of
[] -> lexicalError $
"`" ++ directive ++ " directive outside of an `if/`endif block"
c : cs' -> setCondStack cs' >> return c
isIdentChar :: Char -> Bool isIdentChar :: Char -> Bool
isIdentChar ch = isIdentChar ch =
('a' <= ch && ch <= 'z') || ('a' <= ch && ch <= 'z') ||
...@@ -677,36 +709,22 @@ handleDirective macrosOnly = do ...@@ -677,36 +709,22 @@ handleDirective macrosOnly = do
"ifdef" -> do "ifdef" -> do
dropSpaces dropSpaces
name <- takeIdentifier name <- takeIdentifier
let newCond = if Map.member name env pushCondStack $ ifCond $ Map.member name env
then CurrentlyTrue
else NeverTrue
setCondStack $ newCond : condStack
"ifndef" -> do "ifndef" -> do
dropSpaces dropSpaces
name <- takeIdentifier name <- takeIdentifier
let newCond = if Map.notMember name env pushCondStack $ ifCond $ Map.notMember name env
then CurrentlyTrue
else NeverTrue
setCondStack $ newCond : condStack
"else" -> do "else" -> do
let newCond = if head condStack == NeverTrue c <- popCondStack "else"
then CurrentlyTrue pushCondStack $ elseCond c
else NeverTrue
setCondStack $ newCond : tail condStack
"elsif" -> do "elsif" -> do
dropSpaces dropSpaces
name <- takeIdentifier name <- takeIdentifier
let currCond = head condStack c <- popCondStack "elsif"
let newCond = pushCondStack $ elsifCond (Map.member name env) c
if currCond /= NeverTrue then
PreviouslyTrue
else if Map.member name env then
CurrentlyTrue
else
NeverTrue
setCondStack $ newCond : tail condStack
"endif" -> do "endif" -> do
setCondStack $ tail condStack _ <- popCondStack "endif"
return ()
"define" -> do "define" -> do
dropSpaces dropSpaces
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment