Skip to content

Commit

Permalink
doccursor: simplify API for current docstring behaviour
Browse files Browse the repository at this point in the history
We want to do better than this later on, but for now we need to simplify
the parser so that we focus instead on API alone with docstring as the
biggest consumer. In order to do that the easy way, we will start by
providing an API that reflects the current docstring API, then pass the
cursor to docstring directly simplifying its own API and finally
isolating the parser from docstring entirely.

The idea is _not_ to mimic Clang's cursor any more! Any similarity in
attributes and property names is coincidental from now on.

By this point we've also made up the lost performance from the stub
commit!
  • Loading branch information
BrunoMSantos committed Nov 16, 2023
1 parent 67adbba commit 34282b3
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 59 deletions.
151 changes: 107 additions & 44 deletions src/hawkmoth/doccursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,111 @@ class DocCursor:
def __init__(self, domain=None, cursor=None, comments=None):
self._comments = comments if comments else {}
self._cc = cursor

self.domain = domain
self.hash = self._cc.hash
self.kind = self._cc.kind
self._domain = domain

if self._cc.hash in self._comments:
self.comment = self._comments[self._cc.hash]
self._comment = self._comments[self._cc.hash]
else:
self.comment = None

self.displayname = self._cc.displayname
if self._cc.kind == CursorKind.ENUM_CONSTANT_DECL:
if '=' in [t.spelling for t in self.get_tokens()]:
self.enum_value = self._cc.enum_value
else:
self.enum_value = None
self.is_scoped_enum = self._cc.is_scoped_enum
self.spelling = self._cc.spelling
self.type = self._cc.type
self._comment = None

def __hash__(self):
return self._cc.hash

def get_meta(self):
@property
def meta(self):
return {
'line': self.comment.extent.start.line if self.comment else '',
'line': self.line,
'cursor.kind': self._cc.kind,
'cursor.displayname': self._cc.displayname,
'cursor.spelling': self._cc.spelling,
}

@property
def comment(self):
return self._comment.spelling if self._comment else None

@property
def domain(self):
return self._domain

@property
def kind(self):
return self._cc.kind

@property
def name(self):
return self._cc.spelling

@property
def decl_name(self):
if self._cc.kind in [CursorKind.VAR_DECL, CursorKind.FIELD_DECL]:
return self._var_type_fixup(self)[1]
if self._cc.kind in [CursorKind.STRUCT_DECL,
CursorKind.UNION_DECL,
CursorKind.ENUM_DECL,
CursorKind.CLASS_DECL,
CursorKind.CLASS_TEMPLATE]:
return self._type_definition_fixup()
else:
return self.name

@property
def type(self):
if self._cc.kind in [CursorKind.VAR_DECL, CursorKind.FIELD_DECL]:
return self._var_type_fixup(self)[0]
if self._cc.kind == CursorKind.FUNCTION_DECL:
return self._function_fixup()
if self._cc.kind in [CursorKind.CONSTRUCTOR,
CursorKind.DESTRUCTOR,
CursorKind.CXX_METHOD,
CursorKind.FUNCTION_TEMPLATE]:
return self._method_fixup()
else:
return self._cc.type.spelling

@property
def line(self):
return self._comment.extent.start.line if self._comment else None

@property
def args(self):
if self._cc.kind == CursorKind.MACRO_DEFINITION:
return self._get_macro_args()
if self._cc.kind in [CursorKind.FUNCTION_DECL,
CursorKind.CONSTRUCTOR,
CursorKind.DESTRUCTOR,
CursorKind.CXX_METHOD,
CursorKind.FUNCTION_TEMPLATE]:
return self._get_fn_args()
else:
return None

@property
def quals(self):
if self._cc.kind == CursorKind.FUNCTION_DECL:
return ''
if self._cc.kind in [CursorKind.CONSTRUCTOR,
CursorKind.DESTRUCTOR,
CursorKind.CXX_METHOD,
CursorKind.FUNCTION_TEMPLATE]:
return ' '.join(self._get_method_quals()[1])
else:
return None

@property
def is_scoped_enum(self):
return self._cc.is_scoped_enum()

@property
def value(self):
if self._cc.kind == CursorKind.ENUM_CONSTANT_DECL:
if '=' in [t.spelling for t in self.get_tokens()]:
return self._cc.enum_value
else:
return None
else:
return None

def get_children(self):
"""Get children cursors."""
domain = self.domain
Expand Down Expand Up @@ -102,17 +176,7 @@ def get_tokens(self):

yield from tu.get_tokens(extent=extent)

def var_type_fixup(self):
"""Fix non trivial variable and argument types.
If this is an array, the dimensions should be applied to the name, not
the type. If this is a function pointer, or an array of function
pointers, the name should be within the parenthesis as in ``(*name)``
or ``(*name[N])``.
"""
return self._var_type_fixup(self)

def get_args(self):
def _get_fn_args(self):
"""Get function / method arguments."""
args = []

Expand All @@ -130,10 +194,8 @@ def get_args(self):

return args

def function_fixup(self):
def _function_fixup(self):
"""Parse additional details of a function declaration."""
args = self.get_args()

full_type = self._get_function_quals()

template_line = self._get_template_line()
Expand All @@ -144,19 +206,17 @@ def function_fixup(self):

ttype = ' '.join(full_type)

return ttype, args
return ttype

