packages feed

verismith-1.1.0: src/Verismith/Verilog/Parser.hs

-- |
-- Module      : Verismith.Verilog.Parser
-- Description : Minimal Verilog parser to reconstruct the AST.
-- Copyright   : (c) 2019-2022, Yann Herklotz
-- License     : GPL-3
-- Maintainer  : yann [at] yannherklotz [dot] com
-- Stability   : experimental
-- Portability : POSIX
--
-- Minimal Verilog parser to reconstruct the AST. This parser does not support the
-- whole Verilog syntax, as the AST does not support it either.
module Verismith.Verilog.Parser
  ( -- * Parser
    parseVerilog,
    parseVerilogFile,
    parseSourceInfoFile,

    -- ** Internal parsers
    parseEvent,
    parseStatement,
    parseModItem,
    parseModDecl,
    Parser,
  )
where

import Control.Lens
import Control.Monad (void)
import Data.Bifunctor (bimap)
import Data.Bits
import Data.Functor (($>))
import Data.Functor.Identity (Identity)
import Data.List (isInfixOf, isPrefixOf, null)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Text.Parsec hiding (satisfy)
import Text.Parsec.Expr
import Verismith.Utils
import Verismith.Verilog.AST
import Verismith.Verilog.BitVec
import Verismith.Verilog.Internal
import Verismith.Verilog.Lex
import Verismith.Verilog.Preprocess
import Verismith.Verilog.Token

type Parser = Parsec [Token] ()

type ParseOperator = Operator [Token] () Identity

data Decimal = Decimal Int Integer

instance Num Decimal where
  (Decimal sa na) + (Decimal sb nb) = Decimal (max sa sb) (na + nb)
  (Decimal sa na) - (Decimal sb nb) = Decimal (max sa sb) (na - nb)
  (Decimal sa na) * (Decimal sb nb) = Decimal (max sa sb) (na * nb)
  negate (Decimal s n) = Decimal s $ negate n
  abs (Decimal s n) = Decimal s $ abs n
  signum (Decimal s n) = Decimal s $ signum n
  fromInteger = Decimal 32 . fromInteger

