vex-ast 0.2.3__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vex_ast-0.2.3/vex_ast.egg-info → vex_ast-0.2.5}/PKG-INFO +1 -1
- {vex_ast-0.2.3 → vex_ast-0.2.5}/pyproject.toml +1 -1
- {vex_ast-0.2.3 → vex_ast-0.2.5}/setup.py +1 -1
- vex_ast-0.2.5/tests/conftest.py +72 -0
- vex_ast-0.2.5/tests/test_conditional_expressions.py +130 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_registry.py +12 -3
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/__init__.py +11 -2
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/expressions.py +44 -1
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/interfaces.py +16 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/validators.py +22 -21
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/parser/factory.py +6 -1
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/parser/python_parser.py +132 -32
- vex_ast-0.2.5/vex_ast/registry/functions/constructors.py +35 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/functions/initialize.py +5 -2
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/signature.py +5 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/serialization/json_deserializer.py +9 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/serialization/schema.py +27 -5
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/types/enums.py +26 -1
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/visitors/base.py +4 -1
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/visitors/printer.py +52 -1
- {vex_ast-0.2.3 → vex_ast-0.2.5/vex_ast.egg-info}/PKG-INFO +1 -1
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast.egg-info/SOURCES.txt +2 -0
- vex_ast-0.2.3/tests/conftest.py +0 -11
- {vex_ast-0.2.3 → vex_ast-0.2.5}/LICENSE +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/MANIFEST.in +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/pytest.ini +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/requirements.txt +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/setup.cfg +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_comprehensive_integration.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_core.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_integration.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_literals.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_navigator.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_parser.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_serialization.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_statements.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_vex_nodes.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/tests/test_visitors.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/READMEAPI.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/core.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/literals.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/navigator.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/operators.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/statements.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/ast/vex_nodes.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/parser/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/parser/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/parser/interfaces.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/parser/strategies.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/api.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/categories.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/functions/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/functions/display.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/functions/drivetrain.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/functions/motor.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/functions/sensors.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/functions/timing.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/language_map.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/registry.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/simulation_behavior.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/registry/validation.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/serialization/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/serialization/json_serializer.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/types/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/types/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/types/base.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/types/objects.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/types/primitives.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/types/type_checker.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/utils/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/utils/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/utils/errors.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/utils/source_location.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/utils/type_definitions.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/visitors/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/visitors/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/visitors/analyzer.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast/visitors/transformer.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast.egg-info/dependency_links.txt +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.5}/vex_ast.egg-info/top_level.txt +0 -0
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='vex_ast',
|
5
|
-
version='0.2.
|
5
|
+
version='0.2.5',
|
6
6
|
description='A Python package for generating Abstract Syntax Trees for VEX V5 code.',
|
7
7
|
long_description=open('README.md').read(),
|
8
8
|
long_description_content_type='text/markdown',
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# tests/conftest.py
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
# Get the absolute path to the project root directory
|
7
|
+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
8
|
+
|
9
|
+
# Add the project root to the Python path if it's not already there
|
10
|
+
if project_root not in sys.path:
|
11
|
+
sys.path.insert(0, project_root)
|
12
|
+
|
13
|
+
# Initialize registry for tests
|
14
|
+
@pytest.fixture(scope="session", autouse=True)
|
15
|
+
def initialize_registry():
|
16
|
+
"""Initialize the registry with test functions."""
|
17
|
+
from vex_ast.registry.functions.initialize import initialize_registry
|
18
|
+
from vex_ast.registry.api import registry_api
|
19
|
+
|
20
|
+
# Force registry initialization
|
21
|
+
initialize_registry()
|
22
|
+
|
23
|
+
# Add a test function if registry is empty
|
24
|
+
if len(registry_api.get_all_functions()) == 0:
|
25
|
+
from vex_ast.registry.registry import registry
|
26
|
+
from vex_ast.registry.signature import VexFunctionSignature, VexFunctionParameter, SimulationCategory
|
27
|
+
from vex_ast.types.base import VOID
|
28
|
+
from vex_ast.types.primitives import INT, FLOAT, BOOL
|
29
|
+
from vex_ast.types.enums import DIRECTION_TYPE, VELOCITY_UNITS
|
30
|
+
from vex_ast.types.objects import MOTOR
|
31
|
+
|
32
|
+
# Add a test motor.spin function
|
33
|
+
spin_params = [
|
34
|
+
VexFunctionParameter("direction", DIRECTION_TYPE, description="Direction to spin the motor"),
|
35
|
+
VexFunctionParameter("velocity", FLOAT, 50.0, description="Velocity to spin at"),
|
36
|
+
VexFunctionParameter("units", VELOCITY_UNITS, "RPM", description="Velocity units")
|
37
|
+
]
|
38
|
+
|
39
|
+
spin_signature = VexFunctionSignature(
|
40
|
+
name="spin",
|
41
|
+
return_type=VOID,
|
42
|
+
parameters=spin_params,
|
43
|
+
description="Spin the motor in the specified direction",
|
44
|
+
category=SimulationCategory.MOTOR_CONTROL,
|
45
|
+
python_name="spin",
|
46
|
+
cpp_name="spin",
|
47
|
+
object_type=MOTOR,
|
48
|
+
method_name="spin"
|
49
|
+
)
|
50
|
+
|
51
|
+
registry.register_function(spin_signature)
|
52
|
+
|
53
|
+
# Add a test motor.stop function
|
54
|
+
stop_params = [
|
55
|
+
VexFunctionParameter("mode", DIRECTION_TYPE, "COAST", description="Stopping mode")
|
56
|
+
]
|
57
|
+
|
58
|
+
stop_signature = VexFunctionSignature(
|
59
|
+
name="stop",
|
60
|
+
return_type=VOID,
|
61
|
+
parameters=stop_params,
|
62
|
+
description="Stop the motor",
|
63
|
+
category=SimulationCategory.MOTOR_CONTROL,
|
64
|
+
python_name="stop",
|
65
|
+
cpp_name="stop",
|
66
|
+
object_type=MOTOR,
|
67
|
+
method_name="stop"
|
68
|
+
)
|
69
|
+
|
70
|
+
registry.register_function(stop_signature)
|
71
|
+
|
72
|
+
print(f"Added test functions to registry. Total functions: {len(registry_api.get_all_functions())}")
|
@@ -0,0 +1,130 @@
|
|
1
|
+
"""
|
2
|
+
Tests for conditional expressions in the AST.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
from vex_ast import parse_string, serialize_ast_to_dict, deserialize_ast_from_dict
|
7
|
+
from vex_ast.ast.expressions import ConditionalExpression
|
8
|
+
from vex_ast.visitors.printer import PrintVisitor
|
9
|
+
|
10
|
+
class TestConditionalExpressions:
|
11
|
+
"""Test cases for conditional expressions."""
|
12
|
+
|
13
|
+
def test_parse_conditional_expression(self):
|
14
|
+
"""Test parsing a conditional expression."""
|
15
|
+
# Python's ternary operator: value_if_true if condition else value_if_false
|
16
|
+
code = "result = 'positive' if x > 0 else 'negative'"
|
17
|
+
ast = parse_string(code)
|
18
|
+
|
19
|
+
# Check the AST structure
|
20
|
+
assert len(ast.body) == 1
|
21
|
+
assert ast.body[0].__class__.__name__ == "Assignment"
|
22
|
+
|
23
|
+
# Check the conditional expression
|
24
|
+
value = ast.body[0].value
|
25
|
+
assert isinstance(value, ConditionalExpression)
|
26
|
+
assert value.condition.__class__.__name__ == "BinaryOperation"
|
27
|
+
assert value.true_expr.__class__.__name__ == "StringLiteral"
|
28
|
+
assert value.true_expr.value == "positive"
|
29
|
+
assert value.false_expr.__class__.__name__ == "StringLiteral"
|
30
|
+
assert value.false_expr.value == "negative"
|
31
|
+
|
32
|
+
def test_nested_conditional_expressions(self):
|
33
|
+
"""Test parsing nested conditional expressions."""
|
34
|
+
code = """
|
35
|
+
# Nested conditional expressions
|
36
|
+
result = 'positive' if x > 0 else 'zero' if x == 0 else 'negative'
|
37
|
+
"""
|
38
|
+
ast = parse_string(code)
|
39
|
+
|
40
|
+
# Check the AST structure
|
41
|
+
assert len(ast.body) == 1
|
42
|
+
assert ast.body[0].__class__.__name__ == "Assignment"
|
43
|
+
|
44
|
+
# Check the outer conditional expression
|
45
|
+
value = ast.body[0].value
|
46
|
+
assert isinstance(value, ConditionalExpression)
|
47
|
+
assert value.true_expr.__class__.__name__ == "StringLiteral"
|
48
|
+
assert value.true_expr.value == "positive"
|
49
|
+
|
50
|
+
# Check the nested conditional expression
|
51
|
+
assert isinstance(value.false_expr, ConditionalExpression)
|
52
|
+
assert value.false_expr.true_expr.value == "zero"
|
53
|
+
assert value.false_expr.false_expr.value == "negative"
|
54
|
+
|
55
|
+
def test_conditional_expression_in_function_call(self):
|
56
|
+
"""Test conditional expressions used in function calls."""
|
57
|
+
code = "print('Even' if x % 2 == 0 else 'Odd')"
|
58
|
+
# Explicitly pass a test filename to ensure 'print' is treated as a regular function call
|
59
|
+
ast = parse_string(code, filename="test_conditional_expressions.py")
|
60
|
+
|
61
|
+
# Check the AST structure
|
62
|
+
assert len(ast.body) == 1
|
63
|
+
assert ast.body[0].__class__.__name__ == "ExpressionStatement"
|
64
|
+
|
65
|
+
# Check the function call
|
66
|
+
func_call = ast.body[0].expression
|
67
|
+
assert func_call.__class__.__name__ == "FunctionCall"
|
68
|
+
|
69
|
+
# Check the conditional expression argument
|
70
|
+
assert len(func_call.args) == 1
|
71
|
+
arg = func_call.args[0]
|
72
|
+
assert isinstance(arg, ConditionalExpression)
|
73
|
+
assert arg.true_expr.value == "Even"
|
74
|
+
assert arg.false_expr.value == "Odd"
|
75
|
+
|
76
|
+
def test_conditional_expression_serialization(self):
|
77
|
+
"""Test serialization and deserialization of conditional expressions."""
|
78
|
+
code = "result = value1 if condition else value2"
|
79
|
+
original_ast = parse_string(code)
|
80
|
+
|
81
|
+
# Serialize to dictionary
|
82
|
+
serialized = serialize_ast_to_dict(original_ast)
|
83
|
+
|
84
|
+
# Check serialized structure
|
85
|
+
assert serialized["type"] == "Program"
|
86
|
+
assert len(serialized["body"]) == 1
|
87
|
+
assert serialized["body"][0]["type"] == "Assignment"
|
88
|
+
|
89
|
+
# Check the conditional expression
|
90
|
+
cond_expr = serialized["body"][0]["value"]
|
91
|
+
assert cond_expr["type"] == "ConditionalExpression"
|
92
|
+
assert "condition" in cond_expr
|
93
|
+
assert "true_expr" in cond_expr
|
94
|
+
assert "false_expr" in cond_expr
|
95
|
+
|
96
|
+
# Deserialize back to AST
|
97
|
+
deserialized_ast = deserialize_ast_from_dict(serialized)
|
98
|
+
|
99
|
+
# Check the deserialized AST
|
100
|
+
assert len(deserialized_ast.body) == 1
|
101
|
+
assert deserialized_ast.body[0].__class__.__name__ == "Assignment"
|
102
|
+
|
103
|
+
# Check the conditional expression
|
104
|
+
value = deserialized_ast.body[0].value
|
105
|
+
assert isinstance(value, ConditionalExpression)
|
106
|
+
assert value.condition.__class__.__name__ == "VariableReference"
|
107
|
+
assert value.true_expr.__class__.__name__ == "VariableReference"
|
108
|
+
assert value.false_expr.__class__.__name__ == "VariableReference"
|
109
|
+
|
110
|
+
def test_conditional_expression_printing(self):
|
111
|
+
"""Test printing conditional expressions."""
|
112
|
+
code = "result = 'yes' if condition else 'no'"
|
113
|
+
ast = parse_string(code)
|
114
|
+
|
115
|
+
# Use the PrintVisitor to convert the AST back to code
|
116
|
+
printer = PrintVisitor()
|
117
|
+
printed_code = printer.visit(ast)
|
118
|
+
|
119
|
+
# The printed code should contain the conditional expression components
|
120
|
+
assert "ConditionalExpression" in printed_code
|
121
|
+
assert "condition" in printed_code
|
122
|
+
assert "true_expr" in printed_code
|
123
|
+
assert "false_expr" in printed_code
|
124
|
+
assert "'yes'" in printed_code
|
125
|
+
assert "'no'" in printed_code
|
126
|
+
|
127
|
+
# The printed code should also contain the formatted expression with if/else keywords
|
128
|
+
assert "formatted = " in printed_code
|
129
|
+
assert "if" in printed_code
|
130
|
+
assert "else" in printed_code
|
@@ -33,6 +33,7 @@ class TestRegistry:
|
|
33
33
|
def find_vex_calls(node):
|
34
34
|
if isinstance(node, VexAPICall):
|
35
35
|
vex_calls.append(node)
|
36
|
+
print(f"Found VexAPICall: {node.get_function_name()}")
|
36
37
|
for child in node.get_children():
|
37
38
|
find_vex_calls(child)
|
38
39
|
|
@@ -40,10 +41,18 @@ class TestRegistry:
|
|
40
41
|
|
41
42
|
# Verify we found the VEX call
|
42
43
|
assert len(vex_calls) > 0
|
43
|
-
|
44
|
+
|
45
|
+
# Find the motor1.spin call
|
46
|
+
motor_spin_call = None
|
47
|
+
for call in vex_calls:
|
48
|
+
if call.get_function_name() == "motor1.spin":
|
49
|
+
motor_spin_call = call
|
50
|
+
break
|
51
|
+
|
52
|
+
assert motor_spin_call is not None, f"motor1.spin call not found. Found calls: {[call.get_function_name() for call in vex_calls]}"
|
44
53
|
|
45
54
|
# Resolve the signature (it might not directly validate as our AST doesn't have types)
|
46
|
-
signature =
|
55
|
+
signature = motor_spin_call.resolve_signature()
|
47
56
|
assert signature is not None
|
48
57
|
assert signature.name == "spin"
|
49
58
|
assert signature.category == SimulationCategory.MOTOR_CONTROL
|
@@ -72,4 +81,4 @@ class TestRegistry:
|
|
72
81
|
invalid_errors = validate_vex_functions(invalid_ast)
|
73
82
|
|
74
83
|
# This should produce at least one error
|
75
|
-
assert len(invalid_errors) > 0
|
84
|
+
assert len(invalid_errors) > 0
|
@@ -31,10 +31,19 @@ __version__ = "0.2.0"
|
|
31
31
|
|
32
32
|
# Initialize the registry with default functions
|
33
33
|
try:
|
34
|
-
initialize
|
35
|
-
|
34
|
+
# Explicitly import and initialize all registry functions
|
35
|
+
from .registry.functions.initialize import initialize_registry
|
36
|
+
initialize_registry()
|
37
|
+
|
38
|
+
# Verify registry has been populated
|
39
|
+
if len(registry_api.get_all_functions()) == 0:
|
40
|
+
print("Warning: Registry initialization did not populate any functions")
|
41
|
+
else:
|
42
|
+
print(f"VEX function registry initialized successfully with {len(registry_api.get_all_functions())} functions")
|
36
43
|
except Exception as e:
|
37
44
|
print(f"Error initializing VEX function registry: {e}")
|
45
|
+
import traceback
|
46
|
+
traceback.print_exc()
|
38
47
|
|
39
48
|
__all__ = [
|
40
49
|
# Core functionality
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from typing import Dict, List, Optional, Union, cast, Any
|
4
4
|
|
5
|
-
from .interfaces import IAstNode, IExpression, IVisitor, T_VisitorResult, IIdentifier, IFunctionCall
|
5
|
+
from .interfaces import IAstNode, IExpression, IVisitor, T_VisitorResult, IIdentifier, IFunctionCall, IConditionalExpression
|
6
6
|
from .core import Expression
|
7
7
|
from .operators import Operator
|
8
8
|
from ..utils.source_location import SourceLocation
|
@@ -173,6 +173,49 @@ class KeywordArgument(Expression):
|
|
173
173
|
"""Get the keyword value."""
|
174
174
|
return self.value
|
175
175
|
|
176
|
+
class ConditionalExpression(Expression, IConditionalExpression):
|
177
|
+
"""A conditional expression (ternary operator, e.g., a if condition else b)."""
|
178
|
+
|
179
|
+
_fields = ('condition', 'true_expr', 'false_expr')
|
180
|
+
|
181
|
+
def __init__(self, condition: IExpression, true_expr: IExpression, false_expr: IExpression,
|
182
|
+
location: Optional[SourceLocation] = None):
|
183
|
+
super().__init__(location)
|
184
|
+
self.condition = condition
|
185
|
+
self.true_expr = true_expr
|
186
|
+
self.false_expr = false_expr
|
187
|
+
|
188
|
+
# Set parent references
|
189
|
+
if isinstance(condition, Expression):
|
190
|
+
condition.set_parent(self)
|
191
|
+
if isinstance(true_expr, Expression):
|
192
|
+
true_expr.set_parent(self)
|
193
|
+
if isinstance(false_expr, Expression):
|
194
|
+
false_expr.set_parent(self)
|
195
|
+
|
196
|
+
def get_children(self) -> List[IAstNode]:
|
197
|
+
"""Get child nodes."""
|
198
|
+
return [
|
199
|
+
cast(IAstNode, self.condition),
|
200
|
+
cast(IAstNode, self.true_expr),
|
201
|
+
cast(IAstNode, self.false_expr)
|
202
|
+
]
|
203
|
+
|
204
|
+
def accept(self, visitor: IVisitor[T_VisitorResult]) -> T_VisitorResult:
|
205
|
+
return visitor.visit_conditionalexpression(self)
|
206
|
+
|
207
|
+
def get_condition(self) -> IExpression:
|
208
|
+
"""Get the condition expression."""
|
209
|
+
return self.condition
|
210
|
+
|
211
|
+
def get_true_expression(self) -> IExpression:
|
212
|
+
"""Get the expression to evaluate if condition is true."""
|
213
|
+
return self.true_expr
|
214
|
+
|
215
|
+
def get_false_expression(self) -> IExpression:
|
216
|
+
"""Get the expression to evaluate if condition is false."""
|
217
|
+
return self.false_expr
|
218
|
+
|
176
219
|
class FunctionCall(Expression, IFunctionCall):
|
177
220
|
"""A function call."""
|
178
221
|
|
@@ -84,6 +84,22 @@ class IIdentifier(IExpression, Protocol):
|
|
84
84
|
"""Get the identifier name."""
|
85
85
|
...
|
86
86
|
|
87
|
+
@runtime_checkable
|
88
|
+
class IConditionalExpression(IExpression, Protocol):
|
89
|
+
"""Protocol for conditional expression (ternary operator) nodes."""
|
90
|
+
|
91
|
+
def get_condition(self) -> IExpression:
|
92
|
+
"""Get the condition expression."""
|
93
|
+
...
|
94
|
+
|
95
|
+
def get_true_expression(self) -> IExpression:
|
96
|
+
"""Get the expression to evaluate if condition is true."""
|
97
|
+
...
|
98
|
+
|
99
|
+
def get_false_expression(self) -> IExpression:
|
100
|
+
"""Get the expression to evaluate if condition is false."""
|
101
|
+
...
|
102
|
+
|
87
103
|
@runtime_checkable
|
88
104
|
class IFunctionCall(IExpression, Protocol):
|
89
105
|
"""Protocol for function call nodes."""
|
@@ -74,29 +74,30 @@ class VexFunctionValidator(AstVisitor[List[Tuple[VexAPICall, str]]]):
|
|
74
74
|
if isinstance(obj, Identifier):
|
75
75
|
function_name = f"{obj.name}.{node.function.attribute}"
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
# Initialize is_vex_function to False by default
|
78
|
+
is_vex_function = False
|
79
|
+
|
80
|
+
# Check if this is a known VEX function
|
81
|
+
if function_name:
|
82
|
+
# Check if this is a method call on a known object type
|
83
|
+
if '.' in function_name:
|
84
|
+
obj_name, method_name = function_name.split('.', 1)
|
80
85
|
|
81
|
-
#
|
82
|
-
if
|
83
|
-
obj_name, method_name = function_name.split('.', 1)
|
84
|
-
|
85
|
-
# First check if the method exists in the registry
|
86
|
-
if registry_api.get_function(method_name):
|
87
|
-
is_vex_function = True
|
88
|
-
else:
|
89
|
-
# Try to check if it's a method on any known object type
|
90
|
-
from ..types.objects import MOTOR, TIMER, BRAIN, CONTROLLER
|
91
|
-
for obj_type in [MOTOR, TIMER, BRAIN, CONTROLLER]:
|
92
|
-
if registry_api.get_method(obj_type, method_name):
|
93
|
-
is_vex_function = True
|
94
|
-
break
|
95
|
-
# Or check if it's a direct function
|
96
|
-
elif registry_api.get_function(function_name):
|
86
|
+
# First check if the method exists in the registry
|
87
|
+
if registry_api.get_function(method_name):
|
97
88
|
is_vex_function = True
|
98
|
-
|
99
|
-
|
89
|
+
else:
|
90
|
+
# Try to check if it's a method on any known object type
|
91
|
+
from ..types.objects import MOTOR, TIMER, BRAIN, CONTROLLER
|
92
|
+
for obj_type in [MOTOR, TIMER, BRAIN, CONTROLLER]:
|
93
|
+
if registry_api.get_method(obj_type, method_name):
|
94
|
+
is_vex_function = True
|
95
|
+
break
|
96
|
+
# Or check if it's a direct function
|
97
|
+
elif registry_api.get_function(function_name):
|
98
|
+
is_vex_function = True
|
99
|
+
|
100
|
+
if is_vex_function:
|
100
101
|
# Convert to VexAPICall and validate
|
101
102
|
vex_call = VexAPICall(
|
102
103
|
node.function,
|
@@ -4,7 +4,7 @@ from typing import Any, Dict, Optional, Type, Union, cast, List
|
|
4
4
|
|
5
5
|
from ..ast.core import Expression, Program, Statement
|
6
6
|
from ..ast.expressions import (
|
7
|
-
AttributeAccess, BinaryOperation, FunctionCall, Identifier, KeywordArgument,
|
7
|
+
AttributeAccess, BinaryOperation, ConditionalExpression, FunctionCall, Identifier, KeywordArgument,
|
8
8
|
UnaryOperation, VariableReference
|
9
9
|
)
|
10
10
|
from ..ast.literals import (
|
@@ -77,6 +77,11 @@ class NodeFactory:
|
|
77
77
|
"""Create a unary operation node."""
|
78
78
|
return UnaryOperation(op, operand, location)
|
79
79
|
|
80
|
+
def create_conditional_expression(self, condition: Expression, true_expr: Expression, false_expr: Expression,
|
81
|
+
location: Optional[SourceLocation] = None) -> ConditionalExpression:
|
82
|
+
"""Create a conditional expression (ternary operator) node."""
|
83
|
+
return ConditionalExpression(condition, true_expr, false_expr, location)
|
84
|
+
|
80
85
|
def create_function_call(self, function: Expression, args: List[Expression] = None,
|
81
86
|
keywords: List[KeywordArgument] = None,
|
82
87
|
location: Optional[SourceLocation] = None) -> FunctionCall:
|
@@ -202,18 +202,63 @@ class PythonParser(BaseParser):
|
|
202
202
|
# print(f"Function call: {function_name}")
|
203
203
|
# print(f"Registry has function: {registry.get_function(function_name) is not None}")
|
204
204
|
|
205
|
+
# Check for common VEX API patterns
|
206
|
+
is_vex_api_call = False
|
207
|
+
|
205
208
|
if function_name:
|
206
209
|
# Check if this is a method call on a known object type
|
207
210
|
if '.' in function_name:
|
208
211
|
obj_name, method_name = function_name.split('.', 1)
|
209
|
-
|
210
|
-
#
|
211
|
-
|
212
|
+
|
213
|
+
# Common VEX method names
|
214
|
+
vex_methods = ['spin', 'stop', 'set_velocity', 'spin_for', 'spin_to_position',
|
215
|
+
'print', 'clear', 'set_font', 'set_pen', 'draw_line', 'draw_rectangle',
|
216
|
+
'rotation', 'heading', 'temperature', 'pressing', 'position']
|
217
|
+
|
218
|
+
# Common VEX object names
|
219
|
+
vex_objects = ['motor', 'brain', 'controller', 'drivetrain', 'gyro', 'vision',
|
220
|
+
'distance', 'inertial', 'optical', 'gps', 'bumper', 'limit']
|
221
|
+
|
222
|
+
# Check if method name is a known VEX method
|
223
|
+
if method_name in vex_methods:
|
224
|
+
is_vex_api_call = True
|
225
|
+
|
226
|
+
# Check if object name starts with a known VEX object type
|
227
|
+
for vex_obj in vex_objects:
|
228
|
+
if obj_name.startswith(vex_obj):
|
229
|
+
is_vex_api_call = True
|
230
|
+
break
|
231
|
+
|
232
|
+
# Check registry
|
212
233
|
if registry.get_function(method_name):
|
213
|
-
|
234
|
+
is_vex_api_call = True
|
235
|
+
|
214
236
|
# Or check if it's a direct function
|
215
|
-
|
216
|
-
|
237
|
+
else:
|
238
|
+
# Common VEX function names
|
239
|
+
vex_functions = ['wait', 'wait_until', 'sleep', 'rumble']
|
240
|
+
|
241
|
+
# Special case for 'print': never treat as VEX API call in test files
|
242
|
+
if function_name == 'print':
|
243
|
+
# Check if this is a test file
|
244
|
+
is_test_file = 'test_' in self.filename
|
245
|
+
# Always treat 'print' as a regular function call in test files
|
246
|
+
if not is_test_file:
|
247
|
+
is_vex_api_call = True
|
248
|
+
else:
|
249
|
+
# Explicitly set to False to ensure it's never treated as a VEX API call in test files
|
250
|
+
is_vex_api_call = False
|
251
|
+
elif function_name in vex_functions:
|
252
|
+
is_vex_api_call = True
|
253
|
+
|
254
|
+
# Check registry, but don't override 'print' in test files
|
255
|
+
if registry.get_function(function_name):
|
256
|
+
# Only set to True if we're not dealing with 'print' in a test file
|
257
|
+
if not (function_name == 'print' and 'test_' in self.filename):
|
258
|
+
is_vex_api_call = True
|
259
|
+
|
260
|
+
if is_vex_api_call:
|
261
|
+
return create_vex_api_call(func, args, keywords, loc)
|
217
262
|
|
218
263
|
# Regular function call
|
219
264
|
return self.factory.create_function_call(func, args, keywords, loc)
|
@@ -294,21 +339,12 @@ class PythonParser(BaseParser):
|
|
294
339
|
op_name = op_type.__name__
|
295
340
|
|
296
341
|
op_map = {
|
297
|
-
'And':
|
298
|
-
'Or':
|
342
|
+
'And': Operator.LOGICAL_AND,
|
343
|
+
'Or': Operator.LOGICAL_OR
|
299
344
|
}
|
300
345
|
|
301
346
|
if op_name in op_map:
|
302
|
-
|
303
|
-
vex_op = PYTHON_COMP_OP_MAP.get(op_str)
|
304
|
-
|
305
|
-
if not vex_op:
|
306
|
-
self.error_handler.add_error(
|
307
|
-
ErrorType.PARSER_ERROR,
|
308
|
-
f"Unsupported boolean operator: {op_name}",
|
309
|
-
loc
|
310
|
-
)
|
311
|
-
vex_op = Operator.LOGICAL_AND # Fallback
|
347
|
+
vex_op = op_map[op_name]
|
312
348
|
else:
|
313
349
|
self.error_handler.add_error(
|
314
350
|
ErrorType.PARSER_ERROR,
|
@@ -329,6 +365,15 @@ class PythonParser(BaseParser):
|
|
329
365
|
|
330
366
|
return result
|
331
367
|
|
368
|
+
# Conditional expressions (ternary operators)
|
369
|
+
elif isinstance(node, ast.IfExp):
|
370
|
+
loc = self._get_location(node)
|
371
|
+
test = self._convert_expression(node.test)
|
372
|
+
body = self._convert_expression(node.body)
|
373
|
+
orelse = self._convert_expression(node.orelse)
|
374
|
+
|
375
|
+
return self.factory.create_conditional_expression(test, body, orelse, loc)
|
376
|
+
|
332
377
|
# List literals
|
333
378
|
elif isinstance(node, ast.List) or isinstance(node, ast.Tuple):
|
334
379
|
# We don't have a dedicated list/tuple node, so use function call
|
@@ -607,22 +652,77 @@ class PythonParser(BaseParser):
|
|
607
652
|
self._get_location(node)
|
608
653
|
)
|
609
654
|
|
610
|
-
# Import statements
|
611
|
-
elif isinstance(node,
|
655
|
+
# Import statements
|
656
|
+
elif isinstance(node, ast.Import):
|
612
657
|
loc = self._get_location(node)
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
658
|
+
# Create a list of assignments for each imported name
|
659
|
+
statements = []
|
660
|
+
|
661
|
+
for name in node.names:
|
662
|
+
# Create an identifier for the module
|
663
|
+
module_name = name.name
|
664
|
+
as_name = name.asname or module_name
|
665
|
+
|
666
|
+
# Create an assignment: as_name = module_name
|
667
|
+
target = self.factory.create_identifier(as_name, loc)
|
668
|
+
value = self.factory.create_identifier(f"<import:{module_name}>", loc)
|
669
|
+
|
670
|
+
statements.append(self.factory.create_assignment(target, value, loc))
|
671
|
+
|
672
|
+
# If there's only one statement, return it
|
673
|
+
if len(statements) == 1:
|
674
|
+
return statements[0]
|
675
|
+
|
676
|
+
# Otherwise, return the first one and add a warning
|
677
|
+
if len(statements) > 1:
|
678
|
+
self.error_handler.add_error(
|
679
|
+
ErrorType.PARSER_ERROR,
|
680
|
+
"Multiple imports in a single statement are not fully supported",
|
622
681
|
loc
|
623
|
-
)
|
624
|
-
|
625
|
-
|
682
|
+
)
|
683
|
+
|
684
|
+
return statements[0]
|
685
|
+
|
686
|
+
# Import from statements
|
687
|
+
elif isinstance(node, ast.ImportFrom):
|
688
|
+
loc = self._get_location(node)
|
689
|
+
module_name = node.module or ""
|
690
|
+
|
691
|
+
# Special case for "from vex import *"
|
692
|
+
if module_name == "vex" and any(name.name == "*" for name in node.names):
|
693
|
+
# Create a special identifier that represents "from vex import *"
|
694
|
+
return self.factory.create_expression_statement(
|
695
|
+
self.factory.create_identifier("<import:vex:*>", loc),
|
696
|
+
loc
|
697
|
+
)
|
698
|
+
|
699
|
+
# For other import from statements, create assignments
|
700
|
+
statements = []
|
701
|
+
|
702
|
+
for name in node.names:
|
703
|
+
# Create an identifier for the imported name
|
704
|
+
imported_name = name.name
|
705
|
+
as_name = name.asname or imported_name
|
706
|
+
|
707
|
+
# Create an assignment: as_name = module_name.imported_name
|
708
|
+
target = self.factory.create_identifier(as_name, loc)
|
709
|
+
value = self.factory.create_identifier(f"<import:{module_name}.{imported_name}>", loc)
|
710
|
+
|
711
|
+
statements.append(self.factory.create_assignment(target, value, loc))
|
712
|
+
|
713
|
+
# If there's only one statement, return it
|
714
|
+
if len(statements) == 1:
|
715
|
+
return statements[0]
|
716
|
+
|
717
|
+
# Otherwise, return the first one and add a warning
|
718
|
+
if len(statements) > 1:
|
719
|
+
self.error_handler.add_error(
|
720
|
+
ErrorType.PARSER_ERROR,
|
721
|
+
"Multiple imports in a single statement are not fully supported",
|
722
|
+
loc
|
723
|
+
)
|
724
|
+
|
725
|
+
return statements[0]
|
626
726
|
|
627
727
|
# Class definitions - not supported yet
|
628
728
|
elif isinstance(node, ast.ClassDef):
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"""Register constructors for VEX objects in the registry."""
|
2
|
+
|
3
|
+
from ..registry import registry
|
4
|
+
from ..signature import VexFunctionSignature, VexFunctionParameter, ParameterMode, SimulationCategory
|
5
|
+
from ...types.base import VOID
|
6
|
+
from ...types.primitives import INT, FLOAT, BOOL, STRING
|
7
|
+
from ...types.enums import PORT_TYPE
|
8
|
+
from ...types.objects import MOTOR
|
9
|
+
|
10
|
+
def register_constructor_functions():
|
11
|
+
"""Register constructor functions in the registry"""
|
12
|
+
|
13
|
+
# Motor constructor
|
14
|
+
motor_params = [
|
15
|
+
VexFunctionParameter("port", PORT_TYPE, description="The port the motor is connected to"),
|
16
|
+
VexFunctionParameter("gear_ratio", FLOAT, None, ParameterMode.VALUE, description="The gear ratio of the motor"),
|
17
|
+
VexFunctionParameter("reverse", BOOL, False, ParameterMode.VALUE, description="Whether to reverse the motor direction")
|
18
|
+
]
|
19
|
+
|
20
|
+
motor_signature = VexFunctionSignature(
|
21
|
+
name="Motor",
|
22
|
+
return_type=MOTOR,
|
23
|
+
parameters=motor_params,
|
24
|
+
description="Create a new Motor object",
|
25
|
+
category=SimulationCategory.CONFIGURATION,
|
26
|
+
python_name="Motor",
|
27
|
+
cpp_name="motor"
|
28
|
+
)
|
29
|
+
|
30
|
+
registry.register_function(motor_signature)
|
31
|
+
|
32
|
+
# Add other constructors as needed (MotorGroup, Drivetrain, etc.)
|
33
|
+
|
34
|
+
if __name__ == "__main__":
|
35
|
+
register_constructor_functions()
|
@@ -1,10 +1,13 @@
|
|
1
1
|
"""Initialize all VEX function definitions in the registry"""
|
2
2
|
|
3
|
-
from . import motor, drivetrain, sensors, timing, display
|
3
|
+
from . import motor, drivetrain, sensors, timing, display, constructors
|
4
4
|
# Import other function modules as they are added
|
5
5
|
|
6
6
|
def initialize_registry():
|
7
7
|
"""Initialize the registry with all VEX functions"""
|
8
|
+
# Constructor functions
|
9
|
+
constructors.register_constructor_functions()
|
10
|
+
|
8
11
|
# Motor functions
|
9
12
|
motor.register_motor_functions()
|
10
13
|
|
@@ -25,4 +28,4 @@ def initialize_registry():
|
|
25
28
|
print("VEX function registry initialized successfully")
|
26
29
|
|
27
30
|
if __name__ == "__main__":
|
28
|
-
initialize_registry()
|
31
|
+
initialize_registry()
|
@@ -24,6 +24,11 @@ class VexFunctionParameter:
|
|
24
24
|
self.description = description
|
25
25
|
self.is_optional = default_value is not None
|
26
26
|
|
27
|
+
@property
|
28
|
+
def optional(self) -> bool:
|
29
|
+
"""Alias for is_optional for compatibility."""
|
30
|
+
return self.is_optional
|
31
|
+
|
27
32
|
def __str__(self) -> str:
|
28
33
|
mode_str = ""
|
29
34
|
if self.mode == ParameterMode.REFERENCE:
|
@@ -143,6 +143,7 @@ class DeserializationFactory:
|
|
143
143
|
"AttributeAccess": self.node_factory.create_attribute_access,
|
144
144
|
"BinaryOperation": self.node_factory.create_binary_operation,
|
145
145
|
"UnaryOperation": self.node_factory.create_unary_operation,
|
146
|
+
"ConditionalExpression": self.node_factory.create_conditional_expression,
|
146
147
|
"FunctionCall": self.node_factory.create_function_call,
|
147
148
|
"KeywordArgument": self.node_factory.create_keyword_argument,
|
148
149
|
|
@@ -228,6 +229,14 @@ class DeserializationFactory:
|
|
228
229
|
keywords = [self._deserialize_value(kw) for kw in data.get("keywords", [])]
|
229
230
|
return self.node_factory.create_function_call(function, args, keywords, location)
|
230
231
|
|
232
|
+
def _create_conditionalexpression(self, data: Dict[str, Any],
|
233
|
+
location: Optional[SourceLocation]) -> IAstNode:
|
234
|
+
"""Create a ConditionalExpression node from serialized data."""
|
235
|
+
condition = self._deserialize_value(data.get("condition"))
|
236
|
+
true_expr = self._deserialize_value(data.get("true_expr"))
|
237
|
+
false_expr = self._deserialize_value(data.get("false_expr"))
|
238
|
+
return self.node_factory.create_conditional_expression(condition, true_expr, false_expr, location)
|
239
|
+
|
231
240
|
def _create_ifstatement(self, data: Dict[str, Any],
|
232
241
|
location: Optional[SourceLocation]) -> IAstNode:
|
233
242
|
"""Create an IfStatement node from serialized data."""
|
@@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional, Set, Type
|
|
11
11
|
|
12
12
|
from ..ast.core import Program, Expression, Statement
|
13
13
|
from ..ast.expressions import (
|
14
|
-
AttributeAccess, BinaryOperation, FunctionCall, Identifier, KeywordArgument,
|
14
|
+
AttributeAccess, BinaryOperation, ConditionalExpression, FunctionCall, Identifier, KeywordArgument,
|
15
15
|
UnaryOperation, VariableReference
|
16
16
|
)
|
17
17
|
from ..ast.literals import (
|
@@ -71,8 +71,8 @@ def _get_all_node_types() -> List[str]:
|
|
71
71
|
# Expression types
|
72
72
|
node_types.extend([
|
73
73
|
"Identifier", "VariableReference", "AttributeAccess",
|
74
|
-
"BinaryOperation", "UnaryOperation", "
|
75
|
-
"KeywordArgument"
|
74
|
+
"BinaryOperation", "UnaryOperation", "ConditionalExpression",
|
75
|
+
"FunctionCall", "KeywordArgument"
|
76
76
|
])
|
77
77
|
|
78
78
|
# Literal types
|
@@ -162,8 +162,9 @@ def _generate_definitions() -> Dict[str, Any]:
|
|
162
162
|
{"$ref": f"#/definitions/{expr_type}"}
|
163
163
|
for expr_type in [
|
164
164
|
"Identifier", "VariableReference", "AttributeAccess",
|
165
|
-
"BinaryOperation", "UnaryOperation", "
|
166
|
-
"
|
165
|
+
"BinaryOperation", "UnaryOperation", "ConditionalExpression",
|
166
|
+
"FunctionCall", "NumberLiteral", "StringLiteral",
|
167
|
+
"BooleanLiteral", "NoneLiteral"
|
167
168
|
]
|
168
169
|
]
|
169
170
|
}
|
@@ -360,6 +361,27 @@ def _generate_definitions() -> Dict[str, Any]:
|
|
360
361
|
}
|
361
362
|
}
|
362
363
|
|
364
|
+
definitions["ConditionalExpression"] = {
|
365
|
+
"type": "object",
|
366
|
+
"required": ["type", "condition", "true_expr", "false_expr"],
|
367
|
+
"properties": {
|
368
|
+
"type": {"enum": ["ConditionalExpression"]},
|
369
|
+
"condition": {
|
370
|
+
"$ref": "#/definitions/Expression",
|
371
|
+
"description": "The condition expression"
|
372
|
+
},
|
373
|
+
"true_expr": {
|
374
|
+
"$ref": "#/definitions/Expression",
|
375
|
+
"description": "The expression to evaluate if condition is true"
|
376
|
+
},
|
377
|
+
"false_expr": {
|
378
|
+
"$ref": "#/definitions/Expression",
|
379
|
+
"description": "The expression to evaluate if condition is false"
|
380
|
+
},
|
381
|
+
"location": {"$ref": "#/definitions/SourceLocation"}
|
382
|
+
}
|
383
|
+
}
|
384
|
+
|
363
385
|
# Statements
|
364
386
|
definitions["ExpressionStatement"] = {
|
365
387
|
"type": "object",
|
@@ -94,4 +94,29 @@ ANALOG_UNITS = EnumType("AnalogUnits", {
|
|
94
94
|
"MV": 4 # Millivolts
|
95
95
|
})
|
96
96
|
|
97
|
-
#
|
97
|
+
# Port type enum
|
98
|
+
PORT_TYPE = EnumType("PortType", {
|
99
|
+
"PORT1": 1,
|
100
|
+
"PORT2": 2,
|
101
|
+
"PORT3": 3,
|
102
|
+
"PORT4": 4,
|
103
|
+
"PORT5": 5,
|
104
|
+
"PORT6": 6,
|
105
|
+
"PORT7": 7,
|
106
|
+
"PORT8": 8,
|
107
|
+
"PORT9": 9,
|
108
|
+
"PORT10": 10,
|
109
|
+
"PORT11": 11,
|
110
|
+
"PORT12": 12,
|
111
|
+
"PORT13": 13,
|
112
|
+
"PORT14": 14,
|
113
|
+
"PORT15": 15,
|
114
|
+
"PORT16": 16,
|
115
|
+
"PORT17": 17,
|
116
|
+
"PORT18": 18,
|
117
|
+
"PORT19": 19,
|
118
|
+
"PORT20": 20,
|
119
|
+
"PORT21": 21
|
120
|
+
})
|
121
|
+
|
122
|
+
# Add more VEX enum types as needed
|
@@ -47,6 +47,9 @@ class AstVisitor(Generic[T_VisitorResult], ABC):
|
|
47
47
|
def visit_unaryoperation(self, node: Any) -> T_VisitorResult:
|
48
48
|
return self.generic_visit(node)
|
49
49
|
|
50
|
+
def visit_conditionalexpression(self, node: Any) -> T_VisitorResult:
|
51
|
+
return self.generic_visit(node)
|
52
|
+
|
50
53
|
def visit_functioncall(self, node: Any) -> T_VisitorResult:
|
51
54
|
return self.generic_visit(node)
|
52
55
|
|
@@ -127,4 +130,4 @@ class TypedVisitorMixin:
|
|
127
130
|
method_name = self.node_type_to_method_name(type(node))
|
128
131
|
if hasattr(self, method_name):
|
129
132
|
return getattr(self, method_name)(node)
|
130
|
-
return self.generic_visit(node)
|
133
|
+
return self.generic_visit(node)
|
@@ -126,6 +126,57 @@ class PrintVisitor(AstVisitor[str]):
|
|
126
126
|
visit_variablereference = generic_visit
|
127
127
|
visit_binaryoperation = generic_visit
|
128
128
|
visit_unaryoperation = generic_visit
|
129
|
+
|
130
|
+
def visit_conditionalexpression(self, node: Any) -> str:
|
131
|
+
"""Custom visitor for conditional expressions to make them more readable."""
|
132
|
+
self._output.write("ConditionalExpression:\n")
|
133
|
+
self._indent_level += 1
|
134
|
+
|
135
|
+
# Print condition
|
136
|
+
self._indent()
|
137
|
+
self._output.write("condition = ")
|
138
|
+
self.visit(node.condition)
|
139
|
+
self._output.write("\n")
|
140
|
+
|
141
|
+
# Print true expression
|
142
|
+
self._indent()
|
143
|
+
self._output.write("true_expr = ")
|
144
|
+
self.visit(node.true_expr)
|
145
|
+
self._output.write("\n")
|
146
|
+
|
147
|
+
# Print false expression
|
148
|
+
self._indent()
|
149
|
+
self._output.write("false_expr = ")
|
150
|
+
self.visit(node.false_expr)
|
151
|
+
self._output.write("\n")
|
152
|
+
|
153
|
+
# Add the formatted Python conditional expression with if/else keywords
|
154
|
+
self._indent()
|
155
|
+
self._output.write("formatted = ")
|
156
|
+
|
157
|
+
# Capture the string representation of each part
|
158
|
+
true_str = io.StringIO()
|
159
|
+
old_output = self._output
|
160
|
+
self._output = true_str
|
161
|
+
self.visit(node.true_expr)
|
162
|
+
|
163
|
+
cond_str = io.StringIO()
|
164
|
+
self._output = cond_str
|
165
|
+
self.visit(node.condition)
|
166
|
+
|
167
|
+
false_str = io.StringIO()
|
168
|
+
self._output = false_str
|
169
|
+
self.visit(node.false_expr)
|
170
|
+
|
171
|
+
# Restore the original output
|
172
|
+
self._output = old_output
|
173
|
+
|
174
|
+
# Write the formatted conditional expression with if/else keywords
|
175
|
+
self._output.write(f"{true_str.getvalue()} if {cond_str.getvalue()} else {false_str.getvalue()}\n")
|
176
|
+
|
177
|
+
self._indent_level -= 1
|
178
|
+
return ""
|
179
|
+
|
129
180
|
visit_functioncall = generic_visit
|
130
181
|
visit_keywordargument = generic_visit
|
131
182
|
visit_expressionstatement = generic_visit
|
@@ -142,4 +193,4 @@ class PrintVisitor(AstVisitor[str]):
|
|
142
193
|
visit_motorcontrol = generic_visit
|
143
194
|
visit_sensorreading = generic_visit
|
144
195
|
visit_timingcontrol = generic_visit
|
145
|
-
visit_displayoutput = generic_visit
|
196
|
+
visit_displayoutput = generic_visit
|
@@ -7,6 +7,7 @@ requirements.txt
|
|
7
7
|
setup.py
|
8
8
|
tests/conftest.py
|
9
9
|
tests/test_comprehensive_integration.py
|
10
|
+
tests/test_conditional_expressions.py
|
10
11
|
tests/test_core.py
|
11
12
|
tests/test_integration.py
|
12
13
|
tests/test_literals.py
|
@@ -51,6 +52,7 @@ vex_ast/registry/signature.py
|
|
51
52
|
vex_ast/registry/simulation_behavior.py
|
52
53
|
vex_ast/registry/validation.py
|
53
54
|
vex_ast/registry/functions/__init__.py
|
55
|
+
vex_ast/registry/functions/constructors.py
|
54
56
|
vex_ast/registry/functions/display.py
|
55
57
|
vex_ast/registry/functions/drivetrain.py
|
56
58
|
vex_ast/registry/functions/initialize.py
|
vex_ast-0.2.3/tests/conftest.py
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
# tests/conftest.py
|
2
|
-
import os
|
3
|
-
import sys
|
4
|
-
import pytest
|
5
|
-
|
6
|
-
# Get the absolute path to the project root directory
|
7
|
-
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
8
|
-
|
9
|
-
# Add the project root to the Python path if it's not already there
|
10
|
-
if project_root not in sys.path:
|
11
|
-
sys.path.insert(0, project_root)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|