IncludeCPP 3.3.11__py3-none-any.whl → 3.4.2__py3-none-any.whl

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.
@@ -25,6 +25,65 @@ def _safe_get(lst: List[Any], index: int, default: Any = None) -> Any:
25
25
  return default
26
26
 
27
27
 
28
+ # v3.3.22: Python reserved keywords - names that need escaping when used as identifiers
29
+ PYTHON_KEYWORDS = {
30
+ 'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
31
+ 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
32
+ 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
33
+ 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try',
34
+ 'while', 'with', 'yield'
35
+ }
36
+
37
+ # v3.4.1: C++ reserved words that need escaping when used as Python identifiers
38
+ # These are C++ types/keywords that are valid Python identifiers but confusing
39
+ CPP_RESERVED_WORDS = {
40
+ # Primitive types
41
+ 'int', 'float', 'double', 'char', 'bool', 'void', 'auto',
42
+ 'short', 'long', 'signed', 'unsigned', 'wchar_t',
43
+ # Type modifiers
44
+ 'const', 'static', 'virtual', 'volatile', 'mutable', 'extern',
45
+ 'register', 'inline', 'explicit', 'constexpr', 'consteval',
46
+ # Access modifiers
47
+ 'public', 'private', 'protected',
48
+ # Other keywords
49
+ 'template', 'typename', 'namespace', 'using', 'typedef',
50
+ 'struct', 'union', 'enum', 'sizeof', 'alignof', 'decltype',
51
+ 'new', 'delete', 'operator', 'friend', 'this',
52
+ 'throw', 'catch', 'noexcept', 'final', 'override',
53
+ }
54
+
55
+
56
+ def _escape_python_keyword(name: str) -> str:
57
+ """Escape Python reserved keywords by adding underscore suffix.
58
+
59
+ Example: 'class' -> 'class_', 'def' -> 'def_', 'import' -> 'import_'
60
+ """
61
+ if name in PYTHON_KEYWORDS:
62
+ return name + '_'
63
+ return name
64
+
65
+
66
+ def _escape_cpp_reserved(name: str) -> str:
67
+ """Escape C++ reserved words when used as Python identifiers.
68
+
69
+ Example: 'double' -> 'double_', 'int' -> 'int_', 'void' -> 'void_'
70
+ v3.4.1: Prevents C++ type names from being used as Python function/variable names.
71
+ """
72
+ if name in CPP_RESERVED_WORDS:
73
+ return name + '_'
74
+ return name
75
+
76
+
77
+ def _escape_identifier(name: str) -> str:
78
+ """Escape both Python keywords and C++ reserved words.
79
+
80
+ Combines both escaping functions for comprehensive identifier safety.
81
+ """
82
+ name = _escape_python_keyword(name)
83
+ name = _escape_cpp_reserved(name)
84
+ return name
85
+
86
+
28
87
  # Python type to C++ type mapping
29
88
  PY_TO_CPP_TYPES = {
30
89
  'int': 'int',
@@ -260,6 +319,52 @@ class StructInfo:
260
319
 
261
320
 
262
321
  class PythonToCppConverter:
322
+ # Common parameter name patterns to infer types
323
+ PARAM_TYPE_HINTS = {
324
+ # Integer-like names
325
+ 'n': 'int', 'k': 'int', 'i': 'int', 'j': 'int', 'count': 'int', 'num': 'int',
326
+ 'index': 'int', 'idx': 'int', 'size': 'size_t', 'length': 'size_t', 'len': 'size_t',
327
+ 'start': 'int', 'end': 'int', 'begin': 'int', 'stop': 'int', 'step': 'int',
328
+ 'seed': 'int', 'limit': 'int', 'offset': 'int', 'position': 'int', 'pos': 'int',
329
+ 'width': 'int', 'height': 'int', 'depth': 'int', 'x': 'int', 'y': 'int', 'z': 'int',
330
+ 'row': 'int', 'col': 'int', 'rows': 'int', 'cols': 'int', 'port': 'int',
331
+ # Float-like names
332
+ 'alpha': 'double', 'beta': 'double', 'gamma': 'double', 'delta': 'double',
333
+ 'mu': 'double', 'sigma': 'double', 'lambd': 'double', 'lambda_': 'double',
334
+ 'rate': 'double', 'ratio': 'double', 'scale': 'double', 'weight': 'double',
335
+ 'probability': 'double', 'prob': 'double', 'threshold': 'double',
336
+ 'min_val': 'double', 'max_val': 'double',
337
+ 'temperature': 'double', 'temp': 'double', 'factor': 'double',
338
+ # Template-matching parameters (for generic functions with T_CONTAINER)
339
+ # These will be T when used with a T_CONTAINER parameter
340
+ 'value': 'T_ELEMENT', 'val': 'T_ELEMENT', 'item': 'T_ELEMENT', 'element': 'T_ELEMENT',
341
+ 'target': 'T_ELEMENT', 'needle': 'T_ELEMENT', 'search': 'T_ELEMENT',
342
+ # String-like names
343
+ 'name': 'std::string', 'text': 'std::string', 'msg': 'std::string', 'message': 'std::string',
344
+ 'path': 'std::string', 'filename': 'std::string', 'file': 'std::string', 'dir': 'std::string',
345
+ 'url': 'std::string', 'key': 'std::string', 'prefix': 'std::string', 'suffix': 'std::string',
346
+ 'pattern': 'std::string', 'format': 'std::string', 'fmt': 'std::string',
347
+ 'title': 'std::string', 'label': 'std::string', 'description': 'std::string', 'desc': 'std::string',
348
+ 'content': 'std::string', 'data': 'std::string', 'input': 'std::string', 'output': 'std::string',
349
+ 's': 'std::string', 'str': 'std::string', 'string': 'std::string', 'line': 'std::string',
350
+ 'sep': 'std::string', 'delimiter': 'std::string', 'delim': 'std::string',
351
+ # Boolean-like names
352
+ 'flag': 'bool', 'enabled': 'bool', 'disabled': 'bool', 'active': 'bool',
353
+ 'is_valid': 'bool', 'is_empty': 'bool', 'success': 'bool', 'ok': 'bool',
354
+ 'verbose': 'bool', 'quiet': 'bool', 'force': 'bool', 'recursive': 'bool',
355
+ # Container-like names - use T as template parameter marker
356
+ # These will trigger template generation for the containing function
357
+ 'items': 'T_CONTAINER', 'elements': 'T_CONTAINER', 'values': 'T_CONTAINER',
358
+ 'list': 'T_CONTAINER', 'lst': 'T_CONTAINER', 'array': 'T_CONTAINER',
359
+ 'choices': 'T_CONTAINER', 'options': 'T_CONTAINER',
360
+ 'population': 'T_CONTAINER', 'sample': 'T_CONTAINER',
361
+ 'weights': 'std::vector<double>',
362
+ }
363
+
364
+ # Template parameter markers - functions with these params become templates
365
+ TEMPLATE_MARKER = 'T_CONTAINER'
366
+ TEMPLATE_ELEMENT = 'T_ELEMENT' # For parameters that match container element type
367
+
263
368
  def __init__(self):
264
369
  self.imports: Set[str] = set()
265
370
  self.forward_decls: Set[str] = set()
@@ -267,6 +372,7 @@ class PythonToCppConverter:
267
372
  self.warnings: List[str] = []
268
373
  self.python_imports: Set[str] = set()
269
374
  self.seeded_rngs: Dict[str, str] = {} # var_name -> seed expression
375
+ self.var_types: Dict[str, str] = {} # Variable name -> C++ type tracking
270
376
 
271
377
  def convert(self, source: str, module_name: str) -> Tuple[str, str]:
272
378
  """Convert Python source to C++ (.cpp and .h content)."""
@@ -276,6 +382,7 @@ class PythonToCppConverter:
276
382
  self.warnings = []
277
383
  self.python_imports = set()
278
384
  self.seeded_rngs = {}
385
+ self.var_types = {}
279
386
 
280
387
  try:
281
388
  tree = ast.parse(source)
@@ -392,6 +499,78 @@ class PythonToCppConverter:
392
499
  constructors=constructors
393
500
  )
