Skip to content

Commit

Permalink
[sourcegen] Improve autodetection based on YAML input
Browse files Browse the repository at this point in the history
  • Loading branch information
ischoegl committed Dec 30, 2024
1 parent 832611e commit 5f75eca
Show file tree
Hide file tree
Showing 12 changed files with 67 additions and 72 deletions.
19 changes: 9 additions & 10 deletions interfaces/sourcegen/sourcegen/_HeaderFileParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,18 @@ def _parse_yaml(self) -> HeaderFile:
derived = cabinet.get("derived", [])
uses = cabinet.get("uses", [])
for func in cabinet["functions"]:
func_name = f"{prefix}_{func['name']}"
if func_name in self._ignore_funcs:
if func['name'] in self._ignore_funcs:
continue
recipes.append(
Recipe(prefix,
func_name,
base,
parents,
derived,
uses,
func.get("implements", ""),
func.get("relates", []),
func.get("what", "")))
func['name'],
base,
parents,
derived,
uses,
func.get("implements", ""),
func.get("relates", []),
func.get("what", "")))
return HeaderFile(self._path, [], recipes)

@classmethod
Expand Down
6 changes: 5 additions & 1 deletion interfaces/sourcegen/sourcegen/_TagFileParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def __init__(self, bases: dict[str, str]) -> None:
logging.info("Parsing doxygen tags...")
self._parse_doxyfile(doxygen_tags, bases)

def _parse_doxyfile(self, doxygen_tags: str, bases: list[str]):
def _parse_doxyfile(self, doxygen_tags: str, bases: list[str]) -> None:
"""Retrieve class and function information from Cantera namespace."""

def xml_compounds(kind: str, names: list[str]) -> dict[str,str]:
Expand Down Expand Up @@ -151,6 +151,10 @@ def xml_members(kind: str, text: str, prefix="") -> dict[str, str]:
prefix = f"{name}::"
self._known.update(xml_members("function", cls, prefix))

def exists(self, cxx_func: str) -> bool:
"""Check whether doxygen tag exists."""
return cxx_func in self._known

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]
Expand Down
11 changes: 0 additions & 11 deletions interfaces/sourcegen/sourcegen/_data/ctfunc_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,25 @@ cabinet:
implements: checkFunc1
- name: newBasic
implements: newFunc1(const string&, double)
what: constructor
- name: newAdvanced
implements: newFunc1(const string&, const vector<double>&)
what: constructor
- name: newCompound
implements: newFunc1(const string&, const shared_ptr<Func1>, const shared_ptr<Func1>)
what: constructor
- name: newModified
implements: newFunc1(const string&, const shared_ptr<Func1>, double)
what: constructor
- name: newSum
implements: newSumFunction
what: constructor
- name: newDiff
implements: newDiffFunction
what: constructor
- name: newProd
implements: newProdFunction
what: constructor
- name: newRatio
implements: newRatioFunction
what: constructor
- name: type
implements: Func1::type
- name: value
implements: Func1::eval
- name: derivative
implements: Func1::derivative
what: constructor
- name: write
implements: Func1::write
- name: del
what: destructor
1 change: 0 additions & 1 deletion interfaces/sourcegen/sourcegen/_data/ctkin_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ cabinet:
uses: [Solution] # List of referenced cabinets
functions:
- name: nReactions
implements: Kinetics::nReactions
9 changes: 0 additions & 9 deletions interfaces/sourcegen/sourcegen/_data/ctsoln_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,21 @@ cabinet:
functions:
- name: newSolution
implements: newSolution(const string&, const string&, const string&)
what: constructor
- name: newInterface # currently disabled in CLib's config.yaml
implements:
newInterface(const string&, const string&, const vector<shared_ptr<Solution>>&)
what: constructor
- name: del
what: destructor
relates: # methods used to retrieve instances of managed objects
- "Solution::thermo"
- "Solution::kinetics"
- "Solution::transport"
- name: name
implements: Solution::name
- name: setName
implements: Solution::setName
- name: thermo
implements: Solution::thermo
- name: kinetics
implements: Solution::kinetics
- name: transport
implements: Solution::transport
- name: setTransport
implements: Solution::setTransport
- name: nAdjacent
implements: Solution::nAdjacent
- name: adjacent
implements: Solution::adjacent(size_t)
12 changes: 0 additions & 12 deletions interfaces/sourcegen/sourcegen/_data/ctthermo_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,18 @@ cabinet:
uses: [Solution] # List of referenced cabinets
functions:
- name: nElements
implements: Phase::nElements
- name: nSpecies
implements: Phase::nSpecies
- name: temperature
implements: Phase::temperature
- name: setTemperature
implements: Phase::setTemperature
- name: density
implements: Phase::density
- name: setDensity
implements: Phase::setDensity
- name: molarDensity
implements: Phase::molarDensity
- name: meanMolecularWeight
implements: Phase::meanMolecularWeight
- name: moleFraction
implements: Phase::moleFraction(size_t)
- name: massFraction
implements: Phase::massFraction(size_t)
- name: getMoleFractions
implements: Phase::getMoleFractions
- name: getMassFractions
implements: Phase::getMassFractions
- name: setMoleFractions
implements: Phase::setMoleFractions
- name: setMassFractions
implements: Phase::setMassFractions
2 changes: 0 additions & 2 deletions interfaces/sourcegen/sourcegen/_data/cttrans_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,4 @@ cabinet:
uses: [Solution] # List of referenced cabinets
functions:
- name: viscosity
implements: Transport::viscosity
- name: thermalConductivity
implements: Transport::thermalConductivity
2 changes: 1 addition & 1 deletion interfaces/sourcegen/sourcegen/_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def short_declaration(self) -> str:
return f"{self.ret_type} {ret}"


