tinycwrap 0.0.4__tar.gz → 0.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinycwrap
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: Lightweight C-to-Python wrapper generator using CFFI and NumPy
5
5
  Author: TinyCWrap Contributors
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tinycwrap"
7
- version = "0.0.4"
7
+ version = "0.0.6"
8
8
  description = "Lightweight C-to-Python wrapper generator using CFFI and NumPy"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -14,9 +14,9 @@ def cm():
14
14
 
15
15
 
16
16
  def test_dot(cm):
17
- x = np.arange(10, dtype=np.float64)
18
- y = np.ones_like(x)
19
- assert cm.dot(x, y, len_x=len(x)) == np.sum(x * y)
17
+ x = np.array([1.0, 2.0, 3.0], dtype=np.float64)
18
+ y = np.array([4.0, 5.0, 6.0], dtype=np.float64)
19
+ assert cm.dot(x, y, len_x=len(x)) == np.dot(x, y)
20
20
 
21
21
 
22
22
  def test_scale_auto_output(cm):
@@ -52,3 +52,7 @@ def test_repr_pointer_array(cp):
52
52
  repr_str = repr(path)
53
53
  assert "segments=<segments* 0x" in repr_str
54
54
  assert "len_segments=2" in repr_str
55
+
56
+ def test_post_contract(cp):
57
+ segments, len_segments = cp.geom2d_segments_from_rectellipse(1,2,3,4)
58
+ assert segments.shape == (4,)
@@ -6,6 +6,6 @@ from .cmodule import CModule
6
6
  try:
7
7
  __version__ = version("tinycwrap")
8
8
  except PackageNotFoundError:
9
- __version__ = "0.0.4"
9
+ __version__ = "0.0.6"
10
10
 
11
11
  __all__ = ["CModule", "__version__"]
@@ -381,24 +381,22 @@ class CModule:
381
381
  fspec.doc = doc
382
382
  contracts = []
383
383
  for line in doc.splitlines():
384
- if "Contract:" in line or "Post-Contract:" in line:
385
- after = (
386
- line.split("Contract:", 1)[1]
387
- if "Contract:" in line
388
- else line.split("Post-Contract:", 1)[1]
389
- )
390
- is_post = "Post-Contract" in line
391
- for part in after.split(";"):
392
- part = part.strip()
393
- if not part:
394
- continue
395
- mlen = re.match(r"len\((\w+)\)\s*=\s*(.+)", part)
396
- if not mlen:
397
- mlen = re.match(r"(\w+)\s*=\s*(.+)", part)
398
- if mlen:
399
- target = mlen.group(1)
400
- expr = mlen.group(2).strip()
401
- contracts.append((target, expr, is_post))
384
+ m_contract = re.search(r"(post-)?contract:\s*(.*)", line, flags=re.IGNORECASE)
385
+ if not m_contract:
386
+ continue
387
+ is_post = m_contract.group(1) is not None
388
+ after = m_contract.group(2)
389
+ for part in after.split(";"):
390
+ part = part.strip()
391
+ if not part:
392
+ continue
393
+ mlen = re.match(r"len\((\w+)\)\s*=\s*(.+)", part)
394
+ if not mlen:
395
+ mlen = re.match(r"(\w+)\s*=\s*(.+)", part)
396
+ if mlen:
397
+ target = mlen.group(1)
398
+ expr = mlen.group(2).strip()
399
+ contracts.append((target, expr, is_post))
402
400
  fspec.contracts = contracts or None
403
401
 
404
402
  def _mark_length_params_from_contracts(self):
@@ -430,13 +428,14 @@ class CModule:
430
428
  "size_t",
431
429
  "ssize_t",
432
430
  )
433
- and (not arg.is_pointer)
434
431
  and arg.name in referenced
435
432
  ):
436
433
  arg.is_length_param = True
437
434
  for arg in fspec.args:
438
435
  if arg.is_length_param:
439
436
  arg.is_scalar = False