394
501
 
502
+ def _infer_param_type(self, param_name: str, func_node) -> str:
503
+ """Infer C++ type from parameter name and usage patterns."""
504
+ # Check direct name hints
505
+ if param_name in self.PARAM_TYPE_HINTS:
506
+ return self.PARAM_TYPE_HINTS[param_name]
507
+
508
+ # Check partial matches (e.g., 'file_name' contains 'name')
509
+ param_lower = param_name.lower()
510
+ for hint_name, hint_type in self.PARAM_TYPE_HINTS.items():
511
+ if hint_name in param_lower or param_lower.endswith('_' + hint_name):
512
+ return hint_type
513
+
514
+ # Analyze how the parameter is used in the function body
515
+ inferred = self._analyze_param_usage(param_name, func_node)
516
+ if inferred and inferred != 'auto':
517
+ return inferred
518
+
519
+ # Default: use auto (will become template)
520
+ return 'auto'
521
+
522
+ def _analyze_param_usage(self, param_name: str, func_node) -> str:
523
+ """Analyze how a parameter is used to infer its type."""
524
+ for node in ast.walk(func_node):
525
+ # Check if used in comparison with numeric literal
526
+ if isinstance(node, ast.Compare):
527
+ left = node.left
528
+ if isinstance(left, ast.Name) and left.id == param_name:
529
+ for comp in node.comparators:
530
+ if isinstance(comp, ast.Constant):
531
+ if isinstance(comp.value, int):
532
+ return 'int'
533
+ elif isinstance(comp.value, float):
534
+ return 'double'
535
+
536
+ # Check if used in arithmetic operations
537
+ if isinstance(node, ast.BinOp):
538
+ left = node.left
539
+ right = node.right
540
+ if (isinstance(left, ast.Name) and left.id == param_name) or \
541
+ (isinstance(right, ast.Name) and right.id == param_name):
542
+ if isinstance(node.op, (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)):
543
+ # Check if other operand hints at type
544
+ other = right if isinstance(left, ast.Name) and left.id == param_name else left
545
+ if isinstance(other, ast.Constant):
546
+ if isinstance(other.value, float):
547
+ return 'double'
548
+ elif isinstance(other.value, int):
549
+ return 'int'
550
+
551
+ # Check if used in string operations
552
+ if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
553
+ left = node.left
554
+ right = node.right
555
+ if isinstance(left, ast.Name) and left.id == param_name:
556
+ if isinstance(right, ast.Constant) and isinstance(right.value, str):
557
+ return 'std::string'
558
+ if isinstance(right, ast.Name) and right.id == param_name:
559
+ if isinstance(left, ast.Constant) and isinstance(left.value, str):
560
+ return 'std::string'
561
+
562
+ # Check if used in subscript (likely a container)
563
+ if isinstance(node, ast.Subscript):
564
+ if isinstance(node.value, ast.Name) and node.value.id == param_name:
565
+ return 'auto' # Container, will become template
566
+
567
+ # Check if used in for loop iteration
568
+ if isinstance(node, ast.For):
569
+ if isinstance(node.iter, ast.Name) and node.iter.id == param_name:
570
+ return 'auto' # Iterable, will become template
571
+
572
+ return 'auto'
573
+
395
574
  def _convert_function(self, node, is_method: bool = False) -> FunctionInfo:
396
575
  decorators = []
397
576
  is_static = False
@@ -414,8 +593,11 @@ class PythonToCppConverter:
414
593
  if arg.annotation:
415
594
  param_type = self._convert_type_annotation(arg.annotation)
416
595
  else:
417
- param_type = 'auto'
596
+ # Use smart type inference
597
+ param_type = self._infer_param_type(arg.arg, node)
418
598
  params.append((arg.arg, param_type))
599
+ # Track parameter types for use in body conversion
600
+ self.var_types[arg.arg] = param_type
419
601
 
420
602
  if node.returns:
421
603
  return_type = self._convert_type_annotation(node.returns)
@@ -590,6 +772,82 @@ class PythonToCppConverter:
590
772
  return self._infer_type(stmt.value)
591
773
  return 'void'
592
774
 
