IncludeCPP 3.7.3__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.
Potentially problematic release.
This version of IncludeCPP might be problematic. Click here for more details.
- includecpp/__init__.py +59 -0
- includecpp/__init__.pyi +255 -0
- includecpp/__main__.py +4 -0
- includecpp/cli/__init__.py +4 -0
- includecpp/cli/commands.py +8270 -0
- includecpp/cli/config_parser.py +127 -0
- includecpp/core/__init__.py +19 -0
- includecpp/core/ai_integration.py +2132 -0
- includecpp/core/build_manager.py +2416 -0
- includecpp/core/cpp_api.py +376 -0
- includecpp/core/cpp_api.pyi +95 -0
- includecpp/core/cppy_converter.py +3448 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +2075 -0
- includecpp/core/cssl/__init__.py +42 -0
- includecpp/core/cssl/cssl_builtins.py +2271 -0
- includecpp/core/cssl/cssl_builtins.pyi +1393 -0
- includecpp/core/cssl/cssl_events.py +621 -0
- includecpp/core/cssl/cssl_modules.py +2803 -0
- includecpp/core/cssl/cssl_parser.py +2575 -0
- includecpp/core/cssl/cssl_runtime.py +3051 -0
- includecpp/core/cssl/cssl_syntax.py +488 -0
- includecpp/core/cssl/cssl_types.py +1512 -0
- includecpp/core/cssl_bridge.py +882 -0
- includecpp/core/cssl_bridge.pyi +488 -0
- includecpp/core/error_catalog.py +802 -0
- includecpp/core/error_formatter.py +1016 -0
- includecpp/core/exceptions.py +97 -0
- includecpp/core/path_discovery.py +77 -0
- includecpp/core/project_ui.py +3370 -0
- includecpp/core/settings_ui.py +326 -0
- includecpp/generator/__init__.py +1 -0
- includecpp/generator/parser.cpp +1903 -0
- includecpp/generator/parser.h +281 -0
- includecpp/generator/type_resolver.cpp +363 -0
- includecpp/generator/type_resolver.h +68 -0
- includecpp/py.typed +0 -0
- includecpp/templates/cpp.proj.template +18 -0
- includecpp/vscode/__init__.py +1 -0
- includecpp/vscode/cssl/__init__.py +1 -0
- includecpp/vscode/cssl/language-configuration.json +38 -0
- includecpp/vscode/cssl/package.json +50 -0
- includecpp/vscode/cssl/snippets/cssl.snippets.json +1080 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +341 -0
- includecpp-3.7.3.dist-info/METADATA +1076 -0
- includecpp-3.7.3.dist-info/RECORD +49 -0
- includecpp-3.7.3.dist-info/WHEEL +5 -0
- includecpp-3.7.3.dist-info/entry_points.txt +2 -0
- includecpp-3.7.3.dist-info/licenses/LICENSE +21 -0
- includecpp-3.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,3448 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CPPY Converter - Python <-> C++ bidirectional code conversion.
|
|
3
|
+
Full implementation with struct, class, function, template support.
|
|
4
|
+
Maximum stability version with comprehensive error handling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import ast
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Tuple, Optional, Set, Any
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _safe_arg(args: List[str], index: int, default: str = '0') -> str:
|
|
15
|
+
"""Safely get argument at index, returning default if not available."""
|
|
16
|
+
if args and 0 <= index < len(args):
|
|
17
|
+
return args[index]
|
|
18
|
+
return default
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _safe_get(lst: List[Any], index: int, default: Any = None) -> Any:
|
|
22
|
+
"""Safely get element from list at index."""
|
|
23
|
+
if lst and 0 <= index < len(lst):
|
|
24
|
+
return lst[index]
|
|
25
|
+
return default
|
|
26
|
+
|
|
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
|
+
|
|
87
|
+
# Python type to C++ type mapping
|
|
88
|
+
PY_TO_CPP_TYPES = {
|
|
89
|
+
'int': 'int',
|
|
90
|
+
'float': 'double',
|
|
91
|
+
'str': 'std::string',
|
|
92
|
+
'bool': 'bool',
|
|
93
|
+
'bytes': 'std::vector<uint8_t>',
|
|
94
|
+
'bytearray': 'std::vector<uint8_t>',
|
|
95
|
+
'None': 'void',
|
|
96
|
+
'any': 'auto',
|
|
97
|
+
'Any': 'auto',
|
|
98
|
+
'object': 'py::object',
|
|
99
|
+
'list': 'std::vector',
|
|
100
|
+
'List': 'std::vector',
|
|
101
|
+
'dict': 'std::unordered_map',
|
|
102
|
+
'Dict': 'std::unordered_map',
|
|
103
|
+
'set': 'std::unordered_set',
|
|
104
|
+
'Set': 'std::unordered_set',
|
|
105
|
+
'tuple': 'std::tuple',
|
|
106
|
+
'Tuple': 'std::tuple',
|
|
107
|
+
'Optional': 'std::optional',
|
|
108
|
+
'Union': 'std::variant',
|
|
109
|
+
'Callable': 'std::function',
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# C++ type to Python type mapping
|
|
113
|
+
CPP_TO_PY_TYPES = {
|
|
114
|
+
'int': 'int',
|
|
115
|
+
'long': 'int',
|
|
116
|
+
'long long': 'int',
|
|
117
|
+
'short': 'int',
|
|
118
|
+
'unsigned int': 'int',
|
|
119
|
+
'unsigned long': 'int',
|
|
120
|
+
'size_t': 'int',
|
|
121
|
+
'float': 'float',
|
|
122
|
+
'double': 'float',
|
|
123
|
+
'bool': 'bool',
|
|
124
|
+
'char': 'str',
|
|
125
|
+
'std::string': 'str',
|
|
126
|
+
'string': 'str',
|
|
127
|
+
'void': 'None',
|
|
128
|
+
'auto': 'Any',
|
|
129
|
+
'std::vector': 'list',
|
|
130
|
+
'vector': 'list',
|
|
131
|
+
'std::map': 'dict',
|
|
132
|
+
'std::unordered_map': 'dict',
|
|
133
|
+
'map': 'dict',
|
|
134
|
+
'std::set': 'set',
|
|
135
|
+
'std::unordered_set': 'set',
|
|
136
|
+
'set': 'set',
|
|
137
|
+
'std::tuple': 'tuple',
|
|
138
|
+
'tuple': 'tuple',
|
|
139
|
+
'std::optional': 'Optional',
|
|
140
|
+
'optional': 'Optional',
|
|
141
|
+
'std::variant': 'Union',
|
|
142
|
+
'std::function': 'Callable',
|
|
143
|
+
'uint8_t': 'int',
|
|
144
|
+
'int8_t': 'int',
|
|
145
|
+
'uint16_t': 'int',
|
|
146
|
+
'int16_t': 'int',
|
|
147
|
+
'uint32_t': 'int',
|
|
148
|
+
'int32_t': 'int',
|
|
149
|
+
'uint64_t': 'int',
|
|
150
|
+
'int64_t': 'int',
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Modules that are fully unconvertible (GUI frameworks, etc.)
|
|
154
|
+
UNCONVERTIBLE_MODULES = {
|
|
155
|
+
'tkinter': 'GUI framework - no C++ equivalent',
|
|
156
|
+
'tk': 'tkinter alias - no C++ equivalent',
|
|
157
|
+
'PyQt5': 'GUI framework - use Qt C++ directly',
|
|
158
|
+
'PyQt6': 'GUI framework - use Qt C++ directly',
|
|
159
|
+
'PySide2': 'GUI framework - use Qt C++ directly',
|
|
160
|
+
'PySide6': 'GUI framework - use Qt C++ directly',
|
|
161
|
+
'pygame': 'Game library - no direct C++ equivalent',
|
|
162
|
+
'kivy': 'GUI framework - no C++ equivalent',
|
|
163
|
+
'wx': 'wxPython - use wxWidgets in C++',
|
|
164
|
+
'curses': 'Terminal UI - use ncurses in C++',
|
|
165
|
+
'turtle': 'Graphics - no C++ equivalent',
|
|
166
|
+
'PIL': 'Image library - use OpenCV/stb_image',
|
|
167
|
+
'pillow': 'Image library - use OpenCV/stb_image',
|
|
168
|
+
'matplotlib': 'Plotting library - no direct equivalent',
|
|
169
|
+
'numpy': 'Numerical library - use Eigen/Armadillo',
|
|
170
|
+
'pandas': 'Data analysis - no direct equivalent',
|
|
171
|
+
'scipy': 'Scientific computing - use specialized C++ libs',
|
|
172
|
+
'sklearn': 'Machine learning - use specialized C++ libs',
|
|
173
|
+
'tensorflow': 'ML framework - has C++ API',
|
|
174
|
+
'torch': 'PyTorch - has libtorch C++ API',
|
|
175
|
+
'flask': 'Web framework - no C++ equivalent',
|
|
176
|
+
'django': 'Web framework - no C++ equivalent',
|
|
177
|
+
'requests': 'HTTP library - use libcurl/cpp-httplib',
|
|
178
|
+
'asyncio': 'Async library - use std::async/coroutines',
|
|
179
|
+
'aiohttp': 'Async HTTP - use async C++ libs',
|
|
180
|
+
'socket': 'Network sockets - use C socket API',
|
|
181
|
+
'http': 'HTTP library - use cpp-httplib',
|
|
182
|
+
'urllib': 'URL library - use libcurl',
|
|
183
|
+
'multiprocessing': 'Process-based parallelism - use fork/spawn',
|
|
184
|
+
'subprocess': 'Process execution - use system()/popen()',
|
|
185
|
+
'logging': 'Logging - use spdlog/similar',
|
|
186
|
+
'unittest': 'Testing - use gtest/catch2',
|
|
187
|
+
'pytest': 'Testing - use gtest/catch2',
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Modules with partial C++ equivalent mappings
|
|
191
|
+
MODULE_CONVERSIONS = {
|
|
192
|
+
# os module
|
|
193
|
+
'os.getcwd': {'cpp': 'std::filesystem::current_path().string()', 'include': '<filesystem>'},
|
|
194
|
+
'os.chdir': {'cpp': 'std::filesystem::current_path', 'include': '<filesystem>', 'pattern': 'std::filesystem::current_path({})'},
|
|
195
|
+
'os.listdir': {'cpp': '_os_listdir', 'include': '<filesystem>'},
|
|
196
|
+
'os.mkdir': {'cpp': 'std::filesystem::create_directory', 'include': '<filesystem>'},
|
|
197
|
+
'os.makedirs': {'cpp': 'std::filesystem::create_directories', 'include': '<filesystem>'},
|
|
198
|
+
'os.remove': {'cpp': 'std::filesystem::remove', 'include': '<filesystem>'},
|
|
199
|
+
'os.rmdir': {'cpp': 'std::filesystem::remove', 'include': '<filesystem>'},
|
|
200
|
+
'os.rename': {'cpp': 'std::filesystem::rename', 'include': '<filesystem>'},
|
|
201
|
+
'os.path.exists': {'cpp': 'std::filesystem::exists', 'include': '<filesystem>'},
|
|
202
|
+
'os.path.isfile': {'cpp': 'std::filesystem::is_regular_file', 'include': '<filesystem>'},
|
|
203
|
+
'os.path.isdir': {'cpp': 'std::filesystem::is_directory', 'include': '<filesystem>'},
|
|
204
|
+
'os.path.join': {'cpp': '_path_join', 'include': '<filesystem>'},
|
|
205
|
+
'os.path.dirname': {'cpp': '_path_dirname', 'include': '<filesystem>'},
|
|
206
|
+
'os.path.basename': {'cpp': '_path_basename', 'include': '<filesystem>'},
|
|
207
|
+
'os.path.splitext': {'cpp': '_path_splitext', 'include': '<filesystem>'},
|
|
208
|
+
'os.path.getsize': {'cpp': 'std::filesystem::file_size', 'include': '<filesystem>'},
|
|
209
|
+
'os.getenv': {'cpp': 'std::getenv', 'include': '<cstdlib>'},
|
|
210
|
+
'os.environ.get': {'cpp': 'std::getenv', 'include': '<cstdlib>'},
|
|
211
|
+
'os.system': {'cpp': 'std::system', 'include': '<cstdlib>'},
|
|
212
|
+
|
|
213
|
+
# sys module
|
|
214
|
+
'sys.exit': {'cpp': 'std::exit', 'include': '<cstdlib>'},
|
|
215
|
+
'sys.argv': {'cpp': '_sys_argv', 'include': None, 'note': 'Requires main() args'},
|
|
216
|
+
'sys.platform': {'cpp': '_sys_platform()', 'include': None},
|
|
217
|
+
|
|
218
|
+
# time module
|
|
219
|
+
'time.sleep': {'cpp': 'std::this_thread::sleep_for(std::chrono::duration<double>({}))', 'include': '<thread>'},
|
|
220
|
+
'time.time': {'cpp': '_time_now()', 'include': '<chrono>'},
|
|
221
|
+
'time.perf_counter': {'cpp': '_time_perf_counter()', 'include': '<chrono>'},
|
|
222
|
+
'time.monotonic': {'cpp': '_time_monotonic()', 'include': '<chrono>'},
|
|
223
|
+
|
|
224
|
+
# threading module
|
|
225
|
+
'threading.Thread': {'cpp': 'std::thread', 'include': '<thread>'},
|
|
226
|
+
'threading.Lock': {'cpp': 'std::mutex', 'include': '<mutex>'},
|
|
227
|
+
'threading.RLock': {'cpp': 'std::recursive_mutex', 'include': '<mutex>'},
|
|
228
|
+
'threading.Event': {'cpp': '_threading_event', 'include': '<condition_variable>'},
|
|
229
|
+
'threading.Semaphore': {'cpp': 'std::counting_semaphore', 'include': '<semaphore>'},
|
|
230
|
+
'threading.Barrier': {'cpp': 'std::barrier', 'include': '<barrier>'},
|
|
231
|
+
'threading.current_thread': {'cpp': 'std::this_thread::get_id', 'include': '<thread>'},
|
|
232
|
+
|
|
233
|
+
# json module
|
|
234
|
+
'json.dumps': {'cpp': '/* TODO: json.dumps - use nlohmann/json */', 'include': None},
|
|
235
|
+
'json.loads': {'cpp': '/* TODO: json.loads - use nlohmann/json */', 'include': None},
|
|
236
|
+
'json.load': {'cpp': '/* TODO: json.load - use nlohmann/json */', 'include': None},
|
|
237
|
+
'json.dump': {'cpp': '/* TODO: json.dump - use nlohmann/json */', 'include': None},
|
|
238
|
+
|
|
239
|
+
# re module (regex)
|
|
240
|
+
're.match': {'cpp': 'std::regex_match', 'include': '<regex>'},
|
|
241
|
+
're.search': {'cpp': 'std::regex_search', 'include': '<regex>'},
|
|
242
|
+
're.sub': {'cpp': 'std::regex_replace', 'include': '<regex>'},
|
|
243
|
+
're.findall': {'cpp': '_regex_findall', 'include': '<regex>'},
|
|
244
|
+
're.compile': {'cpp': 'std::regex', 'include': '<regex>'},
|
|
245
|
+
|
|
246
|
+
# math module
|
|
247
|
+
'math.sqrt': {'cpp': 'std::sqrt', 'include': '<cmath>'},
|
|
248
|
+
'math.pow': {'cpp': 'std::pow', 'include': '<cmath>'},
|
|
249
|
+
'math.sin': {'cpp': 'std::sin', 'include': '<cmath>'},
|
|
250
|
+
'math.cos': {'cpp': 'std::cos', 'include': '<cmath>'},
|
|
251
|
+
'math.tan': {'cpp': 'std::tan', 'include': '<cmath>'},
|
|
252
|
+
'math.log': {'cpp': 'std::log', 'include': '<cmath>'},
|
|
253
|
+
'math.log10': {'cpp': 'std::log10', 'include': '<cmath>'},
|
|
254
|
+
'math.exp': {'cpp': 'std::exp', 'include': '<cmath>'},
|
|
255
|
+
'math.floor': {'cpp': 'std::floor', 'include': '<cmath>'},
|
|
256
|
+
'math.ceil': {'cpp': 'std::ceil', 'include': '<cmath>'},
|
|
257
|
+
'math.fabs': {'cpp': 'std::fabs', 'include': '<cmath>'},
|
|
258
|
+
'math.pi': {'cpp': 'M_PI', 'include': '<cmath>'},
|
|
259
|
+
'math.e': {'cpp': 'M_E', 'include': '<cmath>'},
|
|
260
|
+
|
|
261
|
+
# collections module
|
|
262
|
+
'collections.deque': {'cpp': 'std::deque', 'include': '<deque>'},
|
|
263
|
+
'collections.defaultdict': {'cpp': '/* TODO: defaultdict - use std::map with default */', 'include': '<map>'},
|
|
264
|
+
'collections.Counter': {'cpp': '/* TODO: Counter - use std::map<T, int> */', 'include': '<map>'},
|
|
265
|
+
'collections.OrderedDict': {'cpp': 'std::map', 'include': '<map>'},
|
|
266
|
+
|
|
267
|
+
# functools module
|
|
268
|
+
'functools.partial': {'cpp': 'std::bind', 'include': '<functional>'},
|
|
269
|
+
'functools.reduce': {'cpp': 'std::accumulate', 'include': '<numeric>'},
|
|
270
|
+
|
|
271
|
+
# itertools module
|
|
272
|
+
'itertools.chain': {'cpp': '/* TODO: itertools.chain - manual concatenation */', 'include': None},
|
|
273
|
+
'itertools.zip_longest': {'cpp': '/* TODO: zip_longest - manual implementation */', 'include': None},
|
|
274
|
+
|
|
275
|
+
# pathlib module
|
|
276
|
+
'pathlib.Path': {'cpp': 'std::filesystem::path', 'include': '<filesystem>'},
|
|
277
|
+
'Path': {'cpp': 'std::filesystem::path', 'include': '<filesystem>'},
|
|
278
|
+
|
|
279
|
+
# typing module (type hints - ignore in conversion)
|
|
280
|
+
'typing.List': {'cpp': 'std::vector', 'include': '<vector>'},
|
|
281
|
+
'typing.Dict': {'cpp': 'std::unordered_map', 'include': '<unordered_map>'},
|
|
282
|
+
'typing.Set': {'cpp': 'std::unordered_set', 'include': '<unordered_set>'},
|
|
283
|
+
'typing.Tuple': {'cpp': 'std::tuple', 'include': '<tuple>'},
|
|
284
|
+
'typing.Optional': {'cpp': 'std::optional', 'include': '<optional>'},
|
|
285
|
+
'typing.Union': {'cpp': 'std::variant', 'include': '<variant>'},
|
|
286
|
+
'typing.Callable': {'cpp': 'std::function', 'include': '<functional>'},
|
|
287
|
+
'typing.Any': {'cpp': 'auto', 'include': None},
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dataclass
|
|
292
|
+
class FunctionInfo:
|
|
293
|
+
name: str
|
|
294
|
+
return_type: str
|
|
295
|
+
params: List[Tuple[str, str]]
|
|
296
|
+
body: str
|
|
297
|
+
is_method: bool = False
|
|
298
|
+
is_static: bool = False
|
|
299
|
+
is_const: bool = False
|
|
300
|
+
is_virtual: bool = False
|
|
301
|
+
is_property: bool = False
|
|
302
|
+
decorators: List[str] = field(default_factory=list)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass
|
|
306
|
+
class ClassInfo:
|
|
307
|
+
name: str
|
|
308
|
+
bases: List[str]
|
|
309
|
+
methods: List[FunctionInfo]
|
|
310
|
+
fields: List[Tuple[str, str, Optional[str]]]
|
|
311
|
+
is_struct: bool = False
|
|
312
|
+
constructors: List[FunctionInfo] = field(default_factory=list)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@dataclass
|
|
316
|
+
class StructInfo:
|
|
317
|
+
name: str
|
|
318
|
+
fields: List[Tuple[str, str]]
|
|
319
|
+
|
|
320
|
+
|
|
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
|
+
|
|
368
|
+
def __init__(self):
|
|
369
|
+
self.imports: Set[str] = set()
|
|
370
|
+
self.forward_decls: Set[str] = set()
|
|
371
|
+
self.unconvertible: List[Tuple[str, str, int]] = [] # (item, reason, line)
|
|
372
|
+
self.warnings: List[str] = []
|
|
373
|
+
self.python_imports: Set[str] = set()
|
|
374
|
+
self.seeded_rngs: Dict[str, str] = {} # var_name -> seed expression
|
|
375
|
+
self.var_types: Dict[str, str] = {} # Variable name -> C++ type tracking
|
|
376
|
+
|
|
377
|
+
def convert(self, source: str, module_name: str) -> Tuple[str, str]:
|
|
378
|
+
"""Convert Python source to C++ (.cpp and .h content)."""
|
|
379
|
+
self.imports = set()
|
|
380
|
+
self.forward_decls = set()
|
|
381
|
+
self.unconvertible = []
|
|
382
|
+
self.warnings = []
|
|
383
|
+
self.python_imports = set()
|
|
384
|
+
self.seeded_rngs = {}
|
|
385
|
+
self.var_types = {}
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
tree = ast.parse(source)
|
|
389
|
+
except SyntaxError as e:
|
|
390
|
+
raise ValueError(f"Python syntax error: {e}")
|
|
391
|
+
|
|
392
|
+
# First pass: detect imports and check for unconvertible modules
|
|
393
|
+
self._analyze_imports(tree)
|
|
394
|
+
|
|
395
|
+
classes = []
|
|
396
|
+
functions = []
|
|
397
|
+
global_vars = []
|
|
398
|
+
|
|
399
|
+
for node in ast.iter_child_nodes(tree):
|
|
400
|
+
if isinstance(node, ast.ClassDef):
|
|
401
|
+
classes.append(self._convert_class(node))
|
|
402
|
+
elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
|
|
403
|
+
functions.append(self._convert_function(node))
|
|
404
|
+
elif isinstance(node, ast.Assign):
|
|
405
|
+
for target in node.targets:
|
|
406
|
+
if isinstance(target, ast.Name):
|
|
407
|
+
var_type = self._infer_type(node.value)
|
|
408
|
+
var_value = self._convert_expr(node.value)
|
|
409
|
+
global_vars.append((target.id, var_type, var_value))
|
|
410
|
+
elif isinstance(node, ast.AnnAssign):
|
|
411
|
+
if isinstance(node.target, ast.Name):
|
|
412
|
+
var_type = self._convert_type_annotation(node.annotation)
|
|
413
|
+
var_value = self._convert_expr(node.value) if node.value else None
|
|
414
|
+
global_vars.append((node.target.id, var_type, var_value))
|
|
415
|
+
|
|
416
|
+
header = self._generate_header(module_name, classes, functions, global_vars)
|
|
417
|
+
source_cpp = self._generate_source(module_name, classes, functions, global_vars)
|
|
418
|
+
|
|
419
|
+
return source_cpp, header
|
|
420
|
+
|
|
421
|
+
def _analyze_imports(self, tree):
|
|
422
|
+
"""Analyze imports and detect unconvertible modules."""
|
|
423
|
+
for node in ast.walk(tree):
|
|
424
|
+
if isinstance(node, ast.Import):
|
|
425
|
+
for alias in node.names:
|
|
426
|
+
mod_name = alias.name.split('.')[0]
|
|
427
|
+
self.python_imports.add(mod_name)
|
|
428
|
+
if mod_name in UNCONVERTIBLE_MODULES:
|
|
429
|
+
reason = UNCONVERTIBLE_MODULES[mod_name]
|
|
430
|
+
self.unconvertible.append((mod_name, reason, node.lineno))
|
|
431
|
+
elif isinstance(node, ast.ImportFrom):
|
|
432
|
+
if node.module:
|
|
433
|
+
mod_name = node.module.split('.')[0]
|
|
434
|
+
self.python_imports.add(mod_name)
|
|
435
|
+
if mod_name in UNCONVERTIBLE_MODULES:
|
|
436
|
+
reason = UNCONVERTIBLE_MODULES[mod_name]
|
|
437
|
+
self.unconvertible.append((mod_name, reason, node.lineno))
|
|
438
|
+
|
|
439
|
+
def has_unconvertible_code(self) -> bool:
|
|
440
|
+
"""Check if there is any unconvertible code."""
|
|
441
|
+
return len(self.unconvertible) > 0
|
|
442
|
+
|
|
443
|
+
def get_unconvertible_summary(self) -> str:
|
|
444
|
+
"""Get a summary of unconvertible code."""
|
|
445
|
+
if not self.unconvertible:
|
|
446
|
+
return ""
|
|
447
|
+
lines = ["UNCONVERTIBLE CODE DETECTED:"]
|
|
448
|
+
for item, reason, line in self.unconvertible:
|
|
449
|
+
lines.append(f" Line {line}: {item} - {reason}")
|
|
450
|
+
return '\n'.join(lines)
|
|
451
|
+
|
|
452
|
+
def _convert_class(self, node: ast.ClassDef) -> ClassInfo:
|
|
453
|
+
bases = []
|
|
454
|
+
for base in node.bases:
|
|
455
|
+
if isinstance(base, ast.Name):
|
|
456
|
+
bases.append(base.id)
|
|
457
|
+
elif isinstance(base, ast.Attribute):
|
|
458
|
+
bases.append(self._get_attr_name(base))
|
|
459
|
+
|
|
460
|
+
methods = []
|
|
461
|
+
constructors = []
|
|
462
|
+
fields = []
|
|
463
|
+
|
|
464
|
+
for item in node.body:
|
|
465
|
+
if isinstance(item, ast.FunctionDef) or isinstance(item, ast.AsyncFunctionDef):
|
|
466
|
+
func = self._convert_function(item, is_method=True)
|
|
467
|
+
if func.name == '__init__':
|
|
468
|
+
func.name = node.name
|
|
469
|
+
constructors.append(func)
|
|
470
|
+
for stmt in item.body:
|
|
471
|
+
if isinstance(stmt, ast.Assign):
|
|
472
|
+
for target in stmt.targets:
|
|
473
|
+
if isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name):
|
|
474
|
+
if target.value.id == 'self':
|
|
475
|
+
field_type = self._infer_type(stmt.value)
|
|
476
|
+
field_val = self._convert_expr(stmt.value)
|
|
477
|
+
fields.append((target.attr, field_type, field_val))
|
|
478
|
+
elif isinstance(stmt, ast.AnnAssign):
|
|
479
|
+
if isinstance(stmt.target, ast.Attribute) and isinstance(stmt.target.value, ast.Name):
|
|
480
|
+
if stmt.target.value.id == 'self':
|
|
481
|
+
field_type = self._convert_type_annotation(stmt.annotation)
|
|
482
|
+
field_val = self._convert_expr(stmt.value) if stmt.value else None
|
|
483
|
+
fields.append((stmt.target.attr, field_type, field_val))
|
|
484
|
+
elif func.name.startswith('__') and func.name.endswith('__'):
|
|
485
|
+
continue
|
|
486
|
+
else:
|
|
487
|
+
methods.append(func)
|
|
488
|
+
elif isinstance(item, ast.AnnAssign):
|
|
489
|
+
if isinstance(item.target, ast.Name):
|
|
490
|
+
field_type = self._convert_type_annotation(item.annotation)
|
|
491
|
+
field_val = self._convert_expr(item.value) if item.value else None
|
|
492
|
+
fields.append((item.target.id, field_type, field_val))
|
|
493
|
+
|
|
494
|
+
return ClassInfo(
|
|
495
|
+
name=node.name,
|
|
496
|
+
bases=bases,
|
|
497
|
+
methods=methods,
|
|
498
|
+
fields=fields,
|
|
499
|
+
constructors=constructors
|
|
500
|
+
)
|
|
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
|
+
|
|
574
|
+
def _convert_function(self, node, is_method: bool = False) -> FunctionInfo:
|
|
575
|
+
decorators = []
|
|
576
|
+
is_static = False
|
|
577
|
+
is_property = False
|
|
578
|
+
|
|
579
|
+
for dec in node.decorator_list:
|
|
580
|
+
if isinstance(dec, ast.Name):
|
|
581
|
+
if dec.id == 'staticmethod':
|
|
582
|
+
is_static = True
|
|
583
|
+
elif dec.id == 'classmethod':
|
|
584
|
+
is_static = True
|
|
585
|
+
elif dec.id == 'property':
|
|
586
|
+
is_property = True
|
|
587
|
+
decorators.append(dec.id)
|
|
588
|
+
|
|
589
|
+
params = []
|
|
590
|
+
for arg in node.args.args:
|
|
591
|
+
if arg.arg == 'self' or arg.arg == 'cls':
|
|
592
|
+
continue
|
|
593
|
+
if arg.annotation:
|
|
594
|
+
param_type = self._convert_type_annotation(arg.annotation)
|
|
595
|
+
else:
|
|
596
|
+
# Use smart type inference
|
|
597
|
+
param_type = self._infer_param_type(arg.arg, node)
|
|
598
|
+
params.append((arg.arg, param_type))
|
|
599
|
+
# Track parameter types for use in body conversion
|
|
600
|
+
self.var_types[arg.arg] = param_type
|
|
601
|
+
|
|
602
|
+
if node.returns:
|
|
603
|
+
return_type = self._convert_type_annotation(node.returns)
|
|
604
|
+
else:
|
|
605
|
+
return_type = self._infer_return_type(node)
|
|
606
|
+
|
|
607
|
+
body = self._convert_body(node.body)
|
|
608
|
+
|
|
609
|
+
return FunctionInfo(
|
|
610
|
+
name=node.name,
|
|
611
|
+
return_type=return_type,
|
|
612
|
+
params=params,
|
|
613
|
+
body=body,
|
|
614
|
+
is_method=is_method,
|
|
615
|
+
is_static=is_static,
|
|
616
|
+
is_property=is_property,
|
|
617
|
+
decorators=decorators
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
def _convert_type_annotation(self, node) -> str:
|
|
621
|
+
if node is None:
|
|
622
|
+
return 'auto'
|
|
623
|
+
|
|
624
|
+
if isinstance(node, ast.Name):
|
|
625
|
+
py_type = node.id
|
|
626
|
+
return PY_TO_CPP_TYPES.get(py_type, py_type)
|
|
627
|
+
|
|
628
|
+
elif isinstance(node, ast.Subscript):
|
|
629
|
+
if isinstance(node.value, ast.Name):
|
|
630
|
+
container = node.value.id
|
|
631
|
+
cpp_container = PY_TO_CPP_TYPES.get(container, container)
|
|
632
|
+
|
|
633
|
+
if isinstance(node.slice, ast.Tuple):
|
|
634
|
+
inner_types = [self._convert_type_annotation(elt) for elt in node.slice.elts]
|
|
635
|
+
return f"{cpp_container}<{', '.join(inner_types)}>"
|
|
636
|
+
else:
|
|
637
|
+
inner_type = self._convert_type_annotation(node.slice)
|
|
638
|
+
return f"{cpp_container}<{inner_type}>"
|
|
639
|
+
|
|
640
|
+
elif isinstance(node, ast.Constant):
|
|
641
|
+
if node.value is None:
|
|
642
|
+
return 'void'
|
|
643
|
+
return str(type(node.value).__name__)
|
|
644
|
+
|
|
645
|
+
elif isinstance(node, ast.Attribute):
|
|
646
|
+
return self._get_attr_name(node)
|
|
647
|
+
|
|
648
|
+
elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
649
|
+
left = self._convert_type_annotation(node.left)
|
|
650
|
+
right = self._convert_type_annotation(node.right)
|
|
651
|
+
return f"std::variant<{left}, {right}>"
|
|
652
|
+
|
|
653
|
+
return 'auto'
|
|
654
|
+
|
|
655
|
+
def _infer_type(self, node) -> str:
|
|
656
|
+
"""Infer C++ type from AST node with robust empty container handling."""
|
|
657
|
+
if node is None:
|
|
658
|
+
return 'auto'
|
|
659
|
+
|
|
660
|
+
try:
|
|
661
|
+
if isinstance(node, ast.Constant):
|
|
662
|
+
val = node.value
|
|
663
|
+
if isinstance(val, bool):
|
|
664
|
+
return 'bool'
|
|
665
|
+
elif isinstance(val, int):
|
|
666
|
+
return 'int'
|
|
667
|
+
elif isinstance(val, float):
|
|
668
|
+
return 'double'
|
|
669
|
+
elif isinstance(val, str):
|
|
670
|
+
return 'std::string'
|
|
671
|
+
elif isinstance(val, bytes):
|
|
672
|
+
return 'std::vector<uint8_t>'
|
|
673
|
+
elif val is None:
|
|
674
|
+
return 'void'
|
|
675
|
+
|
|
676
|
+
elif isinstance(node, ast.List):
|
|
677
|
+
first_elt = _safe_get(node.elts, 0)
|
|
678
|
+
if first_elt is not None:
|
|
679
|
+
inner = self._infer_type(first_elt)
|
|
680
|
+
return f'std::vector<{inner}>'
|
|
681
|
+
return 'std::vector<int>'
|
|
682
|
+
|
|
683
|
+
elif isinstance(node, ast.Dict):
|
|
684
|
+
first_key = _safe_get(node.keys, 0)
|
|
685
|
+
first_val = _safe_get(node.values, 0)
|
|
686
|
+
if first_key is not None and first_val is not None:
|
|
687
|
+
key_type = self._infer_type(first_key)
|
|
688
|
+
val_type = self._infer_type(first_val)
|
|
689
|
+
return f'std::unordered_map<{key_type}, {val_type}>'
|
|
690
|
+
return 'std::unordered_map<std::string, int>'
|
|
691
|
+
|
|
692
|
+
elif isinstance(node, ast.Set):
|
|
693
|
+
first_elt = _safe_get(node.elts, 0)
|
|
694
|
+
if first_elt is not None:
|
|
695
|
+
inner = self._infer_type(first_elt)
|
|
696
|
+
return f'std::unordered_set<{inner}>'
|
|
697
|
+
return 'std::unordered_set<int>'
|
|
698
|
+
|
|
699
|
+
elif isinstance(node, ast.Tuple):
|
|
700
|
+
if node.elts:
|
|
701
|
+
types = [self._infer_type(elt) for elt in node.elts]
|
|
702
|
+
return f'std::tuple<{", ".join(types)}>'
|
|
703
|
+
return 'std::tuple<>'
|
|
704
|
+
|
|
705
|
+
elif isinstance(node, ast.Call):
|
|
706
|
+
if isinstance(node.func, ast.Name):
|
|
707
|
+
func_name = node.func.id
|
|
708
|
+
if func_name == 'bytearray':
|
|
709
|
+
return 'std::vector<uint8_t>'
|
|
710
|
+
if func_name == 'len':
|
|
711
|
+
return 'size_t'
|
|
712
|
+
if func_name == 'range':
|
|
713
|
+
return 'int' # Range iteration variable
|
|
714
|
+
if func_name == 'enumerate':
|
|
715
|
+
return 'auto' # Tuple of (index, value)
|
|
716
|
+
return PY_TO_CPP_TYPES.get(func_name, 'auto')
|
|
717
|
+
|
|
718
|
+
elif isinstance(node, ast.BinOp):
|
|
719
|
+
left_type = self._infer_type(node.left)
|
|
720
|
+
right_type = self._infer_type(node.right)
|
|
721
|
+
# More precise type checking
|
|
722
|
+
if left_type == 'double' or right_type == 'double':
|
|
723
|
+
return 'double'
|
|
724
|
+
if left_type == 'std::string' or right_type == 'std::string':
|
|
725
|
+
return 'std::string'
|
|
726
|
+
return left_type if left_type != 'auto' else right_type
|
|
727
|
+
|
|
728
|
+
elif isinstance(node, ast.Compare):
|
|
729
|
+
return 'bool'
|
|
730
|
+
|
|
731
|
+
elif isinstance(node, ast.BoolOp):
|
|
732
|
+
return 'bool'
|
|
733
|
+
|
|
734
|
+
elif isinstance(node, ast.UnaryOp):
|
|
735
|
+
if isinstance(node.op, ast.Not):
|
|
736
|
+
return 'bool'
|
|
737
|
+
return self._infer_type(node.operand)
|
|
738
|
+
|
|
739
|
+
elif isinstance(node, ast.Name):
|
|
740
|
+
# Check for common type names
|
|
741
|
+
name = node.id
|
|
742
|
+
if name in PY_TO_CPP_TYPES:
|
|
743
|
+
return PY_TO_CPP_TYPES[name]
|
|
744
|
+
return 'auto'
|
|
745
|
+
|
|
746
|
+
elif isinstance(node, ast.Attribute):
|
|
747
|
+
return 'auto'
|
|
748
|
+
|
|
749
|
+
elif isinstance(node, ast.Subscript):
|
|
750
|
+
return 'auto'
|
|
751
|
+
|
|
752
|
+
elif isinstance(node, ast.IfExp):
|
|
753
|
+
return self._infer_type(node.body)
|
|
754
|
+
|
|
755
|
+
elif isinstance(node, ast.ListComp):
|
|
756
|
+
return f'std::vector<{self._infer_type(node.elt)}>'
|
|
757
|
+
|
|
758
|
+
elif isinstance(node, ast.DictComp):
|
|
759
|
+
return f'std::unordered_map<{self._infer_type(node.key)}, {self._infer_type(node.value)}>'
|
|
760
|
+
|
|
761
|
+
elif isinstance(node, ast.SetComp):
|
|
762
|
+
return f'std::unordered_set<{self._infer_type(node.elt)}>'
|
|
763
|
+
|
|
764
|
+
except Exception:
|
|
765
|
+
pass # Fall through to 'auto'
|
|
766
|
+
|
|
767
|
+
return 'auto'
|
|
768
|
+
|
|
769
|
+
def _infer_return_type(self, node) -> str:
|
|
770
|
+
for stmt in ast.walk(node):
|
|
771
|
+
if isinstance(stmt, ast.Return) and stmt.value:
|
|
772
|
+
return self._infer_type(stmt.value)
|
|
773
|
+
return 'void'
|
|
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
|
+
|
|
851
|
+
def _convert_body(self, stmts: List, indent: int = 1) -> str:
|
|
852
|
+
lines = []
|
|
853
|
+
ind = ' ' * indent
|
|
854
|
+
|
|
855
|
+
for stmt in stmts:
|
|
856
|
+
lines.extend(self._convert_stmt(stmt, indent))
|
|
857
|
+
|
|
858
|
+
return '\n'.join(lines)
|
|
859
|
+
|
|
860
|
+
def _convert_stmt(self, stmt, indent: int) -> List[str]:
|
|
861
|
+
ind = ' ' * indent
|
|
862
|
+
lines = []
|
|
863
|
+
|
|
864
|
+
if isinstance(stmt, ast.Return):
|
|
865
|
+
if stmt.value:
|
|
866
|
+
val = self._convert_expr(stmt.value)
|
|
867
|
+
lines.append(f'{ind}return {val};')
|
|
868
|
+
else:
|
|
869
|
+
lines.append(f'{ind}return;')
|
|
870
|
+
|
|
871
|
+
elif isinstance(stmt, ast.Assign):
|
|
872
|
+
for target in stmt.targets:
|
|
873
|
+
# Check for tuple unpacking: a, b = func() or a, b = (1, 2)
|
|
874
|
+
if isinstance(target, ast.Tuple):
|
|
875
|
+
value_str = self._convert_expr(stmt.value)
|
|
876
|
+
var_names = [self._convert_expr(elt) for elt in target.elts]
|
|
877
|
+
# Use C++17 structured bindings
|
|
878
|
+
lines.append(f'{ind}auto [{", ".join(var_names)}] = {value_str};')
|
|
879
|
+
continue
|
|
880
|
+
|
|
881
|
+
target_str = self._convert_expr(target)
|
|
882
|
+
# Check if this is a seeded random assignment: rng = random.Random(seed)
|
|
883
|
+
if (isinstance(stmt.value, ast.Call) and
|
|
884
|
+
isinstance(stmt.value.func, ast.Attribute) and
|
|
885
|
+
isinstance(stmt.value.func.value, ast.Name) and
|
|
886
|
+
stmt.value.func.value.id == 'random' and
|
|
887
|
+
stmt.value.func.attr == 'Random' and
|
|
888
|
+
isinstance(target, ast.Name)):
|
|
889
|
+
# Track this variable as a seeded RNG
|
|
890
|
+
seed_expr = self._convert_expr(stmt.value.args[0]) if stmt.value.args else '0'
|
|
891
|
+
self.seeded_rngs[target.id] = seed_expr
|
|
892
|
+
self.imports.add('<random>')
|
|
893
|
+
lines.append(f'{ind}std::mt19937 {target_str}({seed_expr});')
|
|
894
|
+
else:
|
|
895
|
+
value_str = self._convert_expr(stmt.value)
|
|
896
|
+
var_type = self._infer_type(stmt.value)
|
|
897
|
+
if isinstance(target, ast.Name):
|
|
898
|
+
# Track this variable's type
|
|
899
|
+
self.var_types[target.id] = var_type
|
|
900
|
+
lines.append(f'{ind}{var_type} {target_str} = {value_str};')
|
|
901
|
+
else:
|
|
902
|
+
lines.append(f'{ind}{target_str} = {value_str};')
|
|
903
|
+
|
|
904
|
+
elif isinstance(stmt, ast.AnnAssign):
|
|
905
|
+
target_str = self._convert_expr(stmt.target)
|
|
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
|
|
910
|
+
if stmt.value:
|
|
911
|
+
value_str = self._convert_expr(stmt.value)
|
|
912
|
+
lines.append(f'{ind}{var_type} {target_str} = {value_str};')
|
|
913
|
+
else:
|
|
914
|
+
lines.append(f'{ind}{var_type} {target_str};')
|
|
915
|
+
|
|
916
|
+
elif isinstance(stmt, ast.AugAssign):
|
|
917
|
+
target_str = self._convert_expr(stmt.target)
|
|
918
|
+
value_str = self._convert_expr(stmt.value)
|
|
919
|
+
op = self._convert_binop(stmt.op)
|
|
920
|
+
lines.append(f'{ind}{target_str} {op}= {value_str};')
|
|
921
|
+
|
|
922
|
+
elif isinstance(stmt, ast.Expr):
|
|
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};')
|
|
934
|
+
|
|
935
|
+
elif isinstance(stmt, ast.If):
|
|
936
|
+
test = self._convert_expr(stmt.test)
|
|
937
|
+
lines.append(f'{ind}if ({test}) {{')
|
|
938
|
+
lines.extend(self._convert_stmt_list(stmt.body, indent + 1))
|
|
939
|
+
if stmt.orelse:
|
|
940
|
+
if len(stmt.orelse) == 1 and isinstance(stmt.orelse[0], ast.If):
|
|
941
|
+
lines.append(f'{ind}}} else')
|
|
942
|
+
lines.extend(self._convert_stmt(stmt.orelse[0], indent))
|
|
943
|
+
else:
|
|
944
|
+
lines.append(f'{ind}}} else {{')
|
|
945
|
+
lines.extend(self._convert_stmt_list(stmt.orelse, indent + 1))
|
|
946
|
+
lines.append(f'{ind}}}')
|
|
947
|
+
else:
|
|
948
|
+
lines.append(f'{ind}}}')
|
|
949
|
+
|
|
950
|
+
elif isinstance(stmt, ast.For):
|
|
951
|
+
target = self._convert_expr(stmt.target)
|
|
952
|
+
iter_expr = stmt.iter
|
|
953
|
+
|
|
954
|
+
if isinstance(iter_expr, ast.Call) and isinstance(iter_expr.func, ast.Name):
|
|
955
|
+
if iter_expr.func.id == 'range':
|
|
956
|
+
args = iter_expr.args
|
|
957
|
+
if len(args) == 1:
|
|
958
|
+
end = self._convert_expr(args[0])
|
|
959
|
+
lines.append(f'{ind}for (size_t {target} = 0; {target} < static_cast<size_t>({end}); ++{target}) {{')
|
|
960
|
+
elif len(args) == 2:
|
|
961
|
+
start = self._convert_expr(args[0])
|
|
962
|
+
end = self._convert_expr(args[1])
|
|
963
|
+
lines.append(f'{ind}for (size_t {target} = {start}; {target} < static_cast<size_t>({end}); ++{target}) {{')
|
|
964
|
+
elif len(args) == 3:
|
|
965
|
+
start = self._convert_expr(args[0])
|
|
966
|
+
end = self._convert_expr(args[1])
|
|
967
|
+
step = self._convert_expr(args[2])
|
|
968
|
+
lines.append(f'{ind}for (size_t {target} = {start}; {target} < static_cast<size_t>({end}); {target} += {step}) {{')
|
|
969
|
+
elif iter_expr.func.id == 'enumerate':
|
|
970
|
+
# Handle enumerate(items, start=N)
|
|
971
|
+
args = iter_expr.args
|
|
972
|
+
iterable_node = args[0] if args else None
|
|
973
|
+
iterable = self._convert_expr(args[0]) if args else 'items'
|
|
974
|
+
start_idx = '0'
|
|
975
|
+
# Check for start keyword arg
|
|
976
|
+
for kw in iter_expr.keywords:
|
|
977
|
+
if kw.arg == 'start':
|
|
978
|
+
start_idx = self._convert_expr(kw.value)
|
|
979
|
+
if len(args) >= 2:
|
|
980
|
+
start_idx = self._convert_expr(args[1])
|
|
981
|
+
# Parse target - it's usually a Tuple like (index, item)
|
|
982
|
+
if isinstance(stmt.target, ast.Tuple) and len(stmt.target.elts) == 2:
|
|
983
|
+
idx_name = self._convert_expr(stmt.target.elts[0])
|
|
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
|
+
|
|
1011
|
+
lines.append(f'{ind}size_t {idx_name} = {start_idx};')
|
|
1012
|
+
lines.append(f'{ind}for (auto& {item_name} : {iterable}) {{')
|
|
1013
|
+
# Add index increment at end of loop body
|
|
1014
|
+
body_lines = self._convert_stmt_list(stmt.body, indent + 1)
|
|
1015
|
+
body_lines.append(f'{ind} ++{idx_name};')
|
|
1016
|
+
lines.extend(body_lines)
|
|
1017
|
+
lines.append(f'{ind}}}')
|
|
1018
|
+
return lines
|
|
1019
|
+
else:
|
|
1020
|
+
# Fallback: just iterate
|
|
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)
|
|
1025
|
+
lines.append(f'{ind}for (auto& {target} : {iter_str}) {{')
|
|
1026
|
+
else:
|
|
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)
|
|
1031
|
+
lines.append(f'{ind}for (auto& {target} : {iter_str}) {{')
|
|
1032
|
+
else:
|
|
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)
|
|
1037
|
+
lines.append(f'{ind}for (auto& {target} : {iter_str}) {{')
|
|
1038
|
+
|
|
1039
|
+
lines.extend(self._convert_stmt_list(stmt.body, indent + 1))
|
|
1040
|
+
lines.append(f'{ind}}}')
|
|
1041
|
+
|
|
1042
|
+
elif isinstance(stmt, ast.While):
|
|
1043
|
+
test = self._convert_expr(stmt.test)
|
|
1044
|
+
lines.append(f'{ind}while ({test}) {{')
|
|
1045
|
+
lines.extend(self._convert_stmt_list(stmt.body, indent + 1))
|
|
1046
|
+
lines.append(f'{ind}}}')
|
|
1047
|
+
|
|
1048
|
+
elif isinstance(stmt, ast.Break):
|
|
1049
|
+
lines.append(f'{ind}break;')
|
|
1050
|
+
|
|
1051
|
+
elif isinstance(stmt, ast.Continue):
|
|
1052
|
+
lines.append(f'{ind}continue;')
|
|
1053
|
+
|
|
1054
|
+
elif isinstance(stmt, ast.Pass):
|
|
1055
|
+
pass
|
|
1056
|
+
|
|
1057
|
+
elif isinstance(stmt, ast.Try):
|
|
1058
|
+
lines.append(f'{ind}try {{')
|
|
1059
|
+
lines.extend(self._convert_stmt_list(stmt.body, indent + 1))
|
|
1060
|
+
lines.append(f'{ind}}}')
|
|
1061
|
+
for handler in stmt.handlers:
|
|
1062
|
+
exc_type = 'std::exception'
|
|
1063
|
+
exc_name = 'e'
|
|
1064
|
+
if handler.type:
|
|
1065
|
+
exc_type = self._convert_expr(handler.type)
|
|
1066
|
+
if handler.name:
|
|
1067
|
+
exc_name = handler.name
|
|
1068
|
+
lines.append(f'{ind}catch (const {exc_type}& {exc_name}) {{')
|
|
1069
|
+
lines.extend(self._convert_stmt_list(handler.body, indent + 1))
|
|
1070
|
+
lines.append(f'{ind}}}')
|
|
1071
|
+
|
|
1072
|
+
elif isinstance(stmt, ast.Raise):
|
|
1073
|
+
if stmt.exc:
|
|
1074
|
+
exc = self._convert_exception(stmt.exc)
|
|
1075
|
+
lines.append(f'{ind}throw std::runtime_error({exc});')
|
|
1076
|
+
else:
|
|
1077
|
+
lines.append(f'{ind}throw;')
|
|
1078
|
+
|
|
1079
|
+
elif isinstance(stmt, ast.With):
|
|
1080
|
+
for item in stmt.items:
|
|
1081
|
+
ctx = self._convert_expr(item.context_expr)
|
|
1082
|
+
if item.optional_vars:
|
|
1083
|
+
var = self._convert_expr(item.optional_vars)
|
|
1084
|
+
lines.append(f'{ind}auto {var} = {ctx};')
|
|
1085
|
+
else:
|
|
1086
|
+
lines.append(f'{ind}{ctx};')
|
|
1087
|
+
lines.extend(self._convert_stmt_list(stmt.body, indent))
|
|
1088
|
+
|
|
1089
|
+
elif isinstance(stmt, ast.FunctionDef):
|
|
1090
|
+
func = self._convert_function(stmt)
|
|
1091
|
+
lines.append(f'{ind}auto {func.name} = []({self._format_params(func.params)}) -> {func.return_type} {{')
|
|
1092
|
+
lines.append(func.body)
|
|
1093
|
+
lines.append(f'{ind}}};')
|
|
1094
|
+
|
|
1095
|
+
return lines
|
|
1096
|
+
|
|
1097
|
+
def _convert_exception(self, node) -> str:
|
|
1098
|
+
"""Convert a Python exception to a C++ exception string."""
|
|
1099
|
+
if isinstance(node, ast.Call):
|
|
1100
|
+
if isinstance(node.func, ast.Name):
|
|
1101
|
+
if node.args:
|
|
1102
|
+
msg = self._convert_expr(node.args[0])
|
|
1103
|
+
return msg
|
|
1104
|
+
return '"Unknown error"'
|
|
1105
|
+
|
|
1106
|
+
def _convert_stmt_list(self, stmts: List, indent: int) -> List[str]:
|
|
1107
|
+
lines = []
|
|
1108
|
+
for stmt in stmts:
|
|
1109
|
+
lines.extend(self._convert_stmt(stmt, indent))
|
|
1110
|
+
return lines
|
|
1111
|
+
|
|
1112
|
+
def _convert_expr(self, node) -> str:
|
|
1113
|
+
"""Convert AST expression to C++ with comprehensive error handling."""
|
|
1114
|
+
if node is None:
|
|
1115
|
+
return ''
|
|
1116
|
+
|
|
1117
|
+
try:
|
|
1118
|
+
return self._convert_expr_impl(node)
|
|
1119
|
+
except Exception as e:
|
|
1120
|
+
line_info = getattr(node, 'lineno', '?')
|
|
1121
|
+
self.warnings.append(f"Expression conversion failed at line {line_info}: {type(e).__name__}: {e}")
|
|
1122
|
+
return f'/* ERROR at line {line_info}: {type(e).__name__} */'
|
|
1123
|
+
|
|
1124
|
+
def _convert_expr_impl(self, node) -> str:
|
|
1125
|
+
"""Internal implementation of expression conversion."""
|
|
1126
|
+
if isinstance(node, ast.Constant):
|
|
1127
|
+
val = node.value
|
|
1128
|
+
if isinstance(val, str):
|
|
1129
|
+
# Complete string escaping for C++
|
|
1130
|
+
escaped = val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t').replace('\0', '\\0').replace('\f', '\\f').replace('\b', '\\b').replace('\a', '\\a').replace('\v', '\\v')
|
|
1131
|
+
return f'"{escaped}"'
|
|
1132
|
+
elif isinstance(val, bool):
|
|
1133
|
+
return 'true' if val else 'false'
|
|
1134
|
+
elif val is None:
|
|
1135
|
+
return 'nullptr'
|
|
1136
|
+
else:
|
|
1137
|
+
return str(val)
|
|
1138
|
+
|
|
1139
|
+
elif isinstance(node, ast.Name):
|
|
1140
|
+
name = node.id
|
|
1141
|
+
if name == 'True':
|
|
1142
|
+
return 'true'
|
|
1143
|
+
elif name == 'False':
|
|
1144
|
+
return 'false'
|
|
1145
|
+
elif name == 'None':
|
|
1146
|
+
return 'nullptr'
|
|
1147
|
+
return name
|
|
1148
|
+
|
|
1149
|
+
elif isinstance(node, ast.Attribute):
|
|
1150
|
+
value = self._convert_expr(node.value)
|
|
1151
|
+
if value == 'self':
|
|
1152
|
+
return f'this->{node.attr}'
|
|
1153
|
+
return f'{value}.{node.attr}'
|
|
1154
|
+
|
|
1155
|
+
elif isinstance(node, ast.Subscript):
|
|
1156
|
+
# Check if this is random.choices(...)[0] - skip the subscript since we return single element
|
|
1157
|
+
if (isinstance(node.value, ast.Call) and
|
|
1158
|
+
isinstance(node.value.func, ast.Attribute) and
|
|
1159
|
+
isinstance(node.value.func.value, ast.Name) and
|
|
1160
|
+
node.value.func.value.id == 'random' and
|
|
1161
|
+
node.value.func.attr == 'choices'):
|
|
1162
|
+
# Check if subscript is [0] and k=1
|
|
1163
|
+
if isinstance(node.slice, ast.Constant) and node.slice.value == 0:
|
|
1164
|
+
# Just return the weighted choice call without subscript
|
|
1165
|
+
return self._convert_expr(node.value)
|
|
1166
|
+
|
|
1167
|
+
value = self._convert_expr(node.value)
|
|
1168
|
+
if isinstance(node.slice, ast.Slice):
|
|
1169
|
+
lower = self._convert_expr(node.slice.lower) if node.slice.lower else '0'
|
|
1170
|
+
upper = self._convert_expr(node.slice.upper) if node.slice.upper else f'{value}.size()'
|
|
1171
|
+
return f'std::vector<uint8_t>({value}.begin() + {lower}, {value}.begin() + {upper})'
|
|
1172
|
+
else:
|
|
1173
|
+
slice_val = self._convert_expr(node.slice)
|
|
1174
|
+
return f'{value}[{slice_val}]'
|
|
1175
|
+
|
|
1176
|
+
elif isinstance(node, ast.Call):
|
|
1177
|
+
# Handle chained method calls on random.Random(seed)
|
|
1178
|
+
if (isinstance(node.func, ast.Attribute) and
|
|
1179
|
+
isinstance(node.func.value, ast.Call) and
|
|
1180
|
+
isinstance(node.func.value.func, ast.Attribute)):
|
|
1181
|
+
inner_func = node.func.value.func
|
|
1182
|
+
if hasattr(inner_func, 'value') and hasattr(inner_func.value, 'id'):
|
|
1183
|
+
if inner_func.value.id == 'random' and inner_func.attr == 'Random':
|
|
1184
|
+
seed_arg = self._convert_expr(node.func.value.args[0]) if node.func.value.args else '0'
|
|
1185
|
+
method = node.func.attr
|
|
1186
|
+
args = [self._convert_expr(arg) for arg in node.args]
|
|
1187
|
+
self.imports.add('<random>')
|
|
1188
|
+
if method == 'randint':
|
|
1189
|
+
# Generate inline seeded randint
|
|
1190
|
+
return f'[&](){{ std::mt19937 rng({seed_arg}); std::uniform_int_distribution<int> dist({_safe_arg(args, 0)}, {_safe_arg(args, 1)}); return dist(rng); }}()'
|
|
1191
|
+
elif method == 'uniform':
|
|
1192
|
+
return f'[&](){{ std::mt19937 rng({seed_arg}); std::uniform_real_distribution<double> dist({_safe_arg(args, 0, "0.0")}, {_safe_arg(args, 1, "1.0")}); return dist(rng); }}()'
|
|
1193
|
+
elif method == 'choice':
|
|
1194
|
+
arg0 = _safe_arg(args, 0, 'std::vector<int>{}')
|
|
1195
|
+
return f'[&](){{ std::mt19937 rng({seed_arg}); std::uniform_int_distribution<size_t> dist(0, {arg0}.size()-1); return {arg0}[dist(rng)]; }}()'
|
|
1196
|
+
|
|
1197
|
+
# Handle method calls on seeded RNG variables: rng.randint(start, end)
|
|
1198
|
+
if (isinstance(node.func, ast.Attribute) and
|
|
1199
|
+
isinstance(node.func.value, ast.Name) and
|
|
1200
|
+
node.func.value.id in self.seeded_rngs):
|
|
1201
|
+
rng_var = node.func.value.id
|
|
1202
|
+
method = node.func.attr
|
|
1203
|
+
args = [self._convert_expr(arg) for arg in node.args]
|
|
1204
|
+
self.imports.add('<random>')
|
|
1205
|
+
if method == 'randint':
|
|
1206
|
+
return f'[&](){{ std::uniform_int_distribution<int> dist({_safe_arg(args, 0)}, {_safe_arg(args, 1)}); return dist({rng_var}); }}()'
|
|
1207
|
+
elif method == 'uniform':
|
|
1208
|
+
return f'[&](){{ std::uniform_real_distribution<double> dist({_safe_arg(args, 0, "0.0")}, {_safe_arg(args, 1, "1.0")}); return dist({rng_var}); }}()'
|
|
1209
|
+
elif method == 'choice':
|
|
1210
|
+
arg0 = _safe_arg(args, 0, 'std::vector<int>{}')
|
|
1211
|
+
return f'[&](){{ std::uniform_int_distribution<size_t> dist(0, {arg0}.size()-1); return {arg0}[dist({rng_var})]; }}()'
|
|
1212
|
+
elif method == 'random':
|
|
1213
|
+
return f'[&](){{ std::uniform_real_distribution<double> dist(0.0, 1.0); return dist({rng_var}); }}()'
|
|
1214
|
+
|
|
1215
|
+
func = self._convert_expr(node.func)
|
|
1216
|
+
args = [self._convert_expr(arg) for arg in node.args]
|
|
1217
|
+
|
|
1218
|
+
# Check MODULE_CONVERSIONS first for standard library functions
|
|
1219
|
+
if func in MODULE_CONVERSIONS:
|
|
1220
|
+
conv = MODULE_CONVERSIONS[func]
|
|
1221
|
+
cpp = conv['cpp']
|
|
1222
|
+
if conv.get('include'):
|
|
1223
|
+
self.imports.add(conv['include'])
|
|
1224
|
+
# Check if it's a pattern with {} placeholder
|
|
1225
|
+
if '{}' in cpp:
|
|
1226
|
+
return cpp.format(args[0] if args else '')
|
|
1227
|
+
elif '(' in cpp:
|
|
1228
|
+
# Already a function call
|
|
1229
|
+
return cpp
|
|
1230
|
+
else:
|
|
1231
|
+
# Simple function name replacement
|
|
1232
|
+
return f'{cpp}({", ".join(args)})'
|
|
1233
|
+
|
|
1234
|
+
# Check for unconvertible module calls
|
|
1235
|
+
for mod_name in UNCONVERTIBLE_MODULES:
|
|
1236
|
+
if func.startswith(f'{mod_name}.'):
|
|
1237
|
+
reason = UNCONVERTIBLE_MODULES[mod_name]
|
|
1238
|
+
self.unconvertible.append((func, reason, getattr(node, 'lineno', 0)))
|
|
1239
|
+
return f'/* UNCONVERTIBLE: {func}({", ".join(args)}) - {reason} */'
|
|
1240
|
+
|
|
1241
|
+
# Builtin functions (with safe argument access)
|
|
1242
|
+
if func == 'len':
|
|
1243
|
+
return f'{_safe_arg(args, 0, "container")}.size()'
|
|
1244
|
+
elif func == 'bytearray':
|
|
1245
|
+
if args:
|
|
1246
|
+
return f'std::vector<uint8_t>({_safe_arg(args, 0)}, 0)'
|
|
1247
|
+
return 'std::vector<uint8_t>()'
|
|
1248
|
+
elif func == 'str':
|
|
1249
|
+
return f'std::to_string({_safe_arg(args, 0)})'
|
|
1250
|
+
elif func == 'int':
|
|
1251
|
+
return f'static_cast<int>({_safe_arg(args, 0)})'
|
|
1252
|
+
elif func == 'float':
|
|
1253
|
+
return f'static_cast<double>({_safe_arg(args, 0)})'
|
|
1254
|
+
elif func == 'print':
|
|
1255
|
+
if args:
|
|
1256
|
+
return f'std::cout << {" << \" \" << ".join(args)} << std::endl'
|
|
1257
|
+
return 'std::cout << std::endl'
|
|
1258
|
+
elif func == 'range':
|
|
1259
|
+
return f'/* range({", ".join(args) if args else ""}) */'
|
|
1260
|
+
elif func == 'list':
|
|
1261
|
+
if args:
|
|
1262
|
+
return f'std::vector({_safe_arg(args, 0)})'
|
|
1263
|
+
return 'std::vector<int>{}'
|
|
1264
|
+
elif func == 'dict':
|
|
1265
|
+
return 'std::unordered_map<std::string, int>{}'
|
|
1266
|
+
elif func == 'set':
|
|
1267
|
+
if args:
|
|
1268
|
+
return f'std::unordered_set({_safe_arg(args, 0)})'
|
|
1269
|
+
return 'std::unordered_set<int>{}'
|
|
1270
|
+
elif func == 'abs':
|
|
1271
|
+
return f'std::abs({_safe_arg(args, 0)})'
|
|
1272
|
+
elif func == 'min':
|
|
1273
|
+
self.imports.add('<algorithm>')
|
|
1274
|
+
if len(args) == 2:
|
|
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())'
|
|
1280
|
+
elif args:
|
|
1281
|
+
return f'std::min({{{", ".join(args)}}})'
|
|
1282
|
+
return '0 /* min() requires arguments */'
|
|
1283
|
+
elif func == 'max':
|
|
1284
|
+
self.imports.add('<algorithm>')
|
|
1285
|
+
if len(args) == 2:
|
|
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())'
|
|
1291
|
+
elif args:
|
|
1292
|
+
return f'std::max({{{", ".join(args)}}})'
|
|
1293
|
+
return '0 /* max() requires arguments */'
|
|
1294
|
+
elif func == 'sum':
|
|
1295
|
+
self.imports.add('<numeric>')
|
|
1296
|
+
arg0 = _safe_arg(args, 0, 'std::vector<int>{}')
|
|
1297
|
+
return f'std::accumulate({arg0}.begin(), {arg0}.end(), 0)'
|
|
1298
|
+
elif func == 'sorted':
|
|
1299
|
+
self.imports.add('<algorithm>')
|
|
1300
|
+
arg0 = _safe_arg(args, 0, 'std::vector<int>{}')
|
|
1301
|
+
return f'[&](){{ auto tmp = {arg0}; std::sort(tmp.begin(), tmp.end()); return tmp; }}()'
|
|
1302
|
+
elif func == 'reversed':
|
|
1303
|
+
self.imports.add('<algorithm>')
|
|
1304
|
+
arg0 = _safe_arg(args, 0, 'std::vector<int>{}')
|
|
1305
|
+
return f'[&](){{ auto tmp = {arg0}; std::reverse(tmp.begin(), tmp.end()); return tmp; }}()'
|
|
1306
|
+
elif func.endswith('.append'):
|
|
1307
|
+
obj = func.rsplit('.', 1)[0]
|
|
1308
|
+
return f'{obj}.push_back({_safe_arg(args, 0)})'
|
|
1309
|
+
elif func.endswith('.pop'):
|
|
1310
|
+
obj = func.rsplit('.', 1)[0]
|
|
1311
|
+
if args:
|
|
1312
|
+
return f'{obj}.erase({obj}.begin() + {_safe_arg(args, 0)})'
|
|
1313
|
+
return f'[&](){{ auto tmp = {obj}.back(); {obj}.pop_back(); return tmp; }}()'
|
|
1314
|
+
elif func.endswith('.insert'):
|
|
1315
|
+
obj = func.rsplit('.', 1)[0]
|
|
1316
|
+
return f'{obj}.insert({obj}.begin() + {_safe_arg(args, 0)}, {_safe_arg(args, 1)})'
|
|
1317
|
+
elif func.endswith('.remove'):
|
|
1318
|
+
obj = func.rsplit('.', 1)[0]
|
|
1319
|
+
self.imports.add('<algorithm>')
|
|
1320
|
+
return f'{obj}.erase(std::remove({obj}.begin(), {obj}.end(), {_safe_arg(args, 0)}), {obj}.end())'
|
|
1321
|
+
elif func.endswith('.clear'):
|
|
1322
|
+
obj = func.rsplit('.', 1)[0]
|
|
1323
|
+
return f'{obj}.clear()'
|
|
1324
|
+
|
|
1325
|
+
# Python random module conversions (with safe argument access)
|
|
1326
|
+
elif func == 'random.randint':
|
|
1327
|
+
self.imports.add('<random>')
|
|
1328
|
+
return f'_rng_uniform_int({_safe_arg(args, 0)}, {_safe_arg(args, 1)})'
|
|
1329
|
+
elif func == 'random.uniform':
|
|
1330
|
+
self.imports.add('<random>')
|
|
1331
|
+
return f'_rng_uniform_real({_safe_arg(args, 0, "0.0")}, {_safe_arg(args, 1, "1.0")})'
|
|
1332
|
+
elif func == 'random.choice':
|
|
1333
|
+
self.imports.add('<random>')
|
|
1334
|
+
return f'_rng_choice({_safe_arg(args, 0, "std::vector<int>{}")})'
|
|
1335
|
+
elif func == 'random.sample':
|
|
1336
|
+
self.imports.add('<random>')
|
|
1337
|
+
self.imports.add('<algorithm>')
|
|
1338
|
+
return f'_rng_sample({_safe_arg(args, 0, "std::vector<int>{}")}, {_safe_arg(args, 1, "1")})'
|
|
1339
|
+
elif func == 'random.shuffle':
|
|
1340
|
+
self.imports.add('<random>')
|
|
1341
|
+
self.imports.add('<algorithm>')
|
|
1342
|
+
return f'_rng_shuffle({_safe_arg(args, 0, "std::vector<int>{}")})'
|
|
1343
|
+
elif func == 'random.randbytes':
|
|
1344
|
+
self.imports.add('<random>')
|
|
1345
|
+
return f'_rng_bytes({_safe_arg(args, 0, "1")})'
|
|
1346
|
+
elif func == 'random.Random':
|
|
1347
|
+
self.imports.add('<random>')
|
|
1348
|
+
return f'std::mt19937({_safe_arg(args, 0)})'
|
|
1349
|
+
elif func == 'random.gauss' or func == 'random.normalvariate':
|
|
1350
|
+
self.imports.add('<random>')
|
|
1351
|
+
return f'_rng_normal({_safe_arg(args, 0, "0.0")}, {_safe_arg(args, 1, "1.0")})'
|
|
1352
|
+
elif func == 'random.betavariate':
|
|
1353
|
+
self.imports.add('<random>')
|
|
1354
|
+
return f'_rng_beta({_safe_arg(args, 0, "1.0")}, {_safe_arg(args, 1, "1.0")})'
|
|
1355
|
+
elif func == 'random.expovariate':
|
|
1356
|
+
self.imports.add('<random>')
|
|
1357
|
+
return f'_rng_exponential(1.0 / {_safe_arg(args, 0, "1.0")})'
|
|
1358
|
+
elif func == 'random.lognormvariate':
|
|
1359
|
+
self.imports.add('<random>')
|
|
1360
|
+
return f'_rng_lognormal({_safe_arg(args, 0, "0.0")}, {_safe_arg(args, 1, "1.0")})'
|
|
1361
|
+
elif func == 'random.choices':
|
|
1362
|
+
self.imports.add('<random>')
|
|
1363
|
+
# Handle weighted choices - check both positional and keyword args
|
|
1364
|
+
weights_arg = '{}'
|
|
1365
|
+
if len(args) > 1:
|
|
1366
|
+
weights_arg = _safe_arg(args, 1, '{}')
|
|
1367
|
+
else:
|
|
1368
|
+
# Check keyword arguments for 'weights'
|
|
1369
|
+
for kw in node.keywords:
|
|
1370
|
+
if kw.arg == 'weights':
|
|
1371
|
+
weights_arg = self._convert_expr(kw.value)
|
|
1372
|
+
break
|
|
1373
|
+
if args:
|
|
1374
|
+
return f'_rng_weighted_choice({_safe_arg(args, 0)}, {weights_arg})'
|
|
1375
|
+
return f'_rng_choice({_safe_arg(args, 0, "std::vector<int>{}")})'
|
|
1376
|
+
elif func.startswith('random.'):
|
|
1377
|
+
self.imports.add('<random>')
|
|
1378
|
+
# Generic random function - mark as needs implementation
|
|
1379
|
+
method = func.split('.', 1)[1]
|
|
1380
|
+
return f'/* TODO: random.{method} */ 0'
|
|
1381
|
+
|
|
1382
|
+
# tkinter is not convertible - mark as unconvertible
|
|
1383
|
+
elif func.startswith('tk.') or func.startswith('tkinter.'):
|
|
1384
|
+
return f'/* UNCONVERTIBLE: {func}({", ".join(args)}) - tkinter has no C++ equivalent */'
|
|
1385
|
+
elif '.title(' in func or '.pack(' in func or '.mainloop(' in func:
|
|
1386
|
+
obj = func.rsplit('.', 1)[0]
|
|
1387
|
+
method = func.rsplit('.', 1)[1]
|
|
1388
|
+
return f'/* UNCONVERTIBLE: {obj}.{method}() - GUI operation */'
|
|
1389
|
+
|
|
1390
|
+
kwargs = []
|
|
1391
|
+
for kw in node.keywords:
|
|
1392
|
+
kwargs.append(f'/* {kw.arg}= */{self._convert_expr(kw.value)}')
|
|
1393
|
+
|
|
1394
|
+
all_args = args + kwargs
|
|
1395
|
+
return f'{func}({", ".join(all_args)})'
|
|
1396
|
+
|
|
1397
|
+
elif isinstance(node, ast.BinOp):
|
|
1398
|
+
left = self._convert_expr(node.left)
|
|
1399
|
+
right = self._convert_expr(node.right)
|
|
1400
|
+
op = self._convert_binop(node.op)
|
|
1401
|
+
|
|
1402
|
+
if isinstance(node.op, ast.Pow):
|
|
1403
|
+
self.imports.add('<cmath>')
|
|
1404
|
+
return f'std::pow({left}, {right})'
|
|
1405
|
+
elif isinstance(node.op, ast.FloorDiv):
|
|
1406
|
+
return f'static_cast<int>({left} / {right})'
|
|
1407
|
+
|
|
1408
|
+
return f'({left} {op} {right})'
|
|
1409
|
+
|
|
1410
|
+
elif isinstance(node, ast.UnaryOp):
|
|
1411
|
+
operand = self._convert_expr(node.operand)
|
|
1412
|
+
if isinstance(node.op, ast.Not):
|
|
1413
|
+
return f'!{operand}'
|
|
1414
|
+
elif isinstance(node.op, ast.USub):
|
|
1415
|
+
return f'-{operand}'
|
|
1416
|
+
elif isinstance(node.op, ast.UAdd):
|
|
1417
|
+
return f'+{operand}'
|
|
1418
|
+
elif isinstance(node.op, ast.Invert):
|
|
1419
|
+
return f'~{operand}'
|
|
1420
|
+
return operand
|
|
1421
|
+
|
|
1422
|
+
elif isinstance(node, ast.Compare):
|
|
1423
|
+
left = self._convert_expr(node.left)
|
|
1424
|
+
parts = [left]
|
|
1425
|
+
for i, (op, comp) in enumerate(zip(node.ops, node.comparators)):
|
|
1426
|
+
op_str = self._convert_cmpop(op)
|
|
1427
|
+
comp_str = self._convert_expr(comp)
|
|
1428
|
+
if isinstance(op, ast.In):
|
|
1429
|
+
self.imports.add('<algorithm>')
|
|
1430
|
+
prev = parts[-1] if parts else left
|
|
1431
|
+
return f'std::find({comp_str}.begin(), {comp_str}.end(), {prev}) != {comp_str}.end()'
|
|
1432
|
+
elif isinstance(op, ast.NotIn):
|
|
1433
|
+
self.imports.add('<algorithm>')
|
|
1434
|
+
prev = parts[-1] if parts else left
|
|
1435
|
+
return f'std::find({comp_str}.begin(), {comp_str}.end(), {prev}) == {comp_str}.end()'
|
|
1436
|
+
parts.append(f'{op_str} {comp_str}')
|
|
1437
|
+
return ' '.join(parts)
|
|
1438
|
+
|
|
1439
|
+
elif isinstance(node, ast.BoolOp):
|
|
1440
|
+
op = ' && ' if isinstance(node.op, ast.And) else ' || '
|
|
1441
|
+
values = [self._convert_expr(v) for v in node.values]
|
|
1442
|
+
return f'({op.join(values)})'
|
|
1443
|
+
|
|
1444
|
+
elif isinstance(node, ast.IfExp):
|
|
1445
|
+
test = self._convert_expr(node.test)
|
|
1446
|
+
body = self._convert_expr(node.body)
|
|
1447
|
+
orelse = self._convert_expr(node.orelse)
|
|
1448
|
+
return f'({test} ? {body} : {orelse})'
|
|
1449
|
+
|
|
1450
|
+
elif isinstance(node, ast.List):
|
|
1451
|
+
elts = [self._convert_expr(e) for e in node.elts]
|
|
1452
|
+
if elts:
|
|
1453
|
+
inner_type = self._infer_type(node.elts[0])
|
|
1454
|
+
return f'std::vector<{inner_type}>{{{", ".join(elts)}}}'
|
|
1455
|
+
return 'std::vector<int>{}'
|
|
1456
|
+
|
|
1457
|
+
elif isinstance(node, ast.Dict):
|
|
1458
|
+
pairs = []
|
|
1459
|
+
for k, v in zip(node.keys, node.values):
|
|
1460
|
+
key = self._convert_expr(k)
|
|
1461
|
+
val = self._convert_expr(v)
|
|
1462
|
+
pairs.append(f'{{{key}, {val}}}')
|
|
1463
|
+
if pairs:
|
|
1464
|
+
key_type = self._infer_type(node.keys[0]) if node.keys else 'std::string'
|
|
1465
|
+
val_type = self._infer_type(node.values[0]) if node.values else 'int'
|
|
1466
|
+
return f'std::unordered_map<{key_type}, {val_type}>{{{", ".join(pairs)}}}'
|
|
1467
|
+
return 'std::unordered_map<std::string, int>{}'
|
|
1468
|
+
|
|
1469
|
+
elif isinstance(node, ast.Set):
|
|
1470
|
+
elts = [self._convert_expr(e) for e in node.elts]
|
|
1471
|
+
if elts:
|
|
1472
|
+
inner_type = self._infer_type(node.elts[0])
|
|
1473
|
+
return f'std::unordered_set<{inner_type}>{{{", ".join(elts)}}}'
|
|
1474
|
+
return 'std::unordered_set<int>{}'
|
|
1475
|
+
|
|
1476
|
+
elif isinstance(node, ast.Tuple):
|
|
1477
|
+
elts = [self._convert_expr(e) for e in node.elts]
|
|
1478
|
+
return f'std::make_tuple({", ".join(elts)})'
|
|
1479
|
+
|
|
1480
|
+
elif isinstance(node, ast.Lambda):
|
|
1481
|
+
params = []
|
|
1482
|
+
for arg in node.args.args:
|
|
1483
|
+
params.append(f'auto {arg.arg}')
|
|
1484
|
+
body = self._convert_expr(node.body)
|
|
1485
|
+
return f'[&]({", ".join(params)}) {{ return {body}; }}'
|
|
1486
|
+
|
|
1487
|
+
elif isinstance(node, ast.ListComp):
|
|
1488
|
+
self.imports.add('<algorithm>')
|
|
1489
|
+
elt = self._convert_expr(node.elt)
|
|
1490
|
+
gen = _safe_get(node.generators, 0)
|
|
1491
|
+
if gen:
|
|
1492
|
+
iter_var = self._convert_expr(gen.target)
|
|
1493
|
+
iter_src = self._convert_expr(gen.iter)
|
|
1494
|
+
cond_check = ''
|
|
1495
|
+
if gen.ifs:
|
|
1496
|
+
cond = self._convert_expr(_safe_get(gen.ifs, 0))
|
|
1497
|
+
cond_check = f' if({cond})'
|
|
1498
|
+
return f'[&](){{ std::vector<decltype({elt})> result; for(auto& {iter_var} : {iter_src}){cond_check} result.push_back({elt}); return result; }}()'
|
|
1499
|
+
return f'std::vector<decltype({elt})>{{}}'
|
|
1500
|
+
|
|
1501
|
+
elif isinstance(node, ast.DictComp):
|
|
1502
|
+
# Dict comprehension: {k: v for k, v in items}
|
|
1503
|
+
key_expr = self._convert_expr(node.key)
|
|
1504
|
+
val_expr = self._convert_expr(node.value)
|
|
1505
|
+
gen = _safe_get(node.generators, 0)
|
|
1506
|
+
if gen:
|
|
1507
|
+
iter_var = self._convert_expr(gen.target)
|
|
1508
|
+
iter_src = self._convert_expr(gen.iter)
|
|
1509
|
+
cond_check = ''
|
|
1510
|
+
if gen.ifs:
|
|
1511
|
+
cond = self._convert_expr(_safe_get(gen.ifs, 0))
|
|
1512
|
+
cond_check = f' if({cond})'
|
|
1513
|
+
return f'[&](){{ std::unordered_map<decltype({key_expr}), decltype({val_expr})> result; for(auto& {iter_var} : {iter_src}){cond_check} result[{key_expr}] = {val_expr}; return result; }}()'
|
|
1514
|
+
return f'std::unordered_map<decltype({key_expr}), decltype({val_expr})>{{}}'
|
|
1515
|
+
|
|
1516
|
+
elif isinstance(node, ast.SetComp):
|
|
1517
|
+
# Set comprehension: {x for x in items}
|
|
1518
|
+
elt = self._convert_expr(node.elt)
|
|
1519
|
+
gen = _safe_get(node.generators, 0)
|
|
1520
|
+
if gen:
|
|
1521
|
+
iter_var = self._convert_expr(gen.target)
|
|
1522
|
+
iter_src = self._convert_expr(gen.iter)
|
|
1523
|
+
cond_check = ''
|
|
1524
|
+
if gen.ifs:
|
|
1525
|
+
cond = self._convert_expr(_safe_get(gen.ifs, 0))
|
|
1526
|
+
cond_check = f' if({cond})'
|
|
1527
|
+
return f'[&](){{ std::unordered_set<decltype({elt})> result; for(auto& {iter_var} : {iter_src}){cond_check} result.insert({elt}); return result; }}()'
|
|
1528
|
+
return f'std::unordered_set<decltype({elt})>{{}}'
|
|
1529
|
+
|
|
1530
|
+
elif isinstance(node, ast.GeneratorExp):
|
|
1531
|
+
# Generator expression: (x for x in items) - convert to vector for C++
|
|
1532
|
+
elt = self._convert_expr(node.elt)
|
|
1533
|
+
gen = _safe_get(node.generators, 0)
|
|
1534
|
+
if gen:
|
|
1535
|
+
iter_var = self._convert_expr(gen.target)
|
|
1536
|
+
iter_src = self._convert_expr(gen.iter)
|
|
1537
|
+
cond_check = ''
|
|
1538
|
+
if gen.ifs:
|
|
1539
|
+
cond = self._convert_expr(_safe_get(gen.ifs, 0))
|
|
1540
|
+
cond_check = f' if({cond})'
|
|
1541
|
+
return f'[&](){{ std::vector<decltype({elt})> result; for(auto& {iter_var} : {iter_src}){cond_check} result.push_back({elt}); return result; }}()'
|
|
1542
|
+
return f'std::vector<decltype({elt})>{{}}'
|
|
1543
|
+
|
|
1544
|
+
elif isinstance(node, ast.Slice):
|
|
1545
|
+
lower = self._convert_expr(node.lower) if node.lower else '0'
|
|
1546
|
+
upper = self._convert_expr(node.upper) if node.upper else ''
|
|
1547
|
+
return f'{lower}:{upper}'
|
|
1548
|
+
|
|
1549
|
+
elif isinstance(node, ast.JoinedStr):
|
|
1550
|
+
# f-string: convert to std::string concatenation or std::format
|
|
1551
|
+
parts = []
|
|
1552
|
+
for value in node.values:
|
|
1553
|
+
if isinstance(value, ast.Constant):
|
|
1554
|
+
# Plain string part
|
|
1555
|
+
escaped = str(value.value).replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t').replace('\0', '\\0')
|
|
1556
|
+
parts.append(f'"{escaped}"')
|
|
1557
|
+
elif isinstance(value, ast.FormattedValue):
|
|
1558
|
+
# {expr} part - convert to std::to_string or direct if already string
|
|
1559
|
+
expr = self._convert_expr(value.value)
|
|
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:
|
|
1563
|
+
parts.append(expr)
|
|
1564
|
+
else:
|
|
1565
|
+
parts.append(f'std::to_string({expr})')
|
|
1566
|
+
else:
|
|
1567
|
+
parts.append(self._convert_expr(value))
|
|
1568
|
+
if not parts:
|
|
1569
|
+
return '""'
|
|
1570
|
+
return ' + '.join(parts)
|
|
1571
|
+
|
|
1572
|
+
elif isinstance(node, ast.FormattedValue):
|
|
1573
|
+
# Standalone formatted value (rare, usually inside JoinedStr)
|
|
1574
|
+
return self._convert_expr(node.value)
|
|
1575
|
+
|
|
1576
|
+
# Unknown AST node - add warning and return placeholder
|
|
1577
|
+
node_type = type(node).__name__
|
|
1578
|
+
line_info = getattr(node, 'lineno', '?')
|
|
1579
|
+
self.warnings.append(f"Unsupported AST node '{node_type}' at line {line_info}")
|
|
1580
|
+
return f'/* unsupported: {node_type} at line {line_info} */'
|
|
1581
|
+
|
|
1582
|
+
def _convert_binop(self, op) -> str:
|
|
1583
|
+
ops = {
|
|
1584
|
+
ast.Add: '+',
|
|
1585
|
+
ast.Sub: '-',
|
|
1586
|
+
ast.Mult: '*',
|
|
1587
|
+
ast.Div: '/',
|
|
1588
|
+
ast.FloorDiv: '/',
|
|
1589
|
+
ast.Mod: '%',
|
|
1590
|
+
ast.Pow: '**',
|
|
1591
|
+
ast.LShift: '<<',
|
|
1592
|
+
ast.RShift: '>>',
|
|
1593
|
+
ast.BitOr: '|',
|
|
1594
|
+
ast.BitXor: '^',
|
|
1595
|
+
ast.BitAnd: '&',
|
|
1596
|
+
ast.MatMult: '*',
|
|
1597
|
+
}
|
|
1598
|
+
return ops.get(type(op), '+')
|
|
1599
|
+
|
|
1600
|
+
def _convert_cmpop(self, op) -> str:
|
|
1601
|
+
ops = {
|
|
1602
|
+
ast.Eq: '==',
|
|
1603
|
+
ast.NotEq: '!=',
|
|
1604
|
+
ast.Lt: '<',
|
|
1605
|
+
ast.LtE: '<=',
|
|
1606
|
+
ast.Gt: '>',
|
|
1607
|
+
ast.GtE: '>=',
|
|
1608
|
+
ast.Is: '==',
|
|
1609
|
+
ast.IsNot: '!=',
|
|
1610
|
+
ast.In: 'in',
|
|
1611
|
+
ast.NotIn: 'not in',
|
|
1612
|
+
}
|
|
1613
|
+
return ops.get(type(op), '==')
|
|
1614
|
+
|
|
1615
|
+
def _get_attr_name(self, node) -> str:
|
|
1616
|
+
if isinstance(node, ast.Attribute):
|
|
1617
|
+
value = self._get_attr_name(node.value)
|
|
1618
|
+
return f'{value}::{node.attr}'
|
|
1619
|
+
elif isinstance(node, ast.Name):
|
|
1620
|
+
return node.id
|
|
1621
|
+
return ''
|
|
1622
|
+
|
|
1623
|
+
def _format_params(self, params: List[Tuple[str, str]]) -> str:
|
|
1624
|
+
return ', '.join(f'{ptype} {pname}' for pname, ptype in params)
|
|
1625
|
+
|
|
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)
|
|
1691
|
+
result = []
|
|
1692
|
+
for pname, ptype in params:
|
|
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::'):
|
|
1703
|
+
result.append(f'const {ptype}& {pname}')
|
|
1704
|
+
else:
|
|
1705
|
+
result.append(f'{ptype} {pname}')
|
|
1706
|
+
return ', '.join(result)
|
|
1707
|
+
|
|
1708
|
+
def _generate_header(self, module_name: str, classes: List[ClassInfo],
|
|
1709
|
+
functions: List[FunctionInfo], global_vars: List) -> str:
|
|
1710
|
+
lines = []
|
|
1711
|
+
guard = f'{module_name.upper()}_H'
|
|
1712
|
+
|
|
1713
|
+
lines.append(f'#ifndef {guard}')
|
|
1714
|
+
lines.append(f'#define {guard}')
|
|
1715
|
+
lines.append('')
|
|
1716
|
+
|
|
1717
|
+
std_includes = {'<string>', '<vector>', '<unordered_map>', '<unordered_set>',
|
|
1718
|
+
'<tuple>', '<optional>', '<functional>', '<memory>', '<cstdint>'}
|
|
1719
|
+
std_includes.update(self.imports)
|
|
1720
|
+
for inc in sorted(std_includes):
|
|
1721
|
+
lines.append(f'#include {inc}')
|
|
1722
|
+
lines.append('')
|
|
1723
|
+
|
|
1724
|
+
lines.append('namespace includecpp {')
|
|
1725
|
+
lines.append('')
|
|
1726
|
+
|
|
1727
|
+
for name, var_type, _ in global_vars:
|
|
1728
|
+
lines.append(f'extern {var_type} {name};')
|
|
1729
|
+
|
|
1730
|
+
if global_vars:
|
|
1731
|
+
lines.append('')
|
|
1732
|
+
|
|
1733
|
+
for cls in classes:
|
|
1734
|
+
if cls.bases:
|
|
1735
|
+
bases_str = ', '.join(f'public {b}' for b in cls.bases)
|
|
1736
|
+
lines.append(f'class {cls.name} : {bases_str} {{')
|
|
1737
|
+
else:
|
|
1738
|
+
lines.append(f'class {cls.name} {{')
|
|
1739
|
+
lines.append('public:')
|
|
1740
|
+
|
|
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
|
|
1760
|
+
lines.append(f' {ftype} {fname};')
|
|
1761
|
+
|
|
1762
|
+
if cls.fields:
|
|
1763
|
+
lines.append('')
|
|
1764
|
+
|
|
1765
|
+
for ctor in cls.constructors:
|
|
1766
|
+
params = self._format_params_with_const_ref(ctor.params)
|
|
1767
|
+
lines.append(f' {cls.name}({params});')
|
|
1768
|
+
|
|
1769
|
+
for method in cls.methods:
|
|
1770
|
+
params = self._format_params_with_const_ref(method.params)
|
|
1771
|
+
static = 'static ' if method.is_static else ''
|
|
1772
|
+
const = ' const' if method.is_const else ''
|
|
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};')
|
|
1780
|
+
|
|
1781
|
+
lines.append('};')
|
|
1782
|
+
lines.append('')
|
|
1783
|
+
|
|
1784
|
+
for func in functions:
|
|
1785
|
+
params = self._format_params_with_const_ref(func.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});')
|
|
1793
|
+
|
|
1794
|
+
lines.append('')
|
|
1795
|
+
lines.append('} // namespace includecpp')
|
|
1796
|
+
lines.append('')
|
|
1797
|
+
lines.append(f'#endif // {guard}')
|
|
1798
|
+
|
|
1799
|
+
return '\n'.join(lines)
|
|
1800
|
+
|
|
1801
|
+
def _generate_source(self, module_name: str, classes: List[ClassInfo],
|
|
1802
|
+
functions: List[FunctionInfo], global_vars: List) -> str:
|
|
1803
|
+
lines = []
|
|
1804
|
+
|
|
1805
|
+
lines.append(f'#include "{module_name}.h"')
|
|
1806
|
+
lines.append('#include <iostream>')
|
|
1807
|
+
lines.append('#include <cmath>')
|
|
1808
|
+
lines.append('#include <stdexcept>')
|
|
1809
|
+
lines.append('')
|
|
1810
|
+
lines.append('namespace includecpp {')
|
|
1811
|
+
lines.append('')
|
|
1812
|
+
|
|
1813
|
+
# Add random helper functions if needed
|
|
1814
|
+
if '<random>' in self.imports:
|
|
1815
|
+
lines.append('// Random number generation helpers')
|
|
1816
|
+
lines.append('static std::mt19937& _get_rng() {')
|
|
1817
|
+
lines.append(' static std::random_device rd;')
|
|
1818
|
+
lines.append(' static std::mt19937 gen(rd());')
|
|
1819
|
+
lines.append(' return gen;')
|
|
1820
|
+
lines.append('}')
|
|
1821
|
+
lines.append('')
|
|
1822
|
+
lines.append('inline int _rng_uniform_int(int a, int b) {')
|
|
1823
|
+
lines.append(' std::uniform_int_distribution<int> dist(a, b);')
|
|
1824
|
+
lines.append(' return dist(_get_rng());')
|
|
1825
|
+
lines.append('}')
|
|
1826
|
+
lines.append('')
|
|
1827
|
+
lines.append('inline double _rng_uniform_real(double a, double b) {')
|
|
1828
|
+
lines.append(' std::uniform_real_distribution<double> dist(a, b);')
|
|
1829
|
+
lines.append(' return dist(_get_rng());')
|
|
1830
|
+
lines.append('}')
|
|
1831
|
+
lines.append('')
|
|
1832
|
+
lines.append('template<typename T>')
|
|
1833
|
+
lines.append('inline T _rng_choice(const std::vector<T>& choices) {')
|
|
1834
|
+
lines.append(' if (choices.empty()) throw std::runtime_error("Cannot choose from empty list");')
|
|
1835
|
+
lines.append(' std::uniform_int_distribution<size_t> dist(0, choices.size() - 1);')
|
|
1836
|
+
lines.append(' return choices[dist(_get_rng())];')
|
|
1837
|
+
lines.append('}')
|
|
1838
|
+
lines.append('')
|
|
1839
|
+
lines.append('template<typename T>')
|
|
1840
|
+
lines.append('inline std::vector<T> _rng_sample(const std::vector<T>& population, size_t k) {')
|
|
1841
|
+
lines.append(' if (k > population.size()) throw std::runtime_error("Sample larger than population");')
|
|
1842
|
+
lines.append(' std::vector<T> result = population;')
|
|
1843
|
+
lines.append(' std::shuffle(result.begin(), result.end(), _get_rng());')
|
|
1844
|
+
lines.append(' result.resize(k);')
|
|
1845
|
+
lines.append(' return result;')
|
|
1846
|
+
lines.append('}')
|
|
1847
|
+
lines.append('')
|
|
1848
|
+
lines.append('template<typename T>')
|
|
1849
|
+
lines.append('inline std::vector<T> _rng_shuffle(std::vector<T> lst) {')
|
|
1850
|
+
lines.append(' std::shuffle(lst.begin(), lst.end(), _get_rng());')
|
|
1851
|
+
lines.append(' return lst;')
|
|
1852
|
+
lines.append('}')
|
|
1853
|
+
lines.append('')
|
|
1854
|
+
lines.append('inline std::vector<uint8_t> _rng_bytes(size_t n) {')
|
|
1855
|
+
lines.append(' std::vector<uint8_t> result(n);')
|
|
1856
|
+
lines.append(' std::uniform_int_distribution<int> dist(0, 255);')
|
|
1857
|
+
lines.append(' for (size_t i = 0; i < n; ++i) result[i] = static_cast<uint8_t>(dist(_get_rng()));')
|
|
1858
|
+
lines.append(' return result;')
|
|
1859
|
+
lines.append('}')
|
|
1860
|
+
lines.append('')
|
|
1861
|
+
lines.append('inline double _rng_normal(double mu, double sigma) {')
|
|
1862
|
+
lines.append(' std::normal_distribution<double> dist(mu, sigma);')
|
|
1863
|
+
lines.append(' return dist(_get_rng());')
|
|
1864
|
+
lines.append('}')
|
|
1865
|
+
lines.append('')
|
|
1866
|
+
lines.append('inline double _rng_exponential(double lambda) {')
|
|
1867
|
+
lines.append(' std::exponential_distribution<double> dist(lambda);')
|
|
1868
|
+
lines.append(' return dist(_get_rng());')
|
|
1869
|
+
lines.append('}')
|
|
1870
|
+
lines.append('')
|
|
1871
|
+
lines.append('inline double _rng_lognormal(double mu, double sigma) {')
|
|
1872
|
+
lines.append(' std::lognormal_distribution<double> dist(mu, sigma);')
|
|
1873
|
+
lines.append(' return dist(_get_rng());')
|
|
1874
|
+
lines.append('}')
|
|
1875
|
+
lines.append('')
|
|
1876
|
+
lines.append('inline double _rng_beta(double alpha, double beta) {')
|
|
1877
|
+
lines.append(' // Beta distribution via gamma distributions')
|
|
1878
|
+
lines.append(' std::gamma_distribution<double> dist_a(alpha, 1.0);')
|
|
1879
|
+
lines.append(' std::gamma_distribution<double> dist_b(beta, 1.0);')
|
|
1880
|
+
lines.append(' double x = dist_a(_get_rng());')
|
|
1881
|
+
lines.append(' double y = dist_b(_get_rng());')
|
|
1882
|
+
lines.append(' return x / (x + y);')
|
|
1883
|
+
lines.append('}')
|
|
1884
|
+
lines.append('')
|
|
1885
|
+
lines.append('template<typename T>')
|
|
1886
|
+
lines.append('inline T _rng_weighted_choice(const std::vector<T>& choices, const std::vector<double>& weights) {')
|
|
1887
|
+
lines.append(' if (choices.empty()) throw std::runtime_error("Cannot choose from empty list");')
|
|
1888
|
+
lines.append(' std::discrete_distribution<size_t> dist(weights.begin(), weights.end());')
|
|
1889
|
+
lines.append(' return choices[dist(_get_rng())];')
|
|
1890
|
+
lines.append('}')
|
|
1891
|
+
lines.append('')
|
|
1892
|
+
|
|
1893
|
+
# Add filesystem helper functions if needed
|
|
1894
|
+
if '<filesystem>' in self.imports:
|
|
1895
|
+
lines.append('// Filesystem helpers')
|
|
1896
|
+
lines.append('inline std::vector<std::string> _os_listdir(const std::string& path) {')
|
|
1897
|
+
lines.append(' std::vector<std::string> result;')
|
|
1898
|
+
lines.append(' for (const auto& entry : std::filesystem::directory_iterator(path)) {')
|
|
1899
|
+
lines.append(' result.push_back(entry.path().filename().string());')
|
|
1900
|
+
lines.append(' }')
|
|
1901
|
+
lines.append(' return result;')
|
|
1902
|
+
lines.append('}')
|
|
1903
|
+
lines.append('')
|
|
1904
|
+
lines.append('inline std::string _path_join(const std::string& a, const std::string& b) {')
|
|
1905
|
+
lines.append(' return (std::filesystem::path(a) / b).string();')
|
|
1906
|
+
lines.append('}')
|
|
1907
|
+
lines.append('')
|
|
1908
|
+
lines.append('inline std::string _path_dirname(const std::string& path) {')
|
|
1909
|
+
lines.append(' return std::filesystem::path(path).parent_path().string();')
|
|
1910
|
+
lines.append('}')
|
|
1911
|
+
lines.append('')
|
|
1912
|
+
lines.append('inline std::string _path_basename(const std::string& path) {')
|
|
1913
|
+
lines.append(' return std::filesystem::path(path).filename().string();')
|
|
1914
|
+
lines.append('}')
|
|
1915
|
+
lines.append('')
|
|
1916
|
+
lines.append('inline std::pair<std::string, std::string> _path_splitext(const std::string& path) {')
|
|
1917
|
+
lines.append(' auto p = std::filesystem::path(path);')
|
|
1918
|
+
lines.append(' return {p.stem().string(), p.extension().string()};')
|
|
1919
|
+
lines.append('}')
|
|
1920
|
+
lines.append('')
|
|
1921
|
+
|
|
1922
|
+
# Add time helper functions if needed
|
|
1923
|
+
if '<chrono>' in self.imports:
|
|
1924
|
+
lines.append('// Time helpers')
|
|
1925
|
+
lines.append('inline double _time_now() {')
|
|
1926
|
+
lines.append(' auto now = std::chrono::system_clock::now();')
|
|
1927
|
+
lines.append(' auto duration = now.time_since_epoch();')
|
|
1928
|
+
lines.append(' return std::chrono::duration<double>(duration).count();')
|
|
1929
|
+
lines.append('}')
|
|
1930
|
+
lines.append('')
|
|
1931
|
+
lines.append('inline double _time_perf_counter() {')
|
|
1932
|
+
lines.append(' static auto start = std::chrono::high_resolution_clock::now();')
|
|
1933
|
+
lines.append(' auto now = std::chrono::high_resolution_clock::now();')
|
|
1934
|
+
lines.append(' return std::chrono::duration<double>(now - start).count();')
|
|
1935
|
+
lines.append('}')
|
|
1936
|
+
lines.append('')
|
|
1937
|
+
lines.append('inline double _time_monotonic() {')
|
|
1938
|
+
lines.append(' static auto start = std::chrono::steady_clock::now();')
|
|
1939
|
+
lines.append(' auto now = std::chrono::steady_clock::now();')
|
|
1940
|
+
lines.append(' return std::chrono::duration<double>(now - start).count();')
|
|
1941
|
+
lines.append('}')
|
|
1942
|
+
lines.append('')
|
|
1943
|
+
|
|
1944
|
+
# Add sys helpers if needed
|
|
1945
|
+
if '_sys_platform' in '\n'.join(lines) or 'sys' in self.python_imports:
|
|
1946
|
+
lines.append('// Sys helpers')
|
|
1947
|
+
lines.append('inline std::string _sys_platform() {')
|
|
1948
|
+
lines.append('#ifdef _WIN32')
|
|
1949
|
+
lines.append(' return "win32";')
|
|
1950
|
+
lines.append('#elif __APPLE__')
|
|
1951
|
+
lines.append(' return "darwin";')
|
|
1952
|
+
lines.append('#elif __linux__')
|
|
1953
|
+
lines.append(' return "linux";')
|
|
1954
|
+
lines.append('#else')
|
|
1955
|
+
lines.append(' return "unknown";')
|
|
1956
|
+
lines.append('#endif')
|
|
1957
|
+
lines.append('}')
|
|
1958
|
+
lines.append('')
|
|
1959
|
+
|
|
1960
|
+
# Add regex helpers if needed
|
|
1961
|
+
if '<regex>' in self.imports:
|
|
1962
|
+
lines.append('// Regex helpers')
|
|
1963
|
+
lines.append('inline std::vector<std::string> _regex_findall(const std::string& pattern, const std::string& text) {')
|
|
1964
|
+
lines.append(' std::vector<std::string> results;')
|
|
1965
|
+
lines.append(' std::regex re(pattern);')
|
|
1966
|
+
lines.append(' auto begin = std::sregex_iterator(text.begin(), text.end(), re);')
|
|
1967
|
+
lines.append(' auto end = std::sregex_iterator();')
|
|
1968
|
+
lines.append(' for (auto it = begin; it != end; ++it) {')
|
|
1969
|
+
lines.append(' results.push_back((*it)[0].str());')
|
|
1970
|
+
lines.append(' }')
|
|
1971
|
+
lines.append(' return results;')
|
|
1972
|
+
lines.append('}')
|
|
1973
|
+
lines.append('')
|
|
1974
|
+
|
|
1975
|
+
for name, var_type, value in global_vars:
|
|
1976
|
+
if value:
|
|
1977
|
+
lines.append(f'{var_type} {name} = {value};')
|
|
1978
|
+
else:
|
|
1979
|
+
lines.append(f'{var_type} {name};')
|
|
1980
|
+
|
|
1981
|
+
if global_vars:
|
|
1982
|
+
lines.append('')
|
|
1983
|
+
|
|
1984
|
+
for cls in classes:
|
|
1985
|
+
for ctor in cls.constructors:
|
|
1986
|
+
params = self._format_params_with_const_ref(ctor.params)
|
|
1987
|
+
lines.append(f'{cls.name}::{cls.name}({params}) {{')
|
|
1988
|
+
|
|
1989
|
+
for fname, ftype, fdefault in cls.fields:
|
|
1990
|
+
init_val = None
|
|
1991
|
+
for pname, _ in ctor.params:
|
|
1992
|
+
if pname == fname:
|
|
1993
|
+
init_val = pname
|
|
1994
|
+
break
|
|
1995
|
+
if init_val:
|
|
1996
|
+
lines.append(f' this->{fname} = {init_val};')
|
|
1997
|
+
elif fdefault:
|
|
1998
|
+
lines.append(f' this->{fname} = {fdefault};')
|
|
1999
|
+
|
|
2000
|
+
ctor_body_lines = ctor.body.split('\n')
|
|
2001
|
+
for line in ctor_body_lines:
|
|
2002
|
+
if 'this->' in line and '=' in line:
|
|
2003
|
+
pass
|
|
2004
|
+
elif line.strip():
|
|
2005
|
+
lines.append(line)
|
|
2006
|
+
|
|
2007
|
+
lines.append('}')
|
|
2008
|
+
lines.append('')
|
|
2009
|
+
|
|
2010
|
+
for method in cls.methods:
|
|
2011
|
+
params = self._format_params_with_const_ref(method.params)
|
|
2012
|
+
const = ' const' if method.is_const else ''
|
|
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} {{')
|
|
2020
|
+
lines.append(method.body)
|
|
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)
|
|
2028
|
+
lines.append('')
|
|
2029
|
+
|
|
2030
|
+
for func in functions:
|
|
2031
|
+
params = self._format_params_with_const_ref(func.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}) {{')
|
|
2039
|
+
lines.append(func.body)
|
|
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)
|
|
2046
|
+
lines.append('')
|
|
2047
|
+
|
|
2048
|
+
lines.append('} // namespace includecpp')
|
|
2049
|
+
|
|
2050
|
+
return '\n'.join(lines)
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
class CppToPythonConverter:
|
|
2054
|
+
def __init__(self):
|
|
2055
|
+
self.indent = ' '
|
|
2056
|
+
self._current_class_fields = set() # Track fields for self. prefix
|
|
2057
|
+
|
|
2058
|
+
def convert(self, source: str, module_name: str) -> str:
|
|
2059
|
+
"""Convert C++ source to Python."""
|
|
2060
|
+
lines = []
|
|
2061
|
+
|
|
2062
|
+
source = self._remove_comments(source)
|
|
2063
|
+
|
|
2064
|
+
# Extract namespace content
|
|
2065
|
+
namespace_content = self._extract_namespace_content(source)
|
|
2066
|
+
if namespace_content:
|
|
2067
|
+
source = namespace_content
|
|
2068
|
+
|
|
2069
|
+
imports_needed = set()
|
|
2070
|
+
if 'std::sqrt' in source or 'std::cos' in source or 'std::sin' in source or 'M_PI' in source:
|
|
2071
|
+
imports_needed.add('math')
|
|
2072
|
+
if 'std::vector' in source or 'std::string' in source:
|
|
2073
|
+
imports_needed.add('typing')
|
|
2074
|
+
|
|
2075
|
+
# Add imports
|
|
2076
|
+
if 'typing' in imports_needed:
|
|
2077
|
+
lines.append('from typing import List, Dict, Set, Tuple, Optional, Any, Callable')
|
|
2078
|
+
if 'math' in imports_needed:
|
|
2079
|
+
lines.append('import math')
|
|
2080
|
+
if imports_needed:
|
|
2081
|
+
lines.append('')
|
|
2082
|
+
|
|
2083
|
+
structs = self._parse_structs(source)
|
|
2084
|
+
classes = self._parse_classes(source)
|
|
2085
|
+
functions = self._parse_functions(source)
|
|
2086
|
+
constants = self._parse_constants(source)
|
|
2087
|
+
|
|
2088
|
+
# Add dataclass import if structs exist
|
|
2089
|
+
if structs:
|
|
2090
|
+
lines.insert(0, 'from dataclasses import dataclass, field')
|
|
2091
|
+
if not lines[1].startswith('from typing'):
|
|
2092
|
+
lines.insert(1, '')
|
|
2093
|
+
|
|
2094
|
+
for const_name, const_val in constants:
|
|
2095
|
+
lines.append(f'{const_name} = {const_val}')
|
|
2096
|
+
|
|
2097
|
+
if constants:
|
|
2098
|
+
lines.append('')
|
|
2099
|
+
|
|
2100
|
+
for struct in structs:
|
|
2101
|
+
lines.extend(self._generate_struct(struct))
|
|
2102
|
+
lines.append('')
|
|
2103
|
+
|
|
2104
|
+
for cls in classes:
|
|
2105
|
+
lines.extend(self._generate_class(cls))
|
|
2106
|
+
lines.append('')
|
|
2107
|
+
|
|
2108
|
+
for func in functions:
|
|
2109
|
+
lines.extend(self._generate_function(func))
|
|
2110
|
+
lines.append('')
|
|
2111
|
+
|
|
2112
|
+
return '\n'.join(lines)
|
|
2113
|
+
|
|
2114
|
+
def _remove_comments(self, source: str) -> str:
|
|
2115
|
+
source = re.sub(r'//.*$', '', source, flags=re.MULTILINE)
|
|
2116
|
+
source = re.sub(r'/\*.*?\*/', '', source, flags=re.DOTALL)
|
|
2117
|
+
return source
|
|
2118
|
+
|
|
2119
|
+
def _extract_namespace_content(self, source: str) -> Optional[str]:
|
|
2120
|
+
"""Extract content from namespace includecpp { ... }"""
|
|
2121
|
+
match = re.search(r'namespace\s+includecpp\s*\{(.*)$', source, re.DOTALL)
|
|
2122
|
+
if match:
|
|
2123
|
+
content = match.group(1)
|
|
2124
|
+
depth = 1
|
|
2125
|
+
end_pos = 0
|
|
2126
|
+
for i, char in enumerate(content):
|
|
2127
|
+
if char == '{':
|
|
2128
|
+
depth += 1
|
|
2129
|
+
elif char == '}':
|
|
2130
|
+
depth -= 1
|
|
2131
|
+
if depth == 0:
|
|
2132
|
+
end_pos = i
|
|
2133
|
+
break
|
|
2134
|
+
return content[:end_pos]
|
|
2135
|
+
return None
|
|
2136
|
+
|
|
2137
|
+
def _parse_constants(self, source: str) -> List[Tuple[str, str]]:
|
|
2138
|
+
"""Parse constexpr/const global values."""
|
|
2139
|
+
constants = []
|
|
2140
|
+
pattern = r'(?:constexpr|const)\s+(\w+)\s+(\w+)\s*=\s*([^;]+);'
|
|
2141
|
+
for match in re.finditer(pattern, source):
|
|
2142
|
+
# group(1) is type (not used in Python), group(2) is name, group(3) is value
|
|
2143
|
+
const_name = match.group(2)
|
|
2144
|
+
const_val = self._convert_cpp_expr(match.group(3).strip())
|
|
2145
|
+
constants.append((const_name, const_val))
|
|
2146
|
+
return constants
|
|
2147
|
+
|
|
2148
|
+
def _parse_functions(self, source: str) -> List[FunctionInfo]:
|
|
2149
|
+
functions = []
|
|
2150
|
+
|
|
2151
|
+
# Match function with body
|
|
2152
|
+
pattern = r'(?:(?:static|inline|virtual|explicit|constexpr)\s+)*(\w+(?:<[^>]+>)?(?:\s*[*&])?)\s+(\w+)\s*\(([^)]*)\)\s*(?:const)?\s*\{([^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*)\}'
|
|
2153
|
+
|
|
2154
|
+
for match in re.finditer(pattern, source):
|
|
2155
|
+
return_type = match.group(1).strip()
|
|
2156
|
+
name = match.group(2)
|
|
2157
|
+
params_str = match.group(3).strip()
|
|
2158
|
+
body = match.group(4)
|
|
2159
|
+
|
|
2160
|
+
if name in ('if', 'while', 'for', 'switch', 'catch'):
|
|
2161
|
+
continue
|
|
2162
|
+
|
|
2163
|
+
params = self._parse_params(params_str)
|
|
2164
|
+
py_body = self._convert_cpp_body(body)
|
|
2165
|
+
|
|
2166
|
+
functions.append(FunctionInfo(
|
|
2167
|
+
name=name,
|
|
2168
|
+
return_type=self._convert_cpp_type(return_type),
|
|
2169
|
+
params=[(n, self._convert_cpp_type(t)) for n, t in params],
|
|
2170
|
+
body=py_body
|
|
2171
|
+
))
|
|
2172
|
+
|
|
2173
|
+
return functions
|
|
2174
|
+
|
|
2175
|
+
def _parse_classes(self, source: str) -> List[ClassInfo]:
|
|
2176
|
+
classes = []
|
|
2177
|
+
|
|
2178
|
+
pattern = r'class\s+(\w+)(?:\s*:\s*(?:public|private|protected)\s+(\w+))?\s*\{([^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*)\}'
|
|
2179
|
+
|
|
2180
|
+
for match in re.finditer(pattern, source):
|
|
2181
|
+
name = match.group(1)
|
|
2182
|
+
base = match.group(2)
|
|
2183
|
+
body = match.group(3)
|
|
2184
|
+
|
|
2185
|
+
fields = self._parse_class_fields(body)
|
|
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)
|
|
2189
|
+
|
|
2190
|
+
classes.append(ClassInfo(
|
|
2191
|
+
name=name,
|
|
2192
|
+
bases=[base] if base else [],
|
|
2193
|
+
methods=methods,
|
|
2194
|
+
fields=fields,
|
|
2195
|
+
constructors=constructors
|
|
2196
|
+
))
|
|
2197
|
+
|
|
2198
|
+
# Clear class fields after processing this class
|
|
2199
|
+
self._current_class_fields = set()
|
|
2200
|
+
|
|
2201
|
+
return classes
|
|
2202
|
+
|
|
2203
|
+
def _parse_structs(self, source: str) -> List[StructInfo]:
|
|
2204
|
+
structs = []
|
|
2205
|
+
|
|
2206
|
+
pattern = r'struct\s+(\w+)\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}'
|
|
2207
|
+
|
|
2208
|
+
for match in re.finditer(pattern, source):
|
|
2209
|
+
name = match.group(1)
|
|
2210
|
+
body = match.group(2)
|
|
2211
|
+
|
|
2212
|
+
fields = []
|
|
2213
|
+
field_pattern = r'(\w+(?:<[^>]+>)?(?:\s*[*&])?)\s+(\w+)\s*(?:=\s*[^;]+)?;'
|
|
2214
|
+
for fm in re.finditer(field_pattern, body):
|
|
2215
|
+
field_type = fm.group(1).strip()
|
|
2216
|
+
field_name = fm.group(2)
|
|
2217
|
+
if field_name not in ('if', 'for', 'while', 'return'):
|
|
2218
|
+
fields.append((field_name, self._convert_cpp_type(field_type)))
|
|
2219
|
+
|
|
2220
|
+
structs.append(StructInfo(name=name, fields=fields))
|
|
2221
|
+
|
|
2222
|
+
return structs
|
|
2223
|
+
|
|
2224
|
+
def _parse_params(self, params_str: str) -> List[Tuple[str, str]]:
|
|
2225
|
+
if not params_str.strip():
|
|
2226
|
+
return []
|
|
2227
|
+
|
|
2228
|
+
params = []
|
|
2229
|
+
depth = 0
|
|
2230
|
+
current = ''
|
|
2231
|
+
|
|
2232
|
+
for char in params_str:
|
|
2233
|
+
if char == '<':
|
|
2234
|
+
depth += 1
|
|
2235
|
+
elif char == '>':
|
|
2236
|
+
depth -= 1
|
|
2237
|
+
elif char == ',' and depth == 0:
|
|
2238
|
+
params.append(current.strip())
|
|
2239
|
+
current = ''
|
|
2240
|
+
continue
|
|
2241
|
+
current += char
|
|
2242
|
+
|
|
2243
|
+
if current.strip():
|
|
2244
|
+
params.append(current.strip())
|
|
2245
|
+
|
|
2246
|
+
result = []
|
|
2247
|
+
for param in params:
|
|
2248
|
+
param = param.strip()
|
|
2249
|
+
if not param:
|
|
2250
|
+
continue
|
|
2251
|
+
|
|
2252
|
+
param = re.sub(r'^const\s+', '', param)
|
|
2253
|
+
param = re.sub(r'\s*[&*]+\s*', ' ', param)
|
|
2254
|
+
|
|
2255
|
+
parts = param.rsplit(None, 1)
|
|
2256
|
+
if len(parts) == 2:
|
|
2257
|
+
ptype, pname = parts
|
|
2258
|
+
pname = re.sub(r'=.*$', '', pname).strip()
|
|
2259
|
+
result.append((pname, ptype))
|
|
2260
|
+
elif len(parts) == 1:
|
|
2261
|
+
result.append((parts[0], 'auto'))
|
|
2262
|
+
|
|
2263
|
+
return result
|
|
2264
|
+
|
|
2265
|
+
def _parse_class_fields(self, body: str) -> List[Tuple[str, str, Optional[str]]]:
|
|
2266
|
+
fields = []
|
|
2267
|
+
all_field_names = set() # v3.4.1: Track ALL fields for self. prefix
|
|
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
|
|
2271
|
+
sections = re.split(r'(?:public|private|protected)\s*:', body)
|
|
2272
|
+
|
|
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
|
+
|
|
2292
|
+
for match in re.finditer(field_pattern, public_section):
|
|
2293
|
+
field_type = match.group(1)
|
|
2294
|
+
field_name = match.group(2)
|
|
2295
|
+
default = match.group(3)
|
|
2296
|
+
|
|
2297
|
+
if '(' in field_type or field_type in ('return', 'if', 'for', 'while'):
|
|
2298
|
+
continue
|
|
2299
|
+
|
|
2300
|
+
py_default = self._convert_cpp_expr(default) if default else None
|
|
2301
|
+
fields.append((field_name, self._convert_cpp_type(field_type), py_default))
|
|
2302
|
+
|
|
2303
|
+
return fields
|
|
2304
|
+
|
|
2305
|
+
def _parse_class_methods(self, body: str, class_name: str, field_names: set = None) -> List[FunctionInfo]:
|
|
2306
|
+
methods = []
|
|
2307
|
+
|
|
2308
|
+
# Set current class fields for self. prefix during body conversion
|
|
2309
|
+
if field_names:
|
|
2310
|
+
self._current_class_fields = field_names
|
|
2311
|
+
|
|
2312
|
+
pattern = r'(?:(static|virtual)\s+)?(\w+(?:<[^>]+>)?(?:\s*[*&])?)\s+(\w+)\s*\(([^)]*)\)\s*(const)?\s*(?:\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}|;)'
|
|
2313
|
+
|
|
2314
|
+
for match in re.finditer(pattern, body):
|
|
2315
|
+
modifier = match.group(1)
|
|
2316
|
+
return_type = match.group(2).strip()
|
|
2317
|
+
name = match.group(3)
|
|
2318
|
+
params_str = match.group(4)
|
|
2319
|
+
is_const = match.group(5) is not None
|
|
2320
|
+
method_body = match.group(6) or ''
|
|
2321
|
+
|
|
2322
|
+
if name == class_name or name == f'~{class_name}':
|
|
2323
|
+
continue
|
|
2324
|
+
|
|
2325
|
+
params = self._parse_params(params_str)
|
|
2326
|
+
py_body = self._convert_cpp_body(method_body)
|
|
2327
|
+
|
|
2328
|
+
methods.append(FunctionInfo(
|
|
2329
|
+
name=name,
|
|
2330
|
+
return_type=self._convert_cpp_type(return_type),
|
|
2331
|
+
params=[(n, self._convert_cpp_type(t)) for n, t in params],
|
|
2332
|
+
body=py_body,
|
|
2333
|
+
is_method=True,
|
|
2334
|
+
is_static=(modifier == 'static'),
|
|
2335
|
+
is_const=is_const
|
|
2336
|
+
))
|
|
2337
|
+
|
|
2338
|
+
return methods
|
|
2339
|
+
|
|
2340
|
+
def _parse_constructors(self, body: str, class_name: str, field_names: set = None) -> List[FunctionInfo]:
|
|
2341
|
+
constructors = []
|
|
2342
|
+
|
|
2343
|
+
# Set current class fields for self. prefix during body conversion
|
|
2344
|
+
if field_names:
|
|
2345
|
+
self._current_class_fields = field_names
|
|
2346
|
+
|
|
2347
|
+
pattern = rf'{class_name}\s*\(([^)]*)\)\s*(?::\s*[^{{]+)?\s*\{{([^{{}}]*(?:\{{[^{{}}]*\}}[^{{}}]*)*)\}}'
|
|
2348
|
+
|
|
2349
|
+
for match in re.finditer(pattern, body):
|
|
2350
|
+
params_str = match.group(1)
|
|
2351
|
+
ctor_body = match.group(2)
|
|
2352
|
+
|
|
2353
|
+
params = self._parse_params(params_str)
|
|
2354
|
+
py_body = self._convert_cpp_body(ctor_body)
|
|
2355
|
+
|
|
2356
|
+
constructors.append(FunctionInfo(
|
|
2357
|
+
name='__init__',
|
|
2358
|
+
return_type='None',
|
|
2359
|
+
params=[(n, self._convert_cpp_type(t)) for n, t in params],
|
|
2360
|
+
body=py_body,
|
|
2361
|
+
is_method=True
|
|
2362
|
+
))
|
|
2363
|
+
|
|
2364
|
+
return constructors
|
|
2365
|
+
|
|
2366
|
+
def _convert_cpp_type(self, cpp_type: str) -> str:
|
|
2367
|
+
cpp_type = cpp_type.strip()
|
|
2368
|
+
cpp_type = re.sub(r'^const\s+', '', cpp_type)
|
|
2369
|
+
cpp_type = re.sub(r'\s*[&*]+\s*$', '', cpp_type)
|
|
2370
|
+
cpp_type = cpp_type.strip()
|
|
2371
|
+
|
|
2372
|
+
template_match = re.match(r'(\w+(?:::\w+)?)<(.+)>$', cpp_type)
|
|
2373
|
+
if template_match:
|
|
2374
|
+
container = template_match.group(1)
|
|
2375
|
+
inner = template_match.group(2)
|
|
2376
|
+
|
|
2377
|
+
py_container = CPP_TO_PY_TYPES.get(container, container)
|
|
2378
|
+
|
|
2379
|
+
inner_types = self._split_template_args(inner)
|
|
2380
|
+
py_inner = [self._convert_cpp_type(t) for t in inner_types]
|
|
2381
|
+
|
|
2382
|
+
if py_container == 'list':
|
|
2383
|
+
return f'List[{py_inner[0]}]' if py_inner else 'List'
|
|
2384
|
+
elif py_container == 'dict':
|
|
2385
|
+
if len(py_inner) >= 2:
|
|
2386
|
+
return f'Dict[{py_inner[0]}, {py_inner[1]}]'
|
|
2387
|
+
return 'Dict'
|
|
2388
|
+
elif py_container == 'set':
|
|
2389
|
+
return f'Set[{py_inner[0]}]' if py_inner else 'Set'
|
|
2390
|
+
elif py_container == 'tuple':
|
|
2391
|
+
return f'Tuple[{", ".join(py_inner)}]'
|
|
2392
|
+
elif py_container == 'Optional':
|
|
2393
|
+
return f'Optional[{py_inner[0]}]' if py_inner else 'Optional'
|
|
2394
|
+
|
|
2395
|
+
return CPP_TO_PY_TYPES.get(cpp_type, cpp_type)
|
|
2396
|
+
|
|
2397
|
+
def _split_template_args(self, args: str) -> List[str]:
|
|
2398
|
+
result = []
|
|
2399
|
+
depth = 0
|
|
2400
|
+
current = ''
|
|
2401
|
+
|
|
2402
|
+
for char in args:
|
|
2403
|
+
if char == '<':
|
|
2404
|
+
depth += 1
|
|
2405
|
+
current += char
|
|
2406
|
+
elif char == '>':
|
|
2407
|
+
depth -= 1
|
|
2408
|
+
current += char
|
|
2409
|
+
elif char == ',' and depth == 0:
|
|
2410
|
+
result.append(current.strip())
|
|
2411
|
+
current = ''
|
|
2412
|
+
else:
|
|
2413
|
+
current += char
|
|
2414
|
+
|
|
2415
|
+
if current.strip():
|
|
2416
|
+
result.append(current.strip())
|
|
2417
|
+
|
|
2418
|
+
return result
|
|
2419
|
+
|
|
2420
|
+
def _convert_cpp_body(self, body: str) -> str:
|
|
2421
|
+
if not body.strip():
|
|
2422
|
+
return 'pass'
|
|
2423
|
+
|
|
2424
|
+
lines = []
|
|
2425
|
+
statements = self._split_statements(body)
|
|
2426
|
+
|
|
2427
|
+
for stmt in statements:
|
|
2428
|
+
py_line = self._convert_cpp_statement(stmt)
|
|
2429
|
+
if py_line:
|
|
2430
|
+
lines.append(py_line)
|
|
2431
|
+
|
|
2432
|
+
return '\n'.join(lines) if lines else 'pass'
|
|
2433
|
+
|
|
2434
|
+
def _split_statements(self, body: str) -> List[str]:
|
|
2435
|
+
"""Split C++ body into statements, handling for loops correctly."""
|
|
2436
|
+
statements = []
|
|
2437
|
+
current = ''
|
|
2438
|
+
depth = 0
|
|
2439
|
+
in_for = False
|
|
2440
|
+
for_paren_depth = 0
|
|
2441
|
+
|
|
2442
|
+
i = 0
|
|
2443
|
+
while i < len(body):
|
|
2444
|
+
char = body[i]
|
|
2445
|
+
|
|
2446
|
+
if body[i:i+3] == 'for' and (i == 0 or not body[i-1].isalnum()):
|
|
2447
|
+
in_for = True
|
|
2448
|
+
|
|
2449
|
+
if char == '(':
|
|
2450
|
+
if in_for and for_paren_depth == 0:
|
|
2451
|
+
for_paren_depth = 1
|
|
2452
|
+
elif in_for:
|
|
2453
|
+
for_paren_depth += 1
|
|
2454
|
+
depth += 1
|
|
2455
|
+
elif char == ')':
|
|
2456
|
+
if in_for:
|
|
2457
|
+
for_paren_depth -= 1
|
|
2458
|
+
if for_paren_depth == 0:
|
|
2459
|
+
in_for = False
|
|
2460
|
+
depth -= 1
|
|
2461
|
+
elif char == '{':
|
|
2462
|
+
depth += 1
|
|
2463
|
+
elif char == '}':
|
|
2464
|
+
depth -= 1
|
|
2465
|
+
|
|
2466
|
+
if char == ';' and depth == 0 and not in_for:
|
|
2467
|
+
statements.append(current.strip())
|
|
2468
|
+
current = ''
|
|
2469
|
+
else:
|
|
2470
|
+
current += char
|
|
2471
|
+
|
|
2472
|
+
i += 1
|
|
2473
|
+
|
|
2474
|
+
if current.strip():
|
|
2475
|
+
statements.append(current.strip())
|
|
2476
|
+
|
|
2477
|
+
return statements
|
|
2478
|
+
|
|
2479
|
+
def _convert_cpp_statement(self, stmt: str) -> str:
|
|
2480
|
+
stmt = stmt.strip()
|
|
2481
|
+
if not stmt or stmt == '{' or stmt == '}':
|
|
2482
|
+
return ''
|
|
2483
|
+
|
|
2484
|
+
# Handle for loops
|
|
2485
|
+
for_match = re.match(r'for\s*\(\s*(?:size_t|int|auto)\s+(\w+)\s*=\s*(\d+)\s*;\s*\1\s*<\s*(.+?)\s*;\s*(?:\+\+\1|\1\s*\+\+|\1\s*\+=\s*\d+)\s*\)\s*\{(.*)\}', stmt, re.DOTALL)
|
|
2486
|
+
if for_match:
|
|
2487
|
+
var = for_match.group(1)
|
|
2488
|
+
start = for_match.group(2)
|
|
2489
|
+
end = self._convert_cpp_expr(for_match.group(3))
|
|
2490
|
+
body = for_match.group(4)
|
|
2491
|
+
body_stmts = self._split_statements(body)
|
|
2492
|
+
body_lines = []
|
|
2493
|
+
for s in body_stmts:
|
|
2494
|
+
converted = self._convert_cpp_statement(s)
|
|
2495
|
+
if converted:
|
|
2496
|
+
body_lines.append(f' {converted}')
|
|
2497
|
+
body_str = '\n'.join(body_lines) if body_lines else ' pass'
|
|
2498
|
+
if start == '0':
|
|
2499
|
+
return f'for {var} in range({end}):\n{body_str}'
|
|
2500
|
+
else:
|
|
2501
|
+
return f'for {var} in range({start}, {end}):\n{body_str}'
|
|
2502
|
+
|
|
2503
|
+
# Range-based for loop
|
|
2504
|
+
range_for_match = re.match(r'for\s*\(\s*(?:const\s+)?(?:auto|[\w:]+)[&*]?\s+(\w+)\s*:\s*(.+?)\s*\)\s*\{(.*)\}', stmt, re.DOTALL)
|
|
2505
|
+
if range_for_match:
|
|
2506
|
+
var = range_for_match.group(1)
|
|
2507
|
+
container = self._convert_cpp_expr(range_for_match.group(2))
|
|
2508
|
+
body = range_for_match.group(3)
|
|
2509
|
+
body_stmts = self._split_statements(body)
|
|
2510
|
+
body_lines = []
|
|
2511
|
+
for s in body_stmts:
|
|
2512
|
+
converted = self._convert_cpp_statement(s)
|
|
2513
|
+
if converted:
|
|
2514
|
+
body_lines.append(f' {converted}')
|
|
2515
|
+
body_str = '\n'.join(body_lines) if body_lines else ' pass'
|
|
2516
|
+
return f'for {var} in {container}:\n{body_str}'
|
|
2517
|
+
|
|
2518
|
+
# If statement
|
|
2519
|
+
if_match = re.match(r'if\s*\((.+?)\)\s*\{(.*)\}(?:\s*else\s*\{(.*)\})?', stmt, re.DOTALL)
|
|
2520
|
+
if if_match:
|
|
2521
|
+
cond = self._convert_cpp_expr(if_match.group(1))
|
|
2522
|
+
if_body = if_match.group(2)
|
|
2523
|
+
else_body = if_match.group(3)
|
|
2524
|
+
if_stmts = self._split_statements(if_body)
|
|
2525
|
+
if_lines = []
|
|
2526
|
+
for s in if_stmts:
|
|
2527
|
+
converted = self._convert_cpp_statement(s)
|
|
2528
|
+
if converted:
|
|
2529
|
+
if_lines.append(f' {converted}')
|
|
2530
|
+
if_str = '\n'.join(if_lines) if if_lines else ' pass'
|
|
2531
|
+
result = f'if {cond}:\n{if_str}'
|
|
2532
|
+
if else_body:
|
|
2533
|
+
else_stmts = self._split_statements(else_body)
|
|
2534
|
+
else_lines = []
|
|
2535
|
+
for s in else_stmts:
|
|
2536
|
+
converted = self._convert_cpp_statement(s)
|
|
2537
|
+
if converted:
|
|
2538
|
+
else_lines.append(f' {converted}')
|
|
2539
|
+
else_str = '\n'.join(else_lines) if else_lines else ' pass'
|
|
2540
|
+
result += f'\nelse:\n{else_str}'
|
|
2541
|
+
return result
|
|
2542
|
+
|
|
2543
|
+
# Single line if with continue/break
|
|
2544
|
+
if_single = re.match(r'if\s*\((.+?)\)\s*(continue|break|return[^;]*)', stmt)
|
|
2545
|
+
if if_single:
|
|
2546
|
+
cond = self._convert_cpp_expr(if_single.group(1))
|
|
2547
|
+
action = if_single.group(2).strip()
|
|
2548
|
+
if action.startswith('return'):
|
|
2549
|
+
ret_val = action[6:].strip()
|
|
2550
|
+
if ret_val:
|
|
2551
|
+
return f'if {cond}:\n return {self._convert_cpp_expr(ret_val)}'
|
|
2552
|
+
return f'if {cond}:\n return'
|
|
2553
|
+
return f'if {cond}:\n {action}'
|
|
2554
|
+
|
|
2555
|
+
if stmt.startswith('return'):
|
|
2556
|
+
expr = stmt[6:].strip()
|
|
2557
|
+
if not expr:
|
|
2558
|
+
return 'return'
|
|
2559
|
+
return f'return {self._convert_cpp_expr(expr)}'
|
|
2560
|
+
|
|
2561
|
+
if stmt == 'continue':
|
|
2562
|
+
return 'continue'
|
|
2563
|
+
|
|
2564
|
+
if stmt == 'break':
|
|
2565
|
+
return 'break'
|
|
2566
|
+
|
|
2567
|
+
# Variable declaration with initialization
|
|
2568
|
+
var_match = re.match(r'(?:const\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*=\s*(.+)$', stmt)
|
|
2569
|
+
if var_match:
|
|
2570
|
+
var_name = var_match.group(2)
|
|
2571
|
+
value = self._convert_cpp_expr(var_match.group(3))
|
|
2572
|
+
return f'{var_name} = {value}'
|
|
2573
|
+
|
|
2574
|
+
# Variable declaration without initialization
|
|
2575
|
+
decl_match = re.match(r'(?:const\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)$', stmt)
|
|
2576
|
+
if decl_match:
|
|
2577
|
+
var_type = decl_match.group(1)
|
|
2578
|
+
var_name = decl_match.group(2)
|
|
2579
|
+
if var_name not in ('if', 'for', 'while', 'return'):
|
|
2580
|
+
return f'{var_name} = {self._get_default_value(var_type)}'
|
|
2581
|
+
|
|
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)
|
|
2584
|
+
if assign_match:
|
|
2585
|
+
target = assign_match.group(1)
|
|
2586
|
+
# v3.3.22: Apply full expression conversion to target as well
|
|
2587
|
+
target = self._convert_cpp_expr(target)
|
|
2588
|
+
value = self._convert_cpp_expr(assign_match.group(2))
|
|
2589
|
+
return f'{target} = {value}'
|
|
2590
|
+
|
|
2591
|
+
# Augmented assignment
|
|
2592
|
+
aug_match = re.match(r'(\w+(?:\.\w+)*)\s*(\+\+|--|\+=|-=|\*=|/=)', stmt)
|
|
2593
|
+
if aug_match:
|
|
2594
|
+
# v3.3.22: Apply full expression conversion to target
|
|
2595
|
+
target = self._convert_cpp_expr(aug_match.group(1))
|
|
2596
|
+
op = aug_match.group(2)
|
|
2597
|
+
if op == '++':
|
|
2598
|
+
return f'{target} += 1'
|
|
2599
|
+
elif op == '--':
|
|
2600
|
+
return f'{target} -= 1'
|
|
2601
|
+
# Handle += with value
|
|
2602
|
+
rest = stmt[aug_match.end():].strip()
|
|
2603
|
+
if rest:
|
|
2604
|
+
return f'{target} {op} {self._convert_cpp_expr(rest)}'
|
|
2605
|
+
|
|
2606
|
+
return self._convert_cpp_expr(stmt)
|
|
2607
|
+
|
|
2608
|
+
def _get_default_value(self, cpp_type: str) -> str:
|
|
2609
|
+
"""Get default value for a C++ type in Python."""
|
|
2610
|
+
cpp_type = cpp_type.strip()
|
|
2611
|
+
if cpp_type in ('int', 'long', 'short', 'size_t'):
|
|
2612
|
+
return '0'
|
|
2613
|
+
elif cpp_type in ('float', 'double'):
|
|
2614
|
+
return '0.0'
|
|
2615
|
+
elif cpp_type == 'bool':
|
|
2616
|
+
return 'False'
|
|
2617
|
+
elif cpp_type in ('std::string', 'string'):
|
|
2618
|
+
return "''"
|
|
2619
|
+
elif 'vector' in cpp_type:
|
|
2620
|
+
return '[]'
|
|
2621
|
+
elif 'map' in cpp_type:
|
|
2622
|
+
return '{}'
|
|
2623
|
+
else:
|
|
2624
|
+
return f'{cpp_type}()'
|
|
2625
|
+
|
|
2626
|
+
def _convert_cpp_expr(self, expr: str) -> str:
|
|
2627
|
+
if not expr:
|
|
2628
|
+
return ''
|
|
2629
|
+
|
|
2630
|
+
expr = expr.strip()
|
|
2631
|
+
|
|
2632
|
+
# Handle C++ patterns
|
|
2633
|
+
expr = expr.replace('this->', 'self.')
|
|
2634
|
+
expr = expr.replace('->', '.')
|
|
2635
|
+
expr = expr.replace('::', '.')
|
|
2636
|
+
expr = expr.replace('nullptr', 'None')
|
|
2637
|
+
expr = expr.replace('true', 'True')
|
|
2638
|
+
expr = expr.replace('false', 'False')
|
|
2639
|
+
expr = expr.replace('&&', ' and ')
|
|
2640
|
+
expr = expr.replace('||', ' or ')
|
|
2641
|
+
|
|
2642
|
+
# Handle M_PI
|
|
2643
|
+
expr = re.sub(r'\bM_PI\b', 'math.pi', expr)
|
|
2644
|
+
|
|
2645
|
+
# Handle std:: functions
|
|
2646
|
+
expr = re.sub(r'std::sqrt\(([^)]+)\)', r'math.sqrt(\1)', expr)
|
|
2647
|
+
expr = re.sub(r'std::cos\(([^)]+)\)', r'math.cos(\1)', expr)
|
|
2648
|
+
expr = re.sub(r'std::sin\(([^)]+)\)', r'math.sin(\1)', expr)
|
|
2649
|
+
expr = re.sub(r'std::tan\(([^)]+)\)', r'math.tan(\1)', expr)
|
|
2650
|
+
expr = re.sub(r'std::abs\(([^)]+)\)', r'abs(\1)', expr)
|
|
2651
|
+
expr = re.sub(r'std::pow\(([^,]+),\s*([^)]+)\)', r'(\1) ** (\2)', expr)
|
|
2652
|
+
expr = re.sub(r'std::min\(([^,]+),\s*([^)]+)\)', r'min(\1, \2)', expr)
|
|
2653
|
+
expr = re.sub(r'std::max\(([^,]+),\s*([^)]+)\)', r'max(\1, \2)', expr)
|
|
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
|
+
|
|
2681
|
+
# Handle .size() -> len()
|
|
2682
|
+
expr = re.sub(r'(\w+)\.size\(\)', r'len(\1)', expr)
|
|
2683
|
+
|
|
2684
|
+
# Handle .push_back -> .append
|
|
2685
|
+
expr = re.sub(r'(\w+)\.push_back\(([^)]+)\)', r'\1.append(\2)', expr)
|
|
2686
|
+
|
|
2687
|
+
# Handle .empty() -> len() == 0
|
|
2688
|
+
expr = re.sub(r'(\w+)\.empty\(\)', r'len(\1) == 0', expr)
|
|
2689
|
+
|
|
2690
|
+
# Handle static_cast
|
|
2691
|
+
expr = re.sub(r'static_cast<\w+>\(([^)]+)\)', r'\1', expr)
|
|
2692
|
+
|
|
2693
|
+
# Handle std::to_string
|
|
2694
|
+
expr = re.sub(r'std::to_string\(([^)]+)\)', r'str(\1)', expr)
|
|
2695
|
+
|
|
2696
|
+
# Handle std::stoi/stof
|
|
2697
|
+
expr = re.sub(r'std::stoi\(([^)]+)\)', r'int(\1)', expr)
|
|
2698
|
+
expr = re.sub(r'std::stof\(([^)]+)\)', r'float(\1)', expr)
|
|
2699
|
+
|
|
2700
|
+
# Handle vector/container literals
|
|
2701
|
+
expr = re.sub(r'std::vector<[^>]+>\{([^}]*)\}', r'[\1]', expr)
|
|
2702
|
+
expr = re.sub(r'std::unordered_map<[^>]+>\{([^}]*)\}', r'{\1}', expr)
|
|
2703
|
+
expr = re.sub(r'std::unordered_set<[^>]+>\{([^}]*)\}', r'{\1}', expr)
|
|
2704
|
+
expr = re.sub(r'std::make_tuple\(([^)]*)\)', r'(\1)', expr)
|
|
2705
|
+
|
|
2706
|
+
# Handle cout
|
|
2707
|
+
expr = re.sub(r'std::cout\s*<<\s*(.+?)(?:\s*<<\s*std::endl)?', r'print(\1)', expr)
|
|
2708
|
+
|
|
2709
|
+
# Handle string quotes
|
|
2710
|
+
expr = re.sub(r'"([^"]*)"', r"'\1'", expr)
|
|
2711
|
+
|
|
2712
|
+
# Clean up remaining std::
|
|
2713
|
+
expr = expr.replace('std.', '')
|
|
2714
|
+
|
|
2715
|
+
# Handle negation !
|
|
2716
|
+
expr = re.sub(r'!(\w)', r'not \1', expr)
|
|
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
|
+
|
|
2738
|
+
return expr
|
|
2739
|
+
|
|
2740
|
+
def _generate_struct(self, struct: StructInfo) -> List[str]:
|
|
2741
|
+
lines = ['@dataclass']
|
|
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}:')
|
|
2745
|
+
|
|
2746
|
+
if not struct.fields:
|
|
2747
|
+
lines.append(f'{self.indent}pass')
|
|
2748
|
+
else:
|
|
2749
|
+
for fname, ftype in struct.fields:
|
|
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}')
|
|
2753
|
+
|
|
2754
|
+
return lines
|
|
2755
|
+
|
|
2756
|
+
def _generate_class(self, cls: ClassInfo) -> List[str]:
|
|
2757
|
+
lines = []
|
|
2758
|
+
|
|
2759
|
+
# v3.4.1: Escape Python keywords and C++ reserved words in class names
|
|
2760
|
+
class_name = _escape_identifier(cls.name)
|
|
2761
|
+
if cls.bases:
|
|
2762
|
+
lines.append(f'class {class_name}({", ".join(cls.bases)}):')
|
|
2763
|
+
else:
|
|
2764
|
+
lines.append(f'class {class_name}:')
|
|
2765
|
+
|
|
2766
|
+
if not cls.fields and not cls.methods and not cls.constructors:
|
|
2767
|
+
lines.append(f'{self.indent}pass')
|
|
2768
|
+
return lines
|
|
2769
|
+
|
|
2770
|
+
for ctor in cls.constructors:
|
|
2771
|
+
lines.extend(self._generate_method(ctor, is_init=True))
|
|
2772
|
+
|
|
2773
|
+
for method in cls.methods:
|
|
2774
|
+
lines.extend(self._generate_method(method))
|
|
2775
|
+
|
|
2776
|
+
return lines
|
|
2777
|
+
|
|
2778
|
+
def _generate_method(self, method: FunctionInfo, is_init: bool = False) -> List[str]:
|
|
2779
|
+
lines = []
|
|
2780
|
+
|
|
2781
|
+
if method.is_static:
|
|
2782
|
+
lines.append(f'{self.indent}@staticmethod')
|
|
2783
|
+
|
|
2784
|
+
params = ['self'] if not method.is_static else []
|
|
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)
|
|
2788
|
+
if ptype and ptype != 'Any':
|
|
2789
|
+
params.append(f'{escaped_pname}: {ptype}')
|
|
2790
|
+
else:
|
|
2791
|
+
params.append(escaped_pname)
|
|
2792
|
+
|
|
2793
|
+
ret_type = ''
|
|
2794
|
+
if not is_init and method.return_type and method.return_type != 'None':
|
|
2795
|
+
ret_type = f' -> {method.return_type}'
|
|
2796
|
+
|
|
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)
|
|
2799
|
+
lines.append(f'{self.indent}def {method_name}({", ".join(params)}){ret_type}:')
|
|
2800
|
+
|
|
2801
|
+
body_lines = method.body.split('\n')
|
|
2802
|
+
if body_lines and body_lines[0].strip():
|
|
2803
|
+
for bl in body_lines:
|
|
2804
|
+
lines.append(f'{self.indent}{self.indent}{bl}')
|
|
2805
|
+
else:
|
|
2806
|
+
lines.append(f'{self.indent}{self.indent}pass')
|
|
2807
|
+
|
|
2808
|
+
return lines
|
|
2809
|
+
|
|
2810
|
+
def _generate_function(self, func: FunctionInfo) -> List[str]:
|
|
2811
|
+
lines = []
|
|
2812
|
+
|
|
2813
|
+
params = []
|
|
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)
|
|
2817
|
+
if ptype and ptype != 'Any':
|
|
2818
|
+
params.append(f'{escaped_pname}: {ptype}')
|
|
2819
|
+
else:
|
|
2820
|
+
params.append(escaped_pname)
|
|
2821
|
+
|
|
2822
|
+
ret_type = ''
|
|
2823
|
+
if func.return_type and func.return_type != 'None':
|
|
2824
|
+
ret_type = f' -> {func.return_type}'
|
|
2825
|
+
|
|
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}:')
|
|
2829
|
+
|
|
2830
|
+
body_lines = func.body.split('\n')
|
|
2831
|
+
if body_lines and body_lines[0].strip():
|
|
2832
|
+
for bl in body_lines:
|
|
2833
|
+
lines.append(f'{self.indent}{bl}')
|
|
2834
|
+
else:
|
|
2835
|
+
lines.append(f'{self.indent}pass')
|
|
2836
|
+
|
|
2837
|
+
return lines
|
|
2838
|
+
|
|
2839
|
+
|
|
2840
|
+
def convert_python_to_cpp(source: str, module_name: str) -> Tuple[str, str]:
|
|
2841
|
+
"""Convert Python to C++. Returns (cpp_content, header_content)."""
|
|
2842
|
+
converter = PythonToCppConverter()
|
|
2843
|
+
return converter.convert(source, module_name)
|
|
2844
|
+
|
|
2845
|
+
|
|
2846
|
+
def convert_cpp_to_python(source: str, module_name: str) -> str:
|
|
2847
|
+
"""Convert C++ to Python. Returns python content."""
|
|
2848
|
+
converter = CppToPythonConverter()
|
|
2849
|
+
return converter.convert(source, module_name)
|
|
2850
|
+
|
|
2851
|
+
|
|
2852
|
+
# ============================================================================
|
|
2853
|
+
# AI-Assisted Conversion System
|
|
2854
|
+
# ============================================================================
|
|
2855
|
+
|
|
2856
|
+
# Comprehensive rulebase for AI-assisted code conversion
|
|
2857
|
+
AI_CONVERSION_RULEBASE = {
|
|
2858
|
+
'python_to_cpp': {
|
|
2859
|
+
# Type mapping rules
|
|
2860
|
+
'types': {
|
|
2861
|
+
'int': {'cpp': 'int', 'notes': 'Direct mapping'},
|
|
2862
|
+
'float': {'cpp': 'double', 'notes': 'Python float is 64-bit'},
|
|
2863
|
+
'str': {'cpp': 'std::string', 'notes': 'Requires <string>'},
|
|
2864
|
+
'bool': {'cpp': 'bool', 'notes': 'Direct mapping'},
|
|
2865
|
+
'bytes': {'cpp': 'std::vector<uint8_t>', 'notes': 'Raw bytes'},
|
|
2866
|
+
'bytearray': {'cpp': 'std::vector<uint8_t>', 'notes': 'Mutable bytes'},
|
|
2867
|
+
'list': {'cpp': 'std::vector<T>', 'notes': 'Requires element type'},
|
|
2868
|
+
'dict': {'cpp': 'std::unordered_map<K,V>', 'notes': 'Hash map'},
|
|
2869
|
+
'set': {'cpp': 'std::unordered_set<T>', 'notes': 'Hash set'},
|
|
2870
|
+
'tuple': {'cpp': 'std::tuple<...>', 'notes': 'Fixed-size'},
|
|
2871
|
+
'None': {'cpp': 'void', 'notes': 'Return type'},
|
|
2872
|
+
'Any': {'cpp': 'auto', 'notes': 'Type deduction'},
|
|
2873
|
+
'Optional': {'cpp': 'std::optional<T>', 'notes': 'Requires <optional>'},
|
|
2874
|
+
'Union': {'cpp': 'std::variant<...>', 'notes': 'Requires <variant>'},
|
|
2875
|
+
'Callable': {'cpp': 'std::function<R(Args...)>', 'notes': 'Requires <functional>'},
|
|
2876
|
+
},
|
|
2877
|
+
# Built-in function mappings
|
|
2878
|
+
'builtins': {
|
|
2879
|
+
'len': {'cpp': '.size()', 'pattern': 'len({x})', 'to': '{x}.size()'},
|
|
2880
|
+
'print': {'cpp': 'std::cout', 'notes': 'Requires <iostream>'},
|
|
2881
|
+
'range': {'cpp': 'for loop', 'notes': 'Convert to C-style for'},
|
|
2882
|
+
'str': {'cpp': 'std::to_string', 'notes': 'Numeric conversion'},
|
|
2883
|
+
'int': {'cpp': 'static_cast<int>', 'notes': 'Type cast'},
|
|
2884
|
+
'float': {'cpp': 'static_cast<double>', 'notes': 'Type cast'},
|
|
2885
|
+
'abs': {'cpp': 'std::abs', 'notes': 'Requires <cmath>'},
|
|
2886
|
+
'min': {'cpp': 'std::min', 'notes': 'Requires <algorithm>'},
|
|
2887
|
+
'max': {'cpp': 'std::max', 'notes': 'Requires <algorithm>'},
|
|
2888
|
+
'sum': {'cpp': 'std::accumulate', 'notes': 'Requires <numeric>'},
|
|
2889
|
+
'sorted': {'cpp': 'std::sort', 'notes': 'In-place, use copy'},
|
|
2890
|
+
'reversed': {'cpp': 'std::reverse', 'notes': 'In-place, use copy'},
|
|
2891
|
+
'enumerate': {'cpp': 'index loop', 'notes': 'Manual index tracking'},
|
|
2892
|
+
'zip': {'cpp': 'parallel iteration', 'notes': 'Manual or ranges::zip'},
|
|
2893
|
+
'map': {'cpp': 'std::transform', 'notes': 'Requires <algorithm>'},
|
|
2894
|
+
'filter': {'cpp': 'std::copy_if', 'notes': 'Requires <algorithm>'},
|
|
2895
|
+
'any': {'cpp': 'std::any_of', 'notes': 'Requires <algorithm>'},
|
|
2896
|
+
'all': {'cpp': 'std::all_of', 'notes': 'Requires <algorithm>'},
|
|
2897
|
+
'isinstance': {'cpp': 'dynamic_cast', 'notes': 'Runtime type check'},
|
|
2898
|
+
'type': {'cpp': 'typeid', 'notes': 'Requires <typeinfo>'},
|
|
2899
|
+
'id': {'cpp': '&', 'notes': 'Address as identity'},
|
|
2900
|
+
'hash': {'cpp': 'std::hash', 'notes': 'Requires <functional>'},
|
|
2901
|
+
'open': {'cpp': 'std::fstream', 'notes': 'Requires <fstream>'},
|
|
2902
|
+
'input': {'cpp': 'std::cin', 'notes': 'Requires <iostream>'},
|
|
2903
|
+
},
|
|
2904
|
+
# Python-unique patterns that need workarounds
|
|
2905
|
+
'workarounds': {
|
|
2906
|
+
'list_comprehension': {
|
|
2907
|
+
'pattern': '[expr for x in iterable]',
|
|
2908
|
+
'cpp': 'Lambda IIFE with loop',
|
|
2909
|
+
'example': '[&]() { std::vector<T> r; for(auto& x : iterable) r.push_back(expr); return r; }()',
|
|
2910
|
+
},
|
|
2911
|
+
'dict_comprehension': {
|
|
2912
|
+
'pattern': '{k: v for ...}',
|
|
2913
|
+
'cpp': 'Lambda IIFE with loop',
|
|
2914
|
+
'notes': 'Similar to list comprehension',
|
|
2915
|
+
},
|
|
2916
|
+
'generator': {
|
|
2917
|
+
'pattern': 'yield x',
|
|
2918
|
+
'cpp': 'Iterator class or callback',
|
|
2919
|
+
'notes': 'No direct equivalent, use custom iterator',
|
|
2920
|
+
},
|
|
2921
|
+
'async_await': {
|
|
2922
|
+
'pattern': 'async def / await',
|
|
2923
|
+
'cpp': 'std::future/std::async or coroutines (C++20)',
|
|
2924
|
+
'notes': 'Requires threading support',
|
|
2925
|
+
},
|
|
2926
|
+
'decorators': {
|
|
2927
|
+
'pattern': '@decorator',
|
|
2928
|
+
'cpp': 'Wrapper function or template',
|
|
2929
|
+
'notes': 'Manual wrapping required',
|
|
2930
|
+
},
|
|
2931
|
+
'context_manager': {
|
|
2932
|
+
'pattern': 'with ... as x:',
|
|
2933
|
+
'cpp': 'RAII class',
|
|
2934
|
+
'notes': 'Use constructor/destructor pattern',
|
|
2935
|
+
},
|
|
2936
|
+
'multiple_inheritance': {
|
|
2937
|
+
'pattern': 'class A(B, C)',
|
|
2938
|
+
'cpp': 'Multiple inheritance',
|
|
2939
|
+
'notes': 'Virtual inheritance may be needed',
|
|
2940
|
+
},
|
|
2941
|
+
'duck_typing': {
|
|
2942
|
+
'pattern': 'Dynamic attribute access',
|
|
2943
|
+
'cpp': 'Templates or concepts (C++20)',
|
|
2944
|
+
'notes': 'Use template constraints',
|
|
2945
|
+
},
|
|
2946
|
+
'slice_assignment': {
|
|
2947
|
+
'pattern': 'a[1:3] = [x, y]',
|
|
2948
|
+
'cpp': 'Vector operations',
|
|
2949
|
+
'notes': 'Use erase + insert',
|
|
2950
|
+
},
|
|
2951
|
+
'unpacking': {
|
|
2952
|
+
'pattern': 'a, b = func()',
|
|
2953
|
+
'cpp': 'structured bindings (C++17)',
|
|
2954
|
+
'notes': 'auto [a, b] = func();',
|
|
2955
|
+
},
|
|
2956
|
+
},
|
|
2957
|
+
# Method mappings for common types
|
|
2958
|
+
'methods': {
|
|
2959
|
+
'list.append': {'cpp': 'push_back', 'notes': 'Same semantics'},
|
|
2960
|
+
'list.extend': {'cpp': 'insert(end, begin, end)', 'notes': 'Range insert'},
|
|
2961
|
+
'list.insert': {'cpp': 'insert(begin+i, val)', 'notes': 'Index insert'},
|
|
2962
|
+
'list.remove': {'cpp': 'erase(remove(...), end())', 'notes': 'Erase-remove idiom'},
|
|
2963
|
+
'list.pop': {'cpp': 'back() + pop_back()', 'notes': 'Two operations'},
|
|
2964
|
+
'list.clear': {'cpp': 'clear()', 'notes': 'Same name'},
|
|
2965
|
+
'list.index': {'cpp': 'find + distance', 'notes': 'No direct method'},
|
|
2966
|
+
'list.count': {'cpp': 'std::count', 'notes': 'Requires <algorithm>'},
|
|
2967
|
+
'list.sort': {'cpp': 'std::sort', 'notes': 'Requires <algorithm>'},
|
|
2968
|
+
'list.reverse': {'cpp': 'std::reverse', 'notes': 'Requires <algorithm>'},
|
|
2969
|
+
'str.split': {'cpp': 'Manual or stringstream', 'notes': 'No direct method'},
|
|
2970
|
+
'str.join': {'cpp': 'Loop with append', 'notes': 'No direct method'},
|
|
2971
|
+
'str.strip': {'cpp': 'Manual trim', 'notes': 'No direct method'},
|
|
2972
|
+
'str.replace': {'cpp': 'Manual or regex_replace', 'notes': 'Requires loop'},
|
|
2973
|
+
'str.find': {'cpp': 'find()', 'notes': 'Returns size_t'},
|
|
2974
|
+
'str.startswith': {'cpp': 'substr(0, n) == prefix', 'notes': 'Manual check'},
|
|
2975
|
+
'str.endswith': {'cpp': 'substr(len-n) == suffix', 'notes': 'Manual check'},
|
|
2976
|
+
'str.upper': {'cpp': 'std::transform with toupper', 'notes': 'Requires <cctype>'},
|
|
2977
|
+
'str.lower': {'cpp': 'std::transform with tolower', 'notes': 'Requires <cctype>'},
|
|
2978
|
+
'str.format': {'cpp': 'std::format (C++20) or sprintf', 'notes': 'Format string'},
|
|
2979
|
+
'dict.keys': {'cpp': 'Loop or ranges', 'notes': 'No direct method'},
|
|
2980
|
+
'dict.values': {'cpp': 'Loop or ranges', 'notes': 'No direct method'},
|
|
2981
|
+
'dict.items': {'cpp': 'Loop over pairs', 'notes': 'Iterate map directly'},
|
|
2982
|
+
'dict.get': {'cpp': 'find + check', 'notes': 'Manual default handling'},
|
|
2983
|
+
'dict.update': {'cpp': 'insert or merge', 'notes': 'Loop or ranges'},
|
|
2984
|
+
'set.add': {'cpp': 'insert', 'notes': 'Same semantics'},
|
|
2985
|
+
'set.remove': {'cpp': 'erase', 'notes': 'Same semantics'},
|
|
2986
|
+
'set.union': {'cpp': 'set_union', 'notes': 'Requires <algorithm>'},
|
|
2987
|
+
'set.intersection': {'cpp': 'set_intersection', 'notes': 'Requires <algorithm>'},
|
|
2988
|
+
},
|
|
2989
|
+
},
|
|
2990
|
+
'cpp_to_python': {
|
|
2991
|
+
# Type mapping rules
|
|
2992
|
+
'types': {
|
|
2993
|
+
'int': {'python': 'int', 'notes': 'Direct mapping'},
|
|
2994
|
+
'long': {'python': 'int', 'notes': 'Python int is arbitrary precision'},
|
|
2995
|
+
'long long': {'python': 'int', 'notes': 'Python int is arbitrary precision'},
|
|
2996
|
+
'short': {'python': 'int', 'notes': 'Python int is arbitrary precision'},
|
|
2997
|
+
'unsigned': {'python': 'int', 'notes': 'Python handles large numbers'},
|
|
2998
|
+
'size_t': {'python': 'int', 'notes': 'Python int is arbitrary precision'},
|
|
2999
|
+
'float': {'python': 'float', 'notes': 'Direct mapping'},
|
|
3000
|
+
'double': {'python': 'float', 'notes': 'Python float is 64-bit'},
|
|
3001
|
+
'bool': {'python': 'bool', 'notes': 'Direct mapping'},
|
|
3002
|
+
'char': {'python': 'str', 'notes': 'Single character'},
|
|
3003
|
+
'std::string': {'python': 'str', 'notes': 'Direct mapping'},
|
|
3004
|
+
'string': {'python': 'str', 'notes': 'Direct mapping'},
|
|
3005
|
+
'void': {'python': 'None', 'notes': 'Return type'},
|
|
3006
|
+
'auto': {'python': 'Any', 'notes': 'Type deduction'},
|
|
3007
|
+
'std::vector': {'python': 'List', 'notes': 'Requires typing'},
|
|
3008
|
+
'vector': {'python': 'List', 'notes': 'Requires typing'},
|
|
3009
|
+
'std::array': {'python': 'List', 'notes': 'Fixed-size to dynamic'},
|
|
3010
|
+
'std::map': {'python': 'Dict', 'notes': 'Ordered dict'},
|
|
3011
|
+
'std::unordered_map': {'python': 'Dict', 'notes': 'Hash map'},
|
|
3012
|
+
'std::set': {'python': 'Set', 'notes': 'Ordered set'},
|
|
3013
|
+
'std::unordered_set': {'python': 'Set', 'notes': 'Hash set'},
|
|
3014
|
+
'std::tuple': {'python': 'Tuple', 'notes': 'Direct mapping'},
|
|
3015
|
+
'std::optional': {'python': 'Optional', 'notes': 'Requires typing'},
|
|
3016
|
+
'std::variant': {'python': 'Union', 'notes': 'Requires typing'},
|
|
3017
|
+
'std::function': {'python': 'Callable', 'notes': 'Requires typing'},
|
|
3018
|
+
'std::shared_ptr': {'python': 'object', 'notes': 'Python handles GC'},
|
|
3019
|
+
'std::unique_ptr': {'python': 'object', 'notes': 'Python handles GC'},
|
|
3020
|
+
},
|
|
3021
|
+
# C++ patterns to Python
|
|
3022
|
+
'patterns': {
|
|
3023
|
+
'for_index': {
|
|
3024
|
+
'cpp': 'for (size_t i = 0; i < n; ++i)',
|
|
3025
|
+
'python': 'for i in range(n)',
|
|
3026
|
+
},
|
|
3027
|
+
'for_range': {
|
|
3028
|
+
'cpp': 'for (auto& x : container)',
|
|
3029
|
+
'python': 'for x in container',
|
|
3030
|
+
},
|
|
3031
|
+
'nullptr': {
|
|
3032
|
+
'cpp': 'nullptr',
|
|
3033
|
+
'python': 'None',
|
|
3034
|
+
},
|
|
3035
|
+
'true_false': {
|
|
3036
|
+
'cpp': 'true/false',
|
|
3037
|
+
'python': 'True/False',
|
|
3038
|
+
},
|
|
3039
|
+
'this_pointer': {
|
|
3040
|
+
'cpp': 'this->member',
|
|
3041
|
+
'python': 'self.member',
|
|
3042
|
+
},
|
|
3043
|
+
'scope_resolution': {
|
|
3044
|
+
'cpp': 'Class::method',
|
|
3045
|
+
'python': 'Class.method',
|
|
3046
|
+
},
|
|
3047
|
+
'stream_io': {
|
|
3048
|
+
'cpp': 'std::cout << x',
|
|
3049
|
+
'python': 'print(x)',
|
|
3050
|
+
},
|
|
3051
|
+
'lambda': {
|
|
3052
|
+
'cpp': '[&](auto x) { return x; }',
|
|
3053
|
+
'python': 'lambda x: x',
|
|
3054
|
+
},
|
|
3055
|
+
},
|
|
3056
|
+
# C++ unique features that need workarounds
|
|
3057
|
+
'workarounds': {
|
|
3058
|
+
'templates': {
|
|
3059
|
+
'pattern': 'template<typename T>',
|
|
3060
|
+
'python': 'Generic[T] or Any',
|
|
3061
|
+
'notes': 'Use typing.Generic for type hints',
|
|
3062
|
+
},
|
|
3063
|
+
'pointers': {
|
|
3064
|
+
'pattern': 'T* ptr',
|
|
3065
|
+
'python': 'Reference semantics (native)',
|
|
3066
|
+
'notes': 'Python uses references by default',
|
|
3067
|
+
},
|
|
3068
|
+
'references': {
|
|
3069
|
+
'pattern': 'T& ref',
|
|
3070
|
+
'python': 'Reference semantics (native)',
|
|
3071
|
+
'notes': 'Python uses references by default',
|
|
3072
|
+
},
|
|
3073
|
+
'const': {
|
|
3074
|
+
'pattern': 'const T&',
|
|
3075
|
+
'python': 'No direct equivalent',
|
|
3076
|
+
'notes': 'Use conventions or @property',
|
|
3077
|
+
},
|
|
3078
|
+
'operator_overload': {
|
|
3079
|
+
'pattern': 'operator+',
|
|
3080
|
+
'python': '__add__',
|
|
3081
|
+
'notes': 'Magic methods',
|
|
3082
|
+
},
|
|
3083
|
+
'destructor': {
|
|
3084
|
+
'pattern': '~ClassName()',
|
|
3085
|
+
'python': '__del__',
|
|
3086
|
+
'notes': 'Not guaranteed to be called',
|
|
3087
|
+
},
|
|
3088
|
+
'move_semantics': {
|
|
3089
|
+
'pattern': 'std::move(x)',
|
|
3090
|
+
'python': 'No equivalent (GC handles)',
|
|
3091
|
+
'notes': 'Python uses reference counting',
|
|
3092
|
+
},
|
|
3093
|
+
'raii': {
|
|
3094
|
+
'pattern': 'Resource acquisition',
|
|
3095
|
+
'python': 'Context manager (with)',
|
|
3096
|
+
'notes': 'Use __enter__/__exit__',
|
|
3097
|
+
},
|
|
3098
|
+
'preprocessor': {
|
|
3099
|
+
'pattern': '#define, #ifdef',
|
|
3100
|
+
'python': 'No equivalent',
|
|
3101
|
+
'notes': 'Use constants or conditionals',
|
|
3102
|
+
},
|
|
3103
|
+
},
|
|
3104
|
+
},
|
|
3105
|
+
# Common includes needed for converted code
|
|
3106
|
+
'cpp_includes': {
|
|
3107
|
+
'string': '<string>',
|
|
3108
|
+
'vector': '<vector>',
|
|
3109
|
+
'map': '<unordered_map>',
|
|
3110
|
+
'set': '<unordered_set>',
|
|
3111
|
+
'optional': '<optional>',
|
|
3112
|
+
'variant': '<variant>',
|
|
3113
|
+
'functional': '<functional>',
|
|
3114
|
+
'algorithm': '<algorithm>',
|
|
3115
|
+
'numeric': '<numeric>',
|
|
3116
|
+
'cmath': '<cmath>',
|
|
3117
|
+
'iostream': '<iostream>',
|
|
3118
|
+
'fstream': '<fstream>',
|
|
3119
|
+
'sstream': '<sstream>',
|
|
3120
|
+
'memory': '<memory>',
|
|
3121
|
+
'tuple': '<tuple>',
|
|
3122
|
+
'stdexcept': '<stdexcept>',
|
|
3123
|
+
},
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
# AI prompt for conversion assistance
|
|
3127
|
+
AI_CONVERSION_PROMPT = '''You are an expert code converter for Python <-> C++ translation.
|
|
3128
|
+
|
|
3129
|
+
RULEBASE (follow strictly):
|
|
3130
|
+
{rulebase}
|
|
3131
|
+
|
|
3132
|
+
CONVERSION MODE: {mode}
|
|
3133
|
+
SOURCE CODE:
|
|
3134
|
+
```{source_lang}
|
|
3135
|
+
{source_code}
|
|
3136
|
+
```
|
|
3137
|
+
|
|
3138
|
+
TASK: Convert the code {direction}. Process section by section.
|
|
3139
|
+
|
|
3140
|
+
RULES:
|
|
3141
|
+
1. Preserve ALL functionality exactly
|
|
3142
|
+
2. Use type hints in Python, proper types in C++
|
|
3143
|
+
3. For Python->C++: ALL code MUST be in namespace includecpp {{ }}
|
|
3144
|
+
4. For unique features without direct equivalent, provide workaround with WORKAROUND comment
|
|
3145
|
+
5. Report any API changes at the end
|
|
3146
|
+
|
|
3147
|
+
OUTPUT FORMAT:
|
|
3148
|
+
For each section (class, function, struct):
|
|
3149
|
+
|
|
3150
|
+
SECTION: <name>
|
|
3151
|
+
ANALYSIS: <brief analysis of conversion needs>
|
|
3152
|
+
WORKAROUNDS: <list any workarounds needed, or "None">
|
|
3153
|
+
```{target_lang}
|
|
3154
|
+
<converted code>
|
|
3155
|
+
```
|
|
3156
|
+
|
|
3157
|
+
After all sections:
|
|
3158
|
+
|
|
3159
|
+
API_CHANGES:
|
|
3160
|
+
- <list any changes to function signatures, removed features, etc.>
|
|
3161
|
+
- If no changes: "None - API preserved"
|
|
3162
|
+
|
|
3163
|
+
WARNINGS:
|
|
3164
|
+
- <list any potential issues or limitations>
|
|
3165
|
+
|
|
3166
|
+
Convert now:'''
|
|
3167
|
+
|
|
3168
|
+
|
|
3169
|
+
class AIConversionAssistant:
|
|
3170
|
+
"""AI-assisted code conversion helper."""
|
|
3171
|
+
|
|
3172
|
+
def __init__(self):
|
|
3173
|
+
self.workarounds_used = []
|
|
3174
|
+
self.api_changes = []
|
|
3175
|
+
self.warnings = []
|
|
3176
|
+
|
|
3177
|
+
def prepare_prompt(self, source: str, mode: str) -> str:
|
|
3178
|
+
"""Prepare AI prompt with rulebase and source."""
|
|
3179
|
+
if mode == 'py_to_cpp':
|
|
3180
|
+
rules = AI_CONVERSION_RULEBASE['python_to_cpp']
|
|
3181
|
+
direction = 'from Python to C++'
|
|
3182
|
+
source_lang = 'python'
|
|
3183
|
+
target_lang = 'cpp'
|
|
3184
|
+
else:
|
|
3185
|
+
rules = AI_CONVERSION_RULEBASE['cpp_to_python']
|
|
3186
|
+
direction = 'from C++ to Python'
|
|
3187
|
+
source_lang = 'cpp'
|
|
3188
|
+
target_lang = 'python'
|
|
3189
|
+
|
|
3190
|
+
# Format rulebase for prompt
|
|
3191
|
+
rulebase_str = self._format_rulebase(rules)
|
|
3192
|
+
|
|
3193
|
+
return AI_CONVERSION_PROMPT.format(
|
|
3194
|
+
rulebase=rulebase_str,
|
|
3195
|
+
mode=mode.upper(),
|
|
3196
|
+
source_code=source,
|
|
3197
|
+
direction=direction,
|
|
3198
|
+
source_lang=source_lang,
|
|
3199
|
+
target_lang=target_lang
|
|
3200
|
+
)
|
|
3201
|
+
|
|
3202
|
+
def _format_rulebase(self, rules: dict) -> str:
|
|
3203
|
+
"""Format rulebase as readable text for AI."""
|
|
3204
|
+
lines = []
|
|
3205
|
+
|
|
3206
|
+
if 'types' in rules:
|
|
3207
|
+
lines.append("TYPE MAPPINGS:")
|
|
3208
|
+
for py_type, info in list(rules['types'].items())[:15]:
|
|
3209
|
+
target = info.get('cpp') or info.get('python')
|
|
3210
|
+
lines.append(f" {py_type} -> {target}")
|
|
3211
|
+
|
|
3212
|
+
if 'builtins' in rules:
|
|
3213
|
+
lines.append("\nBUILTIN FUNCTIONS:")
|
|
3214
|
+
for func, info in list(rules['builtins'].items())[:10]:
|
|
3215
|
+
lines.append(f" {func}() -> {info['cpp']} ({info.get('notes', '')})")
|
|
3216
|
+
|
|
3217
|
+
if 'workarounds' in rules:
|
|
3218
|
+
lines.append("\nPATTERNS NEEDING WORKAROUNDS:")
|
|
3219
|
+
for name, info in list(rules['workarounds'].items())[:8]:
|
|
3220
|
+
lines.append(f" {name}: {info['pattern']} -> {info.get('cpp') or info.get('python')}")
|
|
3221
|
+
|
|
3222
|
+
if 'methods' in rules:
|
|
3223
|
+
lines.append("\nMETHOD MAPPINGS:")
|
|
3224
|
+
for method, info in list(rules['methods'].items())[:10]:
|
|
3225
|
+
lines.append(f" {method} -> {info['cpp']}")
|
|
3226
|
+
|
|
3227
|
+
return '\n'.join(lines)
|
|
3228
|
+
|
|
3229
|
+
def analyze_source(self, source: str, mode: str) -> dict:
|
|
3230
|
+
"""Analyze source code for conversion complexity."""
|
|
3231
|
+
result = {
|
|
3232
|
+
'sections': [],
|
|
3233
|
+
'workarounds_needed': [],
|
|
3234
|
+
'complexity': 'simple',
|
|
3235
|
+
'estimated_changes': 0,
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
if mode == 'py_to_cpp':
|
|
3239
|
+
result = self._analyze_python(source)
|
|
3240
|
+
else:
|
|
3241
|
+
result = self._analyze_cpp(source)
|
|
3242
|
+
|
|
3243
|
+
return result
|
|
3244
|
+
|
|
3245
|
+
def _analyze_python(self, source: str) -> dict:
|
|
3246
|
+
"""Analyze Python source for conversion needs."""
|
|
3247
|
+
import ast
|
|
3248
|
+
result = {
|
|
3249
|
+
'sections': [],
|
|
3250
|
+
'workarounds_needed': [],
|
|
3251
|
+
'complexity': 'simple',
|
|
3252
|
+
'estimated_changes': 0,
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
try:
|
|
3256
|
+
tree = ast.parse(source)
|
|
3257
|
+
except SyntaxError:
|
|
3258
|
+
result['complexity'] = 'error'
|
|
3259
|
+
return result
|
|
3260
|
+
|
|
3261
|
+
workarounds = AI_CONVERSION_RULEBASE['python_to_cpp']['workarounds']
|
|
3262
|
+
|
|
3263
|
+
for node in ast.walk(tree):
|
|
3264
|
+
# Check for patterns needing workarounds
|
|
3265
|
+
if isinstance(node, ast.ListComp):
|
|
3266
|
+
result['workarounds_needed'].append({
|
|
3267
|
+
'type': 'list_comprehension',
|
|
3268
|
+
'line': node.lineno,
|
|
3269
|
+
'suggestion': workarounds['list_comprehension']['cpp'],
|
|
3270
|
+
})
|
|
3271
|
+
elif isinstance(node, ast.DictComp):
|
|
3272
|
+
result['workarounds_needed'].append({
|
|
3273
|
+
'type': 'dict_comprehension',
|
|
3274
|
+
'line': node.lineno,
|
|
3275
|
+
'suggestion': workarounds['dict_comprehension']['cpp'],
|
|
3276
|
+
})
|
|
3277
|
+
elif isinstance(node, ast.Yield) or isinstance(node, ast.YieldFrom):
|
|
3278
|
+
result['workarounds_needed'].append({
|
|
3279
|
+
'type': 'generator',
|
|
3280
|
+
'line': node.lineno,
|
|
3281
|
+
'suggestion': workarounds['generator']['cpp'],
|
|
3282
|
+
})
|
|
3283
|
+
elif isinstance(node, ast.AsyncFunctionDef):
|
|
3284
|
+
result['workarounds_needed'].append({
|
|
3285
|
+
'type': 'async_await',
|
|
3286
|
+
'line': node.lineno,
|
|
3287
|
+
'suggestion': workarounds['async_await']['cpp'],
|
|
3288
|
+
})
|
|
3289
|
+
elif isinstance(node, ast.With):
|
|
3290
|
+
result['workarounds_needed'].append({
|
|
3291
|
+
'type': 'context_manager',
|
|
3292
|
+
'line': node.lineno,
|
|
3293
|
+
'suggestion': workarounds['context_manager']['cpp'],
|
|
3294
|
+
})
|
|
3295
|
+
|
|
3296
|
+
# Extract sections
|
|
3297
|
+
for node in ast.iter_child_nodes(tree):
|
|
3298
|
+
if isinstance(node, ast.ClassDef):
|
|
3299
|
+
result['sections'].append({
|
|
3300
|
+
'type': 'class',
|
|
3301
|
+
'name': node.name,
|
|
3302
|
+
'line': node.lineno,
|
|
3303
|
+
'methods': len([n for n in ast.walk(node) if isinstance(n, ast.FunctionDef)]),
|
|
3304
|
+
})
|
|
3305
|
+
elif isinstance(node, ast.FunctionDef):
|
|
3306
|
+
result['sections'].append({
|
|
3307
|
+
'type': 'function',
|
|
3308
|
+
'name': node.name,
|
|
3309
|
+
'line': node.lineno,
|
|
3310
|
+
})
|
|
3311
|
+
|
|
3312
|
+
# Determine complexity
|
|
3313
|
+
if len(result['workarounds_needed']) > 5:
|
|
3314
|
+
result['complexity'] = 'complex'
|
|
3315
|
+
elif len(result['workarounds_needed']) > 0:
|
|
3316
|
+
result['complexity'] = 'moderate'
|
|
3317
|
+
|
|
3318
|
+
result['estimated_changes'] = len(result['workarounds_needed'])
|
|
3319
|
+
return result
|
|
3320
|
+
|
|
3321
|
+
def _analyze_cpp(self, source: str) -> dict:
|
|
3322
|
+
"""Analyze C++ source for conversion needs."""
|
|
3323
|
+
result = {
|
|
3324
|
+
'sections': [],
|
|
3325
|
+
'workarounds_needed': [],
|
|
3326
|
+
'complexity': 'simple',
|
|
3327
|
+
'estimated_changes': 0,
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
workarounds = AI_CONVERSION_RULEBASE['cpp_to_python']['workarounds']
|
|
3331
|
+
|
|
3332
|
+
# Check for templates
|
|
3333
|
+
if re.search(r'template\s*<', source):
|
|
3334
|
+
result['workarounds_needed'].append({
|
|
3335
|
+
'type': 'templates',
|
|
3336
|
+
'suggestion': workarounds['templates']['python'],
|
|
3337
|
+
})
|
|
3338
|
+
|
|
3339
|
+
# Check for pointers
|
|
3340
|
+
if re.search(r'\w+\s*\*\s*\w+', source):
|
|
3341
|
+
result['workarounds_needed'].append({
|
|
3342
|
+
'type': 'pointers',
|
|
3343
|
+
'suggestion': workarounds['pointers']['python'],
|
|
3344
|
+
})
|
|
3345
|
+
|
|
3346
|
+
# Check for operator overloads
|
|
3347
|
+
if re.search(r'operator\s*[+\-*/=<>!&|^~\[\]]+', source):
|
|
3348
|
+
result['workarounds_needed'].append({
|
|
3349
|
+
'type': 'operator_overload',
|
|
3350
|
+
'suggestion': workarounds['operator_overload']['python'],
|
|
3351
|
+
})
|
|
3352
|
+
|
|
3353
|
+
# Check for destructors
|
|
3354
|
+
if re.search(r'~\w+\s*\(', source):
|
|
3355
|
+
result['workarounds_needed'].append({
|
|
3356
|
+
'type': 'destructor',
|
|
3357
|
+
'suggestion': workarounds['destructor']['python'],
|
|
3358
|
+
})
|
|
3359
|
+
|
|
3360
|
+
# Check for move semantics
|
|
3361
|
+
if 'std::move' in source:
|
|
3362
|
+
result['workarounds_needed'].append({
|
|
3363
|
+
'type': 'move_semantics',
|
|
3364
|
+
'suggestion': workarounds['move_semantics']['python'],
|
|
3365
|
+
})
|
|
3366
|
+
|
|
3367
|
+
# Check for preprocessor
|
|
3368
|
+
if re.search(r'#\s*(define|ifdef|ifndef|endif)', source):
|
|
3369
|
+
result['workarounds_needed'].append({
|
|
3370
|
+
'type': 'preprocessor',
|
|
3371
|
+
'suggestion': workarounds['preprocessor']['python'],
|
|
3372
|
+
})
|
|
3373
|
+
|
|
3374
|
+
# Extract sections (classes, structs, functions)
|
|
3375
|
+
for match in re.finditer(r'class\s+(\w+)', source):
|
|
3376
|
+
result['sections'].append({'type': 'class', 'name': match.group(1)})
|
|
3377
|
+
|
|
3378
|
+
for match in re.finditer(r'struct\s+(\w+)', source):
|
|
3379
|
+
result['sections'].append({'type': 'struct', 'name': match.group(1)})
|
|
3380
|
+
|
|
3381
|
+
# Functions outside classes
|
|
3382
|
+
func_pattern = r'(?:^|\n)\s*(?:static\s+|inline\s+|virtual\s+)*(\w+(?:<[^>]+>)?(?:\s*[*&])?)\s+(\w+)\s*\([^)]*\)\s*(?:const)?\s*\{'
|
|
3383
|
+
for match in re.finditer(func_pattern, source):
|
|
3384
|
+
name = match.group(2)
|
|
3385
|
+
if name not in ('if', 'while', 'for', 'switch'):
|
|
3386
|
+
result['sections'].append({'type': 'function', 'name': name})
|
|
3387
|
+
|
|
3388
|
+
# Determine complexity
|
|
3389
|
+
if len(result['workarounds_needed']) > 3:
|
|
3390
|
+
result['complexity'] = 'complex'
|
|
3391
|
+
elif len(result['workarounds_needed']) > 0:
|
|
3392
|
+
result['complexity'] = 'moderate'
|
|
3393
|
+
|
|
3394
|
+
result['estimated_changes'] = len(result['workarounds_needed'])
|
|
3395
|
+
return result
|
|
3396
|
+
|
|
3397
|
+
def parse_ai_response(self, response: str) -> dict:
|
|
3398
|
+
"""Parse AI conversion response."""
|
|
3399
|
+
result = {
|
|
3400
|
+
'sections': [],
|
|
3401
|
+
'api_changes': [],
|
|
3402
|
+
'warnings': [],
|
|
3403
|
+
'full_code': '',
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
# Extract sections
|
|
3407
|
+
section_pattern = r'SECTION:\s*(\w+)\s*\nANALYSIS:\s*([^\n]+)\s*\nWORKAROUNDS:\s*([^\n]+)\s*\n```\w*\n(.*?)```'
|
|
3408
|
+
for match in re.finditer(section_pattern, response, re.DOTALL):
|
|
3409
|
+
result['sections'].append({
|
|
3410
|
+
'name': match.group(1),
|
|
3411
|
+
'analysis': match.group(2),
|
|
3412
|
+
'workarounds': match.group(3),
|
|
3413
|
+
'code': match.group(4).strip(),
|
|
3414
|
+
})
|
|
3415
|
+
|
|
3416
|
+
# Extract API changes
|
|
3417
|
+
api_match = re.search(r'API_CHANGES:\s*(.*?)(?=WARNINGS:|$)', response, re.DOTALL)
|
|
3418
|
+
if api_match:
|
|
3419
|
+
changes_text = api_match.group(1).strip()
|
|
3420
|
+
if 'None' not in changes_text:
|
|
3421
|
+
for line in changes_text.split('\n'):
|
|
3422
|
+
line = line.strip().lstrip('-').strip()
|
|
3423
|
+
if line:
|
|
3424
|
+
result['api_changes'].append(line)
|
|
3425
|
+
|
|
3426
|
+
# Extract warnings
|
|
3427
|
+
warn_match = re.search(r'WARNINGS:\s*(.*?)$', response, re.DOTALL)
|
|
3428
|
+
if warn_match:
|
|
3429
|
+
for line in warn_match.group(1).strip().split('\n'):
|
|
3430
|
+
line = line.strip().lstrip('-').strip()
|
|
3431
|
+
if line:
|
|
3432
|
+
result['warnings'].append(line)
|
|
3433
|
+
|
|
3434
|
+
# Combine code sections
|
|
3435
|
+
if result['sections']:
|
|
3436
|
+
result['full_code'] = '\n\n'.join(s['code'] for s in result['sections'])
|
|
3437
|
+
|
|
3438
|
+
return result
|
|
3439
|
+
|
|
3440
|
+
|
|
3441
|
+
def get_ai_assistant() -> AIConversionAssistant:
|
|
3442
|
+
"""Get AI conversion assistant instance."""
|
|
3443
|
+
return AIConversionAssistant()
|
|
3444
|
+
|
|
3445
|
+
|
|
3446
|
+
def get_conversion_rulebase() -> dict:
|
|
3447
|
+
"""Get the full conversion rulebase."""
|
|
3448
|
+
return AI_CONVERSION_RULEBASE
|