Skip to content

Commit

Permalink
[sourcegen] Improve setter/getter code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
ischoegl committed Dec 31, 2024
1 parent 5f75eca commit e6d029d
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 27 deletions.
1 change: 1 addition & 0 deletions interfaces/sourcegen/sourcegen/_HeaderFileParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def _parse_yaml(self) -> HeaderFile:
uses,
func.get("implements", ""),
func.get("relates", []),
func.get("size-fcn", ""),
func.get("what", "")))
return HeaderFile(self._path, [], recipes)

Expand Down
3 changes: 2 additions & 1 deletion interfaces/sourcegen/sourcegen/_data/ctfunc_auto.yaml
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: func3
prefix: func13
base: Func1
functions:
- name: check
Expand All @@ -30,6 +30,7 @@ cabinet:
- name: value
implements: Func1::eval
- name: derivative
what: constructor
implements: Func1::derivative
- name: write
- name: del
Expand Down
4 changes: 4 additions & 0 deletions interfaces/sourcegen/sourcegen/_data/ctthermo_auto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ cabinet:
- name: massFraction
implements: Phase::massFraction(size_t)
- name: getMoleFractions
size-fcn: nSpecies
- name: getMassFractions
size-fcn: nSpecies
- name: setMoleFractions
size-fcn: nSpecies
- name: setMassFractions
size-fcn: nSpecies
8 changes: 7 additions & 1 deletion interfaces/sourcegen/sourcegen/_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ def short_declaration(self) -> str:
return f"{self.ret_type} {self.base}::{ret}"
return f"{self.ret_type} {ret}"

@property
def ret_param(self):
"""Assemble return parameter."""
return Param(self.ret_type, "", self.returns)


@dataclass
@with_unpack_iter
Expand All @@ -171,7 +176,8 @@ class Recipe:
derived: list[str] #: List of C++ specializations
uses: list[str] #: List of referenced CLib cabinets
implements: str #: Signature of implemented method
relates: list[str] = None #: Methods used to retrieve instances of managed objects
relates: list[str]=None #: Methods used to retrieve instances of managed objects
size_fcn: str="" #: Method used to check array size
what: str = "" #: Non-empty for special methods: "constructor", "destructor"


Expand Down
105 changes: 86 additions & 19 deletions interfaces/sourcegen/sourcegen/clib/_CLibSourceGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,32 +145,97 @@ def _prop_crosswalk(self, par_list: list[Param]) -> list[Param]:
sys.exit(1)
return params

@staticmethod
def _reverse_crosswalk(
c_args: list[Param], cxx_args: list[Param]) -> list[tuple[str, str]]:
"""Translate CLib arguments back to C++."""
ret = []
c_ix = 0
for cxx_par in cxx_args:
c_par = c_args[c_ix]
if "shared_ptr" in cxx_par.p_type:
# retrieve object from cabinet
base = cxx_par.p_type.split("<")[-1].split(">")[0]
ret.append((f"{base}Cabinet::at({c_par.name})", None))
elif c_par.name.endswith("Len"):
# need to create buffer variable
ret.append((None, None))
c_ix += 1
else:
# regular parameter
ret.append((c_par.name, None))
c_ix += 1
return ret

def _scaffold_getter(self, c_func: CFunc, recipe: Recipe) -> tuple[str, list[str]]:
"""Build getter via jinja."""
loader = Environment(loader=BaseLoader)
cxx_func = c_func.implements
args = {
"c_arg": [arg.name for arg in c_func.arglist],
"cxx_base": recipe.base, "cxx_name": cxx_func.name,
"cxx_implements": cxx_func.short_declaration(),
"c_func": c_func.name, "size_fcn": recipe.size_fcn, "obj_base": None,
}

if "string" in cxx_func.ret_type:
template = loader.from_string(self._templates["clib-string-getter"])
elif "void" in cxx_func.ret_type:
template = loader.from_string(self._templates["clib-array-getter"])
elif "shared_ptr" in cxx_func.ret_type:
args["obj_base"] = cxx_func.ret_type.split("<")[-1].split(">")[0]
template = loader.from_string(self._templates["clib-object-getter"])
elif "double" in c_func.ret_type:
template = loader.from_string(self._templates["clib-double-getter"])
elif "int" in c_func.ret_type:
template = loader.from_string(self._templates["clib-int-getter"])
else:
logging.critical(f"Failed to scaffold getter: {args['cxx_implements']}")
sys.exit(1)

return template.render(**args), [args["obj_base"]]

def _scaffold_setter(self, c_func: CFunc, recipe: Recipe) -> tuple[str, list[str]]:
loader = Environment(loader=BaseLoader)
cxx_func = c_func.implements
args = {
"c_arg": [arg.name for arg in c_func.arglist],
"cxx_base": recipe.base, "cxx_name": cxx_func.name,
"cxx_implements": cxx_func.short_declaration(),
"c_func": c_func.name, "size_fcn": recipe.size_fcn, "obj_base": None,
}

p_type = cxx_func.arglist[0].p_type
simple = ["string&", "int", "size_t", "double"]
if "*" in p_type:
template = loader.from_string(self._templates["clib-array-setter"])
elif any(typ in p_type.split() for typ in simple):
template = loader.from_string(self._templates["clib-simple-setter"])
elif "shared_ptr" in p_type:
args["obj_base"] = p_type.split("<")[-1].split(">")[0]
template = loader.from_string(self._templates["clib-object-setter"])
else:
logging.critical(f"Failed to scaffold getter: {args['cxx_implements']}")
sys.exit(1)

