Skip to content

Commit 7670fd6

Browse files
authored
Merge pull request #4 from ACRIOS-Systems/feature/automated_documentation_fixed_multicomments
Feature/automated documentation fixed multicomments
2 parents b7788a5 + 8863e56 commit 7670fd6

File tree

2 files changed

+154
-57
lines changed

2 files changed

+154
-57
lines changed

dissect/cstruct/cstruct.py

Lines changed: 87 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class {name}(Structure):
7070
def __init__(self, cstruct, structure, source=None):
7171
self.structure = structure
7272
self.source = source
73-
super({name}, self).__init__(cstruct, structure.name, structure.fields)
73+
super({name}, self).__init__(cstruct, structure.name, structure.fields, structure.commentAttributes)
7474
7575
def _read(self, stream):
7676
r = OrderedDict()
@@ -88,6 +88,14 @@ def __repr__(self):
8888
return '<Structure {name} +compiled>'
8989
"""
9090

91+
COMMENT_MULTILINE = r'/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
92+
COMMENT_INLINE = r'//[^\n]*'
93+
COMMENT_MULTILINE_REPEATED = r'(^[ ]*('+COMMENT_INLINE+r'|'+COMMENT_MULTILINE+r'([ ]*('+COMMENT_INLINE+r'|'+COMMENT_MULTILINE+r'))*)[ \t\r]*\n?)*^[ ]*('+COMMENT_INLINE+r'|(?P<commentBlock>'+COMMENT_MULTILINE+r'))+'
94+
COMMENT_REGEX_START=r'('+COMMENT_MULTILINE_REPEATED+r')?[ \t\r\n]*'
95+
COMMENT_REGEX_END =r'(?P<commentBlockAfter>(([ ]*'+COMMENT_MULTILINE+r')+)?[ ]*('+COMMENT_INLINE+r')?)?[ \t\r]*'
96+
97+
#print(f"COMMENT_REGEX_START:{COMMENT_REGEX_START}")
98+
#print(f"COMMENT_REGEX_END:{COMMENT_REGEX_END}")
9199

92100
class Error(Exception):
93101
pass
@@ -333,44 +341,58 @@ def _constants(self, data):
333341

334342
def _enums(self, data):
335343
r = re.finditer(
336-
r'enum\s+(?P<name>[^\s:{]+)\s*(:\s*(?P<type>[^\s]+)\s*)?\{(?P<values>[^}]+)\}\s*;',
337-
data,
344+
COMMENT_REGEX_START+r'enum\s+(?P<name>[^\s:{]+)\s*(:\s*(?P<type>[^\s]+)\s*)?\{(?P<values>[^}]+)\}\s*;'+COMMENT_REGEX_END,
345+
data, re.MULTILINE
338346
)
339347
for t in r:
340348
d = t.groupdict()
341349

342-
nextval = 0
343-
values = {}
344-
for line in d['values'].split('\n'):
345-
line, sep, comment = line.partition("//")
346-
for v in line.split(","):
347-
key, sep, val = v.partition("=")
348-
key = key.strip()
349-
val = val.strip()
350-
if not key:
351-
continue
352-
if not val:
353-
val = nextval
354-
else:
355-
val = Expression(self.cstruct, val).evaluate({})
356-
357-
nextval = val + 1
358-
359-
values[key] = val
360-
361350
if not d['type']:
362351
d['type'] = 'uint32'
363352

353+
values, valuesDetails = self._parse_fields_enums(d['values'])
354+
355+
commentAttributes = self.parse_comment_block(d['commentBlock'])
356+
364357
enum = Enum(
365-
self.cstruct, d['name'], self.cstruct.resolve(d['type']), values
358+
self.cstruct, d['name'], self.cstruct.resolve(d['type']), values, valuesDetails, commentAttributes
366359
)
367360
self.cstruct.addtype(enum.name, enum)
368361

362+
def _parse_fields_enums(self, s):
363+
nextval = 0
364+
values = {}
365+
valuesDetails = {}
366+
fields = re.finditer(
367+
COMMENT_REGEX_START+r'(?P<value>[a-zA-z][^\n,/]*),?'+COMMENT_REGEX_END,
368+
s, re.MULTILINE
369+
)
370+
371+
for f in fields:
372+
d = f.groupdict()
373+
374+
# Ignore fo now
375+
commentAttributes = self.parse_comment_block(d['commentBlock'])
376+
377+
field = re.finditer(
378+
r'(?P<key>[a-zA-z][^ =]*)[ ]*=?[ ]*(?P<value>[^ ]+)?',
379+
d["value"],
380+
)
381+
382+
f = list(field)[0].groupdict()
383+
384+
values[f["key"]] = Expression(self.cstruct, f["value"]).evaluate({}) if f["value"] != None else nextval
385+
386+
nextval = values[f["key"]] + 1
387+
valuesDetails[f["key"]] = {"value":values[f["key"]], "commentAttributes":commentAttributes}
388+
389+
return values, valuesDetails
390+
369391
def _structs(self, data):
370392
compiler = Compiler(self.cstruct)
371393
r = re.finditer(
372-
r'(#(?P<flags>(?:compile))\s+)?((?P<typedef>typedef)\s+)?(?P<type>[^\s]+)\s+(__attribute__\(\([^)]+\)\)\s*)?(?P<name>[^\s]+)?(?P<fields>\s*\{(\s*//[^\n]*|/\*[^*]*\*/|[^}])+\}(?P<defs>\s+[^;\n]+)?)?\s*;',
373-
data,
394+
COMMENT_REGEX_START+r'(#(?P<flags>(?:compile))\s+)?((?P<typedef>typedef)\s+)?(?P<type>[^\s]+)\s+(__attribute__\(\([^)]+\)\)\s*)?(?P<name>[^\s]+)?(?P<fields>\s*\{(\s*//[^\n]*|/\*[^*]*\*/|[^}])+\}(?P<defs>\s+[^;\n]+)?)?\s*;'+COMMENT_REGEX_END,
395+
data, re.MULTILINE
374396
)
375397
for t in r:
376398
d = t.groupdict()
@@ -383,8 +405,9 @@ def _structs(self, data):
383405
raise ParserError("No name for struct")
384406

385407
if d['type'] == 'struct':
386-
data = self._parse_fields(d['fields'][1:-1].strip())
387-
st = Structure(self.cstruct, name, data)
408+
data = self._parse_fields_struct(d['fields'][1:-1].strip())
409+
commentAttributes = self.parse_comment_block(d['commentBlock'])
410+
st = Structure(self.cstruct, name, data, commentAttributes)
388411
if d['flags'] == 'compile' or self.compiled:
389412
st = compiler.compile(st)
390413
elif d['typedef'] == 'typedef':
@@ -400,32 +423,18 @@ def _structs(self, data):
400423
td = td.strip()
401424
self.cstruct.addtype(td, st)
402425

403-
def _parse_fields(self, s):
404-
commentAttributes = {}
426+
def _parse_fields_struct(self, s):
405427
fields = re.finditer(
406-
r'(?P<commentBlock>\/\*(\*(?!\/)|[^*])*\*\/)?[ \t\r\n]*(?P<type>[^\s]+)\s+(?P<name>[^\s\[:]+)(\s*:\s*(?P<bits>\d+))?(\[(?P<count>[^;\n]*)\])?;',
407-
s,
428+
COMMENT_REGEX_START+r'(?P<type>[^\s]+)\s+(?P<name>[^\s\[:]+)(\s*:\s*(?P<bits>\d+))?(\[(?P<count>[^;\n]*)\])?;'+COMMENT_REGEX_END,
429+
s, re.MULTILINE
408430
)
409431
r = []
410432
for f in fields:
411433
d = f.groupdict()
412434
if d['type'].startswith('//'):
413435
continue
414436

415-
commentAttributes={}
416-
417-
#parse the comment header
418-
if d['commentBlock'] is not None and d['commentBlock'].startswith('/*'):
419-
commentfields = re.finditer(
420-
r'@(?P<commentType>[^@,;:\\]+):[ \t]*(?P<commentVal>[^@,;:\s\\]+)',
421-
d['commentBlock'],
422-
)
423-
for cf in commentfields:
424-
cd=cf.groupdict()
425-
try:
426-
commentAttributes[cd['commentType']]=cd['commentVal']
427-
except Exception:
428-
pass
437+
commentAttributes = self.parse_comment_block(d['commentBlock'])
429438

430439
type_ = self.cstruct.resolve(d['type'])
431440

@@ -449,11 +458,32 @@ def _parse_fields(self, s):
449458
d['name'] = d['name'][1:]
450459
type_ = Pointer(self.cstruct, type_)
451460

452-
field = Field(d['name'], type_, int(d['bits']) if d['bits'] else None, commentAttributes=commentAttributes)
461+
field = StructureField(d['name'], type_, int(d['bits']) if d['bits'] else None, commentAttributes=commentAttributes)
453462
r.append(field)
454463

455464
return r
456465

466+
def parse_comment_block(self,s):
467+
commentAttributes={}
468+
469+
#parse the comment header
470+
if s is not None and s.startswith('/*'):
471+
commentfields = re.finditer(
472+
r'@(?P<commentType>[^@,;:\\]+):[ \t]*(?P<commentVal>[^@\n]+)',
473+
s,
474+
)
475+
for cf in commentfields:
476+
cd=cf.groupdict()
477+
try:
478+
oldData = commentAttributes.get(cd['commentType'],"")
479+
if "" != oldData:
480+
oldData += " "
481+
commentAttributes[cd['commentType']]=oldData + cd['commentVal']
482+
except Exception:
483+
pass
484+
485+
return commentAttributes
486+
457487
def _lookups(self, data, consts):
458488
r = re.finditer(r'\$(?P<name>[^\s]+) = ({[^}]+})\w*\n', data)
459489

@@ -757,11 +787,12 @@ def __repr__(self):
757787
class Structure(BaseType):
758788
"""Type class for structures."""
759789

760-
def __init__(self, cstruct, name, fields=None):
790+
def __init__(self, cstruct, name, fields=None, commentAttributes={}):
761791
self.name = name
762792
self.size = None
763793
self.lookup = OrderedDict()
764794
self.fields = fields if fields else []
795+
self.commentAttributes = commentAttributes
765796

766797

767798
for f in self.fields:
@@ -885,7 +916,7 @@ def add_fields(self, name, type_, offset=None, commentAttributes={}):
885916
type_: The field type.
886917
offset: The field offset.
887918
"""
888-
field = Field(name, type_, offset=offset, commentAttributes=commentAttributes)
919+
field = StructureField(name, type_, offset=offset, commentAttributes=commentAttributes)
889920
self.fields.append(field)
890921
self.lookup[name] = field
891922
self.size = None
@@ -915,7 +946,7 @@ def __repr__(self):
915946
def show(self, indent=0):
916947
"""Pretty print this structure."""
917948
if indent == 0:
918-
print("struct {}".format(self.name))
949+
print("{} struct {}".format(self.commentAttributes, self.name))
919950