437
+ arg.is_array_in = False
438
+ arg.is_array_out = False
440
439
  elif (
441
440
  (not arg.is_pointer)
442
441
  and arg.array_len is None
@@ -496,7 +495,15 @@ class CModule:
496
495
  target_dtype = _struct_dtypes.get(f.base_type)
497
496
  provided = kwargs.get(f.name)
498
497
  if provided is not None and target_dtype is not None:
499
- arr = np.ascontiguousarray(provided, dtype=target_dtype)
498
+ try:
499
+ arr = np.ascontiguousarray(provided, dtype=target_dtype)
500
+ except ValueError:
501
+ if hasattr(provided, "_data"):
502
+ arr = np.ascontiguousarray(provided._data, dtype=target_dtype)
503
+ elif isinstance(provided, (list, tuple)) and provided and hasattr(provided[0], "_data"):
504
+ arr = np.ascontiguousarray([p._data for p in provided], dtype=target_dtype)
505
+ else:
506
+ raise
500
507
  children[f.name] = arr
501
508
  data[f.name] = arr.ctypes.data
502
509
  if len_field in dtype.names and len_field not in kwargs:
@@ -747,6 +754,7 @@ class CModule:
747
754
  else:
748
755
  contract_map[target] = expr
749
756
 
757
+ length_pointer_names = {a.name for a in fspec.args if a.is_length_param and a.is_pointer}
750
758
  pointer_scalar_names = {
751
759
  a.name
752
760
  for a in fspec.args
@@ -767,7 +775,7 @@ class CModule:
767
775
  "ssize_t",
768
776
  )
769
777
  )
770
- }
778
+ } | length_pointer_names
771
779
 
772
780
  func_names = set(self._func_specs.keys())
773
781
 
@@ -776,6 +784,7 @@ class CModule:
776
784
  expr = re.sub(r"len\((\w+)\)", r"len(arr_\1)", expr)
777
785
  for name in pointer_scalar_names:
778
786
  expr = re.sub(rf"\b{name}\b", f"int(arr_{name}.ravel()[0])", expr)
787
+ expr = re.sub(rf"\b{name}\b", f"scalar_{name}", expr)
779
788
  if func_names:
780
789
  pattern = r"\b(" + "|".join(re.escape(n) for n in func_names) + r")\s*\(([^()]*)\)"
781
790
 
@@ -803,6 +812,17 @@ class CModule:
803
812
  scalar_lines: list[str] = []
804
813
 
805
814
  for a in fspec.args:
815
+ if a.is_length_param and a.is_pointer:
816
+ base_dtype = "np.dtype('int32')"
817
+ out_lines = [
818
+ f" arr_{a.name} = np.zeros((), dtype={base_dtype})",
819
+ f" ptr_{a.name} = _self._ffi.cast('{a.base_type} *', _self._ffi.from_buffer(arr_{a.name}))",
820
+ ]
821
+ call_args.append(f"ptr_{a.name}")
822
+ output_vars.append(f"arr_{a.name}")
823
+ output_names.append(a.name)
824
+ pointer_scalar_outputs.append((a.name, f"arr_{a.name}"))
825
+ continue
806
826
  if a.is_array_in:
807
827
  const_prefix = "const " if a.is_const else ""
808
828
  if a.base_type in struct_names:
@@ -834,21 +854,25 @@ class CModule:
834
854
  scalar_lines.append(f" {a.name} = {a.name}")
835
855
  call_args.append(a.name)
836
856
  elif a.is_length_param:
837
- if a.name in contract_map:
838
- expr_py = _expr_py(contract_map[a.name])
839
- length_lines += [
840
- f" if {a.name} is None:",
841
- f" {a.name} = int({expr_py})",
842
- f" else:",
843
- f" {a.name} = int({a.name})",
844
- ]
857
+ if a.is_pointer:
858
+ # handled in array/pointer branch
859
+ call_args.append(None)
845
860
  else:
846
- length_lines += [
847
- f" if {a.name} is None:",
848
- f" raise ValueError('{fspec.name}: length parameter {a.name} requires an explicit Contract')",
849
- f" {a.name} = int({a.name})",
850
- ]
851
- call_args.append(a.name)
861
+ if a.name in contract_map:
862
+ expr_py = _expr_py(contract_map[a.name])
863
+ length_lines += [
864
+ f" if {a.name} is None:",
865
+ f" {a.name} = int({expr_py})",
866
+ f" else:",
867
+ f" {a.name} = int({a.name})",
868
+ ]
869
+ else:
870
+ length_lines += [
871
+ f" if {a.name} is None:",
872
+ f" raise ValueError('{fspec.name}: length parameter {a.name} requires an explicit Contract')",
873
+ f" {a.name} = int({a.name})",
874
+ ]
875
+ call_args.append(a.name)
852
876
  else:
