diff --git a/BUILD.bazel b/BUILD.bazel index 95cc957..6a968dd 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -16,6 +16,7 @@ haskell_library( name = "lexer", srcs = [ "src/Language/Cimple/Lexer.hs", + "src/Language/Cimple/ParseResult.hs", "src/Language/Cimple/Tokens.hs", ], src_strip_prefix = "src", @@ -33,7 +34,6 @@ haskell_library( src_strip_prefix = "src", visibility = ["//hs-cimple:__subpackages__"], deps = [ - ":lexer", hazel_library("aeson"), hazel_library("base"), hazel_library("data-fix"), @@ -81,6 +81,28 @@ haskell_library( ], ) +happy_parser( + name = "CommentParser", + src = "src/Language/Cimple/CommentParser.y", + preproc = "expand_yacc.pl", +) + +haskell_library( + name = "comment-parser", + srcs = [":CommentParser"], + src_strip_prefix = "src", + visibility = ["//hs-cimple:__subpackages__"], + deps = [ + ":ast", + ":describe-ast", + ":lexer", + hazel_library("array"), + hazel_library("base"), + hazel_library("data-fix"), + hazel_library("text"), + ], +) + happy_parser( name = "TreeParser", src = "src/Language/Cimple/TreeParser.y", @@ -94,6 +116,7 @@ haskell_library( visibility = ["//hs-cimple:__subpackages__"], deps = [ ":ast", + ":comment-parser", ":describe-ast", ":lexer", hazel_library("array"), @@ -111,6 +134,7 @@ haskell_library( "src/Language/Cimple/Ast.hs", "src/Language/Cimple/DescribeAst.hs", "src/Language/Cimple/Flatten.hs", + "src/Language/Cimple/ParseResult.hs", "src/Language/Cimple/Tokens.hs", ], ), diff --git a/cimple.cabal b/cimple.cabal index d737e6d..ad08c9b 100644 --- a/cimple.cabal +++ b/cimple.cabal @@ -36,10 +36,12 @@ library other-modules: Language.Cimple.Annot , Language.Cimple.Ast + , Language.Cimple.CommentParser , Language.Cimple.DescribeAst , Language.Cimple.Flatten , Language.Cimple.Graph , Language.Cimple.Lexer + , Language.Cimple.ParseResult , Language.Cimple.Parser , Language.Cimple.SemCheck.Includes , Language.Cimple.Tokens diff --git a/src/Language/Cimple/Ast.hs b/src/Language/Cimple/Ast.hs index 06b06dd..d4cbe44 100644 --- a/src/Language/Cimple/Ast.hs +++ b/src/Language/Cimple/Ast.hs @@ -13,6 +13,8 @@ module Language.Cimple.Ast , Node, NodeF (..) , Scope (..) , CommentStyle (..) + , Comment + , CommentF (..) ) where import Data.Aeson (FromJSON, FromJSON1, ToJSON, @@ -47,6 +49,7 @@ data NodeF lexeme a | CommentSection a [a] a | CommentSectionEnd lexeme | Commented a a + | CommentInfo (Comment lexeme) -- Namespace-like blocks | ExternC [a] -- An inferred coherent block of nodes, printed without empty lines @@ -126,6 +129,40 @@ type Node lexeme = Fix (NodeF lexeme) instance FromJSON lexeme => FromJSON1 (NodeF lexeme) instance ToJSON lexeme => ToJSON1 (NodeF lexeme) +data CommentF lexeme a + = DocComment [a] + | DocWord lexeme + | DocSentence [a] lexeme + | DocNewline + + | DocBrief [a] + | DocDeprecated [a] + | DocParam (Maybe lexeme) lexeme [a] + | DocReturn [a] + | DocRetval [lexeme] [a] + | DocSee lexeme [a] + + | DocLine [a] + | DocBullet [a] [a] + | DocBulletList [a] + + | DocColon lexeme + | DocRef lexeme + | DocP lexeme + | DocLParen a + | DocRParen a + | DocAssignOp AssignOp lexeme lexeme + | DocBinaryOp BinaryOp lexeme lexeme + | DocMinus lexeme a + | DocSlash lexeme a + deriving (Show, Read, Eq, Ord, Generic, Generic1, Functor, Foldable, Traversable) + deriving (Show1, Read1, Eq1, Ord1) via FunctorClassesDefault (CommentF lexeme) + +type Comment lexeme = Fix (CommentF lexeme) + +instance FromJSON lexeme => FromJSON1 (CommentF lexeme) +instance ToJSON lexeme => ToJSON1 (CommentF lexeme) + data AssignOp = AopEq | AopMul diff --git a/src/Language/Cimple/CommentParser.y b/src/Language/Cimple/CommentParser.y new file mode 100644 index 0000000..009ce07 --- /dev/null +++ b/src/Language/Cimple/CommentParser.y @@ -0,0 +1,199 @@ +{ +{-# LANGUAGE OverloadedStrings #-} +module Language.Cimple.CommentParser + ( parseComment + ) where + +import Data.Fix (Fix (..)) +import Data.Text (Text) +import qualified Data.Text as Text + +import Language.Cimple.Ast (AssignOp (..), BinaryOp (..), + Comment, CommentF (..), Node, + NodeF (..)) +import Language.Cimple.DescribeAst (describeLexeme, sloc) +import Language.Cimple.Lexer (Lexeme (..)) +import Language.Cimple.ParseResult (ParseResult) +import Language.Cimple.Tokens (LexemeClass (..)) +} + +%name parseComment Comment + +%error {parseError} +%errorhandlertype explist +%monad {ParseResult} +%tokentype {Term} +%token + '@brief' { L _ CmtCommand "@brief" } + '@deprecated' { L _ CmtCommand "@deprecated" } + '@implements' { L _ CmtCommand "@implements" } + '@param' { L _ CmtCommand "@param" } + '@ref' { L _ CmtCommand "@ref" } + '@p' { L _ CmtCommand "@p" } + '@return' { L _ CmtCommand "@return" } + '@retval' { L _ CmtCommand "@retval" } + '@see' { L _ CmtCommand "@see" } + + ' ' { L _ CmtIndent " " } + 'INDENT1' { L _ CmtIndent " " } + 'INDENT2' { L _ CmtIndent " " } + + '(' { L _ PctLParen _ } + ')' { L _ PctRParen _ } + ',' { L _ PctComma _ } + ':' { L _ PctColon _ } + '/' { L _ PctSlash _ } + '=' { L _ PctEq _ } + '==' { L _ PctEqEq _ } + '!=' { L _ PctEMarkEq _ } + '>=' { L _ PctGreaterEq _ } + ';' { L _ PctSemicolon _ } + '.' { L _ PctPeriod _ } + '...' { L _ PctEllipsis _ } + '?' { L _ PctQMark _ } + '!' { L _ PctEMark _ } + '-' { L _ PctMinus _ } + '\n' { L _ PpNewline _ } + '/**' { L _ CmtStartDoc _ } + '*/' { L _ CmtEnd _ } + LIT_INTEGER { L _ LitInteger _ } + LIT_STRING { L _ LitString _ } + CMT_ATTR { L _ CmtAttr _ } + CMT_CODE { L _ CmtCode _ } + CMT_WORD { L _ CmtWord _ } + CMT_REF { L _ CmtRef _ } + +%left '.' '?' ',' ';' +%left '-' +%left '/' +%left '(' ')' +%right NEG + +%% + +Comment :: { NonTerm } +Comment +: '/**' '\n' Blocks '*/' { Fix $ DocComment $3 } +| '/**' WordsWithoutNewlines '*/' { Fix $ DocComment [Fix $ DocLine $2] } +| '/**' Command(WordsWithoutNewlines) '*/' { Fix $ DocComment [$2] } +| '/**' Command(WordsWithoutNewlines) '\n' Blocks '*/' { Fix $ DocComment ($2 : $4) } + +Blocks :: { [NonTerm] } +Blocks +: BlockList { reverse $1 } + +BlockList :: { [NonTerm] } +BlockList +: Block { [$1] } +| BlockList Block { $2 : $1 } + +Block :: { NonTerm } +Block +: '\n' { Fix DocNewline } +| ' ' Command(IndentedSentence) { $2 } +| ' ' WordsWithoutNewlines '\n' { Fix $ DocLine $2 } +| BulletListI { Fix $ DocBulletList (reverse $1) } + +Command(x) +: '@brief' x { Fix $ DocBrief $2 } +| '@param' CMT_WORD x { Fix $ DocParam Nothing $2 $3 } +| '@param' CMT_ATTR CMT_WORD x { Fix $ DocParam (Just $2) $3 $4 } +| '@retval' ConstExpr x { Fix $ DocRetval $2 $3 } +| '@return' x { Fix $ DocReturn $2 } +| '@return' '\n' BulletListII { Fix $ DocReturn (Fix (DocLine []) : $3) } +| '@see' CMT_WORD x { Fix $ DocSee $2 $3 } +| '@deprecated' x { Fix $ DocDeprecated $2 } + +BulletListI :: { [NonTerm] } +BulletListI +: BulletI { [$1] } +| BulletListI BulletI { $2 : $1 } + +BulletI :: { NonTerm } +BulletI +: ' ' '-' WordsWithoutNewlines '\n' BulletListII { Fix $ DocBullet $3 (reverse $5) } + +BulletListII :: { [NonTerm] } +BulletListII +: { [] } +| BulletListII BulletII { $2 : $1 } + +BulletII :: { NonTerm } +BulletII +: 'INDENT1' '-' WordsWithoutNewlines '\n' BulletIIConts { Fix $ DocBullet ($3 ++ $5) [] } + +BulletIIConts :: { [NonTerm] } +BulletIIConts +: { [] } +| BulletIIConts BulletIICont { $1 ++ $2 } + +BulletIICont :: { [NonTerm] } +BulletIICont +: 'INDENT2' WordsWithoutNewlines '\n' { $2 } + +IndentedSentence :: { [NonTerm] } +IndentedSentence +: WordsWithoutNewlines '\n' { [Fix $ DocLine $1] } +| WordsWithoutNewlines '\n' 'INDENT1' IndentedSentence { Fix (DocLine $1) : $4 } + +WordsWithoutNewlines :: { [NonTerm] } +WordsWithoutNewlines +: SentenceList(WordList) { $1 } + +WordList :: { [NonTerm] } +WordList +: Word { [$1] } +| WordList Word { $2 : $1 } + +SentenceList(x) +: x { reverse $1 } +| SentenceList(x) ';' { [Fix (DocSentence $1 $2)] } +| SentenceList(x) ',' { [Fix (DocSentence $1 $2)] } +| SentenceList(x) '.' { [Fix (DocSentence $1 $2)] } +| SentenceList(x) '?' { [Fix (DocSentence $1 $2)] } +| SentenceList(x) ';' SentenceList(x) { Fix (DocSentence $1 $2) : $3 } +| SentenceList(x) ',' SentenceList(x) { Fix (DocSentence $1 $2) : $3 } +| SentenceList(x) '.' SentenceList(x) { Fix (DocSentence $1 $2) : $3 } +| SentenceList(x) '?' SentenceList(x) { Fix (DocSentence $1 $2) : $3 } + +Word :: { NonTerm } +Word +: Atom { Fix $ DocWord $1 } +| Atom ':' { Fix $ DocColon $1 } +| Atom '-' Word { Fix $ DocMinus $1 $3 } +| Atom '/' Word { Fix $ DocSlash $1 $3 } +| Atom '=' Atom { Fix $ DocAssignOp AopEq $1 $3 } +| Atom '!=' Atom { Fix $ DocBinaryOp BopNe $1 $3 } +| Atom '>=' Atom { Fix $ DocBinaryOp BopGe $1 $3 } +| Atom '==' Atom { Fix $ DocBinaryOp BopEq $1 $3 } +| '@ref' Atom { Fix $ DocRef $2 } +| '@p' Atom { Fix $ DocP $2 } +| '(' Word { Fix $ DocLParen $2 } +| Word ')' { Fix $ DocRParen $1 } + +Atom :: { Term } +Atom +: CMT_WORD { $1 } +| CMT_CODE { $1 } +| LIT_INTEGER { $1 } +| LIT_STRING { $1 } +| '...' { $1 } + +ConstExpr :: { [Term] } +ConstExpr +: Atom { [$1] } +| '-' Atom { [$1, $2] } + +{ +type Term = Lexeme Text +type NonTerm = Comment Term + +failAt :: Lexeme Text -> String -> ParseResult a +failAt n msg = + fail $ Text.unpack (sloc "" n) <> ": unexpected " <> describeLexeme n <> msg + +parseError :: ([Lexeme Text], [String]) -> ParseResult a +parseError ([], options) = fail $ " end of comment; expected one of " <> show options +parseError (n:_, []) = failAt n "; expected end of comment" +parseError (n:_, options) = failAt n $ "; expected one of " <> show options +} diff --git a/src/Language/Cimple/DescribeAst.hs b/src/Language/Cimple/DescribeAst.hs index 779572c..56ceb41 100644 --- a/src/Language/Cimple/DescribeAst.hs +++ b/src/Language/Cimple/DescribeAst.hs @@ -4,6 +4,7 @@ {-# LANGUAGE StrictData #-} module Language.Cimple.DescribeAst ( HasLocation (..) + , describeLexeme , describeNode ) where @@ -37,3 +38,6 @@ describeNode node = case unFix node of where ellipsis :: String ellipsis = "..." + +describeLexeme :: Show a => Lexeme a -> String +describeLexeme = show diff --git a/src/Language/Cimple/Flatten.hs b/src/Language/Cimple/Flatten.hs index 7c92772..fc9da05 100644 --- a/src/Language/Cimple/Flatten.hs +++ b/src/Language/Cimple/Flatten.hs @@ -7,10 +7,12 @@ {-# LANGUAGE TypeOperators #-} module Language.Cimple.Flatten (lexemes) where +import Data.Fix (Fix (..)) import Data.Maybe (maybeToList) import GHC.Generics -import Language.Cimple.Ast (AssignOp, BinaryOp, CommentStyle, - LiteralType, NodeF (..), Scope, UnaryOp) +import Language.Cimple.Ast (AssignOp, BinaryOp, CommentF, + CommentStyle, LiteralType, NodeF (..), + Scope, UnaryOp) class Concats t a where concats :: t -> [a] @@ -53,11 +55,15 @@ instance {-# OVERLAPPING #-} GenConcatsFlatten a a where instance {-# OVERLAPPABLE #-} GenConcatsFlatten b a => GenConcatsFlatten [b] a where gconcatsFlatten = concatMap gconcatsFlatten +instance GenConcatsFlatten (Fix (CommentF a)) a where + gconcatsFlatten = error "TODO: gconcatsFlatten for CommentF" + instance GenConcatsFlatten t a => GenConcats (Rec0 t) a where gconcats (K1 x) = gconcatsFlatten x -- Uses the default signature, which delegates to the generic stuff instance Concats (NodeF a [a]) a +instance Concats (CommentF a [a]) a lexemes :: NodeF lexeme [lexeme] -> [lexeme] lexemes = concats diff --git a/src/Language/Cimple/IO.hs b/src/Language/Cimple/IO.hs index 0f7f1db..daea8f7 100644 --- a/src/Language/Cimple/IO.hs +++ b/src/Language/Cimple/IO.hs @@ -21,6 +21,7 @@ import Language.Cimple.Lexer (Lexeme, runAlex) import Language.Cimple.MapAst (TextActions, mapAst, textActions) import qualified Language.Cimple.Parser as Parser +import qualified Language.Cimple.ParseResult as ParseResult import Language.Cimple.Program (Program) import qualified Language.Cimple.Program as Program import Language.Cimple.TranslationUnit (TranslationUnit) @@ -51,7 +52,7 @@ parseText contents = parseTextPedantic :: Text -> Either String [TextNode] parseTextPedantic = - parseText >=> TreeParser.toEither . TreeParser.parseTranslationUnit + parseText >=> ParseResult.toEither . TreeParser.parseTranslationUnit parseFile :: FilePath -> IO (Either String (TranslationUnit Text)) diff --git a/src/Language/Cimple/Lexer.x b/src/Language/Cimple/Lexer.x index a06cc4c..ed25afe 100644 --- a/src/Language/Cimple/Lexer.x +++ b/src/Language/Cimple/Lexer.x @@ -189,7 +189,7 @@ tokens :- <0,ppSC> [0-9]+"."[0-9]+f? { mkL LitInteger } <0,ppSC> 0x[0-9a-fA-F]+[LU]* { mkL LitInteger } <0,ppSC,cmtSC> "=" { mkL PctEq } -<0,ppSC> "==" { mkL PctEqEq } +<0,ppSC,cmtSC> "==" { mkL PctEqEq } <0,ppSC> "&" { mkL PctAmpersand } <0,ppSC> "&&" { mkL PctAmpersandAmpersand } <0,ppSC> "&=" { mkL PctAmpersandEq } @@ -205,7 +205,7 @@ tokens :- <0,ppSC,cmtSC> "/" { mkL PctSlash } <0,ppSC> "/=" { mkL PctSlashEq } <0,ppSC,cmtSC> "." { mkL PctPeriod } -<0,ppSC> "..." { mkL PctEllipsis } +<0,ppSC,cmtSC> "..." { mkL PctEllipsis } <0,ppSC> "%" { mkL PctPercent } <0,ppSC> "%=" { mkL PctPercentEq } <0,ppSC,cmtSC> ";" { mkL PctSemicolon } @@ -217,7 +217,7 @@ tokens :- <0,ppSC,cmtSC> ">" { mkL PctGreater } <0,ppSC> ">>" { mkL PctGreaterGreater } <0,ppSC> ">>=" { mkL PctGreaterGreaterEq } -<0,ppSC> ">=" { mkL PctGreaterEq } +<0,ppSC,cmtSC> ">=" { mkL PctGreaterEq } <0,ppSC> "|" { mkL PctPipe } <0,ppSC> "||" { mkL PctPipePipe } <0,ppSC> "|=" { mkL PctPipeEq } @@ -229,7 +229,7 @@ tokens :- <0,ppSC,cmtSC> ")" { mkL PctRParen } <0,ppSC,cmtSC> "?" { mkL PctQMark } <0,ppSC,cmtSC> "!" { mkL PctEMark } -<0,ppSC> "!=" { mkL PctEMarkEq } +<0,ppSC,cmtSC> "!=" { mkL PctEMarkEq } <0,ppSC> "*" { mkL PctAsterisk } <0,ppSC> "*=" { mkL PctAsteriskEq } <0,ppSC> "^" { mkL PctCaret } @@ -240,6 +240,11 @@ tokens :- "SPDX-License-Identifier:" { mkL CmtSpdxLicense } "GPL-3.0-or-later" { mkL CmtWord } "TODO("[^\)]+"):" { mkL CmtWord } + "E.g." { mkL CmtWord } + "e.g." { mkL CmtWord } + "I.e." { mkL CmtWord } + "i.e." { mkL CmtWord } + "v"?[0-9]"."[0-9]"."[0-9] { mkL CmtWord } "@code" { mkL CmtCode `andBegin` codeSC } "" { mkL CmtCode `andBegin` codeSC } "["[^\]]+"]" { mkL CmtAttr } @@ -258,17 +263,21 @@ tokens :- " "+ ; " "+"*"+"/" { mkL CmtEnd `andBegin` 0 } - " "+"*" { mkL CmtIndent `andBegin` cmtSC } + " "+"*" { begin cmtStartSC } + + " "+ { mkL CmtIndent `andBegin` cmtSC } + \n { mkL PpNewline `andBegin` cmtNewlineSC } -- blocks in comments. - "@endcode" { mkL CmtCode `andBegin` cmtSC } + " @endcode" { mkL CmtCode `andBegin` cmtSC } "" { mkL CmtCode `andBegin` cmtSC } - \n { mkL PpNewline } - \n" "+"*" { mkL PpNewline } + \n { mkL PpNewline `andBegin` codeNewlineSC } [^@\<]+ { mkL CmtCode } + " "+"*" { begin codeSC } + -- Error handling. -<0,ppSC,cmtSC,codeSC> . { mkL Error } +<0,ppSC,cmtSC,codeSC> . { mkL ErrorToken } { deriving instance Ord AlexPosn diff --git a/src/Language/Cimple/MapAst.hs b/src/Language/Cimple/MapAst.hs index 55ea065..ef97e88 100644 --- a/src/Language/Cimple/MapAst.hs +++ b/src/Language/Cimple/MapAst.hs @@ -11,6 +11,7 @@ module Language.Cimple.MapAst , doFiles, doFile , doNodes, doNode + , doComment, doComments , doLexemes, doLexeme , doText @@ -20,7 +21,8 @@ module Language.Cimple.MapAst ) where import Data.Fix (Fix (..)) -import Language.Cimple.Ast (Node, NodeF (..)) +import Language.Cimple.Ast (Comment, CommentF (..), Node, + NodeF (..)) import Language.Cimple.Lexer (Lexeme (..)) class MapAst itext otext a where @@ -39,13 +41,15 @@ mapAst mapAst = flip mapFileAst "" data AstActions f itext otext = AstActions - { doFiles :: [(FilePath, [Node (Lexeme itext)])] -> f [(FilePath, [Node (Lexeme otext)])] -> f [(FilePath, [Node (Lexeme otext)])] - , doFile :: (FilePath, [Node (Lexeme itext)]) -> f (FilePath, [Node (Lexeme otext)]) -> f (FilePath, [Node (Lexeme otext)]) - , doNodes :: FilePath -> [Node (Lexeme itext)] -> f [Node (Lexeme otext)] -> f [Node (Lexeme otext)] - , doNode :: FilePath -> Node (Lexeme itext) -> f (Node (Lexeme otext)) -> f (Node (Lexeme otext)) - , doLexemes :: FilePath -> [Lexeme itext] -> f [Lexeme otext] -> f [Lexeme otext] - , doLexeme :: FilePath -> Lexeme itext -> f (Lexeme otext) -> f (Lexeme otext) - , doText :: FilePath -> itext -> f otext + { doFiles :: [(FilePath, [Node (Lexeme itext)])] -> f [(FilePath, [Node (Lexeme otext)])] -> f [(FilePath, [Node (Lexeme otext)])] + , doFile :: (FilePath, [Node (Lexeme itext)]) -> f (FilePath, [Node (Lexeme otext)]) -> f (FilePath, [Node (Lexeme otext)]) + , doNodes :: FilePath -> [Node (Lexeme itext)] -> f [Node (Lexeme otext)] -> f [Node (Lexeme otext)] + , doNode :: FilePath -> Node (Lexeme itext) -> f (Node (Lexeme otext)) -> f (Node (Lexeme otext)) + , doComment :: FilePath -> Comment (Lexeme itext) -> f (Comment (Lexeme otext)) -> f (Comment (Lexeme otext)) + , doComments :: FilePath -> [Comment (Lexeme itext)] -> f [Comment (Lexeme otext)] -> f [Comment (Lexeme otext)] + , doLexemes :: FilePath -> [Lexeme itext] -> f [Lexeme otext] -> f [Lexeme otext] + , doLexeme :: FilePath -> Lexeme itext -> f (Lexeme otext) -> f (Lexeme otext) + , doText :: FilePath -> itext -> f otext } instance MapAst itext otext a @@ -64,6 +68,8 @@ astActions ft = AstActions , doFile = const id , doNodes = const $ const id , doNode = const $ const id + , doComment = const $ const id + , doComments = const $ const id , doLexeme = const $ const id , doLexemes = const $ const id , doText = const ft @@ -92,6 +98,74 @@ instance MapAst itext otext [Lexeme itext] where mapFileAst actions@AstActions{..} currentFile = doLexemes currentFile <*> traverse (mapFileAst actions currentFile) +instance MapAst itext otext (Comment (Lexeme itext)) where + type Mapped itext otext (Comment (Lexeme itext)) + = Comment (Lexeme otext) + mapFileAst + :: forall f . Applicative f + => AstActions f itext otext + -> FilePath + -> Comment (Lexeme itext) + -> f (Comment (Lexeme otext)) + mapFileAst actions@AstActions{..} currentFile = doComment currentFile <*> \comment -> case unFix comment of + DocComment docs -> + Fix <$> (DocComment <$> recurse docs) + DocWord word -> + Fix <$> (DocWord <$> recurse word) + DocSentence docs ending -> + Fix <$> (DocSentence <$> recurse docs <*> recurse ending) + DocNewline -> pure $ Fix DocNewline + + DocBrief docs -> + Fix <$> (DocBrief <$> recurse docs) + DocDeprecated docs -> + Fix <$> (DocDeprecated <$> recurse docs) + DocParam attr name docs -> + Fix <$> (DocParam <$> recurse attr <*> recurse name <*> recurse docs) + DocReturn docs -> + Fix <$> (DocReturn <$> recurse docs) + DocRetval expr docs -> + Fix <$> (DocRetval <$> recurse expr <*> recurse docs) + DocSee ref docs -> + Fix <$> (DocSee <$> recurse ref <*> recurse docs) + + DocLine docs -> + Fix <$> (DocLine <$> recurse docs) + DocBullet docs sublist -> + Fix <$> (DocBullet <$> recurse docs <*> recurse sublist) + DocBulletList docs -> + Fix <$> (DocBulletList <$> recurse docs) + + DocColon docs -> + Fix <$> (DocColon <$> recurse docs) + DocRef doc -> + Fix <$> (DocRef <$> recurse doc) + DocP doc -> + Fix <$> (DocP <$> recurse doc) + DocLParen docs -> + Fix <$> (DocLParen <$> recurse docs) + DocRParen docs -> + Fix <$> (DocRParen <$> recurse docs) + DocAssignOp op lhs rhs -> + Fix <$> (DocAssignOp op <$> recurse lhs <*> recurse rhs) + DocBinaryOp op lhs rhs -> + Fix <$> (DocBinaryOp op <$> recurse lhs <*> recurse rhs) + DocMinus lhs rhs -> + Fix <$> (DocMinus <$> recurse lhs <*> recurse rhs) + DocSlash lhs rhs -> + Fix <$> (DocSlash <$> recurse lhs <*> recurse rhs) + + where + recurse :: MapAst itext otext a => a -> f (Mapped itext otext a) + recurse = mapFileAst actions currentFile + +instance MapAst itext otext [Comment (Lexeme itext)] where + type Mapped itext otext [Comment (Lexeme itext)] + = [Comment (Lexeme otext)] + mapFileAst actions@AstActions{..} currentFile = doComments currentFile <*> + traverse (mapFileAst actions currentFile) + + instance MapAst itext otext (Node (Lexeme itext)) where type Mapped itext otext (Node (Lexeme itext)) = Node (Lexeme otext) @@ -146,6 +220,8 @@ instance MapAst itext otext (Node (Lexeme itext)) where Fix <$> (CommentSectionEnd <$> recurse comment) Commented comment subject -> Fix <$> (Commented <$> recurse comment <*> recurse subject) + CommentInfo comment -> + Fix <$> (CommentInfo <$> recurse comment) ExternC decls -> Fix <$> (ExternC <$> recurse decls) Group decls -> diff --git a/src/Language/Cimple/ParseResult.hs b/src/Language/Cimple/ParseResult.hs new file mode 100644 index 0000000..7d3f33b --- /dev/null +++ b/src/Language/Cimple/ParseResult.hs @@ -0,0 +1,11 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module Language.Cimple.ParseResult + ( ParseResult + , toEither + ) where + +newtype ParseResult a = ParseResult { toEither :: Either String a } + deriving (Functor, Applicative, Monad) + +instance MonadFail ParseResult where + fail = ParseResult . Left diff --git a/src/Language/Cimple/Parser.y b/src/Language/Cimple/Parser.y index a3effbb..8751dea 100644 --- a/src/Language/Cimple/Parser.y +++ b/src/Language/Cimple/Parser.y @@ -123,7 +123,8 @@ import Language.Cimple.Tokens (LexemeClass (..)) '/** @{' { L _ CmtStartDocSection _ } '/** @} */' { L _ CmtEndDocSection _ } '/***' { L _ CmtStartBlock _ } - ' * ' { L _ CmtIndent _ } + ' * ' { L _ CmtPrefix _ } + ' ' { L _ CmtIndent _ } '*/' { L _ CmtEnd _ } 'Copyright' { L _ CmtSpdxCopyright _ } 'License' { L _ CmtSpdxLicense _ } @@ -170,7 +171,7 @@ CopyrightDecls CopyrightDecl :: { NonTerm } CopyrightDecl -: ' * ' 'Copyright' CopyrightDates CopyrightOwner '\n' { Fix $ CopyrightDecl (fst $3) (snd $3) $4 } +: ' ' 'Copyright' CopyrightDates CopyrightOwner '\n' { Fix $ CopyrightDecl (fst $3) (snd $3) $4 } CopyrightDates :: { (StringLexeme, Maybe StringLexeme) } CopyrightDates @@ -225,6 +226,7 @@ CommentToken : CommentWord { $1 } | '\n' { $1 } | ' * ' { $1 } +| ' ' { $1 } CommentWords :: { [StringLexeme] } CommentWords @@ -241,6 +243,7 @@ CommentWord | LIT_INTEGER { $1 } | LIT_STRING { $1 } | '.' { $1 } +| '...' { $1 } | '?' { $1 } | '!' { $1 } | ',' { $1 } @@ -254,6 +257,9 @@ CommentWord | '+' { $1 } | '-' { $1 } | '=' { $1 } +| '==' { $1 } +| '!=' { $1 } +| '>=' { $1 } Ignore :: { NonTerm } Ignore diff --git a/src/Language/Cimple/Pretty.hs b/src/Language/Cimple/Pretty.hs index 0ea0b8a..efc85fc 100644 --- a/src/Language/Cimple/Pretty.hs +++ b/src/Language/Cimple/Pretty.hs @@ -13,6 +13,7 @@ import qualified Data.List.Split as List import Data.Text (Text) import qualified Data.Text as Text import Language.Cimple (AssignOp (..), BinaryOp (..), + Comment, CommentF (..), CommentStyle (..), Lexeme (..), LexemeClass (..), Node, NodeF (..), Scope (..), @@ -20,6 +21,9 @@ import Language.Cimple (AssignOp (..), BinaryOp (..), import Prelude hiding ((<$>)) import Text.PrettyPrint.ANSI.Leijen +indentWidth :: Int +indentWidth = 2 + kwBreak = dullred $ text "break" kwCase = dullred $ text "case" kwConst = dullgreen $ text "const" @@ -45,6 +49,18 @@ kwTypedef = dullgreen $ text "typedef" kwUnion = dullgreen $ text "union" kwWhile = dullred $ text "while" +kwDocBrief = dullcyan $ text "@brief" +kwDocDeprecated = dullcyan $ text "@deprecated" +kwDocParam = dullcyan $ text "@param" +kwDocRef = dullcyan $ text "@ref" +kwDocReturn = dullcyan $ text "@return" +kwDocRetval = dullcyan $ text "@retval" +kwDocP = dullcyan $ text "@p" +kwDocSee = dullcyan $ text "@see" + +cmtPrefix :: Doc +cmtPrefix = dullyellow (char '*') + ppText :: Text -> Doc ppText = text . Text.unpack @@ -113,13 +129,14 @@ ppCommentStart = dullyellow . \case Ignore -> text "//!TOKSTYLE-" ppCommentBody :: [Lexeme Text] -> Doc -ppCommentBody = vsep . map (hsep . map ppWord) . groupLines +ppCommentBody = vsep . prefixStars . map (hsep . map ppWord) . groupLines where + prefixStars xs = zipWith (<>) (empty : replicate (length xs - 2) cmtPrefix ++ [empty]) xs groupLines = List.splitWhen $ \case L _ PpNewline _ -> True _ -> False -ppWord (L _ CmtIndent _) = dullyellow $ char '*' +ppWord (L _ CmtIndent _) = empty ppWord (L _ CmtCommand t) = dullcyan $ ppText t ppWord (L _ _ t) = dullyellow $ ppText t @@ -185,7 +202,7 @@ ppSwitchStmt -> [Doc] -> Doc ppSwitchStmt c body = - nest 2 ( + nest indentWidth ( kwSwitch <+> parens c <+> lbrace <$> vcat body ) <$> rbrace @@ -202,7 +219,7 @@ ppVLA ty n sz = ppCompoundStmt :: [Doc] -> Doc ppCompoundStmt body = - nest 2 ( + nest indentWidth ( lbrace <$> ppToplevel body ) <$> rbrace @@ -234,6 +251,57 @@ ppMacroBody = . renderS . plain +ppVerbatimComment :: Doc -> Doc +ppVerbatimComment = + vcat + . map dullyellow + . zipWith (<>) (empty : repeat (text " * ")) + . map text + . List.splitOn "\n" + . renderS + . plain + +ppCommentInfo :: Comment (Lexeme Text) -> Doc +ppCommentInfo = foldFix go + where + ppBody = vcat . zipWith (<>) ( repeat (dullyellow (text " * " ))) + ppIndented = vcat . zipWith (<>) (empty : repeat (dullyellow (text " * "))) + ppRef = underline . cyan . ppLexeme + ppAttr = maybe empty (blue . ppLexeme) + + go :: CommentF (Lexeme Text) Doc -> Doc + go = dullyellow . \case + DocComment docs -> + text "/**" <$> + ppBody docs <$> + dullyellow (text " */") + DocWord w -> ppLexeme w + DocSentence docs ending -> fillSep docs <> ppLexeme ending + DocNewline -> empty + + DocParam attr name docs -> + kwDocParam <> ppAttr attr <+> underline (cyan (ppLexeme name)) <+> ppIndented docs + + DocBrief docs -> kwDocBrief <+> ppIndented docs + DocDeprecated docs -> kwDocDeprecated <+> ppIndented docs + DocReturn docs -> kwDocReturn <+> ppIndented docs + DocRetval expr docs -> kwDocRetval <+> dullred (hcat (map ppLexeme expr)) <+> ppIndented docs + DocSee name docs -> kwDocSee <+> ppRef name <+> ppIndented docs + DocRef name -> kwDocRef <+> ppRef name + DocP name -> kwDocP <+> ppRef name + + DocBullet docs sublist -> char '-' <+> nest 2 (vsep $ fillSep docs : sublist) + DocBulletList l -> ppVerbatimComment $ vcat l + DocLine docs -> fillSep docs + + DocLParen doc -> lparen <> doc + DocRParen doc -> doc <> rparen + DocColon doc -> ppLexeme doc <> char ':' + DocAssignOp op l r -> ppLexeme l <+> ppAssignOp op <+> ppLexeme r + DocBinaryOp op l r -> ppLexeme l <+> ppBinaryOp op <+> ppLexeme r + DocMinus l r -> ppLexeme l <> char '-' <> r + DocSlash l r -> ppLexeme l <> char '/' <> r + ppNode :: Node (Lexeme Text) -> Doc ppNode = foldFix go where @@ -258,6 +326,8 @@ ppNode = foldFix go dullyellow $ ppLexeme cs Commented c d -> c <$> d + CommentInfo docs -> + ppCommentInfo docs VarExpr var -> ppLexeme var LiteralExpr _ l -> dullred $ ppLexeme l @@ -293,13 +363,13 @@ ppNode = foldFix go ExternC decls -> dullmagenta (text "#ifdef __cplusplus") <$> - text "extern \"C\" {" <$> + kwExtern <+> dullred (text "\"C\"") <+> lbrace <$> dullmagenta (text "#endif") <$> line <> ppToplevel decls <$> line <> dullmagenta (text "#ifdef __cplusplus") <$> - text "}" <$> + rbrace <$> dullmagenta (text "#endif") Group decls -> vcat decls @@ -333,12 +403,12 @@ ppNode = foldFix go dullmagenta (text "#ifdef" <+> ppLexeme name) <$> ppToplevel decls <> elseBranch <$> - dullmagenta (text "#endif") + dullmagenta (text "#endif //" <+> ppLexeme name) PreprocIfndef name decls elseBranch -> dullmagenta (text "#ifndef" <+> ppLexeme name) <$> ppToplevel decls <> elseBranch <$> - dullmagenta (text "#endif") + dullmagenta (text "#endif //" <+> ppLexeme name) PreprocElse [] -> empty PreprocElse decls -> linebreak <> @@ -368,12 +438,12 @@ ppNode = foldFix go AggregateDecl struct -> struct <> semi Struct name members -> - nest 2 ( + nest indentWidth ( kwStruct <+> ppLexeme name <+> lbrace <$> vcat members ) <$> rbrace Union name members -> - nest 2 ( + nest indentWidth ( kwUnion <+> ppLexeme name <+> lbrace <$> vcat members ) <$> rbrace @@ -393,17 +463,17 @@ ppNode = foldFix go ppLexeme name <+> equals <+> value <> comma EnumConsts Nothing enums -> - nest 2 ( + nest indentWidth ( kwEnum <+> lbrace <$> vcat enums ) <$> text "};" EnumConsts (Just name) enums -> - nest 2 ( + nest indentWidth ( kwEnum <+> ppLexeme name <+> lbrace <$> vcat enums ) <$> text "};" EnumDecl name enums ty -> - nest 2 ( + nest indentWidth ( kwTypedef <+> kwEnum <+> dullgreen (ppLexeme name) <+> lbrace <$> vcat enums ) <$> rbrace <+> dullgreen (ppLexeme ty) <> semi diff --git a/src/Language/Cimple/Tokens.hs b/src/Language/Cimple/Tokens.hs index 2571be7..7153232 100644 --- a/src/Language/Cimple/Tokens.hs +++ b/src/Language/Cimple/Tokens.hs @@ -106,6 +106,7 @@ data LexemeClass | CmtCommand | CmtAttr | CmtEndDocSection + | CmtPrefix | CmtIndent | CmtStart | CmtStartBlock @@ -121,7 +122,7 @@ data LexemeClass | IgnBody | IgnEnd - | Error + | ErrorToken | Eof deriving (Enum, Bounded, Ord, Eq, Show, Generic) diff --git a/src/Language/Cimple/TraverseAst.hs b/src/Language/Cimple/TraverseAst.hs index 975d085..b8aec34 100644 --- a/src/Language/Cimple/TraverseAst.hs +++ b/src/Language/Cimple/TraverseAst.hs @@ -12,6 +12,7 @@ module Language.Cimple.TraverseAst , doFiles, doFile , doNodes, doNode + , doComment, doComments , doLexemes, doLexeme , doText @@ -20,7 +21,8 @@ module Language.Cimple.TraverseAst import Data.Fix (Fix (..)) import Data.Foldable (traverse_) -import Language.Cimple.Ast (Node, NodeF (..)) +import Language.Cimple.Ast (Comment, CommentF (..), Node, + NodeF (..)) import Language.Cimple.Lexer (Lexeme (..)) {-# ANN module "HLint: ignore Reduce duplication" #-} @@ -40,13 +42,15 @@ traverseAst traverseAst = flip traverseFileAst "" data AstActions f text = AstActions - { doFiles :: [(FilePath, [Node (Lexeme text)])] -> f () -> f () - , doFile :: (FilePath, [Node (Lexeme text)]) -> f () -> f () - , doNodes :: FilePath -> [Node (Lexeme text)] -> f () -> f () - , doNode :: FilePath -> Node (Lexeme text) -> f () -> f () - , doLexemes :: FilePath -> [Lexeme text] -> f () -> f () - , doLexeme :: FilePath -> Lexeme text -> f () -> f () - , doText :: FilePath -> text -> f () + { doFiles :: [(FilePath, [Node (Lexeme text)])] -> f () -> f () + , doFile :: (FilePath, [Node (Lexeme text)]) -> f () -> f () + , doNodes :: FilePath -> [Node (Lexeme text)] -> f () -> f () + , doNode :: FilePath -> Node (Lexeme text) -> f () -> f () + , doComment :: FilePath -> Comment (Lexeme text) -> f () -> f () + , doComments :: FilePath -> [Comment (Lexeme text)] -> f () -> f () + , doLexemes :: FilePath -> [Lexeme text] -> f () -> f () + , doLexeme :: FilePath -> Lexeme text -> f () -> f () + , doText :: FilePath -> text -> f () } instance TraverseAst text a @@ -61,6 +65,8 @@ astActions = AstActions , doFile = const id , doNodes = const $ const id , doNode = const $ const id + , doComment = const $ const id + , doComments = const $ const id , doLexeme = const $ const id , doLexemes = const $ const id , doText = const $ const $ pure () @@ -77,6 +83,88 @@ instance TraverseAst text [Lexeme text] where traverseFileAst actions@AstActions{..} currentFile = doLexemes currentFile <*> traverse_ (traverseFileAst actions currentFile) +instance TraverseAst text (Comment (Lexeme text)) where + traverseFileAst + :: forall f . Applicative f + => AstActions f text + -> FilePath + -> Comment (Lexeme text) + -> f () + traverseFileAst actions@AstActions{..} currentFile = doComment currentFile <*> \comment -> case unFix comment of + DocComment docs -> + recurse docs + DocWord word -> + recurse word + DocSentence docs ending -> do + _ <- recurse docs + _ <- recurse ending + pure () + DocNewline -> pure () + + DocBrief docs -> + recurse docs + DocDeprecated docs -> + recurse docs + DocParam attr name docs -> do + _ <- recurse attr + _ <- recurse name + _ <- recurse docs + pure () + DocReturn docs -> + recurse docs + DocRetval expr docs -> do + _ <- recurse expr + _ <- recurse docs + pure () + DocSee ref docs -> do + _ <- recurse ref + _ <- recurse docs + pure () + + DocLine docs -> + recurse docs + DocBullet docs sublist -> do + _ <- recurse docs + _ <- recurse sublist + pure () + DocBulletList docs -> + recurse docs + + DocColon docs -> + recurse docs + DocRef doc -> + recurse doc + DocP doc -> + recurse doc + DocLParen docs -> + recurse docs + DocRParen docs -> + recurse docs + DocAssignOp _ lhs rhs -> do + _ <- recurse lhs + _ <- recurse rhs + pure () + DocBinaryOp _ lhs rhs -> do + _ <- recurse lhs + _ <- recurse rhs + pure () + DocMinus lhs rhs -> do + _ <- recurse lhs + _ <- recurse rhs + pure () + DocSlash lhs rhs -> do + _ <- recurse lhs + _ <- recurse rhs + pure () + + where + recurse :: TraverseAst text a => a -> f () + recurse = traverseFileAst actions currentFile + +instance TraverseAst text [Comment (Lexeme text)] where + traverseFileAst actions@AstActions{..} currentFile = doComments currentFile <*> + traverse_ (traverseFileAst actions currentFile) + instance TraverseAst text (Node (Lexeme text)) where traverseFileAst :: forall f . Applicative f @@ -165,6 +253,8 @@ instance TraverseAst text (Node (Lexeme text)) where _ <- recurse comment _ <- recurse subject pure () + CommentInfo comment -> + recurse comment ExternC decls -> recurse decls Group decls -> diff --git a/src/Language/Cimple/TreeParser.y b/src/Language/Cimple/TreeParser.y index 6c0813d..6a9c368 100644 --- a/src/Language/Cimple/TreeParser.y +++ b/src/Language/Cimple/TreeParser.y @@ -1,21 +1,20 @@ { {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ViewPatterns #-} -module Language.Cimple.TreeParser - ( TreeParser - , parseTranslationUnit - , toEither - ) where - -import Data.Fix (Fix (..)) -import Data.Maybe (maybeToList) -import Data.Text (Text) -import qualified Data.Text as Text -import Language.Cimple.Ast (CommentStyle (..), Node, - NodeF (..)) -import Language.Cimple.DescribeAst (describeNode, sloc) -import Language.Cimple.Lexer (Lexeme (..)) -import Language.Cimple.Tokens (LexemeClass (..)) +module Language.Cimple.TreeParser (parseTranslationUnit) where + +import Debug.Trace(traceShow) +import Data.Fix (Fix (..)) +import Data.Maybe (maybeToList) +import Data.Text (Text) +import qualified Data.Text as Text +import Language.Cimple.Ast (CommentStyle (..), Node, + NodeF (..)) +import Language.Cimple.CommentParser (parseComment) +import Language.Cimple.DescribeAst (describeNode, sloc) +import Language.Cimple.Lexer (Lexeme (..)) +import Language.Cimple.ParseResult (ParseResult) +import Language.Cimple.Tokens (LexemeClass (..)) } %name parseTranslationUnit TranslationUnit @@ -24,7 +23,7 @@ import Language.Cimple.Tokens (LexemeClass (..)) %error {parseError} %errorhandlertype explist -%monad {TreeParser} +%monad {ParseResult} %tokentype {NonTerm} %token ifndefDefine { Fix (PreprocIfndef _ (isDefine -> True) (Fix (PreprocElse []))) } @@ -185,7 +184,7 @@ Decl : comment { $1 } | commentSectionStart Decls commentSectionEnd { Fix $ CommentSection $1 $2 $3 } | CommentableDecl { $1 } -| docComment CommentableDecl { Fix $ Commented $1 $2 } +| docComment CommentableDecl {% fmap (\c -> Fix $ Commented c $2) $ parseDocComment $1 } CommentableDecl :: { NonTerm } CommentableDecl @@ -225,12 +224,6 @@ NonEmptyList_(x) type TextLexeme = Lexeme Text type NonTerm = Node TextLexeme -newtype TreeParser a = TreeParser { toEither :: Either String a } - deriving (Functor, Applicative, Monad) - -instance MonadFail TreeParser where - fail = TreeParser . Left - mapHead :: (a -> a) -> [a] -> [a] mapHead _ [] = [] @@ -269,7 +262,7 @@ hasInclude style (Fix (PreprocElse ed)) = any (hasInclude style) ed hasInclude _ _ = False -recurse :: ([NonTerm] -> TreeParser [NonTerm]) -> NonTerm -> TreeParser NonTerm +recurse :: ([NonTerm] -> ParseResult [NonTerm]) -> NonTerm -> ParseResult NonTerm recurse f (Fix (ExternC ds)) = Fix <$> (ExternC <$> f ds) recurse f (Fix (PreprocIf c t e)) = Fix <$> (PreprocIf c <$> f t <*> recurse f e) recurse f (Fix (PreprocIfdef c t e)) = Fix <$> (PreprocIfdef c <$> f t <*> recurse f e) @@ -280,11 +273,16 @@ recurse f (Fix (PreprocElse [])) = Fix <$> pure (PreprocElse []) recurse f (Fix (PreprocElse e)) = Fix <$> (PreprocElse <$> f e) recurse _ ns = fail $ "TreeParser.recurse: " <> show ns -failAt :: NonTerm -> String -> TreeParser a +parseDocComment :: NonTerm -> ParseResult NonTerm +parseDocComment (Fix (Comment Doxygen start body end)) = + Fix . CommentInfo <$> parseComment (start : body ++ [end]) +parseDocComment n = return n + +failAt :: NonTerm -> String -> ParseResult a failAt n msg = fail $ Text.unpack (sloc "" n) <> ": unexpected " <> describeNode n <> msg -parseError :: ([NonTerm], [String]) -> TreeParser a +parseError :: ([NonTerm], [String]) -> ParseResult a parseError ([], options) = fail $ " end of file; expected one of " <> show options parseError (n:_, []) = failAt n "; expected end of file" parseError (n:_, options) = failAt n $ "; expected one of " <> show options