775
+ def _track_for_loop_var_type(self, var_name: str, iter_node) -> None:
776
+ """Track the type of a for loop variable based on the iterable."""
777
+ iter_type = self._infer_type(iter_node)
778
+
779
+ # Extract element type from container types
780
+ if '<' in iter_type and '>' in iter_type:
781
+ element_type = iter_type[iter_type.find('<')+1:iter_type.rfind('>')]
782
+ self.var_types[var_name] = element_type
783
+ elif 'string' in iter_type.lower():
784
+ # Iterating over string gives char
785
+ self.var_types[var_name] = 'char'
786
+ elif isinstance(iter_node, ast.Name):
787
+ # Check if iterable is a known variable
788
+ known_type = self.var_types.get(iter_node.id, '')
789
+ if '<' in known_type and '>' in known_type:
790
+ element_type = known_type[known_type.find('<')+1:known_type.rfind('>')]
791
+ self.var_types[var_name] = element_type
792
+ elif 'string' in known_type.lower():
793
+ self.var_types[var_name] = 'std::string'
794
+ else:
795
+ self.var_types[var_name] = 'auto'
796
+ else:
797
+ self.var_types[var_name] = 'auto'
798
+
799
+ def _is_string_expr(self, node, expr: str) -> bool:
800
+ """Check if an expression evaluates to a string type.
801
+
802
+ This prevents wrapping string values in std::to_string() in f-strings.
803
+ """
804
+ # Check obvious string indicators in converted expression
805
+ if any(x in expr for x in ['"', '.c_str()', 'std::string', '_str']):
806
+ return True
807
+
808
+ # Check if it's a string literal constant
809
+ if isinstance(node, ast.Constant) and isinstance(node.value, str):
810
+ return True
811
+
812
+ # Check if it's a variable we know is a string
813
+ if isinstance(node, ast.Name):
814
+ var_name = node.id
815
+ var_type = self.var_types.get(var_name, '')
816
+ if 'string' in var_type.lower() or var_type == 'std::string':
817
+ return True
818
+ # Check if variable name suggests string type
819
+ if var_name in self.PARAM_TYPE_HINTS:
820
+ if 'string' in self.PARAM_TYPE_HINTS[var_name].lower():
821
+ return True
822
+ # Common string variable names
823
+ string_names = {'name', 'text', 'msg', 'message', 'path', 'url', 'title',
824
+ 'label', 'description', 'content', 'data', 'line', 's', 'str',
825
+ 'item', 'key', 'value', 'word', 'char', 'prefix', 'suffix',
826
+ 'filename', 'file', 'dir', 'result', 'output', 'input'}
827
+ if var_name.lower() in string_names or any(var_name.lower().endswith('_' + n) for n in string_names):
828
+ return True
829
+
830
+ # Check string method calls like .upper(), .strip(), etc.
831
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
832
+ string_methods = {'upper', 'lower', 'strip', 'lstrip', 'rstrip', 'title',
833
+ 'capitalize', 'replace', 'format', 'join', 'split',
834
+ 'encode', 'decode', 'center', 'ljust', 'rjust'}
835
+ if node.func.attr in string_methods:
836
+ return True
837
+
838
+ # Check attribute access that returns string (like .name, .path)
839
+ if isinstance(node, ast.Attribute):
840
+ string_attrs = {'name', 'path', 'filename', 'text', 'message', 'value'}
841
+ if node.attr in string_attrs:
842
+ return True
843
+
844
+ # Check inferred type
845
+ inferred_type = self._infer_type(node)
846
+ if 'string' in inferred_type.lower():
847
+ return True
848
+
849
+ return False
850
+
593
851
  def _convert_body(self, stmts: List, indent: int = 1) -> str:
594
852
  lines = []
595
853
  ind = ' ' * indent
@@ -637,6 +895,8 @@ class PythonToCppConverter:
637
895
  value_str = self._convert_expr(stmt.value)
638
896
  var_type = self._infer_type(stmt.value)
639
897
  if isinstance(target, ast.Name):
898
+ # Track this variable's type
899
+ self.var_types[target.id] = var_type
640
900
  lines.append(f'{ind}{var_type} {target_str} = {value_str};')
641
901
  else:
642
902
  lines.append(f'{ind}{target_str} = {value_str};')
@@ -644,6 +904,9 @@ class PythonToCppConverter:
644
904
  elif isinstance(stmt, ast.AnnAssign):
645
905
  target_str = self._convert_expr(stmt.target)
646
906
  var_type = self._convert_type_annotation(stmt.annotation)
907
+ # Track annotated variable types
908
+ if isinstance(stmt.target, ast.Name):
909
+ self.var_types[stmt.target.id] = var_type
647
910
  if stmt.value:
648
911
  value_str = self._convert_expr(stmt.value)
649
912
  lines.append(f'{ind}{var_type} {target_str} = {value_str};')
@@ -657,8 +920,17 @@ class PythonToCppConverter:
657
920
  lines.append(f'{ind}{target_str} {op}= {value_str};')
658
921
 
659
922
  elif isinstance(stmt, ast.Expr):
660
- expr_str = self._convert_expr(stmt.value)
661
- lines.append(f'{ind}{expr_str};')
923
+ # Check if this is a docstring (string literal expression) - convert to comment
924
+ if isinstance(stmt.value, ast.Constant) and isinstance(stmt.value.value, str):
925
+ docstring = stmt.value.value.strip()
926
+ if docstring:
927
+ # Convert to C++ comment (take first line only for brevity)
928
+ first_line = docstring.split('\n')[0].strip()
929
+ if first_line:
930
+ lines.append(f'{ind}// {first_line}')
931
+ else:
932
+ expr_str = self._convert_expr(stmt.value)
933
+ lines.append(f'{ind}{expr_str};')
662
934
 
663
935
  elif isinstance(stmt, ast.If):
664
936
  test = self._convert_expr(stmt.test)
@@ -697,6 +969,7 @@ class PythonToCppConverter:
697
969
  elif iter_expr.func.id == 'enumerate':
698
970
  # Handle enumerate(items, start=N)
699
971
  args = iter_expr.args
