From 76caedf13b7e0f699c5b5a870d129ae6fd9b9a0d Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Wed, 1 Jan 2025 12:15:55 -0700 Subject: [PATCH] [sourcegen] Implement reserved CLib methods --- .../sourcegen/sourcegen/_data/ct_auto.yaml | 12 +- .../sourcegen/_data/ctfunc_auto.yaml | 1 + .../sourcegen/sourcegen/_data/ctkin_auto.yaml | 2 + .../sourcegen/_data/ctsoln_auto.yaml | 1 + .../sourcegen/_data/ctthermo_auto.yaml | 2 + .../sourcegen/_data/cttrans_auto.yaml | 2 + .../sourcegen/sourcegen/_dataclasses.py | 36 ++++- .../sourcegen/sourcegen/_orchestrate.py | 6 +- .../sourcegen/clib/_CLibSourceGenerator.py | 36 ++++- .../sourcegen/sourcegen/clib/templates.yaml | 146 +++++++++++++----- 10 files changed, 189 insertions(+), 55 deletions(-) diff --git a/interfaces/sourcegen/sourcegen/_data/ct_auto.yaml b/interfaces/sourcegen/sourcegen/_data/ct_auto.yaml index 5abc0349f7..d8d8a04126 100644 --- a/interfaces/sourcegen/sourcegen/_data/ct_auto.yaml +++ b/interfaces/sourcegen/sourcegen/_data/ct_auto.yaml @@ -8,21 +8,21 @@ cabinet: prefix: ct3 base: "" functions: - # - name: getCanteraError(int buflen, char* buf) + - name: getCanteraVersion # todo: implement C++ version + - name: getGitCommit + implements: gitCommit + - name: getCanteraError # - name: setLogWriter(void* logger) # - name: setLogCallback(LogCallback writer); - name: addCanteraDirectory implements: addDirectory - name: getDataDirectories - name: findInputFile - # - name: getCanteraVersion - - name: getGitCommit - implements: gitCommit - name: suppress_deprecation_warnings - name: make_deprecation_warnings_fatal - name: suppress_warnings - name: warnings_suppressed - name: make_warnings_fatal - name: suppress_thermo_warnings - # - name: clearStorage(); - # - name: resetStorage(); + - name: clearStorage + - name: resetStorage diff --git a/interfaces/sourcegen/sourcegen/_data/ctfunc_auto.yaml b/interfaces/sourcegen/sourcegen/_data/ctfunc_auto.yaml index f6d63606b7..a671ced618 100644 --- a/interfaces/sourcegen/sourcegen/_data/ctfunc_auto.yaml +++ b/interfaces/sourcegen/sourcegen/_data/ctfunc_auto.yaml @@ -34,3 +34,4 @@ cabinet: - name: write - name: del what: destructor + - name: cabinetSize diff --git a/interfaces/sourcegen/sourcegen/_data/ctkin_auto.yaml b/interfaces/sourcegen/sourcegen/_data/ctkin_auto.yaml index 3525b490c7..18686c777d 100644 --- a/interfaces/sourcegen/sourcegen/_data/ctkin_auto.yaml +++ b/interfaces/sourcegen/sourcegen/_data/ctkin_auto.yaml @@ -13,3 +13,5 @@ cabinet: - name: nReactions - name: del what: noop + - name: cabinetSize + - name: parentHandle diff --git a/interfaces/sourcegen/sourcegen/_data/ctsoln_auto.yaml b/interfaces/sourcegen/sourcegen/_data/ctsoln_auto.yaml index df78e8f791..5223717bb3 100644 --- a/interfaces/sourcegen/sourcegen/_data/ctsoln_auto.yaml +++ b/interfaces/sourcegen/sourcegen/_data/ctsoln_auto.yaml @@ -28,3 +28,4 @@ cabinet: - name: nAdjacent - name: adjacent implements: Solution::adjacent(size_t) + - name: cabinetSize diff --git a/interfaces/sourcegen/sourcegen/_data/ctthermo_auto.yaml b/interfaces/sourcegen/sourcegen/_data/ctthermo_auto.yaml index 4456aae5f8..df461ea21e 100644 --- a/interfaces/sourcegen/sourcegen/_data/ctthermo_auto.yaml +++ b/interfaces/sourcegen/sourcegen/_data/ctthermo_auto.yaml @@ -33,3 +33,5 @@ cabinet: uses: nSpecies - name: del what: noop + - name: cabinetSize + - name: parentHandle diff --git a/interfaces/sourcegen/sourcegen/_data/cttrans_auto.yaml b/interfaces/sourcegen/sourcegen/_data/cttrans_auto.yaml index 92a65b5578..2c7c72dfd5 100644 --- a/interfaces/sourcegen/sourcegen/_data/cttrans_auto.yaml +++ b/interfaces/sourcegen/sourcegen/_data/cttrans_auto.yaml @@ -14,3 +14,5 @@ cabinet: - name: thermalConductivity - name: del what: noop + - name: cabinetSize + - name: parentHandle diff --git a/interfaces/sourcegen/sourcegen/_dataclasses.py b/interfaces/sourcegen/sourcegen/_dataclasses.py index c961f510f2..8cc16e767a 100644 --- a/interfaces/sourcegen/sourcegen/_dataclasses.py +++ b/interfaces/sourcegen/sourcegen/_dataclasses.py @@ -23,7 +23,7 @@ class Param: default: Any = None #: Default value (optional) @classmethod - def from_str(cls, param: str) -> 'Param': + def from_str(cls, param: str, doc: str="") -> 'Param': """Generate Param from parameter string.""" param = param.strip() default = None @@ -32,7 +32,14 @@ def from_str(cls, param: str) -> 'Param': param = param[:param.rfind("=")] parts = param.strip().rsplit(" ", 1) if len(parts) == 2 and parts[0] not in ["const", "virtual", "static"]: - return cls(*parts, "", "", default) + if "@param" not in doc: + return cls(*parts, "", "", default) + items = doc.split() + if items[1] != parts[1]: + msg = f"Documented variable {items[1]!r} does not match {parts[1]!r}" + raise ValueError(msg) + direction = items[0].split("[")[1].split("]")[0] if "[" in items[0] else "" + return cls(*parts, " ".join(items[2:]), direction, default) return cls(param) @classmethod @@ -122,7 +129,7 @@ class Func: @classmethod def from_str(cls, func: str) -> 'Func': """Generate Func from declaration string of a function.""" - func = func.strip() + func = func.rstrip(";").strip() # match all characters before an opening parenthesis '(' or end of line name = re.findall(r'.*?(?=\(|$)', func)[0] arglist = ArgList.from_str(func.replace(name, "").strip()) @@ -147,6 +154,29 @@ class CFunc(Func): base: str = "" #: Qualified scope of function/method (optional) 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.""" + lines = func.split("\n") + func = super().from_str(lines[-1]) + if len(lines) == 1: + return func + brief = "" + returns = "" + args = [] + for ix, line in enumerate(lines[:-1]): + line = line.strip().lstrip("*").strip() + if ix == 1: + brief = line + elif line.startswith("@param"): + # assume that variables are documented in order + arg = func.arglist[len(args)].long_str() + args.append(Param.from_str(arg, line)) + elif line.startswith("@returns"): + returns = line.lstrip("@returns").strip() + args = ArgList(args) + return cls(func.ret_type, func.name, args, brief, None, returns, "", []) + def short_declaration(self) -> str: """Return a short string representation.""" ret = (f"{self.name}{self.arglist.short_str()}").strip() diff --git a/interfaces/sourcegen/sourcegen/_orchestrate.py b/interfaces/sourcegen/sourcegen/_orchestrate.py index a87227c35e..d01f2e5b0f 100644 --- a/interfaces/sourcegen/sourcegen/_orchestrate.py +++ b/interfaces/sourcegen/sourcegen/_orchestrate.py @@ -50,10 +50,12 @@ def generate_source(lang: str, out_dir: str=None, verbose=False): else: # generate CLib headers from YAML specifications files = HeaderFileParser.headers_from_yaml(ignore_files, ignore_funcs) - clib_config = read_config(Path(__file__).parent / "clib" / "config.yaml") + clib_root = Path(__file__).parent / "clib" + clib_config = read_config(clib_root / "config.yaml") + clib_templates = read_config(clib_root / "templates.yaml") for key in ["ignore_files", "ignore_funcs"]: clib_config.pop(key) - clib_scaffolder = CLibSourceGenerator(None, clib_config, {}) + clib_scaffolder = CLibSourceGenerator(None, clib_config, clib_templates) clib_scaffolder.resolve_tags(files) # find and instantiate the language-specific SourceGenerator diff --git a/interfaces/sourcegen/sourcegen/clib/_CLibSourceGenerator.py b/interfaces/sourcegen/sourcegen/clib/_CLibSourceGenerator.py index c27f841e8c..e35eced264 100644 --- a/interfaces/sourcegen/sourcegen/clib/_CLibSourceGenerator.py +++ b/interfaces/sourcegen/sourcegen/clib/_CLibSourceGenerator.py @@ -163,14 +163,18 @@ def shared_object(cxx_type): return cxx_type.split("<")[-1].split(">")[0] c_args = c_func.arglist - cxx_func = (c_func.implements or - CFunc("void", "dummy", ArgList([]), "", None, "", "base")) + cxx_func = c_func.implements + if not cxx_func: + if len(c_args) and "char*" in c_args[-1].p_type: + cxx_func = CFunc("string", "dummy", ArgList([]), "", None, "", "base") + else: + cxx_func = CFunc("void", "dummy", ArgList([]), "", None, "", "base") cxx_ix = 0 check_array = False for c_ix, c_par in enumerate(c_func.arglist): c_name = c_par.name if cxx_ix >= len(cxx_func.arglist): - if c_ix == 0 and cxx_func.base: + if c_ix == 0 and cxx_func.base and "len" not in c_name.lower(): handle = c_name c_ix += 1 if c_ix == len(c_args): @@ -178,13 +182,14 @@ def shared_object(cxx_type): cxx_type = cxx_func.ret_type # Handle output buffer - if "string" in cxx_type: + if "string" in cxx_type: # or cxx_func.name == "dummy": buffer = ["auto out", f"copyString(out, {c_args[c_ix+1].name}, {c_name});", "int(out.size())"] else: _logger.critical(f"Scaffolding failed for {c_func.name!r}: reverse " - f"crosswalk not implemented for {cxx_type!r}.") + f"crosswalk not implemented for {cxx_type!r}:\n" + f"{c_func.declaration()}") exit(1) break @@ -296,6 +301,11 @@ def _scaffold_body(self, c_func: CFunc, recipe: Recipe) -> tuple[str, set[str]]: else: template = loader.from_string(self._templates["clib-method"]) + elif recipe.what == "reserved": + args["cabinets"] = list(self._config.includes.keys()) + template = loader.from_string( + self._templates[f"clib-reserved-{recipe.name}-cpp"]) + else: _logger.critical(f"{recipe.what!r} not implemented: {c_func.name!r}.") exit(1) @@ -325,6 +335,20 @@ def merge_params(implements, cxx_func: CFunc) -> tuple[list[Param], int]: return obj_handle + cxx_func.arglist.params, cxx_func + func_name = f"{recipe.prefix}_{recipe.name}" + reserved = ["cabinetSize", "parentHandle", + "getCanteraVersion", "getCanteraError", + "clearStorage", "resetStorage"] + if recipe.name in reserved: + recipe.what = "reserved" + loader = Environment(loader=BaseLoader) + if not quiet: + _logger.debug(f" generating {func_name!r} -> {recipe.what}") + header = loader.from_string( + self._templates[f"clib-reserved-{recipe.name}-h"] + ).render(base=recipe.base, prefix=recipe.prefix) + return CFunc.from_str(header) + # Ensure that all functions/methods referenced in recipe are detected correctly bases = [recipe.base] + recipe.parents + recipe.derived if not recipe.implements: @@ -332,7 +356,6 @@ def merge_params(implements, cxx_func: CFunc) -> tuple[list[Param], int]: recipe.uses = [self._doxygen_tags.detect(uu.split("(")[0], bases, False) for uu in recipe.uses] - func_name = f"{recipe.prefix}_{recipe.name}" cxx_func = None ret_param = Param("void") args = [] @@ -373,6 +396,7 @@ def merge_params(implements, cxx_func: CFunc) -> tuple[list[Param], int]: recipe.what = "method" else: recipe.what = "function" + elif recipe.name == "del" and not recipe.what: recipe.what = "destructor" diff --git a/interfaces/sourcegen/sourcegen/clib/templates.yaml b/interfaces/sourcegen/sourcegen/clib/templates.yaml index e27d61e611..3e351c0471 100644 --- a/interfaces/sourcegen/sourcegen/clib/templates.yaml +++ b/interfaces/sourcegen/sourcegen/clib/templates.yaml @@ -29,6 +29,57 @@ clib-definition: |- {{ annotations }} {{ declaration }}; +clib-reserved-parentHandle-h: |- + /** + * Return handle to parent of {{ base }} object. + * + * @param handle Handle to queried {{ base }} object. + * @returns Parent handle or -1 for exception handling. + */ + int {{ prefix }}_parentHandle(int handle); + +clib-reserved-cabinetSize-h: |- + /** + * Return size of {{ base }} storage. + * + * @returns Size or -1 for exception handling. + */ + int {{ prefix }}_cabinetSize(); + +clib-reserved-getCanteraVersion-h: |- + /** + * Get Cantera version. + * @param[in] lenBuf Length of reserved array. + * @param[out] charBuf String representation of %Cantera version. + * @returns Actual length of string or -1 for exception handling. + */ + int {{ prefix }}_getCanteraVersion(int lenBuf, char* charBuf); + +clib-reserved-getCanteraError-h: |- + /** + * Get %Cantera error. + * @param[in] lenBuf Length of reserved array. + * @param[out] charBuf String containing %Cantera error. + * @returns Actual length of string or -1 for exception handling. + */ + int {{ prefix }}_getCanteraError(int lenBuf, char* charBuf); + +clib-reserved-resetStorage-h: |- + /** + * Delete all objects and erase mapping. + * + * @returns Zero if successful or -1 for exception handling. + */ + int {{ prefix }}_resetStorage(); + +clib-reserved-clearStorage-h: |- + /** + * Delete all objects with mapping preserved. + * + * @returns Zero if successful or -1 for exception handling. + */ + int {{ prefix }}_clearStorage(); + clib-header-file: |- /** * {{ name.upper() }} - Experimental CLib %Cantera interface library. @@ -60,20 +111,6 @@ clib-header-file: |- {{ headers | indent(4) }} - {% if base %} - {% if no_constructor %} - /** - * Return handle to parent of {{ base }} object (if applicable). - */ - int {{ prefix }}_parentHandle(int handle); - - {% endif %} - /** - * Return size of {{ base }} storage. - */ - int {{ prefix }}_cabinetSize(); - - {% endif %} #ifdef __cplusplus } #endif @@ -226,6 +263,63 @@ clib-implementation: |- {{ body | indent(4) }} } +clib-reserved-parentHandle-cpp: |- + // {{ cxx_base }} cabinet parent + try { + return {{ base }}Cabinet3::parent(handle); + } catch (...) { + return handleAllExceptions(-2, ERR); + } + +clib-reserved-cabinetSize-cpp: |- + // {{ cxx_base }} cabinet size + try { + return {{ base }}Cabinet3::size(); + } catch (...) { + return handleAllExceptions(-1, ERR); + } + +clib-reserved-getCanteraVersion-cpp: |- + // get Cantera version + try { + string out = CANTERA_VERSION; + copyString(out, charBuf, lenBuf); + return int(out.size()); + } catch (...) { + return handleAllExceptions(-1, ERR); + } + +clib-reserved-getCanteraError-cpp: |- + try { + string err = Application::Instance()->lastErrorMessage(); + copyString(err, charBuf, lenBuf); + return int(err.size()); + } catch (...) { + return handleAllExceptions(-1, ERR); + } + +clib-reserved-resetStorage-cpp: |- + // reset storage + try { + {% for base in cabinets %} + {{ base }}Cabinet3::reset(); + {% endfor %} + return 0; + } catch (...) { + return handleAllExceptions(-1, ERR); + } + +clib-reserved-clearStorage-cpp: |- + // clear storage + try { + {% for base in cabinets %} + {{ base }}Cabinet3::clear(); + {% endfor %} + return 0; + } catch (...) { + return handleAllExceptions(-1, ERR); + } + clib-source-file: |- /** * {{ name.upper() }} - Experimental CLib %Cantera interface library. @@ -269,28 +363,4 @@ clib-source-file: |- {{ implementations | indent(4) }} - {% if base %} - {% if no_constructor %} - int {{ prefix }}_parentHandle(int handle) - { - // cabinet parent - try { - return {{ base }}Cabinet3::parent(handle); - } catch (...) { - return handleAllExceptions(-2, ERR); - } - } - - {% endif %} - int {{ prefix }}_cabinetSize() - { - // cabinet size - try { - return {{ base }}Cabinet3::size(); - } catch (...) { - return handleAllExceptions(-1, ERR); - } - } - - {% endif %} } // extern "C"