Skip to content

Commit

Permalink
[sourcegen] Improve nomenclature
Browse files Browse the repository at this point in the history
  • Loading branch information
ischoegl committed Jan 2, 2025
1 parent 76caedf commit 9f3f308
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 157 deletions.
42 changes: 26 additions & 16 deletions interfaces/sourcegen/sourcegen/_HeaderFileParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@
from ._dataclasses import HeaderFile, Func, Recipe
from ._helpers import read_config

_logger = logging.getLogger()

_clib_path = Path(__file__).parents[3] / "include" / "cantera" / "clib"
_clib_ignore = ["clib_defs.h", "ctmatlab.h"]
_LOGGER = logging.getLogger()

_data_path = Path(__file__).parent / "_data"
_CLIB_PATH = Path(__file__).parents[3] / "include" / "cantera" / "clib"
_CLIB_IGNORE = ["clib_defs.h", "ctmatlab.h"]

_DATA_PATH = Path(__file__).parent / "_data"

class HeaderFileParser:
"""
Parser for header files or corresponding YAML specifications.
Provides for convenience methods to generate lists of `HeaderFile` objects, which
themselves are used for subsequent code scaffolding.
"""

def __init__(self, path: Path, ignore_funcs: list[str] = None):
self._path = path
Expand All @@ -26,7 +33,7 @@ def __init__(self, path: Path, ignore_funcs: list[str] = None):
@classmethod
def headers_from_yaml(cls, ignore_files, ignore_funcs) -> list[HeaderFile]:
"""Parse header file YAML configuration."""
files = [ff for ff in _data_path.glob("*.yaml") if ff.name not in ignore_files]
files = [ff for ff in _DATA_PATH.glob("*.yaml") if ff.name not in ignore_files]
files.sort()
return [cls(ff, ignore_funcs.get(ff.name, []))._parse_yaml() for ff in files]

Expand All @@ -45,21 +52,24 @@ def _parse_yaml(self) -> HeaderFile:
if not isinstance(uses, list):
uses = [uses]
recipes.append(
Recipe(prefix,
func['name'],
base,
parents,
derived,
Recipe(func['name'],
func.get("implements", ""),
uses,
func.get("what", "")))
return HeaderFile(self._path, [], recipes, base, prefix)
func.get("what", ""),
func.get("brief", ""),
func.get("code", ""),
prefix,
base,
parents,
derived))

return HeaderFile(self._path, [], prefix, base, parents, derived, recipes)

@classmethod
def headers_from_h(cls, ignore_files, ignore_funcs) -> list[HeaderFile]:
"""Parse existing header file."""
files = [ff for ff in _clib_path.glob("*.h")
if ff.name not in ignore_files + _clib_ignore]
files = [ff for ff in _CLIB_PATH.glob("*.h")
if ff.name not in ignore_files + _CLIB_IGNORE]
files.sort()
return [cls(ff, ignore_funcs.get(ff.name, []))._parse_h() for ff in files]

Expand All @@ -75,9 +85,9 @@ def _parse_h(self) -> HeaderFile:

parsed = map(Func.from_str, c_functions)

_logger.info(f" parsing {self._path.name!r}")
_LOGGER.info(f" parsing {self._path.name!r}")
if self._ignore_funcs:
_logger.info(f" ignoring {self._ignore_funcs!r}")
_LOGGER.info(f" ignoring {self._ignore_funcs!r}")

parsed = [f for f in parsed if f.name not in self._ignore_funcs]

Expand Down
32 changes: 16 additions & 16 deletions interfaces/sourcegen/sourcegen/_TagFileParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
from ._helpers import with_unpack_iter


_logger = logging.getLogger(__name__)
_LOGGER = logging.getLogger(__name__)

_tag_path = Path(__file__).parents[3] / "build" / "doc"
_xml_path = _tag_path / "doxygen" / "xml"
_TAG_PATH = Path(__file__).parents[3] / "build" / "doc"
_XML_PATH = _TAG_PATH / "doxygen" / "xml"


@dataclass(frozen=True)
Expand Down Expand Up @@ -83,11 +83,11 @@ class TagFileParser:
"""Class handling contents of doxygen tag file."""

def __init__(self, bases: dict[str, str]) -> None:
tag_file = _tag_path / "Cantera.tag"
tag_file = _TAG_PATH / "Cantera.tag"
if not tag_file.exists():
msg = (f"Tag file does not exist at expected location:\n {tag_file}\n"
"Run 'scons doxygen' to generate.")
_logger.critical(msg)
_LOGGER.critical(msg)
sys.exit(1)

with tag_file.open() as fid:
Expand All @@ -114,7 +114,7 @@ def xml_compounds(kind: str, names: list[str]) -> dict[str,str]:
missing = '", "'.join(set(names) - set(found))
msg = f"Missing {kind!r} compound(s):\n {missing!r}\nusing regex "
msg += f"{regex}. Continuing with remaining compounds: \n {found!r}"
_logger.error(msg)
_LOGGER.error(msg)