853
877
  # defer outputs / pointer handling
854
878
  call_args.append(None) # placeholder
@@ -954,11 +978,6 @@ class CModule:
954
978
  else:
955
979
  lines.append(f" res = {call_expr}")
956
980
 
957
- for out_name, out_var in zip(output_names, output_vars):
958
- if out_name in post_contract_map:
959
- expr_py = _expr_py(post_contract_map[out_name])
960
- lines.append(f" {out_var} = {out_var}[:int({expr_py})]")
961
-
962
981
  for name, arr_var in pointer_scalar_outputs:
963
982
  lines.append(f" scalar_{name} = int(np.asarray({arr_var}).ravel()[0])")
964
983
 
@@ -966,11 +985,19 @@ class CModule:
966
985
  for name, arr_var, cls_expr in struct_scalar_outputs:
967
986
  lines.append(f" obj_{name} = {cls_expr}()")
968
987
  lines.append(f" object.__setattr__(obj_{name}, '_data', np.array({arr_var}, copy=True))")
988
+ pointer_scalar_map = {arr: (name, f"scalar_{name}") for name, arr in pointer_scalar_outputs}
989
+ pairs = list(zip(output_names, output_vars))
990
+ # place array outputs before scalar pointer lengths
991
+ pairs_sorted = sorted(pairs, key=lambda p: 1 if p[1] in pointer_scalar_map else 0)
992
+ output_names_reordered: list[str] = []
969
993
  output_vars_final: list[str] = []
970
- for ov in output_vars:
994
+ for name, ov in pairs_sorted:
995
+ output_names_reordered.append(name)
971
996
  if ov in struct_scalar_map:
972
997
  n, _ = struct_scalar_map[ov]
973
998
  output_vars_final.append(f"obj_{n}")
999
+ elif ov in pointer_scalar_map:
1000
+ output_vars_final.append(pointer_scalar_map[ov][1])
974
1001
  else:
975
1002
  output_vars_final.append(ov)
976
1003
 
@@ -985,8 +1012,20 @@ class CModule:
985
1012
  ret_expr = f"{output_vars_final[0]}, res"
986
1013
  else:
987
1014
  ret_expr = "(" + ", ".join(output_vars_final) + "), res"
1015
+ # apply post-contract slicing using scalar lengths if available
1016
+ for out_name, out_var in zip(output_names_reordered, output_vars_final):
1017
+ if out_name in post_contract_map:
1018
+ expr_py = _expr_py(post_contract_map[out_name])
1019
+ scalar_names = {n for n, _ in pointer_scalar_outputs}
1020
+ if out_name in scalar_names:
1021
+ expr_py = expr_py.replace(out_name, f"scalar_{out_name}")
1022
+ ret_expr = ret_expr.replace(out_var, f"({out_var})[:int({expr_py})]")
988
1023
  if pointer_scalar_outputs:
989
- scalars = [f"scalar_{n}" for n, _ in pointer_scalar_outputs]
1024
+ scalars = [
1025
+ f"scalar_{n}"
1026
+ for n, _ in pointer_scalar_outputs
1027
+ if f"scalar_{n}" not in output_vars_final
1028
+ ]
990
1029
  if isinstance(ret_expr, str) and ret_expr.startswith("("):
991
1030
  ret_expr = (
992
1031
  ret_expr[:-1]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinycwrap
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: Lightweight C-to-Python wrapper generator using CFFI and NumPy
5
5
  Author: TinyCWrap Contributors
6
6
  Requires-Python: >=3.9
@@ -1,7 +1,7 @@
1
1
  README.md
2
2
  pyproject.toml
3
- tests/test_examples.py
4
3
  tests/test_geom.py
4
+ tests/test_kernels.py
5
5
  tests/test_path.py
6
6
  tinycwrap/__init__.py
7
7
  tinycwrap/cmodule.py
File without changes
File without changes
File without changes