972
+ iterable_node = args[0] if args else None
700
973
  iterable = self._convert_expr(args[0]) if args else 'items'
701
974
  start_idx = '0'
702
975
  # Check for start keyword arg
@@ -709,6 +982,32 @@ class PythonToCppConverter:
709
982
  if isinstance(stmt.target, ast.Tuple) and len(stmt.target.elts) == 2:
710
983
  idx_name = self._convert_expr(stmt.target.elts[0])
711
984
  item_name = self._convert_expr(stmt.target.elts[1])
985
+
986
+ # Track variable types for the loop variables
987
+ self.var_types[idx_name] = 'size_t'
988
+
989
+ # Infer item type from the iterable
990
+ if iterable_node:
991
+ iterable_type = self._infer_type(iterable_node)
992
+ # Extract element type from container (e.g., std::vector<std::string> -> std::string)
993
+ if '<' in iterable_type and '>' in iterable_type:
994
+ element_type = iterable_type[iterable_type.find('<')+1:iterable_type.rfind('>')]
995
+ self.var_types[item_name] = element_type
996
+ else:
997
+ # Check if the iterable is a known variable
998
+ if isinstance(iterable_node, ast.Name):
999
+ var_type = self.var_types.get(iterable_node.id, '')
1000
+ if 'string' in var_type.lower():
1001
+ self.var_types[item_name] = 'std::string'
1002
+ elif var_type == self.TEMPLATE_MARKER or 'std::vector' in var_type:
1003
+ self.var_types[item_name] = 'auto'
1004
+ else:
1005
+ self.var_types[item_name] = 'auto'
1006
+ else:
1007
+ self.var_types[item_name] = 'auto'
1008
+ else:
1009
+ self.var_types[item_name] = 'auto'
1010
+
712
1011
  lines.append(f'{ind}size_t {idx_name} = {start_idx};')
713
1012
  lines.append(f'{ind}for (auto& {item_name} : {iterable}) {{')
714
1013
  # Add index increment at end of loop body
@@ -720,12 +1019,21 @@ class PythonToCppConverter:
720
1019
  else:
721
1020
  # Fallback: just iterate
722
1021
  iter_str = self._convert_expr(iter_expr)
1022
+ # Track loop variable type from iterable
1023
+ if isinstance(stmt.target, ast.Name):
1024
+ self._track_for_loop_var_type(stmt.target.id, iter_expr)
723
1025
  lines.append(f'{ind}for (auto& {target} : {iter_str}) {{')
724
1026
  else:
725
1027
  iter_str = self._convert_expr(iter_expr)
1028
+ # Track loop variable type from iterable
1029
+ if isinstance(stmt.target, ast.Name):
1030
+ self._track_for_loop_var_type(stmt.target.id, iter_expr)
726
1031
  lines.append(f'{ind}for (auto& {target} : {iter_str}) {{')
727
1032
  else:
728
1033
  iter_str = self._convert_expr(iter_expr)
1034
+ # Track loop variable type from iterable
1035
+ if isinstance(stmt.target, ast.Name):
1036
+ self._track_for_loop_var_type(stmt.target.id, iter_expr)
729
1037
  lines.append(f'{ind}for (auto& {target} : {iter_str}) {{')
730
1038
 
731
1039
  lines.extend(self._convert_stmt_list(stmt.body, indent + 1))
@@ -962,14 +1270,24 @@ class PythonToCppConverter:
962
1270
  elif func == 'abs':
963
1271
  return f'std::abs({_safe_arg(args, 0)})'
964
1272
  elif func == 'min':
1273
+ self.imports.add('<algorithm>')
965
1274
  if len(args) == 2:
966
1275
  return f'std::min({_safe_arg(args, 0)}, {_safe_arg(args, 1)})'
1276
+ elif len(args) == 1:
1277
+ # Single container argument - use min_element
1278
+ arg0 = _safe_arg(args, 0)
1279
+ return f'*std::min_element({arg0}.begin(), {arg0}.end())'
967
1280
  elif args:
968
1281
  return f'std::min({{{", ".join(args)}}})'
969
1282
  return '0 /* min() requires arguments */'
970
1283
  elif func == 'max':
1284
+ self.imports.add('<algorithm>')
971
1285
  if len(args) == 2:
972
1286
  return f'std::max({_safe_arg(args, 0)}, {_safe_arg(args, 1)})'
1287
+ elif len(args) == 1:
1288
+ # Single container argument - use max_element
1289
+ arg0 = _safe_arg(args, 0)
1290
+ return f'*std::max_element({arg0}.begin(), {arg0}.end())'
973
1291
  elif args:
974
1292
  return f'std::max({{{", ".join(args)}}})'
975
1293
  return '0 /* max() requires arguments */'
@@ -1239,8 +1557,9 @@ class PythonToCppConverter:
1239
1557
  elif isinstance(value, ast.FormattedValue):
1240
1558
  # {expr} part - convert to std::to_string or direct if already string
1241
1559
  expr = self._convert_expr(value.value)
1242
- # Check if it's likely a string already
1243
- if any(x in expr for x in ['\"', '.c_str()', 'std::string']):
1560
+ # Check if it's a string type (no std::to_string needed)
1561
+ is_string_type = self._is_string_expr(value.value, expr)
1562
+ if is_string_type:
1244
1563
  parts.append(expr)
1245
1564
  else:
1246
1565
  parts.append(f'std::to_string({expr})')
@@ -1304,10 +1623,83 @@ class PythonToCppConverter:
1304
1623
  def _format_params(self, params: List[Tuple[str, str]]) -> str:
1305
1624
  return ', '.join(f'{ptype} {pname}' for pname, ptype in params)
1306
1625
 
