Skip to content

Commit

Permalink
Move some parsing out of bound proc initialiser into parser
Browse files Browse the repository at this point in the history
  • Loading branch information
ZedThree committed Sep 18, 2024
1 parent 6cf37bb commit 65a4f55
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 55 deletions.
107 changes: 52 additions & 55 deletions ford/sourceform.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
)
BOUNDPROC_RE = re.compile(
r"""^(?P<generic>generic|procedure)\s* # Required keyword
(?P<prototype>\([^()]*\))?\s* # Optional interface name
(?:\((?P<prototype>[^()]*)\)\s*)? # Optional interface name
(?:,\s*(?P<attributes>\w[^:]*))? # Optional list of attributes
(?:\s*::)?\s* # Optional double-colon
(?P<names>\w.*)$ # Required name(s)
Expand Down Expand Up @@ -673,41 +673,55 @@ def parse_bound_procedure(
entity.print_error(line, "Unexpected type-bound procedure")
return

names = match["names"].split(",")
attributes = match["attributes"]
generic = match["generic"].lower()
attributes = paren_split(",", match["attributes"] or "")
generic = match["generic"].lower() == "generic"
prototype = match["prototype"]
permission = child_permission
deferred = False

# Generic procedures or single name
if generic == "generic" or len(names) == 1:
entity.boundprocs.append(
FortranBoundProcedure(
source,
self,
entity,
child_permission,
names=match["names"],
attributes=attributes,
generic=generic,
prototype=prototype,
)
)
return
proc_attributes = []

for attribute in attributes:
attribute = attribute.strip().lower()
if not attribute:
continue

if attribute in ["public", "private"]:
permission = attribute
elif attribute == "deferred":
deferred = True
else:
proc_attributes.append(attribute)

def find_bindings(line: str) -> Tuple[str, List[str]]:
"""Get the bound name and what it points to"""
split = POINTS_TO_RE.split(line)
name = split[0].strip()
binds = SPLIT_RE.split(split[1]) if len(split) > 1 else (name,)
return name, [bind.strip() for bind in binds]

# Slight complication here: for generic bound procedures, we
# want to make a single instance for the bound name and all
# its bindings. For non-generic bound procedures, we want to
# make a new instance for each name that appears in the
# declaration. To reduce duplication, we make a list of the
# whole generic declaration
names = [match["names"]] if generic else match["names"].split(",")

# For multiple procedures, parse each one as if it
# were on a line by itself
for bind in reversed(names):
pseudo_line = BOUNDPROC_RE.match(line[: match.start("names")] + bind)
name, bindings = find_bindings(bind)
entity.boundprocs.append(
FortranBoundProcedure(
source,
pseudo_line,
entity,
child_permission,
names=match["names"],
attributes=attributes,
self,
name=name,
bindings=bindings,
parent=entity,
inherited_permission=permission,
attributes=proc_attributes,
generic=generic,
prototype=prototype,
deferred=deferred,
)
)

Expand Down Expand Up @@ -1866,7 +1880,9 @@ def __init__(
self.raw_src = pathlib.Path(self.path).read_text(encoding=settings.encoding)
lexer = FortranFixedLexer() if self.fixed else FortranLexer()
self.src = highlight(
self.raw_src, lexer, HtmlFormatter(lineanchors="ln", cssclass="hl codehilite")
self.raw_src,
lexer,
HtmlFormatter(lineanchors="ln", cssclass="hl codehilite"),
)
except Exception:
pass
Expand Down Expand Up @@ -2677,36 +2693,17 @@ class FortranBoundProcedure(FortranBase):
"""

def _initialize(self, **kwargs) -> None:
self.attribs: List[str] = []
self.deferred = False
"""Is a deferred procedure"""
self.name: str = kwargs["name"]
self.attribs: list = kwargs["attributes"]
self.generic: bool = kwargs["generic"]
self.proto = kwargs["prototype"]
self.bindings: List[Union[str, FortranProcedure, FortranBoundProcedure]] = (
kwargs["bindings"]
)
self.deferred: bool = kwargs["deferred"]
self.protomatch = False
"""Prototype has been matched to procedure"""

for attribute in ford.utils.paren_split(",", kwargs["attributes"] or ""):
attribute = attribute.strip().lower()
if not attribute:
continue

if attribute in ["public", "private"]:
self.permission = attribute
elif attribute == "deferred":
self.deferred = True
else:
self.attribs.append(attribute)

split = POINTS_TO_RE.split(kwargs["names"])
self.name = split[0].strip()
self.generic = kwargs["generic"].lower() == "generic"
self.proto = kwargs["prototype"]
if self.proto:
self.proto = self.proto[1:-1].strip()

binds = SPLIT_RE.split(split[1]) if len(split) > 1 else (self.name,)
self.bindings: List[Union[str, FortranProcedure, FortranBoundProcedure]] = [
bind.strip() for bind in binds
]

@property
def binding_type(self) -> str:
"""String representation of binding type"""
Expand Down
27 changes: 27 additions & 0 deletions test/test_sourceform.py
Original file line number Diff line number Diff line change
Expand Up @@ -2270,3 +2270,30 @@ def test_no_space_after_character_type(parse_fortran_file):
source = parse_fortran_file(data)
function = source.functions[0]
assert function.name.lower() == "firstword"


def test_multiple_bound_procedures(parse_fortran_file):
data = """\
module test
type :: foo
contains
procedure(proto), public, nopass, deferred :: a, b, c
end type
contains
subroutine a
end subroutine
subroutine b
end subroutine
subroutine c
end subroutine
end module
"""

fortran_file = parse_fortran_file(data)
fortran_type = fortran_file.modules[0].types[0]
expected_names = ["a", "b", "c"]
bound_proc_names = sorted([bp.name for bp in fortran_type.boundprocs])
assert bound_proc_names == expected_names

bound_proc_protos = sorted([bp.proto for bp in fortran_type.boundprocs])
assert bound_proc_protos == ["proto", "proto", "proto"]

0 comments on commit 65a4f55

Please sign in to comment.