diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/gen_bindings.py | 21 | ||||
| -rw-r--r-- | scripts/gen_registry.py | 13 | ||||
| -rw-r--r-- | scripts/ts_parser.py | 35 |
3 files changed, 49 insertions, 20 deletions
diff --git a/scripts/gen_bindings.py b/scripts/gen_bindings.py index 845c8a5..f7c0486 100644 --- a/scripts/gen_bindings.py +++ b/scripts/gen_bindings.py @@ -36,6 +36,7 @@ MIN_PARAM_DEFAULTS = { "choppiness_index": {"k": 2}, "performance_ratio": {"k": 2}, "standard_error": {"k": 2}, + "mesa_sine_wave": {"k": 2}, } @@ -64,6 +65,7 @@ def main(): f.write("import ctypes\n") f.write("import ctypes.util\n") f.write("import os\n") + f.write("import subprocess\n") f.write("import numpy as np\n") f.write("import threading\n\n") @@ -81,11 +83,14 @@ def main(): f.write(" libdir = subprocess.check_output(\n") f.write(' ["pkg-config", "--variable=libdir", "ts"],\n') f.write(" stderr=subprocess.DEVNULL,\n") + f.write(" timeout=10,\n") f.write(" ).decode().strip()\n") f.write( ' return ctypes.CDLL(os.path.join(libdir, "libts.so"), ctypes.RTLD_GLOBAL)\n' ) - f.write(" except (OSError, subprocess.CalledProcessError):\n") + f.write( + " except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired):\n" + ) f.write(" pass\n") f.write(" build_dir = os.path.join(\n") f.write( @@ -100,7 +105,11 @@ def main(): ' raise OSError("Cannot find libts.so. Run make install or build from source.")\n\n\n' ) - f.write("__version__ = '0.1.0'\n\n") + f.write("try:\n") + f.write(" from importlib.metadata import version as _version\n") + f.write(" __version__ = _version(__name__)\n") + f.write("except Exception:\n") + f.write(" __version__ = '0.1.0'\n\n") f.write("_lib = _find_lib()\n\n") # --- Helpers --- @@ -108,7 +117,11 @@ def main(): f.write("def _cp(arr):\n") f.write(' """Copy numpy array to ctypes buffer (tracked in TLS list)."""\n') - f.write(" buf = (_CD * len(arr))(*arr)\n") + f.write(" if len(arr) == 0:\n") + f.write(" buf = (_CD * 1)()\n") + f.write(" buf[0] = 0.0\n") + f.write(" else:\n") + f.write(" buf = (_CD * len(arr))(*arr)\n") f.write(" if not hasattr(_tls, 'bufs'):\n") f.write(" _tls.bufs = []\n") f.write(" _tls.bufs.append(buf)\n") @@ -119,7 +132,7 @@ def main(): f.write(" try:\n") f.write(" if hasattr(_tls, 'bufs'):\n") f.write(" _tls.bufs.clear()\n") - f.write(" except Exception:\n") + f.write(" except AttributeError:\n") f.write(" pass\n\n\n") # --- Parameter structs --- diff --git a/scripts/gen_registry.py b/scripts/gen_registry.py index e80759e..dbe6f04 100644 --- a/scripts/gen_registry.py +++ b/scripts/gen_registry.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Generate ts_registry_generated.c with registry array and getter functions.""" +import glob import importlib.util import os import re @@ -39,9 +40,9 @@ def _resolve_params_size(params_size, name): return params_size except FileNotFoundError: pass - # Struct not in header, compute size from definition - import glob - + # Struct not in header, compute size from definition. + # Both fp_t (double) and uint_t (uint64_t) are 8 bytes on 64-bit. + # This is a fallback; sizeof() in the generated C code is preferred. lib_dir = os.path.join(ROOT, "lib") for cfile in glob.glob(os.path.join(lib_dir, "*.c")): try: @@ -57,7 +58,11 @@ def _resolve_params_size(params_size, name): body = struct_m.group(1) n_fp = len(re.findall(r"fp_t\s+", body)) n_uint = len(re.findall(r"uint_t\s+", body)) - return str(n_fp * 8 + n_uint * 8) + n_total = n_fp + n_uint + if n_total == 0: + return "0" + # Assume 8-byte alignment (valid for fp_t=double, uint_t=uint64_t) + return str(n_total * 8) return "0" diff --git a/scripts/ts_parser.py b/scripts/ts_parser.py index 2ad3629..31d926d 100644 --- a/scripts/ts_parser.py +++ b/scripts/ts_parser.py @@ -41,6 +41,19 @@ class FuncMeta: self.struct_fields = struct_fields or [] +def _strip_strings_comments(text): + """Remove C string literals and comments to avoid false brace matches.""" + # Remove block comments + text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) + # Remove line comments + text = re.sub(r"//.*", "", text) + # Remove string literals + text = re.sub(r'"(?:[^"\\]|\\.)*"', '""', text) + # Remove character literals + text = re.sub(r"'(?:[^'\\]|\\.)*'", "''", text) + return text + + def _detect_actual_x_n(content, match_end, name): """Detect actual x[N] access in function body.""" remaining = content[match_end:] @@ -48,18 +61,21 @@ def _detect_actual_x_n(content, match_end, name): if not func_def: return 0 brace_pos = remaining.find("{", func_def.start()) + # Strip strings and comments for safe brace matching + cleaned = _strip_strings_comments(remaining[brace_pos:]) depth = 0 - func_body = "" - for i in range(brace_pos, len(remaining)): - if remaining[i] == "{": + end_pos = 0 + for i, ch in enumerate(cleaned): + if ch == "{": depth += 1 - elif remaining[i] == "}": + elif ch == "}": depth -= 1 if depth == 0: - func_body = remaining[brace_pos : i + 1] + end_pos = i break + func_body = remaining[brace_pos : brace_pos + end_pos + 1] if end_pos else "" indices = [ - int(xm.group(1)) for xm in re.finditer(r"x\s*\[\s*(\d+)\s*\]", func_body) + int(xm.group(1)) for xm in re.finditer(r"\bx\s*\[\s*(\d+)\s*\]", func_body) ] return max(indices) + 1 if indices else 0 @@ -133,11 +149,6 @@ def parse_all(detect_x_access=True): actual = _detect_actual_x_n(content, m.end(), name) if actual > x_n: x_n = actual - if x_n < OHLCV_COUNT and re.search( - r"\bx\s*\[\s*" + str(VOLUME_INDEX) + r"\s*\]", - content[m.end() :], - ): - x_n = OHLCV_COUNT struct_fields = _parse_struct_fields(content, name) if c_n > 0 else [] @@ -153,7 +164,7 @@ def parse_names_only(): Dict mapping name -> {"x_n": int, "y_n": int}. """ meta = {} - for fm in parse_all(detect_x_access=True): + for fm in parse_all(detect_x_access=False): if fm.name not in meta: meta[fm.name] = {"x_n": fm.x_n, "y_n": fm.y_n} return meta |