1307
- def _format_params_with_const_ref(self, params: List[Tuple[str, str]]) -> str:
1626
+ def _needs_template(self, params: List[Tuple[str, str]]) -> bool:
1627
+ """Check if any parameter requires a template (has T_CONTAINER or T_ELEMENT type)."""
1628
+ return any(ptype in (self.TEMPLATE_MARKER, self.TEMPLATE_ELEMENT) for _, ptype in params)
1629
+
1630
+ def _has_container_param(self, params: List[Tuple[str, str]]) -> bool:
1631
+ """Check if params include a T_CONTAINER (useful for element type matching)."""
1632
+ return any(ptype == self.TEMPLATE_MARKER for _, ptype in params)
1633
+
1634
+ def _get_template_return_type(self, return_type: str, body: str, params: List[Tuple[str, str]]) -> str:
1635
+ """Determine the proper return type for a template function."""
1636
+ # If return type is already explicit, use it
1637
+ if return_type not in ('auto', 'void'):
1638
+ return return_type
1639
+ if return_type == 'void':
1640
+ return 'void'
1641
+
1642
+ # Get container parameter names
1643
+ container_params = [pname for pname, ptype in params if ptype == self.TEMPLATE_MARKER]
1644
+
1645
+ # Check if body returns one of the container parameters (e.g., shuffle_list returns items)
1646
+ # Look for patterns like "return items" where items is a container param
1647
+ for pname in container_params:
1648
+ if re.search(rf'\breturn\s+{re.escape(pname)}\b', body):
1649
+ return 'std::vector<T>'
1650
+
1651
+ # Default to T for functions that return an element
1652
+ return 'T'
1653
+
1654
+ def _generate_explicit_instantiation(self, func_name: str, params: List[Tuple[str, str]],
1655
+ return_type: str, class_name: str = None,
1656
+ const: str = '') -> List[str]:
1657
+ """Generate explicit template instantiations for common types."""
1658
+ lines = ['// Explicit instantiations']
1659
+
1660
+ has_element_param = any(ptype == self.TEMPLATE_ELEMENT for _, ptype in params)
1661
+
1662
+ for cpp_type in ['int', 'double', 'std::string']:
1663
+ # Build parameter list for instantiation
1664
+ inst_params = []
1665
+ for pname, ptype in params:
1666
+ if ptype == self.TEMPLATE_MARKER:
1667
+ inst_params.append(f'const std::vector<{cpp_type}>&')
1668
+ elif ptype == self.TEMPLATE_ELEMENT:
1669
+ inst_params.append(f'const {cpp_type}&')
1670
+ # Skip non-template params in explicit instantiation signature
1671
+
1672
+ params_str = ', '.join(inst_params)
1673
+
1674
+ # Determine return type for instantiation
1675
+ if return_type == 'std::vector<T>':
1676
+ inst_ret = f'std::vector<{cpp_type}>'
1677
+ elif return_type == 'T':
1678
+ inst_ret = cpp_type
1679
+ else:
1680
+ inst_ret = return_type
1681
+
1682
+ if class_name:
1683
+ lines.append(f'template {inst_ret} {class_name}::{func_name}<{cpp_type}>({params_str}){const};')
1684
+ else:
1685
+ lines.append(f'template {inst_ret} {func_name}<{cpp_type}>({params_str});')
1686
+
1687
+ return lines
1688
+
1689
+ def _format_params_with_const_ref(self, params: List[Tuple[str, str]], use_template: bool = False) -> str:
1690
+ has_container = self._has_container_param(params)
1308
1691
  result = []
1309
1692
  for pname, ptype in params:
1310
- if ptype in ('std::string', 'std::vector', 'std::unordered_map', 'std::unordered_set') or ptype.startswith('std::'):
1693
+ # Handle template container marker
1694
+ if ptype == self.TEMPLATE_MARKER:
1695
+ result.append(f'const std::vector<T>& {pname}')
1696
+ # Handle template element marker - only becomes T if there's also a container param
1697
+ elif ptype == self.TEMPLATE_ELEMENT:
1698
+ if has_container:
1699
+ result.append(f'const T& {pname}')
1700
+ else:
1701
+ result.append(f'double {pname}') # Fallback to double if no container context
1702
+ elif ptype in ('std::string', 'std::vector', 'std::unordered_map', 'std::unordered_set') or ptype.startswith('std::'):
1311
1703
  result.append(f'const {ptype}& {pname}')
1312
1704
  else:
1313
1705
  result.append(f'{ptype} {pname}')
@@ -1346,7 +1738,25 @@ class PythonToCppConverter:
1346
1738
  lines.append(f'class {cls.name} {{')
1347
1739
  lines.append('public:')
1348
1740
 
1349
- for fname, ftype, _ in cls.fields:
1741
+ for fname, ftype, fval in cls.fields:
1742
+ # 'auto' is invalid for class members - infer from value or use default
1743
+ if ftype == 'auto':
1744
+ if fval:
1745
+ # Try to infer from the default value expression
1746
+ if fval.startswith('"') or fval.startswith("'"):
1747
+ ftype = 'std::string'
1748
+ elif fval in ('true', 'false'):
1749
+ ftype = 'bool'
1750
+ elif '.' in fval and fval.replace('.', '').replace('-', '').isdigit():
1751
+ ftype = 'double'
1752
+ elif fval.lstrip('-').isdigit():
1753
+ ftype = 'int'
1754
+ elif fval.startswith('{') or fval.startswith('std::vector'):
1755
+ ftype = 'std::vector<int>'
1756
+ else:
1757
+ ftype = 'int' # Default fallback
1758
+ else:
1759
+ ftype = 'int' # Default for uninitialized
1350
1760
  lines.append(f' {ftype} {fname};')
1351
1761
 
1352
1762
  if cls.fields:
@@ -1360,14 +1770,26 @@ class PythonToCppConverter:
1360
1770
  params = self._format_params_with_const_ref(method.params)
1361
1771
  static = 'static ' if method.is_static else ''
1362
1772
  const = ' const' if method.is_const else ''
1363
- lines.append(f' {static}{method.return_type} {method.name}({params}){const};')
1773
+ # Add template prefix if method uses generic containers
1774
+ if self._needs_template(method.params):
1775
+ lines.append(f' template<typename T>')
1776
+ ret_type = self._get_template_return_type(method.return_type, method.body, method.params)
1777
+ lines.append(f' {static}{ret_type} {method.name}({params}){const};')
1778
+ else:
1779
+ lines.append(f' {static}{method.return_type} {method.name}({params}){const};')
1364
1780
 
1365
1781
  lines.append('};')
1366
1782
  lines.append('')
