packages feed

cparsing-0.1.0.0: MiniC/Parser.hs

{-# LANGUAGE TupleSections, FlexibleContexts #-}

-- | The parser that generates the Abstract Syntax Tree that is represented in 'MiniC.AST'.
-- C language elements are highly recursive, so this module could only be broken up
-- with a lot of effort and increasing complexity with passing functions arguments.
-- Also the lexical analysis of C requires knowledge about the defined symbols 
-- (this is the so called "Lexer Hack"), so registering and querying of symbol table elements
-- is also incorporated into the parser.
module MiniC.Parser where

import SourceCode.ASTElems
import SourceCode.Parsec
import SourceCode.SourceInfo
import SourceCode.ASTNode
import MiniC.AST
import MiniC.Representation
import MiniC.Semantics(BI)
import MiniC.Instances
import MiniC.Helpers
import MiniC.Parser.Lexical
import MiniC.Parser.Expr
import MiniC.Parser.Base
import MiniC.SourceNotation

import GHC.Generics (Generic)
import Text.Parsec
import Text.Parsec.ExtraCombinators
import Data.Maybe
import Control.Lens
import Control.Monad
import Control.Applicative hiding ((<|>), many, optional)
import Debug.Trace

-- * Declarations

translationUnit :: CParser TranslationUnitBI
translationUnit = withInfo $ TranslationUnit <$> astMany declaration
 
declaration :: CParser DeclarationBI
declaration 
  = (FuncDecl <$> try functionDeclaration)
       -- function declarations can be parsed as variable declarations
       <|> (VarDecl <$> try variableDeclaration)
       -- type declaration can be a prefix of variable declaration
       <|> (TypeDecl <$> typeDefinition)
              
typeDefinition :: CParser TypeDefinitionBI
typeDefinition 
  = withInfo (TypeDefinition <$> definedType <* semicolon) >>= registerDeclaration 
       
functionDeclaration :: CParser FunctionDeclarationBI
functionDeclaration 
  = withInfo (
      do storageSpec <- astMany storageSpecifier
         funcType <- unqualifiedFunctionType
         asmSpec <- astOptionMaybe asmSpecifier
         -- register the function early, to be able to recursively call it
         let funDecl = FunctionDeclaration storageSpec funcType asmSpec
         withInfo (pure (funDecl ASTNothing)) >>= registerDeclaration
         -- return the function declaration with body
         funDecl <$> ((return ASTNothing <* semicolon) 
                        <|> (ASTJust <$> insideScope' (getId funcType) statement))
      ) >>= registerDeclaration 
                          
variableDeclaration :: CParser VariableDeclarationBI
variableDeclaration 
  = -- other things can be parsed as variableDeclaration, but then there is no id
    onlyWhen "No id in variable definition" 
             (\vd -> not ( null $ astGetList $ _varDeclDefinitions vd ) 
                      || isJust (getId $ _qualTypQual (_varDeclQualTyp vd))) $ withInfo (
        do storage <- astMany storageSpecifier
           qualTyp <- try qualifiedFunctionType <|> nonFunctionTypeSpec
           -- to register all separate variables, the multi-declaration must supply additional data
           let registerDefinition vdef 
                 = view ast_triple_1 <$> registerDeclaration (ASTTriple vdef storage qualTyp (vdef ^. info))
           
           VariableDeclaration storage qualTyp
              <$> astOptionMaybe asmSpecifier
              <*> ((ASTJust <$> (symbol "=" *> expressionWithoutComma)) 
                            <|> (optional comma *> return ASTNothing))
              <*> (optional comma *> (variableDefinition >>= registerDefinition) `astSepBy` comma)
              <* semicolon
    ) >>= registerDeclaration
              
parameterDeclaration :: CParser ParameterDeclarationBI
parameterDeclaration 
  = withInfo (ParameterDeclaration <$> qualifiedType) >>= registerDeclaration
              
-- | Parses a variable definition, without type, possibly with qualifier and/or initialization
variableDefinition :: CParser VariableDefinitionBI
variableDefinition = withInfo ( 
  VariableDefinition <$> typeQualifierWithId
                     <*> astOptionMaybe asmSpecifier
                     <*> astOptionMaybe (symbol "=" *> expressionWithoutComma)
  )

                            
storageSpecifier :: CParser StorageSpecifierBI
storageSpecifier = withInfo $ (reserved "auto" *> return Auto) 
               <|> (reserved "register" *> return Register) 
               <|> (reserved "static" *> return Static) 
               <|> (reserved "extern" *> return Extern)
               <|> (reserved "inline" *> return Inline)
               
asmSpecifier :: CParser AsmSpecifierBI
asmSpecifier = withInfo $ AsmSpecifier <$> (reserved "asm" *> parens simpleStringLiteral)
               
-- * Types
   
-- | Parses a type with type qualifiers.
-- Examples: @int@, @int *@, @struct { ... }@, @int *a[10]@, @void f(...)@, ...
qualifiedType :: CParser QualifiedTypeBI
qualifiedType = try unqualifiedFunctionType <|> try qualifiedFunctionType <|> nonFunctionTypeSpec
          
-- | Examples: @void f()@, @int g(int a)@
unqualifiedFunctionType :: CParser QualifiedTypeBI
unqualifiedFunctionType = functionType scalarWithId

-- | Examples: @void (*f)()@, @void (*)()@, @void (f[10])()@
qualifiedFunctionType :: CParser QualifiedTypeBI
qualifiedFunctionType = functionType typeQualifiedIdentifier
        
-- | Parses a function type with a parser given to parse the qualified or unqualified function name.
functionType :: CParser TypeQualifierBI -> CParser QualifiedTypeBI
functionType qualParser 
  = withInfo $
      do retTyp <- returnTypeSpec
         qualId <- qualParser
         -- parameters are in the scope of the function
         let paramDecl = insideScope' (getId qualId) parameterDeclaration
         QualifiedType qualId <$>
            withInfo (FunType retTyp 
                           <$> withInfo (ASTWrapper <$> parens (paramDecl `astSepBy` comma)))      

-- | First parse a nonrecursive type, then parse any 
nonFunctionTypeSpec :: CParser QualifiedTypeBI
nonFunctionTypeSpec = withInfo $ 
                        flip applyQualTo <$> nonRecursiveTypeSpec 
                                         <*> typeQualifiedIdentifier 
           
-- | Parses a type without identifier. The identifier-related type qualifiers will
-- not be parsed unless there is no identifier.
-- Examples: @float@, @int*[10]@, @const int*[10]@
returnTypeSpec :: CParser QualifiedTypeBI
returnTypeSpec = withInfo $ 
                   flip applyQualTo <$> nonRecursiveTypeSpec 
                                    <*> typeQualifierNoId
              
-- | Type qualifiers with or without identifier
-- Examples: @*[10]@, @*a[10]@, @(*a)[10]@
typeQualifiedIdentifier :: CParser TypeQualifierBI
typeQualifiedIdentifier = try typeQualifierWithId
              <|> try (withInfo $ ParenQual <$> parens typeQualifierWithIdStrict)
              <|> withInfo (ParenQual <$> parens typeQualifierNoId)
              <|> typeQualifierNoId
         
-- | Examples: @*[10]@ in @int*[10]@          
-- Semantics: @int*[10]@ = array of ptrs to ints
typeQualifierNoId :: CParser TypeQualifierBI
typeQualifierNoId 
  = ((beforeQual <*> typeQualifierNoId) 
          <|> inheritInfo (ArrayQual <$> afterQual <*> typeQualifierNoId))
      <|> scalarNoId
               
              
-- | Type qualifiers with an identifier, for example @*a[10]@ in @int *a[10]@,
-- @*(*a[10])[10]@
-- Semantics: int **(*a)[2][3] = ptr to array[2] of array[3] of ptr to ptr to int
typeQualifierWithId :: CParser TypeQualifierBI
typeQualifierWithId 
  = let noParenWhenNotNeeded bef inner aft 
         = guard $ not (null bef && null aft 
                                 && case inner of ParenQual {} -> True; _ -> False)
     in typeQualifierWithId' noParenWhenNotNeeded

typeQualifierWithIdStrict 
  = let doesNeedParenOutside bef _ aft = guard $ not (null bef && null aft)
     in typeQualifierWithId' doesNeedParenOutside

typeQualifierWithId' guard
  = do before <- many beforeQual
       inner <- withInfo (ParenQual <$> parens typeQualifierWithId) 
                  <|> scalarWithId
       arrs <- many afterQual
       guard before inner arrs
       return (foldr ($) (foldl (\tq ar -> ArrayQual ar tq noNodeInfo) inner arrs) before)      
       
-- | Parses a qualifier that stand between the type and the id
beforeQual :: CParser (TypeQualifierBI -> TypeQualifierBI)
beforeQual = withInfo (flip <$> (reservedOp "*" *> pure PtrQual)) 
               <|> typBoundedQual
         
-- | Parses an array qualifier         
afterQual :: CParser ArrayTypeQualBI
afterQual 
  = withInfo $ brackets $ ArrayTypeQual 
               <$> astOptionMaybe ((typBoundedQual <*> scalarNoId)
                                              <|> withInfo (reserved "static" *> return StaticQual))
               <*> astOptionMaybe expression 

-- | Parses a qualifier that can happen before and after the type
typBoundedQual :: CParser (TypeQualifierBI -> TypeQualifierBI)
typBoundedQual = withInfo $ flip <$> (
   (reserved "const" *> pure ConstQual)
      <|> (reserved "volatile" *> pure VolatileQual)
      <|> (reserved "restrict" *> pure RestrictQual) )

-- | Parses a type that does not capture identifier and identifier-bounded type qualifiers
-- Examples : @void@, @const int@, @(const int* a[10])@
nonRecursiveTypeSpec :: CParser QualifiedTypeBI
nonRecursiveTypeSpec = 
   withInfo (QualifiedType <$> scalarNoId <*> unqualifiedType)
     <|> withInfo (applyQualTo <$> ( typBoundedQual <*> scalarNoId ) 
                               <*> nonRecursiveTypeSpec)
 
scalarNoId = withInfo $ pure (Scalar ASTNothing)
scalarWithId = withInfo (Scalar <$> ASTJust <$> identifier)
  
unqualifiedType :: CParser TypeBI
unqualifiedType = 
      withInfo (reserved "void" *> return VoidType)
  <|> withInfo (reserved "bool" *> return BoolType)
  <|> withInfo (IntType <$> integerSign <*> integerSize)
  <|> withInfo (FloatingType <$> floatingSize)
  <|> (TypeName <$> (identifier >>= checkIsTypeSymbol))
  <|> withInfo (reserved "typeof" *> parens ( (TypeOfExpr <$> expression) 
                                                 <|> (TypeOfType <$> try returnTypeSpec) ))
  <|> definedType
  
-- | A type that can be defined in a type definition. May have an identifier.
definedType :: CParser TypeBI
definedType = withInfo $ (StructType <$> (reserved "struct" *> structUnionType))
              <|> (UnionType <$> (reserved "union" *> structUnionType) )
              <|> (EnumType <$> (reserved "enum" *> enumeration) )       
              <|> (TypeDef <$> (reserved "typedef" *> qualifiedType) 
                           <*> astOptionMaybe identifier)  

integerSign :: CParser Sign
integerSign = (reserved "unsigned" *> return Unsigned)
                <|> (optional (reserved "signed") *> return Signed)
                              
integerSize :: CParser IntSize
integerSize = reserved "char" *> return Char 
  <|> reserved "short" *> optional (reserved "int") *> return Short 
  <|> reserved "int" *> return Int 
  <|> reserved "long" *> optional (reserved "int") *> return LongInt 
  <|> reserved "long" *> reserved "long" *> optional (reserved "int") *> return LongLongInt 

floatingSize :: CParser FloatingSize
floatingSize = reserved "float" *> return Float 
  <|> reserved "double" *> return Double 
  <|> reserved "long" *> reserved "double" *> return LongDouble

enumeration :: CParser EnumerationBI
enumeration 
  = withInfo $ Enumeration 
                 <$> astOptionMaybe identifier 
                 <*> astOptionMaybe ( braces (withInfo $ ASTCons <$> variant 
                                                                 <*> astMany (try $ comma *> variant) 
                                                                 <* optional comma))

variant :: CParser VariantBI
variant = withInfo $ Variant <$> identifier 
                      <*> astOptionMaybe (reservedOp "=" *> expression)

structUnionType :: CParser StructUnionBI
structUnionType = withInfo $ do
  id <- astOptionMaybe identifier
  StructUnion id <$> astOptionMaybe (braces (astMany (insideScope' (view astMaybe id) declaration)))
                              
-- * Statements

statement :: CParser StatementBI
statement = withInfo $
      try (Label <$> identifier <* colon <*> statement <?> "labeled statement")
  <|> (reserved "case" *> ( 
        try (CaseInterval <$> (ASTPair <$> expression <* reservedOp ".." <*> expression) <* colon <*> statement) 
          <|> (Case <$> expression <* colon <*> statement) ) )
  <|> (Default <$> (reserved "default" *> colon *> statement))
  <|> try (Expr <$> expression <* semicolon)
  <|> (Compound <$> (openBrace *> insideAnonymScope (astMany (try (astParseEither (try declaration) statement ))) <* closeBrace 
         <?> "compound statement"))
  <|> (If <$> (reserved "if" *> parens expression) <*> statement 
          <*> astOptionMaybe (reserved "else" *> statement))
  <|> (Switch <$> (reserved "switch" *> parens expression) <*> statement)
  <|> (While <$> (reserved "while" *> parens expression) <*> statement)
  <|> (DoWhile <$> (reserved "do" *> statement) 
               <*> (reserved "while" *> parens expression <* semicolon))
  <|> insideAnonymScope (
         For <$> (reserved "for" *> openParen 
                                 *> astOptionMaybe (astParseEither (try expression <* semicolon) declaration) )
             <*> astOptionMaybe expression <* semicolon
             <*> astOptionMaybe expression <* closeParen 
             <*> statement )
  <|> (Goto <$> (reserved "goto" *> expression) <* semicolon)
  <|> (reserved "continue" *> return Continue <* semicolon)
  <|> (reserved "break" *> return Break <* semicolon)
  <|> (Return <$> (reserved "return" *> astOptionMaybe expression) <* semicolon)
  <|> (semicolon *> return EmptyStmt <?> "empty instruction")
  <|> (AsmStmt <$> (reserved "asm" *> withInfo
        (InlineAssembly <$> ifAccept (reserved "volatile") 
                        <*> (openParen *> astMany (withInfo $ AssemblyString <$> simpleStringLiteral)) 
                        <*> astOptionMaybe (colon *> asmParams) 
                        <*> astOptionMaybe (colon *> asmParams) 
                        <*> astOptionMaybe (colon *> asmParams) ) <* closeParen <* semicolon ) )
  
asmParams :: CParser (ASTListBI AssemblyParam)
asmParams = withInfo (AssemblyParam <$> simpleStringLiteral 
                                    <*> astOptionMaybe (parens identifier)) `astSepBy` comma
                       
-- * Expressions

expression = chainl1 expressionWithoutComma 
                     (do operator <- withInfo (comma *> return CommaOp) 
                         return (\e1 e2 -> Binary operator e1 e2 noNodeInfo))
                     
-- | Expressions without comma need special treatment, because they
-- cannot appear in function calls.    
expressionWithoutComma = genExpression term

genExpression :: CParser ExpressionBI -> CParser ExpressionBI
genExpression term = buildExpressionParser precedenceTable (unaryExpressionWithSizeOf term) <?> "expression"
  where precedenceTable = 
          [ -- arithmetic operators
            [ binOpL "*" MulOp
            , binOpL "/" DivOp
            , binOpL "%" RemainderOp 
            ]
          , [ binOpL "+" AddOp
            , binOpL "-" SubOp
            ]
          -- bit shifting
          , [ binOpL ">>" ShiftLeftOp
            , binOpL "<<" ShiftRightOp
            ]
          -- compare operators
          , [ binOpL ">" LessOp
            , binOpL "<" GreaterOp
            , binOpL ">=" LessOrEqOp
            , binOpL "<=" GreaterOrEqOp
            ] 
          , [ binOpL "==" EqOp
            , binOpL "!=" NotEqOp
            ]
          -- bitwise operators
          , [ binOpL "&" BitAndOp ] 
          , [ binOpL "^" BitXorOp ]
          , [ binOpL "|" BitOrOp ]
          -- logic operators
          , [ binOpL "&&" LogicAndOp ]
          , [ binOpL "||" LogicOrOp ]
          -- ternary conditional op
          , [ Infix ((\e2 e1 e3 -> Cond e1 e2 e3 noNodeInfo) 
                       <$> (reservedOp "?" *> astOptionMaybe expression <* reservedOp ":")
                    ) AssocRight ]
          -- assignments
          , [ binOpR "=" AssignOp
            , binOpR "*=" MulAssOp
            , binOpR "/=" DivAssOp
            , binOpR "%=" RemainderAssOp
            , binOpR "+=" AddAssOp
            , binOpR "-=" SubAssOp
            , binOpR "<<=" ShiftLeftAssOp
            , binOpR ">>=" ShiftRightAssOp
            , binOpR "&=" BitAndAssOp
            , binOpR "^=" BitXorAssOp
            , binOpR "|=" BitOrAssOp
            ]
          ]
          
        binOpL sign sema = Infix (binOp sign sema) AssocLeft
        binOpR sign sema = Infix (binOp sign sema) AssocRight
        
        binOp sign sema 
          = do operator <- withInfo (reservedOp sign *> return sema)
               return (\e1 e2 -> Binary operator e1 e2 noNodeInfo) <?> "binary operator"
  
-- | Sizeof needs a special treatment because it can either be used on types and expressions,
-- and has the same precedence in both cases.  
unaryExpressionWithSizeOf :: CParser ExpressionBI -> CParser ExpressionBI
unaryExpressionWithSizeOf term
  = withInfo (reserved "sizeof" *> parens ((SizeOfExpr <$> try expression) 
                                               <|> (SizeOfType <$> qualifiedType)))
      <|> unaryExpression term
        
unaryExpression :: CParser ExpressionBI -> CParser ExpressionBI
unaryExpression term = buildExpressionParser precedenceTable term <?> "expression"
  where precedenceTable = 
          [ [ Postfix $ unaryOp "++" PostIncOp
            , Postfix $ unaryOp "--" PostDecOp 
            ]
          , [ Prefix $ unaryOp "++" PreIncOp
            , Prefix $ unaryOp "--" PreDecOp 
            , Prefix $ unaryOp "&" AddressOp
            , Prefix $ unaryOp "*" DereferenceOp
            , Prefix $ unaryOp "+" PrePlusOp
            , Prefix $ unaryOp "-" PreMinOp
            , Prefix $ unaryOp "~" ComplementOp
            , Prefix $ unaryOp "!" LogicNegOp
            ]
          ]
        unaryOp sign sema = do operator <- withInfo (reservedOp sign *> return sema) 
                               return (\expr -> Unary operator expr noNodeInfo)
        
term :: CParser ExpressionBI
term = do base <- nonrecursiveTerm
          termIndexes base
        <?> "term"
        
-- | Tries to parse an array indexing, member access or function call 
--     and applies it to the given expressions. 
-- If does not succeed gives back the original expression.
-- This definition must be separated otherwise grammar would be left-recursive.
termIndexes :: ExpressionBI -> CParser ExpressionBI
termIndexes base 
  = (inheritInfo ( 
         (Indexing base <$> withInfo (ArrayIndex <$> brackets expression) )
            <|> (Member base <$> withInfo ((reservedOp "." *> pure SimpleMember) 
                                              <|> (reservedOp "->" *> pure DerefMember))
                             <*> identifier)
            <|> (Call base <$> withInfo (ASTWrapper <$> parens (expressionWithoutComma `astSepBy` comma)))
         ) >>= termIndexes)
    <|> return base
        
nonrecursiveTerm :: CParser ExpressionBI
nonrecursiveTerm = withInfo $
  (try $ Cast <$> parens qualifiedType <*> expression)
  -- can be prefix of type cast
  <|> (ParenExpr <$> parens expression)
  <|> (LitExpr <$> try literal)
  <|> (NameExpr <$> (identifier >>= checkIsExpression))
  
literal :: CParser LiteralBI
literal = (try float 
    <|> integer -- integer literal can be a prefix of a float literal
    <|> try charLiteral
    <|> (StrLitList <$> astMany1 stringLiteral)
    <|> (withInfo $ CompoundLit <$> braces (compoundElem `astSepBy` comma))
  ) <?> "literal"

compoundElem :: CParser CompoundLitElemBI
compoundElem 
  = withInfo $
     ( CompoundLitElem <$> astMany1 ( withInfo $ ( MemberDesignator <$> (symbol "." *> identifier) )
                                  <|> ( ArrDesignator <$> brackets expression ) )
          <*> ( symbol "=" *> expressionWithoutComma ) )
     <|> (CompoundLitElem <$> pure ASTNil <*> expressionWithoutComma)

-- * Helper parsers. Used to parse common AST elements in special ways.
            
astMany :: CParser (e BI) -> CParser (ASTList e BI)
astMany p = withInfo (ASTCons <$> p <*> astMany p) <|> pure ASTNil

astMany1 :: CParser (e BI) -> CParser (ASTList e BI)
astMany1 p = withInfo (ASTCons <$> p <*> astMany p)

astSepBy :: CParser (e BI) -> CParser sep -> CParser (ASTList e BI)
astSepBy p sep = astSepBy1 p sep <|> pure ASTNil

astSepBy1 :: CParser (e BI) -> CParser sep -> CParser (ASTList e BI)
astSepBy1 p sep = withInfo (ASTCons <$> p <*> astMany (sep *> p))

astOptionMaybe :: CParser (e BI) -> CParser (ASTMaybe e BI)
astOptionMaybe = (view (from astMaybe) <$>) . optionMaybe