Skip to content

Commit 8de896b

Browse files
committed
Add comment parsing from CastXML.
In CastXML 3.6, a new feature was introduced to capture the documentation comments added to the header files of the C++ sent through the program. Add the code to pygccxml to read those new comment nodes and attributes into a new "comment" declaration type.
1 parent aa8661e commit 8de896b

File tree

10 files changed

+204
-1
lines changed

10 files changed

+204
-1
lines changed

pygccxml/declarations/comment.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright 2014-2017 Insight Software Consortium.
2+
# Copyright 2004-2009 Roman Yakovenko.
3+
# Distributed under the Boost Software License, Version 1.0.
4+
# See http://www.boost.org/LICENSE_1_0.txt
5+
6+
"""
7+
Describe a C++ comment declaration.
8+
9+
"""
10+
11+
12+
from . import declaration
13+
14+
15+
class comment_t(declaration.declaration_t):
16+
17+
def __init__(self, name='', declarations=None):
18+
"""
19+
Creates an object that describes a C++ comment declaration.
20+
21+
Args:
22+
23+
"""
24+
declaration.declaration_t.__init__(self, name)
25+
self.location = {}
26+
self._start_line = 0
27+
self._end_line = 0
28+
self._text = ""
29+
30+
@property
31+
def start_line(self):
32+
return self._start_line
33+
34+
@start_line.setter
35+
def start_line(self, start_line):
36+
self._start_line = int(start_line)
37+
38+
@property
39+
def end_line(self):
40+
return self._end_line
41+
42+
@end_line.setter
43+
def end_line(self, end_line):
44+
self._end_line = int(end_line)
45+
46+
@property
47+
def text(self):
48+
return self._text
49+
50+
@text.setter
51+
def text(self, text):
52+
self._text = text

pygccxml/declarations/decl_factory.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .calldef_members import destructor_t
1313
from .calldef_members import member_operator_t
1414
from .calldef_members import casting_operator_t
15+
from .comment import comment_t
1516
from .free_calldef import free_function_t
1617
from .free_calldef import free_operator_t
1718
from .enumeration import enumeration_t
@@ -89,3 +90,7 @@ def create_typedef(self, *arguments, **keywords):
8990
def create_variable(self, *arguments, **keywords):
9091
"""creates instance of class that describes variable declaration"""
9192
return variable_t(*arguments, **keywords)
93+
94+
def create_comment(self, *arguments, **keywords):
95+
"""creates instance of class that describes variable declaration"""
96+
return comment_t(*arguments, **keywords)

pygccxml/declarations/decl_visitor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,6 @@ def visit_typedef(self):
5757

5858
def visit_variable(self):
5959
raise NotImplementedError()
60+
61+
def visit_comment(self):
62+
raise NotImplementedError()