# Parse content of namespace Cantera
namespace = xml_compounds("namespace", ["Cantera"])["Cantera"]
Expand All @@ -125,7 +125,7 @@ def xml_compounds(kind: str, names: list[str]) -> dict[str,str]:
unknown = set(bases) - set(class_names)
if "', '".join(unknown):
unknown = "', '".join(unknown)
_logger.critical("Class(es) in configuration file are missing "
_LOGGER.critical("Class(es) in configuration file are missing "
f"from tag file: {unknown!r}")
exit(1)

Expand Down Expand Up @@ -166,14 +166,14 @@ def detect(self, name, bases, permissive=True):
return name
if permissive:
return None
_logger.critical(f"Unable to detect {name!r} in doxygen tags.")
_LOGGER.critical(f"Unable to detect {name!r} in doxygen tags.")
exit(1)

def tag_info(self, func_string: str) -> TagInfo:
"""Look up tag information based on (partial) function signature."""
cxx_func = func_string.split("(")[0].split(" ")[-1]
if cxx_func not in self._known:
_logger.critical(f"Could not find {cxx_func!r} in doxygen tag file.")
_LOGGER.critical(f"Could not find {cxx_func!r} in doxygen tag file.")
sys.exit(1)
ix = 0
if len(self._known[cxx_func]) > 1:
Expand All @@ -183,7 +183,7 @@ def tag_info(self, func_string: str) -> TagInfo:
known = '\n - '.join(
[""] + [ArgList.from_xml(xml_tag("arglist", xml)).short_str()
for xml in self._known[cxx_func]])
_logger.critical(
_LOGGER.critical(
f"Need argument list to disambiguate {func_string!r}. "
f"possible matches are:{known}")
sys.exit(1)
Expand All @@ -195,7 +195,7 @@ def tag_info(self, func_string: str) -> TagInfo:
ix = i
break
if ix < 0:
_logger.critical(
_LOGGER.critical(
f"Unable to match {func_string!r} to known functions.")
sys.exit(1)

Expand Down Expand Up @@ -227,10 +227,10 @@ def cxx_func(self, func_string: str) -> CFunc:

def tag_lookup(tag_info: TagInfo) -> TagDetails:
"""Retrieve tag details from doxygen tree."""
xml_file = _xml_path / tag_info.anchorfile
xml_file = _XML_PATH / tag_info.anchorfile
if not xml_file.exists():
msg = (f"XML file does not exist at expected location: {xml_file}")
_logger.error(msg)
_LOGGER.error(msg)
return TagDetails()

with xml_file.open() as fid:
Expand All @@ -241,10 +241,10 @@ def tag_lookup(tag_info: TagInfo) -> TagDetails:
matches = re.findall(regex, xml_details)

if not matches:
_logger.error(f"No XML matches found for {tag_info.qualified_name!r}")
_LOGGER.error(f"No XML matches found for {tag_info.qualified_name!r}")
return TagDetails()
if len(matches) != 1:
_logger.warning(f"Inconclusive XML matches found for {tag_info.qualified_name!r}")
_LOGGER.warning(f"Inconclusive XML matches found for {tag_info.qualified_name!r}")
matches = matches[:1]

def cleanup(entry: str) -> str:
Expand Down Expand Up @@ -317,5 +317,5 @@ def xml_tags(tag: str, text: str, suffix: str="", permissive: bool=False) -> lis
blanks = text.split("\n")[-1].split("<")[0]
msg = f"Could not extract {tag!r} from:\n{blanks}{text}\n"
msg += f"using regex: {regex}"
_logger.error(msg)
_LOGGER.error(msg)
return []
1 change: 1 addition & 0 deletions interfaces/sourcegen/sourcegen/_data/ctkin_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ cabinet:
- name: nReactions
- name: del
what: noop
brief: Destructor; required by some APIs although object is managed by Solution.
- name: cabinetSize
- name: parentHandle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# at https://cantera.org/license.txt for license and copyright information.

cabinet:
prefix: soln3
prefix: sol3
base: Solution
parents: [] # List of parent classes
derived: [Interface] # List of specializations
Expand All @@ -28,4 +28,6 @@ cabinet:
- name: nAdjacent
- name: adjacent
implements: Solution::adjacent(size_t)
uses: [thermo, kinetics, transport]
what: constructor # registers object in CLib storage
- name: cabinetSize
1 change: 1 addition & 0 deletions interfaces/sourcegen/sourcegen/_data/ctthermo_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ cabinet:
uses: nSpecies
- name: del
what: noop
brief: Destructor; required by some APIs although object is managed by Solution.
- name: cabinetSize
- name: parentHandle
1 change: 1 addition & 0 deletions interfaces/sourcegen/sourcegen/_data/cttrans_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ cabinet:
- name: thermalConductivity
- name: del
what: noop
brief: Destructor; required by some APIs although object is managed by Solution.
- name: cabinetSize
- name: parentHandle
42 changes: 24 additions & 18 deletions interfaces/sourcegen/sourcegen/_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Param:

p_type: str #: Parameter type
name: str = "" #: Parameter name; may be empty if used for return argument

description: str = "" #: Parameter description (optional annotation)
direction: str = "" #: Direction of parameter (optional annotation)
default: Any = None #: Default value (optional)
Expand Down Expand Up @@ -155,8 +156,8 @@ class CFunc(Func):
uses: list['CFunc'] = None #: List of auxiliary C++ methods (optional)

@classmethod
def from_str(cls, func: str) -> 'CFunc':
"""Generate CFunc from header block of a function."""
def from_str(cls, func: str, brief="") -> 'CFunc':
"""Generate annotated CFunc from header block of a function."""
lines = func.split("\n")
func = super().from_str(lines[-1])
if len(lines) == 1:
Expand All @@ -166,7 +167,7 @@ def from_str(cls, func: str) -> 'CFunc':
args = []
for ix, line in enumerate(lines[:-1]):
line = line.strip().lstrip("*").strip()
if ix == 1:
if ix == 1 and not brief:
brief = line
elif line.startswith("@param"):
# assume that variables are documented in order
Expand Down Expand Up @@ -199,26 +200,31 @@ class Recipe:
Class holds contents of YAML header configuration.
"""

prefix: str #: Prefix used for CLib access function
name: str #: Name of method (without prefix)
base: str #: C++ class implementing method
parents: list[str] #: List of C++ parent class(es)
derived: list[str] #: List of C++ specialization(s)
implements: str #: Signature of implemented method
uses: str | list[str] #: Auxiliary methods used by recipe
what: str #: Non-empty for special methods: "constructor", "destructor"
name: str #: name of method (without prefix)
implements: str #: signature of implemented C++ function/method
uses: str | list[str] #: auxiliary C++ methods used by recipe
what: str #: override auto-detection of recipe type
brief: str #: override brief description from doxygen documentation
code: str #: custom code to override autogenerated code (stub: to be implemented)

prefix: str #: prefix used for CLib access function
base: str #: C++ class implementing method (if applicable)
parents: list[str] #: list of C++ parent classes (if applicable)
derived: list[str] #: list of C++ specializations (if applicable)


@dataclass
# @with_unpack_iter
class HeaderFile:
"""Represents information about a parsed C header file"""
"""Represents information about a parsed C header file."""

path: Path #: output folder
funcs: list[Func] #: list of functions to be scaffolded

path: Path
funcs: list[Func]
recipes: list[Recipe] = None
cabinet: str = ""
prefix: str = ""
prefix: str = "" #: prefix used for CLib function names
base: str = "" #: base class of C++ methods (if applicable)
parents: list[str] = None #: list of C++ parent class(es)
derived: list[str] = None #: list of C++ specialization(s)
recipes: list[Recipe] = None #: list of header recipes read from YAML

def output_name(self, auto="3", suffix=""):
"""Return updated path."""
Expand Down
14 changes: 7 additions & 7 deletions interfaces/sourcegen/sourcegen/_orchestrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ._helpers import read_config


_logger = logging.getLogger()
_LOGGER = logging.getLogger()

class CustomFormatter(logging.Formatter):
"""Minimalistic logging output"""
Expand All @@ -27,12 +27,12 @@ def generate_source(lang: str, out_dir: str=None, verbose=False):
"""Main entry point of sourcegen."""
loghandler = logging.StreamHandler(sys.stdout)
loghandler.setFormatter(CustomFormatter())
_logger.handlers.clear()
_logger.addHandler(loghandler)
_logger.setLevel(logging.DEBUG if verbose else logging.INFO)
_LOGGER.handlers.clear()
_LOGGER.addHandler(loghandler)
_LOGGER.setLevel(logging.DEBUG if verbose else logging.INFO)

if not out_dir:
_logger.critical("Aborting: sourcegen requires output folder information.")
_LOGGER.critical("Aborting: sourcegen requires output folder information.")
exit(1)

module = importlib.import_module(__package__ + "." + lang)
Expand All @@ -59,10 +59,10 @@ def generate_source(lang: str, out_dir: str=None, verbose=False):
clib_scaffolder.resolve_tags(files)

# find and instantiate the language-specific SourceGenerator
_logger.info(f"Generating {lang!r} source files...")
_LOGGER.info(f"Generating {lang!r} source files...")
_, scaffolder_type = inspect.getmembers(module,
lambda m: inspect.isclass(m) and issubclass(m, SourceGenerator))[0]
scaffolder: SourceGenerator = scaffolder_type(out_dir, config, templates)

scaffolder.generate_source(files)
_logger.info("Done.")
_LOGGER.info("Done.")
Loading

0 comments on commit 9f3f308

Please sign in to comment.