Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/implementation_gaps_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ The current implementation can only handle a limited subset of Python features:
| **Method overriding** | ✅ **Complete** | **Proper virtual methods and polymorphism** |
| Error handling | ⚠️ Minimal | Simple try/except structure |
| Context managers | ⚠️ Minimal | Basic structure without resource management |
| List comprehensions | ❌ Missing | Not implemented |
| Dictionary operations | ⚠️ Partial | Simple creation and access |
| List comprehensions | ✅ **Implemented** | Vector creation with push_back |
| Dictionary operations | ⚠️ Partial | Basic creation plus comprehension support |
| String operations | ✅ **Improved** | Advanced f-string support, concatenation |
| Regular expressions | ❌ Missing | Not implemented |
| File I/O | ❌ Missing | Not implemented |
Expand Down Expand Up @@ -143,7 +143,7 @@ The current implementation can only handle a limited subset of Python features:

**Prioritized Based on Recent Progress:**
1. Support for Python standard library mapping to C++ equivalents
2. Add support for list comprehensions and dictionary comprehensions
2. ~~Add support for list comprehensions~~ ✓ **List comprehensions implemented**; add dictionary comprehensions
3. Implement regular expression pattern translation
4. Add code generation for file I/O operations
5. Develop optimized C++ code patterns for common Python idioms
Expand Down
12 changes: 10 additions & 2 deletions src/analyzer/code_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,17 @@ def _infer_variable_type(self, node: ast.Assign) -> None:
elt_types.append(f'std::tuple<{", ".join(nested_types)}>')
else:
elt_types.append(self._infer_expression_type(elt))
self.type_info[node.targets[0].id] = f'std::tuple<{", ".join(elt_types)}>'
if isinstance(node.targets[0], ast.Name):
self.type_info[node.targets[0].id] = (
f"std::tuple<{', '.join(elt_types)}>"
)
elif isinstance(node.targets[0], ast.Tuple):
for tgt, typ in zip(node.targets[0].elts, elt_types):
if isinstance(tgt, ast.Name):
self.type_info[tgt.id] = typ
else:
self.type_info[node.targets[0].id] = 'std::tuple<>'
if isinstance(node.targets[0], ast.Name):
self.type_info[node.targets[0].id] = 'std::tuple<>'
elif isinstance(node.value, ast.Call):
# Try to infer type from function call
if isinstance(node.value.func, ast.Name):
Expand Down
11 changes: 11 additions & 0 deletions src/analyzer/code_analyzer_fixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ def _infer_variable_type(self, node: ast.Assign) -> None:
self._store_type_for_target(node.targets[0], f'std::map<{key_type}, {value_type}>')
else:
self._store_type_for_target(node.targets[0], 'std::map<std::string, int>') # Default
elif isinstance(node.value, ast.DictComp):
key_type = self._infer_expression_type(node.value.key)
value_type = self._infer_expression_type(node.value.value)
self._store_type_for_target(node.targets[0], f'std::map<{key_type}, {value_type}>')
elif isinstance(node.value, ast.Set):
# Try to infer set element type
if node.value.elts:
Expand Down Expand Up @@ -474,6 +478,13 @@ def _infer_expression_type(self, node: ast.AST) -> str:
elt_type = self._infer_expression_type(node.elts[0])
return f'std::vector<{elt_type}>'
return 'std::vector<int>'
elif isinstance(node, ast.ListComp):
elt_type = self._infer_expression_type(node.elt)
return f'std::vector<{elt_type}>'
elif isinstance(node, ast.DictComp):
key_type = self._infer_expression_type(node.key)
value_type = self._infer_expression_type(node.value)
return f'std::map<{key_type}, {value_type}>'
elif isinstance(node, ast.Dict):
if node.keys and node.values:
key_type = self._infer_expression_type(node.keys[0])
Expand Down
25 changes: 18 additions & 7 deletions src/converter/code_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def generate_code(self, analysis_result: AnalysisResult, output_dir: Path) -> No
with open(output_dir / "setup.py", "w") as f:
f.write('\n'.join(setup_content))