pygccxml/declarations/declaration.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(
3737
self._cache = algorithms_cache.declaration_algs_cache_t()
3838
self._partial_name = None
3939
self._decorated_name = None
40+
self._comment = None
4041

4142
def __str__(self):
4243
"""
@@ -338,3 +339,11 @@ def _warn_deprecated():
338339
"Please use the declarations.get_dependencies_from_decl()" +
339340
"function instead.\n",
340341
DeprecationWarning)
342+
343+
@property
344+
def comment(self):
345+
return self._comment
346+
347+
@comment.setter
348+
def comment(self, comment):
349+
self._comment = comment

pygccxml/parser/linker.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ def visit_destructor(self):
107107
def visit_member_operator(self):
108108
self.__link_calldef()
109109

110+
def visit_comment(self):
111+
pass
112+
110113
def visit_casting_operator(self):
111114
self.__link_calldef()
112115
# FIXME: is the patch still needed as the demangled name support has

pygccxml/parser/scanner.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@
1616
XML_AN_ACCESS = "access"
1717
XML_AN_ALIGN = "align"
1818
XML_AN_ARTIFICIAL = "artificial"
19+
XML_AN_ATTACHED = "attached"
1920
XML_AN_ATTRIBUTES = "attributes"
2021
XML_AN_BASE_TYPE = "basetype"
2122
XML_AN_BASES = "bases"
2223
XML_AN_BITS = "bits"
24+
XML_AN_COMMENT = "comment"
2325
XML_AN_CONST = "const"
2426
XML_AN_CONTEXT = "context"
2527
XML_AN_CVS_REVISION = "cvs_revision"
2628
XML_AN_CASTXML_FORMAT = "format"
2729
XML_AN_DEFAULT = "default"
30+
XML_AN_END_LINE = "end_line"
2831
XML_AN_EXPLICIT = "explicit"
2932
XML_AN_EXTERN = "extern"
3033
XML_AN_FILE = "file"
@@ -33,6 +36,7 @@
3336
XML_AN_INIT = "init"
3437
XML_AN_INLINE = "inline"
3538
XML_AN_LINE = "line"
39+
XML_AN_BEGIN_LINE = "begin_line"
3640
XML_AN_MANGLED = "mangled"
3741
XML_AN_MAX = "max"
3842
XML_AN_MEMBERS = "members"
@@ -52,6 +56,7 @@
5256
XML_NN_ARRAY_TYPE = "ArrayType"
5357
XML_NN_CASTING_OPERATOR = "Converter"
5458
XML_NN_CLASS = "Class"
59+
XML_NN_COMMENT = "Comment"
5560
XML_NN_CONSTRUCTOR = "Constructor"
5661
XML_NN_CV_QUALIFIED_TYPE = "CvQualifiedType"
5762
XML_NN_DESTRUCTOR = "Destructor"
@@ -110,6 +115,7 @@ def __init__(self, xml_file, decl_factory, config, *args):
110115
XML_NN_UNION: self.__read_union,
111116
XML_NN_FIELD: self.__read_field,
112117
XML_NN_CASTING_OPERATOR: self.__read_casting_operator,
118+
XML_NN_COMMENT: self.__read_comment,
113119
XML_NN_CONSTRUCTOR: self.__read_constructor,
114120
XML_NN_DESTRUCTOR: self.__read_destructor,
115121
XML_NN_FUNCTION: self.__read_function,
@@ -125,6 +131,7 @@ def __init__(self, xml_file, decl_factory, config, *args):
125131
XML_NN_DESTRUCTOR,
126132
XML_NN_ENUMERATION,
127133
XML_NN_FILE,
134+
XML_NN_COMMENT,
128135
XML_NN_FUNCTION,
129136
XML_NN_FREE_OPERATOR,
130137
XML_NN_MEMBER_OPERATOR,
@@ -186,6 +193,19 @@ def xml_generator_from_xml_file(self):
186193
def read(self):
187194
xml.sax.parse(self.xml_file, self)
188195

196+
def _handle_comment(self, declaration):
197+
comm_decl = self.__declarations.get(declaration.comment)
198+
if comm_decl:
199+
with open(self.__files.get(comm_decl.location.file_name, "r")) as file:
200+
line_list = file.readlines()
201+
comment_text = []
202+
for indx in range(comm_decl.start_line - 1,comm_decl.end_line):
203+
comm_line = line_list[indx]
204+
comm_line = comm_line.strip("\n")
205+
comment_text.append(comm_line)
206+
comm_decl.text = comment_text
207+
return comm_decl
208+
189209
def endDocument(self):
190210
# updating membership
191211
members_mapping = {}
@@ -195,6 +215,8 @@ def endDocument(self):
195215
continue
196216
members_mapping[id(decl)] = members
197217
self.__members = members_mapping
218+
for gccxml_id, decl in self.__declarations.items():
219+
decl.comment = self._handle_comment(decl)
198220

199221
def declarations(self):
200222
return self.__declarations
@@ -287,9 +309,13 @@ def __read_location(decl, attrs, to_skip):
287309
if "name" in attrs and attrs["name"] in to_skip:
288310
decl.location = declarations.location_t('', -1)
289311
else:
312+
if XML_AN_BEGIN_LINE in attrs:
313+
line_number = attrs[XML_AN_BEGIN_LINE]
314+
else:
315+
line_number = attrs[XML_AN_LINE]
290316
decl.location = declarations.location_t(
291317
file_name=attrs[XML_AN_FILE],
292-
line=int(attrs[XML_AN_LINE]))
318+
line=int(line_number))
293319

294320
def __update_membership(self, attrs):
295321
parent = attrs.get(XML_AN_CONTEXT)
@@ -501,6 +527,8 @@ def __read_calldef(self, calldef, attrs, is_declaration):
501527
else:
502528
calldef.does_throw = True
503529
calldef.exceptions = throw_stmt.split()
530+
if attrs.get(XML_AN_COMMENT):
531+
calldef.comment = attrs.get(XML_AN_COMMENT)
504532

505533
def __read_member_function(self, calldef, attrs, is_declaration):
506534
self.__read_calldef(calldef, attrs, is_declaration)
@@ -551,6 +579,8 @@ def __read_variable(self, attrs):
551579
XML_AN_INIT),
552580
bits=bits)
553581
self.__read_byte_offset(decl, attrs)
582+
if attrs.get(XML_AN_COMMENT):
583+
decl.comment = attrs.get(XML_AN_COMMENT)
554584
return decl
555585

556586
__read_field = __read_variable # just a synonym
@@ -568,6 +598,8 @@ def __read_class_impl(self, class_type, attrs):
568598
decl.is_abstract = bool(attrs.get(XML_AN_ABSTRACT, False))
569599
self.__read_byte_size(decl, attrs)
570600
self.__read_byte_align(decl, attrs)
601+
if attrs.get(XML_AN_COMMENT):
602+
decl.comment = attrs.get(XML_AN_COMMENT)
571603
return decl
572604

573605
def __read_class(self, attrs):
@@ -584,6 +616,13 @@ def __read_casting_operator(self, attrs):
584616
self.__read_member_function(operator, attrs, True)
585617
return operator
586618

619+
def __read_comment(self, attrs):
620+
comment = self.__decl_factory.create_comment()
621+
comment._start_line = int(attrs.get(XML_AN_BEGIN_LINE))
622+
comment._end_line = int(attrs.get(XML_AN_END_LINE))
623+
self.__read_location(comment, attrs, self.__name_attrs_to_skip)
624+
return comment
625+
587626
def __read_constructor(self, attrs):
588627
constructor = self.__decl_factory.create_constructor()
589628
self.__read_member_function(constructor, attrs, True)

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
"pygccxml.declarations",
3636
"pygccxml.parser",
3737
"pygccxml.utils"],
38+
install_requires=[
39+
"castxml>=0.3.6"],
3840
extras_require={
3941
"test": list(requirements_test),
4042
"docs": list(requirements_docs),

unittests/data/test_comments.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2014-2017 Insight Software Consortium.
2+
// Copyright 2004-2009 Roman Yakovenko.
3+
// Distributed under the Boost Software License, Version 1.0.
4+
// See http://www.boost.org/LICENSE_1_0.txt
5+
6+
/** class comment */
7+
class test
8+
{
9+
public:
10+
// Non-doc comment before.
11+
/** doc comment */
12+
// Non-doc comment after.
13+
test();
14+
15+
/// cxx comment
16+
/// with multiple lines
17+
int hello();
18+
19+
//! mutable field comment
20+
int val1 = 0;
21+
/// bit field comment
22+
double val2=2;
23+
};

unittests/test_all.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
from . import test_find_noncopyable_vars
8484
from . import test_hash
8585
from . import test_null_comparison
86+
from . import test_comments
8687

8788
testers = [
8889
pep8_tester,
@@ -157,6 +158,7 @@
157158
test_find_noncopyable_vars,
158159
test_hash,
159160
test_null_comparison,
161+
test_comments,
160162
]
161163

162164
if platform.system() != 'Windows':

unittests/test_comments.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2014-2020 Insight Software Consortium.
2+
# Copyright 2004-2009 Roman Yakovenko.
3+
# Distributed under the Boost Software License, Version 1.0.
4+
# See http://www.boost.org/LICENSE_1_0.txt
5+
6+
import unittest
7+
8+
from . import parser_test_case
9+
10+
from pygccxml import parser
11+
from pygccxml import declarations
12+
13+
14+
class Test(parser_test_case.parser_test_case_t):
15+
global_ns = None
16+
17+
def __init__(self, *args):
18+
parser_test_case.parser_test_case_t.__init__(self, *args)
19+
self.header = "test_comments.hpp"
20+
self.global_ns = None
21+
self.config.castxml_epic_version = 1
22+
23+
def setUp(self):
24+
25+
if not self.global_ns:
26+
decls = parser.parse([self.header], self.config)
27+
Test.global_ns = declarations.get_global_namespace(decls)
28+
Test.xml_generator_from_xml_file = \
29+
self.config.xml_generator_from_xml_file
30+
self.xml_generator_from_xml_file = Test.xml_generator_from_xml_file
31+
self.global_ns = Test.global_ns
32+
33+
def test(self):
34+
"""
35+
Check the comment parsing
36+
"""
37+
if self.config.castxml_epic_version != 1:
38+
# Run this test only with castxml epic version == 1
39+
return
40+
tclass = self.global_ns.class_("test")
41+
self.assertIn("comment", dir(tclass))
42+
self.assertEqual(["/** class comment */"], tclass.comment.text)
43+
44+
tmethod = tclass.member_functions()[0]
45+
46+
self.assertIn("comment", dir(tmethod))
47+
self.assertEqual([" /// cxx comment", " /// with multiple lines"], tmethod.comment.text)
48+
49+
tconstructor = tclass.constructors()[0]
50+
51+
self.assertIn("comment", dir(tconstructor))
52+
self.assertEqual([" /** doc comment */"], tconstructor.comment.text)
53+
54+
def create_suite():
55+
suite = unittest.TestSuite()
56+
suite.addTest(unittest.makeSuite(Test))
57+
return suite
58+
59+
60+
def run_suite():
61+
unittest.TextTestRunner(verbosity=2).run(create_suite())
62+
63+
64+
if __name__ == "__main__":
65+
run_suite()

0 commit comments

Comments
 (0)