@dataclass(frozen=True)
@dataclass
@with_unpack_iter
class Recipe:
"""
Expand Down
68 changes: 47 additions & 21 deletions interfaces/sourcegen/sourcegen/clib/_CLibSourceGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def _javadoc_comment(block):
block = "\n * ".join(block).strip() + "\n */"
return "\n".join([line.rstrip() for line in block.split('\n')])

def _scaffold_annotation(self, c_func: CFunc) -> str:
def _scaffold_annotation(self, c_func: CFunc, what: str) -> str:
"""Build annotation block via jinja."""
loader = Environment(loader=BaseLoader)
par_template = loader.from_string(self._templates["clib-param"])
Expand All @@ -47,9 +47,9 @@ def param(item: Param):
ret = par_template.render(par=item)
return f"{ret:<20} {item.description}"

implements = ""
implements = what
if isinstance(c_func.implements, CFunc):
implements = c_func.implements.short_declaration()
implements += f": {c_func.implements.short_declaration()}"
block = template.render(
briefdescription=c_func.brief,
params=[param(par) for par in c_func.arglist],
Expand Down Expand Up @@ -152,19 +152,18 @@ def _scaffold_body(self, c_func: CFunc, recipe: Recipe) -> str:
if not recipe.implements:
return ";"

is_method = "::" in recipe.implements
cxx_ret_type = c_func.implements.ret_type.replace("virtual ", "")
simple = cxx_ret_type in ["void", "int", "double", "size_t"]
arg_len = len(c_func.arglist)

if is_method and simple and arg_len == 1:
if recipe.what == "getter" and arg_len == 1 and simple:
template = loader.from_string(self._templates["clib-simple-getter"])
error = "ERR" if c_func.ret_type == "int" else "DERR"
return template.render(base=recipe.base, handle=c_func.arglist[0].name,
method=c_func.implements.name, error=error,
implements=c_func.implements.short_declaration())

if is_method and simple and arg_len == 2:
if recipe.what == "setter" and arg_len == 2 and simple:
template = loader.from_string(self._templates["clib-simple-setter"])
return template.render(base=recipe.base, handle=c_func.arglist[0].name,
method=c_func.implements.name,
Expand All @@ -173,8 +172,7 @@ def _scaffold_body(self, c_func: CFunc, recipe: Recipe) -> str:

return ";"

def _resolve_clib_header(
self, recipe: Recipe, quiet: bool=True) -> tuple[str, str, list[str]]:
def _resolve_recipe(self, recipe: Recipe, quiet: bool=True) -> CFunc:
"""Build CLib header from recipe and doxygen annotations."""
def merge_params(implements, cxx_func: CFunc) -> list[Param]:
# If class method, add handle as first parameter
Expand All @@ -190,35 +188,62 @@ def merge_params(implements, cxx_func: CFunc) -> list[Param]:
args_annotated = args_annotated[:len(args_short)]
return args_merged + args_annotated

if not recipe.implements:
# autodetection of method
for base in [recipe.base] + recipe.parents + recipe.derived:
name = f"{base}::{recipe.name}"
if self._doxygen_tags.exists(name):
recipe.implements = name
break

cxx_func = None
ret_param = Param("void")
args = []
brief = ""

if recipe.implements:
if not quiet:
_logger.info(f" generating {recipe.name!r} -> {recipe.implements}")
cxx_func = self._doxygen_tags.cxx_func(recipe.implements, recipe.relates)

# convert XML return type to format suitable for crosswalk
# convert C++ return type to format suitable for crosswalk
ret_param, buffer_params = self._ret_crosswalk(
cxx_func.ret_type, recipe.derived)
par_list = merge_params(recipe.implements, cxx_func)
prop_params = self._prop_crosswalk(par_list)
brief = cxx_func.brief
args = prop_params + buffer_params

elif recipe.what == "destructor":
if cxx_func and not recipe.what:
# autodetection of CLib function purpose ("what")
cxx_arglen = len(cxx_func.arglist)
if recipe.base in cxx_func.ret_type:
recipe.what = "constructor"
elif "void" not in cxx_func.ret_type and cxx_arglen == 0:
recipe.what = "getter"
elif "void" in cxx_func.ret_type and cxx_arglen == 1:
p_type = cxx_func.arglist[0].p_type
if "*" in p_type and not p_type.startswith("const"):
recipe.what = "getter" # getter assigns to existing array
else:
recipe.what = "setter"
elif any(recipe.implements.startswith(base)
for base in [recipe.base] + recipe.parents + recipe.derived):
recipe.what = "method"
else:
recipe.what = "function"

if recipe.what == "destructor":
if not quiet:
_logger.info(f" generating {recipe.name!r} -> destructor")
args = [Param("int", "handle", f"Handle to {recipe.base} object.")]
brief= f"Delete {recipe.base} object."
cxx_func = None
ret_param = Param(
"int", "", "Zero for success and -1 for exception handling.")
buffer_params = []

else:
_logger.critical(f"Unable to build declaration for {recipe.name!r} "
f"with type {recipe.what!r}.")
sys.exit(1)

return CFunc(ret_param.p_type, recipe.name, ArgList(args), brief, cxx_func,
func_name = f"{recipe.prefix}_{recipe.name}"
return CFunc(ret_param.p_type, func_name, ArgList(args), brief, cxx_func,
ret_param.description)

def _scaffold_header(self, header: HeaderFile) -> None:
Expand All @@ -227,10 +252,11 @@ def _scaffold_header(self, header: HeaderFile) -> None:

template = loader.from_string(self._templates["clib-definition"])
declarations = []
for c_func in header.funcs:
for c_func, recipe in zip(header.funcs, header.recipes):
declarations.append(
template.render(declaration=c_func.declaration(),
annotations=self._scaffold_annotation(c_func)))
template.render(
declaration=c_func.declaration(),
annotations=self._scaffold_annotation(c_func, recipe.what)))

filename = header.output_name(suffix=".h", auto="3")
guard = f"__{filename.name.upper().replace('.', '_')}__"
Expand Down Expand Up @@ -291,7 +317,7 @@ def get_bases() -> list[str]:
_logger.info(f" resolving recipes in {header.path.name!r}:")
c_funcs = []
for recipe in header.recipes:
c_funcs.append(self._resolve_clib_header(recipe, quiet=quiet))
c_funcs.append(self._resolve_recipe(recipe, quiet=quiet))
header.funcs = c_funcs

def generate_source(self, headers_files: list[HeaderFile]):
Expand Down
4 changes: 2 additions & 2 deletions interfaces/sourcegen/sourcegen/clib/templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ clib-header-file: |-
clib-simple-getter: |-
try {
// implements: {{ implements }}
// getter: {{ implements }}
return {{ base }}Cabinet::at({{ handle }})->{{ method }}();
} catch (...) {
return handleAllExceptions({{ error }}, {{ error }});
}
clib-simple-setter: |-
try {
// implements: {{ implements }}
// setter: {{ implements }}
{{ base }}Cabinet::at({{ handle }})->{{ method }}({{ arg }});
return 0;
} catch (...) {
Expand Down
4 changes: 2 additions & 2 deletions interfaces/sourcegen/sourcegen/yaml/_YamlSourceGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ def _parse_header(self, header: HeaderFile):
preamble = template.render(filename=filename)
definition = loader.from_string(self._templates["yaml-definition"])
declarations = []
for c_func in header.funcs:
for c_func, recipe in zip(header.funcs, header.recipes):

implements = ""
if isinstance(c_func.implements, CFunc):
implements = c_func.implements.short_declaration()
declarations.append(
definition.render(c_func=c_func,
returns=c_func.returns, implements=implements,
relates=c_func.relates))
relates=c_func.relates, what=recipe.what))

guard = f"__{filename.upper().replace('.', '_')}__"
template = loader.from_string(self._templates["yaml-file"])
Expand Down
1 change: 1 addition & 0 deletions interfaces/sourcegen/sourcegen/yaml/templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ yaml-preamble: |-
yaml-definition: |-
{{ c_func.name }}:
description: {{ c_func.brief }}
what: {{ what }}
parameters:
{%- for par in c_func.arglist %}
{{ par.name }}: {{ par.description }}
Expand Down

0 comments on commit 5f75eca

Please sign in to comment.