1367
1783
 
1368
1784
  for func in functions:
1369
1785
  params = self._format_params_with_const_ref(func.params)
1370
- lines.append(f'{func.return_type} {func.name}({params});')
1786
+ # Add template prefix if function uses generic containers
1787
+ if self._needs_template(func.params):
1788
+ lines.append('template<typename T>')
1789
+ ret_type = self._get_template_return_type(func.return_type, func.body, func.params)
1790
+ lines.append(f'{ret_type} {func.name}({params});')
1791
+ else:
1792
+ lines.append(f'{func.return_type} {func.name}({params});')
1371
1793
 
1372
1794
  lines.append('')
1373
1795
  lines.append('} // namespace includecpp')
@@ -1588,16 +2010,39 @@ class PythonToCppConverter:
1588
2010
  for method in cls.methods:
1589
2011
  params = self._format_params_with_const_ref(method.params)
1590
2012
  const = ' const' if method.is_const else ''
1591
- lines.append(f'{method.return_type} {cls.name}::{method.name}({params}){const} {{')
2013
+ # Add template prefix if method uses generic containers
2014
+ if self._needs_template(method.params):
2015
+ lines.append('template<typename T>')
2016
+ ret_type = self._get_template_return_type(method.return_type, method.body, method.params)
2017
+ lines.append(f'{ret_type} {cls.name}::{method.name}({params}){const} {{')
2018
+ else:
2019
+ lines.append(f'{method.return_type} {cls.name}::{method.name}({params}){const} {{')
1592
2020
  lines.append(method.body)
1593
2021
  lines.append('}')
2022
+ # Add explicit template instantiations for common types
2023
+ if self._needs_template(method.params):
2024
+ ret_type = self._get_template_return_type(method.return_type, method.body, method.params)
2025
+ inst_lines = self._generate_explicit_instantiation(
2026
+ method.name, method.params, ret_type, cls.name, const)
2027
+ lines.extend(inst_lines)
1594
2028
  lines.append('')
1595
2029
 
1596
2030
  for func in functions:
1597
2031
  params = self._format_params_with_const_ref(func.params)
1598
- lines.append(f'{func.return_type} {func.name}({params}) {{')
2032
+ # Add template prefix if function uses generic containers
2033
+ if self._needs_template(func.params):
2034
+ lines.append('template<typename T>')
2035
+ ret_type = self._get_template_return_type(func.return_type, func.body, func.params)
2036
+ lines.append(f'{ret_type} {func.name}({params}) {{')
2037
+ else:
2038
+ lines.append(f'{func.return_type} {func.name}({params}) {{')
1599
2039
  lines.append(func.body)
1600
2040
  lines.append('}')
2041
+ # Add explicit template instantiations for common types
2042
+ if self._needs_template(func.params):
2043
+ ret_type = self._get_template_return_type(func.return_type, func.body, func.params)
2044
+ inst_lines = self._generate_explicit_instantiation(func.name, func.params, ret_type)
2045
+ lines.extend(inst_lines)
1601
2046
  lines.append('')
1602
2047
 
1603
2048
  lines.append('} // namespace includecpp')
@@ -1608,6 +2053,7 @@ class PythonToCppConverter:
1608
2053
  class CppToPythonConverter:
1609
2054
  def __init__(self):
1610
2055
  self.indent = ' '
2056
+ self._current_class_fields = set() # Track fields for self. prefix
1611
2057
 
1612
2058
  def convert(self, source: str, module_name: str) -> str:
1613
2059
  """Convert C++ source to Python."""
@@ -1737,8 +2183,9 @@ class CppToPythonConverter:
1737
2183
  body = match.group(3)
1738
2184
 
1739
2185
  fields = self._parse_class_fields(body)
1740
- methods = self._parse_class_methods(body, name)
1741
- constructors = self._parse_constructors(body, name)
2186
+ field_names = {f[0] for f in fields} # Extract field names for self. prefix
2187
+ methods = self._parse_class_methods(body, name, field_names)
2188
+ constructors = self._parse_constructors(body, name, field_names)
1742
2189
 
1743
2190
  classes.append(ClassInfo(
1744
2191
  name=name,
@@ -1748,6 +2195,9 @@ class CppToPythonConverter:
1748
2195
  constructors=constructors
1749
2196
  ))
1750
2197
 
2198
+ # Clear class fields after processing this class
2199
+ self._current_class_fields = set()
2200
+
1751
2201
  return classes
1752
2202
 
1753
2203
  def _parse_structs(self, source: str) -> List[StructInfo]:
@@ -1814,11 +2264,31 @@ class CppToPythonConverter:
1814
2264
 
1815
2265
  def _parse_class_fields(self, body: str) -> List[Tuple[str, str, Optional[str]]]:
1816
2266
  fields = []
2267
+ all_field_names = set() # v3.4.1: Track ALL fields for self. prefix
1817
2268
 
2269
+ # v3.4.1: Parse fields from ALL sections (public, private, protected)
2270
+ # for self. prefix detection, but only return public fields for dataclass
1818
2271
  sections = re.split(r'(?:public|private|protected)\s*:', body)
1819
- public_section = sections[1] if len(sections) > 1 else body
1820
2272
 
1821
2273
  field_pattern = r'(\w+(?:<[^>]+>)?)\s+(\w+)\s*(?:=\s*([^;]+))?\s*;'
2274
+
2275
+ # First pass: collect ALL field names from all sections
2276
+ for section in sections:
2277
+ for match in re.finditer(field_pattern, section):
2278
+ field_type = match.group(1)
2279
+ field_name = match.group(2)
2280
+
2281
+ if '(' in field_type or field_type in ('return', 'if', 'for', 'while'):
2282
+ continue
2283
+
2284
+ all_field_names.add(field_name)
2285
+
2286
+ # Store all field names for self. prefix detection
2287
+ self._current_class_fields = all_field_names
2288
+
2289
+ # Second pass: return only public fields for the Python class definition
2290
+ public_section = sections[1] if len(sections) > 1 else body
2291
+
1822
2292
  for match in re.finditer(field_pattern, public_section):
1823
2293
  field_type = match.group(1)