def method_fixup(self):
def _method_fixup(self):
"""Parse additional details of a method declaration."""
args = self.get_args()

full_type = []

access_spec = self._get_access_specifier()
if access_spec:
full_type.append(access_spec)

pre_quals, pos_quals = self._get_method_quals()
pre_quals, _ = self._get_method_quals()

full_type.extend(pre_quals)

Expand All @@ -168,11 +228,10 @@ def method_fixup(self):
full_type.append(self._cc.result_type.spelling)

ttype = ' '.join(full_type)
quals = ' '.join(pos_quals)

return ttype, args, quals
return ttype

def type_definition_fixup(self):
def _type_definition_fixup(self):
"""Fix non trivial type definitions."""
type_elem = []

Expand Down Expand Up @@ -205,16 +264,13 @@ def type_definition_fixup(self):

return f'{template}{self._cc.spelling}{colon_suffix}'

def get_macro_args(self):
def _get_macro_args(self):
"""Get macro arguments.
Returns:
None for simple macros, a potentially empty list of arguments for
function-like macros
"""
if self._cc.kind != CursorKind.MACRO_DEFINITION:
return None

tokens = self.get_tokens()

# Use the first two tokens to make sure this starts with 'IDENTIFIER('
Expand Down Expand Up @@ -464,6 +520,13 @@ def _normalize_type(type_string):

@staticmethod
def _var_type_fixup(cursor):
"""Fix non trivial variable and argument types.
If this is an array, the dimensions should be applied to the name, not
the type. If this is a function pointer, or an array of function
pointers, the name should be within the parenthesis as in ``(*name)``
or ``(*name[N])``.
"""
cursor_type = cursor._cc.type

stars_and_quals = ''
Expand Down
26 changes: 11 additions & 15 deletions src/hawkmoth/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,17 @@ def _comment_extract(tu):

def _recursive_parse(errors, cursor, nest):
domain = cursor.domain
comment = cursor.comment
name = cursor.spelling
ttype = cursor.type.spelling
text = comment.spelling
meta = cursor.get_meta()
name = cursor.name
decl_name = cursor.decl_name
ttype = cursor.type
text = cursor.comment
meta = cursor.meta
args = cursor.args
quals = cursor.quals

if cursor.kind == CursorKind.MACRO_DEFINITION:
# FIXME: check args against comment
args = cursor.get_macro_args()
args = cursor.args

if args is None:
ds = docstring.MacroDocstring(domain=domain, text=text,
Expand All @@ -215,8 +217,6 @@ def _recursive_parse(errors, cursor, nest):
return [ds]

elif cursor.kind in [CursorKind.VAR_DECL, CursorKind.FIELD_DECL]:
# Note: Preserve original name
ttype, decl_name = cursor.var_type_fixup()

if cursor.kind == CursorKind.VAR_DECL:
ds = docstring.VarDocstring(domain=domain, text=text, nest=nest,
Expand All @@ -243,8 +243,6 @@ def _recursive_parse(errors, cursor, nest):
CursorKind.CLASS_DECL,
CursorKind.CLASS_TEMPLATE]:

decl_name = cursor.type_definition_fixup()

if cursor.kind == CursorKind.STRUCT_DECL:
ds = docstring.StructDocstring(domain=domain, text=text,
nest=nest, name=name,
Expand All @@ -254,7 +252,7 @@ def _recursive_parse(errors, cursor, nest):
nest=nest, name=name,
decl_name=decl_name, meta=meta)
elif cursor.kind == CursorKind.ENUM_DECL:
if cursor.is_scoped_enum():
if cursor.is_scoped_enum:
ds = docstring.EnumClassDocstring(domain=domain, text=text,
nest=nest, name=name,
decl_name=decl_name, meta=meta)
Expand All @@ -275,13 +273,12 @@ def _recursive_parse(errors, cursor, nest):

elif cursor.kind == CursorKind.ENUM_CONSTANT_DECL:
ds = docstring.EnumeratorDocstring(domain=domain, name=name,
value=cursor.enum_value, text=text,
value=cursor.value, text=text,
meta=meta, nest=nest)

return [ds]

elif cursor.kind == CursorKind.FUNCTION_DECL:
ttype, args = cursor.function_fixup()
ds = docstring.FunctionDocstring(domain=domain, text=text,
nest=nest, name=name,
ttype=ttype, args=args,
Expand All @@ -292,7 +289,6 @@ def _recursive_parse(errors, cursor, nest):
CursorKind.DESTRUCTOR,
CursorKind.CXX_METHOD,
CursorKind.FUNCTION_TEMPLATE]:
ttype, args, quals = cursor.method_fixup()
ds = docstring.FunctionDocstring(domain=domain, text=text,
nest=nest, name=name,
ttype=ttype, args=args,
Expand All @@ -301,7 +297,7 @@ def _recursive_parse(errors, cursor, nest):

# If we reach here, nothing matched i.e. there's a documentation comment
# above an unexpected cursor.
message = f'documentation comment attached to unexpected cursor {str(cursor.kind)} {cursor.spelling}' # noqa: E501
message = f'documentation comment attached to unexpected cursor {str(cursor.kind)} {cursor.name}' # noqa: E501
errors.append(ParserError(ErrorLevel.WARNING, cursor.location.file.name,
cursor.location.line, message))

Expand Down

0 comments on commit 34282b3

Please sign in to comment.