-- | This parser succeeds whenever the given predicate returns true when called
-- with parsed `Token`. Same as 'Text.Parsec.Char.satisfy'.
satisfy :: (Token -> Bool) -> Parser TokenName
satisfy f = tokenPrim show nextPos tokeq
  where
    tokeq :: Token -> Maybe TokenName
    tokeq t@(Token t' _ _) = if f t then Just t' else Nothing

satisfy' :: (Token -> Maybe a) -> Parser a
satisfy' = tokenPrim show nextPos

nextPos :: SourcePos -> Token -> [Token] -> SourcePos
nextPos pos _ (Token _ _ (Position _ l c) : _) =
  setSourceColumn (setSourceLine pos l) c
nextPos pos _ [] = pos

-- | Parses given `TokenName`.
tok :: TokenName -> Parser TokenName
tok t = satisfy (\(Token t' _ _) -> t' == t) <?> show t

-- | Parse without returning the `TokenName`.
tok' :: TokenName -> Parser ()
tok' p = void $ tok p

parens :: Parser a -> Parser a
parens = between (tok SymParenL) (tok SymParenR)

brackets :: Parser a -> Parser a
brackets = between (tok SymBrackL) (tok SymBrackR)

braces :: Parser a -> Parser a
braces = between (tok SymBraceL) (tok SymBraceR)

sBinOp :: BinaryOperator -> Expr -> Expr -> Expr
sBinOp = sOp BinOp where sOp f b a = f a b

parseExpr' :: Parser Expr
parseExpr' = buildExpressionParser parseTable parseTerm <?> "expr"

decToExpr :: Decimal -> Expr
decToExpr (Decimal s n) = Number $ bitVec s n

-- | Parse a Number depending on if it is in a hex or decimal form. Octal and
-- binary are not supported yet.
parseNum :: Parser Expr
parseNum = decToExpr <$> number

parseVar :: Parser Expr
parseVar = Id <$> identifier

parseVecSelect :: Parser Expr
parseVecSelect = do
  i <- identifier
  expr <- brackets parseExpr
  return $ VecSelect i expr

parseRangeSelect :: Parser Expr
parseRangeSelect = do
  i <- identifier
  range <- parseRange
  return $ RangeSelect i range

systemFunc :: Parser String
systemFunc = satisfy' matchId
  where
    matchId (Token IdSystem s _) = Just s
    matchId _ = Nothing

parseFun :: Parser Expr
parseFun = do
  f <- systemFunc
  expr <- parens parseExpr
  return $ Appl (Identifier $ T.pack f) expr

parserNonEmpty :: [a] -> Parser (NonEmpty a)
parserNonEmpty (a : b) = return $ a :| b
parserNonEmpty [] = fail "Concatenation cannot be empty."

parseTerm :: Parser Expr
parseTerm =
  parens parseExpr
    <|> (Concat <$> (braces (commaSep parseExpr) >>= parserNonEmpty))
    <|> parseFun
    <|> parseNum
    <|> try parseVecSelect
    <|> try parseRangeSelect
    <|> parseVar
    <?> "simple expr"

-- | Parses the ternary conditional operator. It will behave in a right
-- associative way.
parseCond :: Expr -> Parser Expr
parseCond e = do
  tok' SymQuestion
  expr <- parseExpr
  tok' SymColon
  Cond e expr <$> parseExpr

parseExpr :: Parser Expr
parseExpr = do
  e <- parseExpr'
  option e . try $ parseCond e

parseConstExpr :: Parser ConstExpr
parseConstExpr = fmap exprToConst parseExpr

-- | Table of binary and unary operators that encode the right precedence for
-- each.
parseTable :: [[ParseOperator Expr]]
parseTable =
  [ [prefix SymBang (UnOp UnLNot), prefix SymTildy (UnOp UnNot)],
    [ prefix SymAmp (UnOp UnAnd),
      prefix SymBar (UnOp UnOr),
      prefix SymTildyAmp (UnOp UnNand),
      prefix SymTildyBar (UnOp UnNor),
      prefix SymHat (UnOp UnXor),
      prefix SymTildyHat (UnOp UnNxor),
      prefix SymHatTildy (UnOp UnNxorInv)
    ],
    [prefix SymPlus (UnOp UnPlus), prefix SymDash (UnOp UnMinus)],
    [binary SymAsterAster (sBinOp BinPower) AssocRight],
    [ binary SymAster (sBinOp BinTimes) AssocLeft,
      binary SymSlash (sBinOp BinDiv) AssocLeft,
      binary SymPercent (sBinOp BinMod) AssocLeft
    ],
    [ binary SymPlus (sBinOp BinPlus) AssocLeft,
      binary SymDash (sBinOp BinMinus) AssocLeft
    ],
    [ binary SymLtLt (sBinOp BinLSL) AssocLeft,
      binary SymGtGt (sBinOp BinLSR) AssocLeft
    ],
    [ binary SymLtLtLt (sBinOp BinASL) AssocLeft,
      binary SymGtGtGt (sBinOp BinASR) AssocLeft
    ],
    [ binary SymLt (sBinOp BinLT) AssocNone,
      binary SymGt (sBinOp BinGT) AssocNone,
      binary SymLtEq (sBinOp BinLEq) AssocNone,
      binary SymGtEq (sBinOp BinGEq) AssocNone
    ],
    [ binary SymEqEq (sBinOp BinEq) AssocNone,
      binary SymBangEq (sBinOp BinNEq) AssocNone
    ],
    [ binary SymEqEqEq (sBinOp BinEq) AssocNone,
      binary SymBangEqEq (sBinOp BinNEq) AssocNone
    ],
    [binary SymAmp (sBinOp BinAnd) AssocLeft],
    [ binary SymHat (sBinOp BinXor) AssocLeft,
      binary SymHatTildy (sBinOp BinXNor) AssocLeft,
      binary SymTildyHat (sBinOp BinXNorInv) AssocLeft
    ],
    [binary SymBar (sBinOp BinOr) AssocLeft],
    [binary SymAmpAmp (sBinOp BinLAnd) AssocLeft],
    [binary SymBarBar (sBinOp BinLOr) AssocLeft]
  ]

binary :: TokenName -> (a -> a -> a) -> Assoc -> ParseOperator a
binary name fun = Infix ((tok name <?> "binary") >> return fun)

prefix :: TokenName -> (a -> a) -> ParseOperator a
prefix name fun = Prefix ((tok name <?> "prefix") >> return fun)

commaSep :: Parser a -> Parser [a]
commaSep = flip sepBy $ tok SymComma

toNE :: Parser [a] -> Parser (NonEmpty a)
toNE p = do
  p' <- p
  case p' of
    a : b -> return $ a :| b
    _ -> fail "List is empty."

commaSepNE :: Parser a -> Parser (NonEmpty a)
commaSepNE = toNE . commaSep

parseContAssign :: Parser ContAssign
parseContAssign = do
  var <- tok KWAssign *> identifier
  expr <- tok SymEq *> parseExpr
  tok' SymSemi
  return $ ContAssign var expr

numLit :: Parser String
numLit = satisfy' matchId
  where
    matchId (Token LitNumber s _) = Just s
    matchId _ = Nothing

number :: Parser Decimal
number = number' <$> numLit
  where
    number' :: String -> Decimal
    number' a
      | all (`elem` ['0' .. '9']) a = fromInteger $ read a
      | head a == '\'' = fromInteger $ f a
      | "'" `isInfixOf` a = Decimal (read w) (f b)
      | otherwise = error $ "Invalid number format: " ++ a
      where
        w = takeWhile (/= '\'') a
        b = dropWhile (/= '\'') a
        f a'
          | "'d" `isPrefixOf` a' = read $ drop 2 a'
          | "'h" `isPrefixOf` a' = read $ "0x" ++ drop 2 a'
          | "'b" `isPrefixOf` a' =
            foldl
              (\n b' -> shiftL n 1 .|. (if b' == '1' then 1 else 0))
              0
              (drop 2 a')
          | otherwise = error $ "Invalid number format: " ++ a'

-- toInteger' :: Decimal -> Integer
-- toInteger' (Decimal _ n) = n

toInt' :: Decimal -> Int
toInt' (Decimal _ n) = fromInteger n

-- | Parse a range and return the total size. As it is inclusive, 1 has to be
-- added to the difference.
parseRange :: Parser Range
parseRange = do
  rangeH <- tok SymBrackL *> parseConstExpr
  rangeL <- tok SymColon *> parseConstExpr
  tok' SymBrackR
  return $ Range rangeH rangeL

strId :: Parser String
strId = satisfy' matchId
  where
    matchId (Token IdSimple s _) = Just s
    matchId (Token IdEscaped s _) = Just s
    matchId _ = Nothing

identifier :: Parser Identifier
identifier = Identifier . T.pack <$> strId

parseNetDecl :: Maybe PortDir -> Parser (ModItem ann)
parseNetDecl pd = do
  t <- option Wire type_
  sign <- option False (tok KWSigned $> True)
  range <- option 1 parseRange
  name <- identifier
  i <- option Nothing (fmap Just (tok' SymEq *> parseConstExpr))
  tok' SymSemi
  return $ Decl pd (Port t sign range name) i
  where
    type_ = tok KWWire $> Wire <|> tok KWReg $> Reg

parsePortDir :: Parser PortDir
parsePortDir =
  tok KWOutput
    $> PortOut
    <|> tok KWInput
      $> PortIn
    <|> tok KWInout
      $> PortInOut

parseDecl :: Parser (ModItem ann)
parseDecl = (Just <$> parsePortDir >>= parseNetDecl) <|> parseNetDecl Nothing

parseConditional :: Parser (Statement ann)
parseConditional = do
  expr <- tok' KWIf *> parens parseExpr
  true <- maybeEmptyStatement
  false <- option Nothing (tok' KWElse *> maybeEmptyStatement)
  return $ CondStmnt expr true false

parseLVal :: Parser LVal
parseLVal = fmap RegConcat (braces $ commaSep parseExpr) <|> ident
  where
    ident = do
      i <- identifier
      (try (ex i) <|> try (sz i) <|> return (RegId i))
    ex i = do
      e <- tok' SymBrackL *> parseExpr
      tok' SymBrackR
      return $ RegExpr i e
    sz i = RegSize i <$> parseRange

parseDelay :: Parser Delay
parseDelay = Delay . toInt' <$> (tok' SymPound *> number)

parseAssign :: TokenName -> Parser Assign
parseAssign t = do
  lval <- parseLVal
  tok' t
  delay <- option Nothing (fmap Just parseDelay)
  expr <- parseExpr
  return $ Assign lval delay expr

parseLoop :: Parser (Statement ann)
parseLoop = do
  a <- tok' KWFor *> tok' SymParenL *> parseAssign SymEq
  expr <- tok' SymSemi *> parseExpr
  incr <- tok' SymSemi *> parseAssign SymEq
  tok' SymParenR
  statement <- parseStatement
  return $ ForLoop a expr incr statement

parseDefaultPair :: Parser (Statement a)
parseDefaultPair = tok' KWDefault *> tok' SymColon *> parseStatement

parseCasePair :: Parser (CasePair ann)
parseCasePair = do
  expr <- parseExpr <* tok' SymColon
  CasePair expr <$> parseStatement

parseCase :: Parser (Statement ann)
parseCase = do
  expr <- tok' KWCase *> parseExpr
  cp <- manyTill parseCasePair (lookAhead ((parseDefaultPair $> ()) <|> tok' KWEndcase))
  def <- option Nothing $ Just <$> parseDefaultPair
  tok' KWEndcase
  return (StmntCase CaseStandard expr cp def)

eventList :: TokenName -> Parser [Event]
eventList t = do
  l <- sepBy parseEvent' (tok t)
  if null l then fail "Could not parse list" else return l

parseEvent :: Parser Event
parseEvent =
  (tok' SymAtAster $> EAll)
    <|> try (tok' SymAt *> tok' SymParenLAsterParenR $> EAll)
    <|> try
      ( tok' SymAt
          *> parens (tok' SymAster)
          $> EAll
      )
    <|> try (tok' SymAt *> parens parseEvent')
    <|> try (tok' SymAt *> parens (foldr1 EOr <$> eventList KWOr))
    <|> try (tok' SymAt *> parens (foldr1 EComb <$> eventList SymComma))

parseEvent' :: Parser Event
parseEvent' =
  try (tok' KWPosedge *> fmap EPosEdge identifier)
    <|> try (tok' KWNegedge *> fmap ENegEdge identifier)
    <|> try (fmap EId identifier)
    <|> try (fmap EExpr parseExpr)

parseEventCtrl :: Parser (Statement ann)
parseEventCtrl = do
  event <- parseEvent
  statement <- option Nothing maybeEmptyStatement
  return $ EventCtrl event statement

parseDelayCtrl :: Parser (Statement ann)
parseDelayCtrl = do
  delay <- parseDelay
  statement <- option Nothing maybeEmptyStatement
  return $ TimeCtrl delay statement

parseBlocking :: Parser (Statement ann)
parseBlocking = do
  a <- parseAssign SymEq
  tok' SymSemi
  return $ BlockAssign a

parseNonBlocking :: Parser (Statement ann)
parseNonBlocking = do
  a <- parseAssign SymLtEq
  tok' SymSemi
  return $ NonBlockAssign a

parseSeq :: Parser (Statement ann)
parseSeq = do
  seq' <- tok' KWBegin *> many parseStatement
  tok' KWEnd
  return $ SeqBlock seq'

parseStatement :: Parser (Statement ann)
parseStatement =
  parseSeq
    <|> parseConditional
    <|> parseLoop
    <|> parseCase
    <|> parseEventCtrl
    <|> parseDelayCtrl
    <|> try parseBlocking
    <|> parseNonBlocking

maybeEmptyStatement :: Parser (Maybe (Statement ann))
maybeEmptyStatement =
  (tok' SymSemi >> return Nothing) <|> (Just <$> parseStatement)

parseAlways :: Parser (ModItem ann)
parseAlways = tok' KWAlways *> (Always <$> parseStatement)

parseInitial :: Parser (ModItem ann)
parseInitial = tok' KWInitial *> (Initial <$> parseStatement)

namedModConn :: Parser ModConn
namedModConn = do
  target <- tok' SymDot *> identifier
  expr <- parens parseExpr
  return $ ModConnNamed target expr

parseModConn :: Parser ModConn
parseModConn = try (fmap ModConn parseExpr) <|> namedModConn

parseModInst :: Parser (ModItem ann)
parseModInst = do
  m <- identifier
  params <- option [] $ tok' SymPound *> parens (commaSep parseModConn)
  name <- identifier
  modconns <- parens (commaSep parseModConn)
  tok' SymSemi
  return $ ModInst m params name modconns

parseParam :: Parser Parameter
parseParam = do
  i <- tok' KWParameter *> identifier
  expr <- tok' SymEq *> parseConstExpr
  return $ Parameter i expr

parseParam' :: Parser Parameter
parseParam' = do
  i <- identifier
  expr <- tok' SymEq *> parseConstExpr
  return $ Parameter i expr

parseParams :: Parser [Parameter]
parseParams = tok' SymPound *> parens (commaSep parseParam)

parseParamDecl :: Parser (ModItem ann)
parseParamDecl =
  ParamDecl <$> (tok' KWParameter *> commaSepNE parseParam' <* tok' SymSemi)

parseModItem :: Parser (ModItem ann)
parseModItem =
  try (ModCA <$> parseContAssign)
    <|> try parseDecl
    <|> parseAlways
    <|> parseInitial
    <|> parseModInst
    <|> parseParamDecl

parseModList :: Parser [Identifier]
parseModList = list <|> return [] where list = parens $ commaSep identifier

filterDecl :: PortDir -> (ModItem ann) -> Bool
filterDecl p (Decl (Just p') _ _) = p == p'
filterDecl _ _ = False

modPorts :: PortDir -> [ModItem ann] -> [Port]
modPorts p mis = filter (filterDecl p) mis ^.. traverse . declPort

parseModDecl :: Parser (ModDecl ann)
parseModDecl = do
  name <- tok KWModule *> identifier
  paramList <- option [] $ try parseParams
  _ <- fmap defaultPort <$> parseModList
  tok' SymSemi
  modItem <- option [] . try $ many1 parseModItem
  tok' KWEndmodule
  return $
    ModDecl
      name
      (modPorts PortOut modItem)
      (modPorts PortIn modItem)
      modItem
      paramList

mergeMaybe :: Maybe a -> Maybe a -> Maybe a
mergeMaybe (Just a) Nothing = Just a
mergeMaybe Nothing (Just a) = Just a
mergeMaybe a _ = a

mergeType :: PortType -> PortType -> PortType
mergeType Reg Wire = Reg
mergeType Wire Reg = Reg
mergeType a _ = a

mergePorts :: Port -> Port -> Port
mergePorts (Port t1 s1 r1 n1) (Port t2 s2 r2 n2) =
  Port (mergeType t1 t2) (s1 || s2) (if r1 == 0 then r2 else r1) n1

mergeIO :: ModItem a -> ModItem a -> ModItem a
mergeIO (Decl a1 b1 c1) (Decl a2 b2 c2) = Decl (mergeMaybe a1 a2) (mergePorts b1 b2) (mergeMaybe c1 c2)
mergeIO a _ = a

genmoditem :: Map.Map Identifier (ModItem a) -> ModItem a -> Map.Map Identifier (ModItem a)
genmoditem m (Decl a b c) =
  Map.insertWith mergeIO (b ^. portName) (Decl a b c) m
genmoditem m b = m

modifyelements :: [ModItem a] -> [ModItem a]
modifyelements ma = ndecl <> nodecl
  where
    ndecl = Map.elems $ foldl genmoditem Map.empty ma
    isDecl Decl {} = True
    isDecl _ = False
    nodecl = filter isDecl ma

-- | Parses a 'String' into 'Verilog' by skipping any beginning whitespace
-- and then parsing multiple Verilog source.
parseVerilogSrc :: Parser (Verilog ann)
parseVerilogSrc = Verilog <$> many parseModDecl

-- | Parse a 'String' containing verilog code. The parser currently only supports
-- the subset of Verilog that is being generated randomly.
parseVerilog ::
  -- | Name of parsed object.
  Text ->
  -- | Content to be parsed.
  Text ->
  -- | Returns 'String' with error
  -- message if parse fails.
  Either Text (Verilog ann)
parseVerilog s =
  bimap showT id -- (_Wrapped.traverse.modItems %~ modifyelements)
    . parse parseVerilogSrc (T.unpack s)
    . alexScanTokens
    . preprocess [] (T.unpack s)
    . T.unpack

parseVerilogFile :: Text -> IO (Verilog ann)
parseVerilogFile file = do
  src <- T.readFile $ T.unpack file
  case parseVerilog file src of
    Left s -> error $ T.unpack s
    Right r -> return r

parseSourceInfoFile :: Text -> Text -> IO (SourceInfo ann)
parseSourceInfoFile top = fmap (SourceInfo top) . parseVerilogFile