1824
2294
  field_name = match.group(2)
@@ -1832,9 +2302,13 @@ class CppToPythonConverter:
1832
2302
 
1833
2303
  return fields
1834
2304
 
1835
- def _parse_class_methods(self, body: str, class_name: str) -> List[FunctionInfo]:
2305
+ def _parse_class_methods(self, body: str, class_name: str, field_names: set = None) -> List[FunctionInfo]:
1836
2306
  methods = []
1837
2307
 
2308
+ # Set current class fields for self. prefix during body conversion
2309
+ if field_names:
2310
+ self._current_class_fields = field_names
2311
+
1838
2312
  pattern = r'(?:(static|virtual)\s+)?(\w+(?:<[^>]+>)?(?:\s*[*&])?)\s+(\w+)\s*\(([^)]*)\)\s*(const)?\s*(?:\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}|;)'
1839
2313
 
1840
2314
  for match in re.finditer(pattern, body):
@@ -1863,9 +2337,13 @@ class CppToPythonConverter:
1863
2337
 
1864
2338
  return methods
1865
2339
 
1866
- def _parse_constructors(self, body: str, class_name: str) -> List[FunctionInfo]:
2340
+ def _parse_constructors(self, body: str, class_name: str, field_names: set = None) -> List[FunctionInfo]:
1867
2341
  constructors = []
1868
2342
 
2343
+ # Set current class fields for self. prefix during body conversion
2344
+ if field_names:
2345
+ self._current_class_fields = field_names
2346
+
1869
2347
  pattern = rf'{class_name}\s*\(([^)]*)\)\s*(?::\s*[^{{]+)?\s*\{{([^{{}}]*(?:\{{[^{{}}]*\}}[^{{}}]*)*)\}}'
1870
2348
 
1871
2349
  for match in re.finditer(pattern, body):
@@ -2101,18 +2579,20 @@ class CppToPythonConverter:
2101
2579
  if var_name not in ('if', 'for', 'while', 'return'):
2102
2580
  return f'{var_name} = {self._get_default_value(var_type)}'
2103
2581
 
2104
- # Assignment
2105
- assign_match = re.match(r'(\w+(?:\.\w+)*(?:\[[^\]]+\])?)\s*=\s*(.+)$', stmt)
2582
+ # Assignment - v3.3.22: Expanded pattern to capture -> and ::
2583
+ assign_match = re.match(r'([a-zA-Z_][\w:>-]*(?:->|\.)?[a-zA-Z_][\w]*(?:\[[^\]]+\])?)\s*=\s*(.+)$', stmt)
2106
2584
  if assign_match:
2107
2585
  target = assign_match.group(1)
2108
- target = target.replace('this->', 'self.')
2586
+ # v3.3.22: Apply full expression conversion to target as well
2587
+ target = self._convert_cpp_expr(target)
2109
2588
  value = self._convert_cpp_expr(assign_match.group(2))
2110
2589
  return f'{target} = {value}'
2111
2590
 
2112
2591
  # Augmented assignment
2113
2592
  aug_match = re.match(r'(\w+(?:\.\w+)*)\s*(\+\+|--|\+=|-=|\*=|/=)', stmt)
2114
2593
  if aug_match:
2115
- target = aug_match.group(1).replace('this->', 'self.')
2594
+ # v3.3.22: Apply full expression conversion to target
2595
+ target = self._convert_cpp_expr(aug_match.group(1))
2116
2596
  op = aug_match.group(2)
2117
2597
  if op == '++':
2118
2598
  return f'{target} += 1'
@@ -2172,6 +2652,32 @@ class CppToPythonConverter:
2172
2652
  expr = re.sub(r'std::min\(([^,]+),\s*([^)]+)\)', r'min(\1, \2)', expr)
2173
2653
  expr = re.sub(r'std::max\(([^,]+),\s*([^)]+)\)', r'max(\1, \2)', expr)
2174
2654
 
2655
+ # Handle std::accumulate(container.begin(), container.end(), init) -> sum(container) [+ init]
2656
+ # v3.4.1: Also handle accumulate without std:: prefix
2657
+ def _accumulate_to_sum(m):
2658
+ container = m.group(1)
2659
+ init_val = m.group(2).strip()
2660
+ if init_val == '0' or init_val == '0.0':
2661
+ return f'sum({container})'
2662
+ return f'sum({container}) + {init_val}'
2663
+ expr = re.sub(r'(?:std::)?accumulate\((\w+)\.begin\(\),\s*\1\.end\(\),\s*([^)]+)\)', _accumulate_to_sum, expr)
2664
+
2665
+ # v3.4.1: Handle std::find, std::count, std::sort with .begin()/.end()
2666
+ # std::find(vec.begin(), vec.end(), val) -> val in vec
2667
+ expr = re.sub(r'(?:std::)?find\((\w+)\.begin\(\),\s*\1\.end\(\),\s*([^)]+)\)\s*!=\s*\1\.end\(\)', r'\2 in \1', expr)
2668
+ # std::count(vec.begin(), vec.end(), val) -> vec.count(val)
2669
+ expr = re.sub(r'(?:std::)?count\((\w+)\.begin\(\),\s*\1\.end\(\),\s*([^)]+)\)', r'\1.count(\2)', expr)
2670
+ # std::sort(vec.begin(), vec.end()) -> vec.sort()
2671
+ expr = re.sub(r'(?:std::)?sort\((\w+)\.begin\(\),\s*\1\.end\(\)\)', r'\1.sort()', expr)
2672
+ # std::reverse(vec.begin(), vec.end()) -> vec.reverse()
2673
+ expr = re.sub(r'(?:std::)?reverse\((\w+)\.begin\(\),\s*\1\.end\(\)\)', r'\1.reverse()', expr)
2674
+
2675
+ # v3.4.1: Clean up any remaining .begin()/.end() that couldn't be converted
2676
+ # container.begin() -> iter(container) for iteration context
2677
+ # But typically these are errors - flag as comment if they remain
2678
+ expr = re.sub(r'(\w+)\.begin\(\)', r'\1[0]', expr) # Approximate as first element
2679
+ expr = re.sub(r'(\w+)\.end\(\)', r'len(\1)', expr) # Approximate as length
2680
+
2175
2681
  # Handle .size() -> len()
