summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/gen_bindings.py21
-rw-r--r--scripts/gen_registry.py13
-rw-r--r--scripts/ts_parser.py35
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