return template.render(**args), [args["obj_base"]]

def _scaffold_body(self, c_func: CFunc, recipe: Recipe) -> str:
"""Build function body via jinja."""

loader = Environment(loader=BaseLoader)

if not recipe.implements:
return ";"

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)
return f"// {recipe.what}"

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 recipe.what == "getter":
return self._scaffold_getter(c_func, recipe)[0]

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,
arg=c_func.arglist[1].name,
implements=c_func.implements.short_declaration())
if recipe.what == "setter":
return self._scaffold_setter(c_func, recipe)[0]

return ";"
cxx_func = c_func.implements
return f"// {recipe.what}: {cxx_func.short_declaration()}"

def _resolve_recipe(self, recipe: Recipe, quiet: bool=True) -> CFunc:
"""Build CLib header from recipe and doxygen annotations."""
Expand Down Expand Up @@ -217,7 +282,9 @@ def merge_params(implements, cxx_func: CFunc) -> list[Param]:
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:
if any(base in cxx_func.ret_type
for base in [recipe.base] + recipe.derived) and \
cxx_func.name.startswith("new"):
recipe.what = "constructor"
elif "void" not in cxx_func.ret_type and cxx_arglen == 0:
recipe.what = "getter"
Expand Down
86 changes: 80 additions & 6 deletions interfaces/sourcegen/sourcegen/clib/templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,97 @@ clib-header-file: |-
#endif // {{ guard }}
clib-simple-getter: |-
clib-int-getter: |-
try {
// getter: {{ implements }}
return {{ base }}Cabinet::at({{ handle }})->{{ method }}();
// getter: {{ cxx_implements }}
return {{ base }}Cabinet::at({{ c_arg[0] }})->{{ cxx_name }}();
} catch (...) {
return handleAllExceptions({{ error }}, {{ error }});
return handleAllExceptions(ERR, ERR);
}
clib-double-getter: |-
try {
// getter: {{ cxx_implements }}
return {{ base }}Cabinet::at({{ c_arg[0] }})->{{ cxx_name }}();
} catch (...) {
return handleAllExceptions(DERR, DERR);
}
clib-string-getter: |-
try {
// getter: {{ cxx_implements }}
auto out = {{ cxx_base }}Cabinet::at({{ c_arg[0] }})->{{ cxx_name }}();
copyString(out, {{ c_arg[2] }}, {{ c_arg[1] }});
return int(out.size());
} catch (...) {
return handleAllExceptions(-1, ERR);
}
clib-array-getter: |-
try {
// getter: {{ cxx_implements }}
auto& obj = {{ cxx_base }}Cabinet::at({{ c_arg[0] }});
{% if size_fcn -%}
if ({{ c_arg[1] }} != obj->{{ size_fcn }}()) {
throw CanteraError("{{ c_func }}",
"Invalid output array size; expected size {} but received {}.",
obj->{{ size_fcn }}(), {{ c_arg[1] }});
}
{% else %}
// no size checking specified
{% endif -%}
obj->{{ cxx_name }}({{ c_arg[2] }});
return 0;
} catch (...) {
return handleAllExceptions(-1, ERR);
}
clib-object-getter: |-
try {
// getter: {{ cxx_implements }}
auto& obj = {{ cxx_base }}Cabinet::at({{ c_arg[0] }});
return {{ obj_base }}Cabinet::index(obj->{{ cxx_name }}(), {{ c_arg[0] }});
} catch (...) {
return handleAllExceptions(-2, ERR);
}
clib-simple-setter: |-
try {
// setter: {{ implements }}
{{ base }}Cabinet::at({{ handle }})->{{ method }}({{ arg }});
// setter: {{ cxx_implements }}
{{ cxx_base }}Cabinet::at({{ c_arg[0] }})->{{ cxx_name }}({{ c_arg[1] }});
return 0;
} catch (...) {
return handleAllExceptions(-1, ERR);
}
clib-array-setter: |-
try {
// setter: {{ cxx_implements }}
auto& obj = {{ cxx_base }}Cabinet::at({{ c_arg[0] }});
{% if size_fcn -%}
if ({{ c_arg[1] }} != obj->{{ size_fcn }}()) {
throw CanteraError("{{ c_func }}",
"Invalid input array size; expected size {} but received {}.",
obj->{{ size_fcn }}(), {{ c_arg[1] }});
}
{% else %}
// no size checking specified
{% endif -%}
obj->{{ cxx_name }}({{ c_arg[2] }});
return 0;
} catch (...) {
return handleAllExceptions(-1, ERR);
}
clib-object-setter: |-
try {
// setter: {{ cxx_implements }}
{{ cxx_base }}Cabinet::at({{ c_arg[0] }})->{{ cxx_name }}({{ obj_base }}Cabinet::at({{ c_arg[1] }}));
return 0;
} catch (...) {
return handleAllExceptions(-2, ERR);
}
clib-implementation: |-
{{ declaration }}
{
Expand Down

0 comments on commit e6d029d

Please sign in to comment.