def _generate_header(self, analysis_result: Dict) -> str:
def _generate_header(self, analysis_result: AnalysisResult) -> str:
"""Generate C++ header file."""
header = """#pragma once

Expand All @@ -103,22 +103,29 @@ def _generate_header(self, analysis_result: Dict) -> str:
namespace pytocpp {

"""
type_info = analysis_result.type_info if hasattr(
analysis_result, "type_info"
) else analysis_result.get("functions", {})

# Add function declarations
for func_name, func_info in analysis_result.get('functions', {}).items():
for func_name, func_info in type_info.items():
if func_name.startswith('calculate_'):
# Get return type
return_type = func_info.get('return_type', 'int')
return_type = (
func_info.get('return_type', 'int') if isinstance(func_info, dict) else 'int'
)
# Get parameter types
params = []
for param_name, param_type in func_info.get('params', {}).items():
params.append(f"{param_type} {param_name}")
if isinstance(func_info, dict):
for param_name, param_type in func_info.get('params', {}).items():
params.append(f"{param_type} {param_name}")
# Add function declaration
header += f" {return_type} {func_name}({', '.join(params)});\n\n"

header += "} // namespace pytocpp\n"
return header

def _generate_implementation(self, analysis_result: Dict) -> str:
def _generate_implementation(self, analysis_result: AnalysisResult) -> str:
"""Generate C++ implementation file."""
impl = """#include "generated.hpp"
#include <vector>
Expand All @@ -132,8 +139,12 @@ def _generate_implementation(self, analysis_result: Dict) -> str:
namespace pytocpp {

"""
type_info = analysis_result.type_info if hasattr(
analysis_result, "type_info"
) else analysis_result.get("functions", {})

# Add function implementations
for func_name, func_info in analysis_result.get('functions', {}).items():
for func_name, func_info in type_info.items():
if func_name.startswith('calculate_'):
impl += self._generate_function_impl(func_name, func_info)

Expand Down
20 changes: 19 additions & 1 deletion src/converter/code_generator_fixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,8 +1083,26 @@ def _translate_expression(self, node: ast.AST, local_vars: Dict[str, str]) -> st
# Try to infer element type from the first element if available
if node.elts:
element_type = self._infer_cpp_type(node.elts[0], local_vars)

return f"std::vector<{element_type}>{{{', '.join(elements)}}}"
elif isinstance(node, ast.ListComp):
elt_type = self._infer_cpp_type(node.elt, local_vars)
target = self._translate_expression(node.generators[0].target, local_vars)
iterable = self._translate_expression(node.generators[0].iter, local_vars)
expr = self._translate_expression(node.elt, local_vars)
return (
f"([&]() {{ std::vector<{elt_type}> result; for (const auto& {target} : {iterable}) {{ result.push_back({expr}); }} return result; }})()"
)
elif isinstance(node, ast.DictComp):
key_type = self._infer_cpp_type(node.key, local_vars)
value_type = self._infer_cpp_type(node.value, local_vars)
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes only one generator exists (index [0]) without checking if the generators list is empty. This could cause an IndexError if a malformed AST is processed.

Suggested change
value_type = self._infer_cpp_type(node.value, local_vars)
value_type = self._infer_cpp_type(node.value, local_vars)
if not node.generators:
raise ValueError("Malformed AST: Dict comprehension has no generators.")

Copilot uses AI. Check for mistakes.

target = self._translate_expression(node.generators[0].target, local_vars)
iterable = self._translate_expression(node.generators[0].iter, local_vars)
key_expr = self._translate_expression(node.key, local_vars)
value_expr = self._translate_expression(node.value, local_vars)
return (
f"([&]() {{ std::map<{key_type}, {value_type}> result; for (const auto& {target} : {iterable}) {{ result[{key_expr}] = {value_expr}; }} return result; }})()"
)
elif isinstance(node, ast.Dict):
# Handle dict literals
if not node.keys:
Expand Down
37 changes: 37 additions & 0 deletions tests/test_code_analyzer_fixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,43 @@ def test_inference_expressions(self):
values=[ast.Constant(value=True), ast.Constant(value=False)]
)
assert analyzer._infer_expression_type(bool_op) == 'bool'