2176
2682
  expr = re.sub(r'(\w+)\.size\(\)', r'len(\1)', expr)
2177
2683
 
@@ -2209,27 +2715,53 @@ class CppToPythonConverter:
2209
2715
  # Handle negation !
2210
2716
  expr = re.sub(r'!(\w)', r'not \1', expr)
2211
2717
 
2718
+ # v3.3.22: Add self. prefix for class member access (when not already prefixed)
2719
+ if self._current_class_fields:
2720
+ for field in self._current_class_fields:
2721
+ # Match field name that's not already prefixed with self. or another identifier
2722
+ # Fixed lookahead to be more permissive - match non-word chars or end
2723
+ expr = re.sub(rf'(?<![.\w])(?<!self\.){re.escape(field)}(?=\W|$)', f'self.{field}', expr)
2724
+
2725
+ # v3.4.1: Handle common C++ member naming conventions
2726
+ # Convert m_name to self.name when in class context
2727
+ if self._current_class_fields:
2728
+ # Convert m_xxx to self.xxx
2729
+ expr = re.sub(r'(?<![.\w])m_(\w+)(?=\W|$)', r'self.\1', expr)
2730
+ # Convert _xxx to self.xxx (leading underscore)
2731
+ expr = re.sub(r'(?<![.\w])_(\w+)(?=\W|$)', r'self.\1', expr)
2732
+ # v3.4.1: Convert xxx_ to self.xxx_ (trailing underscore - Google style)
2733
+ # Only if it's a known field or matches the pattern
2734
+ for field in self._current_class_fields:
2735
+ if field.endswith('_') and field not in ('self_',):
2736
+ expr = re.sub(rf'(?<![.\w]){re.escape(field)}(?=\W|$)', f'self.{field}', expr)
2737
+
2212
2738
  return expr
2213
2739
 
2214
2740
  def _generate_struct(self, struct: StructInfo) -> List[str]:
2215
2741
  lines = ['@dataclass']
2216
- lines.append(f'class {struct.name}:')
2742
+ # v3.4.1: Escape Python keywords and C++ reserved words in struct/class names
2743
+ struct_name = _escape_identifier(struct.name)
2744
+ lines.append(f'class {struct_name}:')
2217
2745
 
2218
2746
  if not struct.fields:
2219
2747
  lines.append(f'{self.indent}pass')
2220
2748
  else:
2221
2749
  for fname, ftype in struct.fields:
2222
- lines.append(f'{self.indent}{fname}: {ftype}')
2750
+ # v3.4.1: Escape Python keywords and C++ reserved words in field names
2751
+ escaped_fname = _escape_identifier(fname)
2752
+ lines.append(f'{self.indent}{escaped_fname}: {ftype}')
2223
2753
 
2224
2754
  return lines
2225
2755
 
2226
2756
  def _generate_class(self, cls: ClassInfo) -> List[str]:
2227
2757
  lines = []
2228
2758
 
2759
+ # v3.4.1: Escape Python keywords and C++ reserved words in class names
2760
+ class_name = _escape_identifier(cls.name)
2229
2761
  if cls.bases:
2230
- lines.append(f'class {cls.name}({", ".join(cls.bases)}):')
2762
+ lines.append(f'class {class_name}({", ".join(cls.bases)}):')
2231
2763
  else:
2232
- lines.append(f'class {cls.name}:')
2764
+ lines.append(f'class {class_name}:')
2233
2765
 
2234
2766
  if not cls.fields and not cls.methods and not cls.constructors:
2235
2767
  lines.append(f'{self.indent}pass')
@@ -2251,16 +2783,19 @@ class CppToPythonConverter:
2251
2783
 
2252
2784
  params = ['self'] if not method.is_static else []
2253
2785
  for pname, ptype in method.params:
2786
+ # v3.4.1: Escape Python keywords and C++ reserved words in parameter names
2787
+ escaped_pname = _escape_identifier(pname)
2254
2788
  if ptype and ptype != 'Any':
2255
- params.append(f'{pname}: {ptype}')
2789
+ params.append(f'{escaped_pname}: {ptype}')
2256
2790
  else:
2257
- params.append(pname)
2791
+ params.append(escaped_pname)
2258
2792
 
2259
2793
  ret_type = ''
2260
2794
  if not is_init and method.return_type and method.return_type != 'None':
2261
2795
  ret_type = f' -> {method.return_type}'
2262
2796
 
2263
- method_name = '__init__' if is_init else method.name
2797
+ # v3.4.1: Escape Python keywords and C++ reserved words in method names
2798
+ method_name = '__init__' if is_init else _escape_identifier(method.name)
2264
2799
  lines.append(f'{self.indent}def {method_name}({", ".join(params)}){ret_type}:')
2265
2800
 
2266
2801
  body_lines = method.body.split('\n')
@@ -2277,16 +2812,20 @@ class CppToPythonConverter:
2277
2812
 
2278
2813
  params = []
2279
2814
  for pname, ptype in func.params:
2815
+ # v3.4.1: Escape Python keywords and C++ reserved words in parameter names
2816
+ escaped_pname = _escape_identifier(pname)
2280
2817
  if ptype and ptype != 'Any':
2281
- params.append(f'{pname}: {ptype}')
2818
+ params.append(f'{escaped_pname}: {ptype}')
2282
2819
  else:
2283
- params.append(pname)
2820
+ params.append(escaped_pname)
2284
2821
 
2285
2822
  ret_type = ''
2286
2823
  if func.return_type and func.return_type != 'None':
2287
2824
  ret_type = f' -> {func.return_type}'
2288
2825
 
2289
- lines.append(f'def {func.name}({", ".join(params)}){ret_type}:')
2826
+ # v3.4.1: Escape Python keywords and C++ reserved words in function names
2827
+ func_name = _escape_identifier(func.name)
2828
+ lines.append(f'def {func_name}({", ".join(params)}){ret_type}:')
2290
2829
 
2291
2830
  body_lines = func.body.split('\n')
2292
2831
  if body_lines and body_lines[0].strip():