920951
for field in self.fields:
921952
if field.offset is None:
@@ -983,7 +1014,7 @@ def reset(self):
9831014
self._remaining = 0
9841015

9851016

986-
class Field(object):
1017+
class StructureField(object):
9871018
"""Holds a structure field."""
9881019

9891020
def __init__(self, name, type_, bits=None, offset=None, commentAttributes={}):
@@ -1312,9 +1343,11 @@ class Enum(RawType):
13121343
};
13131344
"""
13141345

1315-
def __init__(self, cstruct, name, type_, values):
1346+
def __init__(self, cstruct, name, type_, values, valuesDetails, commentAttributes={}):
13161347
self.type = type_
13171348
self.values = values
1349+
self.valuesDetails = valuesDetails
1350+
self.commentAttributes = commentAttributes
13181351
self.reverse = {}
13191352

13201353
for k, v in values.items():
@@ -1365,6 +1398,9 @@ def __getattr__(self, attr):
13651398
def __contains__(self, attr):
13661399
return attr in self.values
13671400

1401+
def __repr__(self):
1402+
return '<Enum {}>'.format(self.name)
1403+
13681404

13691405
class EnumInstance(object):
13701406
"""Implements a value instance of an Enum"""

tests/test_basic.py

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,15 @@ def test_enum_comments():
257257
assert c.Inline.foo == 9
258258
assert c.Inline.bar == 10
259259

260+
assert c.Inline.valuesDetails["hello"]["value"] == 7
261+
assert c.Inline.valuesDetails["hello"]["commentAttributes"] == {}
262+
assert c.Inline.valuesDetails["world"]["value"] == 8
263+
assert c.Inline.valuesDetails["world"]["commentAttributes"] == {}
264+
assert c.Inline.valuesDetails["foo"]["value"] == 9
265+
assert c.Inline.valuesDetails["foo"]["commentAttributes"] == {}
266+
assert c.Inline.valuesDetails["bar"]["value"] == 10
267+
assert c.Inline.valuesDetails["bar"]["commentAttributes"] == {}
268+
260269
assert c.Test.a == 2
261270
assert c.Test.b == 3
262271
assert c.Test.c == 4
@@ -772,16 +781,24 @@ def test_dumpstruct(capsys):
772781
assert captured_1.out == captured_2.out
773782

774783

775-
def test_commentfieldparse(capsys):
784+
def test_comment_field_parse_struct(capsys):
776785
c = cstruct.cstruct()
777786
c.load("""
778-
/*discardedCom1*/
787+
/*
788+
* @comment: Hello,
789+
* @comment: how are you?
790+
*/
779791
struct test{
792+
// int notAnStruct;
780793
/*
781-
* @scale: 0.001
782-
* @unit: µtestUnit1
783-
*/
794+
* @comment: I am fine.
795+
* @comment: Thank you.
796+
* @scale: 0.001
797+
* @unit: µtestUnit1
798+
*/
784799
int testVar1;
800+
// int testVar1;
801+
// int notAnStruct;
785802
int testVar2;
786803
/* dicardedCom2
787804
* @scale: 5
@@ -792,15 +809,59 @@ def test_commentfieldparse(capsys):
792809
""", compiled=False)
793810

794811
assert c.test.name == 'test'
812+
assert c.test.commentAttributes['comment'] == 'Hello, how are you?'
795813

796814
assert 'testVar1' in c.test.lookup
797815
assert 'testVar2' in c.test.lookup
798816
assert 'testVar2' in c.test.lookup
799817

818+
assert c.test.lookup['testVar1'].commentAttributes['comment'] == 'I am fine. Thank you.'
800819
assert c.test.lookup['testVar1'].commentAttributes['scale'] == '0.001'
801820
assert c.test.lookup['testVar1'].commentAttributes['unit'] == 'µtestUnit1'
802821

803822
assert c.test.lookup['testVar2'].commentAttributes == {}
804823

805824
assert c.test.lookup['testVar3'].commentAttributes['scale'] == '5'
806-
assert c.test.lookup['testVar3'].commentAttributes['unit'] == '%testUnit2'
825+
assert c.test.lookup['testVar3'].commentAttributes['unit'] == '%testUnit2'
826+
827+
assert "notAnStruct" not in c.test.lookup
828+
829+
def test_comment_field_parse_enum(capsys):
830+
c = cstruct.cstruct()
831+
c.load("""
832+
/*discardedCom1*/
833+
enum test{
834+
// notAnEnum=4,
835+
/*
836+
* @comment: Comments are working
837+
*/
838+
testEnumVar1=5,
839+
// testEnumVar1,
840+
testEnumVar2,
841+
// notAnEnum=14,
842+
/*
843+
* @comment: Comments are working 2
844+
*/
845+
testEnumVar3,
846+
};
847+
""", compiled=False)
848+
849+
assert c.test.name == 'test'
850+
assert 'comment' not in c.test.commentAttributes
851+
852+
assert 'testEnumVar1' in c.test.values
853+
assert 'testEnumVar1' in c.test.valuesDetails
854+
assert 'testEnumVar2' in c.test.values
855+
assert 'testEnumVar2' in c.test.valuesDetails
856+
assert 'testEnumVar3' in c.test.values
857+
assert 'testEnumVar3' in c.test.valuesDetails
858+
859+
860+
assert c.test.valuesDetails['testEnumVar1']["commentAttributes"]['comment'] == 'Comments are working'
861+
862+
assert c.test.valuesDetails['testEnumVar2']["commentAttributes"] == {}
863+
864+
assert c.test.valuesDetails['testEnumVar3']["commentAttributes"]['comment'] == 'Comments are working 2'
865+
866+
assert "notAnEnum" not in c.test.values
867+
assert "notAnEnum" not in c.test.valuesDetails

0 commit comments

Comments
 (0)