list_comp = ast.ListComp(
elt=ast.BinOp(
left=ast.Name(id='x', ctx=ast.Load()),
op=ast.Mult(),
right=ast.Constant(value=2),
),
generators=[
ast.comprehension(
target=ast.Name(id='x', ctx=ast.Store()),
iter=ast.Name(id='nums', ctx=ast.Load()),
ifs=[],
is_async=0,
)
],
)
analyzer.type_info['nums'] = 'std::vector<int>'
assert analyzer._infer_expression_type(list_comp) == 'std::vector<int>'

dict_comp = ast.DictComp(
key=ast.Name(id='x', ctx=ast.Load()),
value=ast.BinOp(
left=ast.Name(id='x', ctx=ast.Load()),
op=ast.Mult(),
right=ast.Constant(value=2),
),
generators=[
ast.comprehension(
target=ast.Name(id='x', ctx=ast.Store()),
iter=ast.Name(id='nums', ctx=ast.Load()),
ifs=[],
is_async=0,
)
],
)
analyzer.type_info['nums'] = 'std::vector<int>'
assert analyzer._infer_expression_type(dict_comp) == 'std::map<int, int>'

def test_type_annotation_handling(self):
"""Test handling of Python type annotations."""
Expand Down
74 changes: 73 additions & 1 deletion tests/test_conversion_fixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,76 @@ def test_fibonacci_conversion(tmp_path):
# Verify CMake content
cmake_content = (output_dir / "CMakeLists.txt").read_text()
assert "cmake_minimum_required" in cmake_content
assert "project(pytocpp_generated)" in cmake_content
assert "project(pytocpp_generated)" in cmake_content


def test_list_comprehension_conversion(tmp_path):
analyzer = CodeAnalyzer()
rule_manager = RuleManager()
rule_manager.register_rule(VariableDeclarationRule())
rule_manager.register_rule(FunctionDefinitionRule())
rule_manager.register_rule(ClassDefinitionRule())
generator = CodeGenerator(rule_manager)

test_file = tmp_path / "list_comp.py"
test_file.write_text(
"""
from typing import List

def double_nums(nums: List[int]) -> List[int]:
return [x * 2 for x in nums]
"""
)

analysis_result = analyzer.analyze_file(test_file)
rule_manager.set_context(
{
"type_info": analysis_result.type_info,
"performance_bottlenecks": analysis_result.performance_bottlenecks,
"memory_usage": analysis_result.memory_usage,
"hot_paths": analysis_result.hot_paths,
}
)

output_dir = tmp_path / "generated"
generator.generate_code(analysis_result, output_dir)

impl_content = (output_dir / "generated.cpp").read_text()
assert "std::vector<int>" in impl_content
assert "result.push_back" in impl_content


def test_dict_comprehension_conversion(tmp_path):
analyzer = CodeAnalyzer()
rule_manager = RuleManager()
rule_manager.register_rule(VariableDeclarationRule())
rule_manager.register_rule(FunctionDefinitionRule())
rule_manager.register_rule(ClassDefinitionRule())
generator = CodeGenerator(rule_manager)

test_file = tmp_path / "dict_comp.py"
test_file.write_text(
"""
from typing import List, Dict

def map_double(nums: List[int]) -> Dict[int, int]:
return {x: x * 2 for x in nums}
"""
)

analysis_result = analyzer.analyze_file(test_file)
rule_manager.set_context(
{
"type_info": analysis_result.type_info,
"performance_bottlenecks": analysis_result.performance_bottlenecks,
"memory_usage": analysis_result.memory_usage,
"hot_paths": analysis_result.hot_paths,
}
)

output_dir = tmp_path / "generated"
generator.generate_code(analysis_result, output_dir)

impl_content = (output_dir / "generated.cpp").read_text()
assert "std::map<int, int>" in impl_content
assert "result[" in impl_content