vex-ast 0.2.1__tar.gz → 0.2.3__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.1/vex_ast.egg-info → vex_ast-0.2.3}/PKG-INFO +1 -1
- {vex_ast-0.2.1 → vex_ast-0.2.3}/pyproject.toml +1 -1
- {vex_ast-0.2.1 → vex_ast-0.2.3}/setup.py +1 -1
- vex_ast-0.2.3/tests/test_comprehensive_integration.py +338 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_parser.py +5 -4
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/__init__.py +6 -2
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/validators.py +9 -3
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/vex_nodes.py +40 -2
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/parser/factory.py +7 -1
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/parser/python_parser.py +7 -1
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/signature.py +44 -1
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/serialization/json_deserializer.py +13 -2
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/serialization/schema.py +3 -4
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/utils/errors.py +2 -2
- {vex_ast-0.2.1 → vex_ast-0.2.3/vex_ast.egg-info}/PKG-INFO +1 -1
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast.egg-info/SOURCES.txt +1 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/LICENSE +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/MANIFEST.in +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/README.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/pytest.ini +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/requirements.txt +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/setup.cfg +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/conftest.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_core.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_integration.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_literals.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_navigator.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_registry.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_serialization.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_statements.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_vex_nodes.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/tests/test_visitors.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/README.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/READMEAPI.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/README.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/__init__.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/core.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/expressions.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/interfaces.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/literals.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/navigator.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/operators.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/ast/statements.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/parser/README.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/parser/__init__.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/parser/interfaces.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/parser/strategies.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/README.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/__init__.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/api.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/categories.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/functions/__init__.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/functions/display.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/functions/drivetrain.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/functions/initialize.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/functions/motor.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/functions/sensors.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/functions/timing.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/language_map.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/registry.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/simulation_behavior.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/registry/validation.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/serialization/__init__.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/serialization/json_serializer.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/types/README.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/types/__init__.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/types/base.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/types/enums.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/types/objects.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/types/primitives.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/types/type_checker.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/utils/README.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/utils/__init__.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/utils/source_location.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/utils/type_definitions.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/visitors/README.md +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/visitors/__init__.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/visitors/analyzer.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/visitors/base.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/visitors/printer.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast/visitors/transformer.py +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/vex_ast.egg-info/dependency_links.txt +0 -0
- {vex_ast-0.2.1 → vex_ast-0.2.3}/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.3',
|
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,338 @@
|
|
1
|
+
"""
|
2
|
+
Comprehensive integration tests for the vex_ast package.
|
3
|
+
|
4
|
+
These tests ensure that all API endpoints work together correctly in various scenarios.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
import json
|
9
|
+
import tempfile
|
10
|
+
import pytest
|
11
|
+
from typing import Dict, Any
|
12
|
+
|
13
|
+
from vex_ast import (
|
14
|
+
parse_string,
|
15
|
+
parse_file,
|
16
|
+
ErrorHandler,
|
17
|
+
VexSyntaxError,
|
18
|
+
VexAstError,
|
19
|
+
PrintVisitor,
|
20
|
+
NodeCounter,
|
21
|
+
VariableCollector,
|
22
|
+
create_navigator,
|
23
|
+
serialize_ast_to_dict,
|
24
|
+
serialize_ast_to_json,
|
25
|
+
deserialize_ast_from_dict,
|
26
|
+
deserialize_ast_from_json,
|
27
|
+
generate_ast_schema,
|
28
|
+
export_schema_to_file,
|
29
|
+
registry_api
|
30
|
+
)
|
31
|
+
from vex_ast.ast.vex_nodes import VexAPICall
|
32
|
+
from vex_ast.ast.validators import validate_vex_functions
|
33
|
+
|
34
|
+
|
35
|
+
class TestComprehensiveIntegration:
|
36
|
+
"""Comprehensive integration tests for the vex_ast package."""
|
37
|
+
|
38
|
+
def test_parse_file_functionality(self):
|
39
|
+
"""Test the parse_file functionality with a temporary file."""
|
40
|
+
# Create a temporary file with VEX code
|
41
|
+
with tempfile.NamedTemporaryFile(mode='w+', suffix='.py', delete=False) as temp_file:
|
42
|
+
temp_file.write("""
|
43
|
+
# Define a motor
|
44
|
+
motor1 = Motor(PORT1)
|
45
|
+
|
46
|
+
# Define a function
|
47
|
+
def move_forward(speed):
|
48
|
+
motor1.spin(FORWARD, speed, PERCENT)
|
49
|
+
wait(1, SECONDS)
|
50
|
+
motor1.stop()
|
51
|
+
return True
|
52
|
+
|
53
|
+
# Call the function
|
54
|
+
success = move_forward(50)
|
55
|
+
""")
|
56
|
+
temp_file_path = temp_file.name
|
57
|
+
|
58
|
+
try:
|
59
|
+
# Parse the file
|
60
|
+
ast = parse_file(temp_file_path)
|
61
|
+
|
62
|
+
# Verify the AST structure
|
63
|
+
assert ast is not None
|
64
|
+
assert len(ast.body) > 0
|
65
|
+
|
66
|
+
# Find function definitions
|
67
|
+
func_defs = [node for node in ast.body if node.__class__.__name__ == "FunctionDefinition"]
|
68
|
+
assert len(func_defs) == 1
|
69
|
+
assert func_defs[0].name == "move_forward"
|
70
|
+
|
71
|
+
# Test that visitors work with the parsed AST
|
72
|
+
printer = PrintVisitor()
|
73
|
+
result = printer.visit(ast)
|
74
|
+
assert isinstance(result, str)
|
75
|
+
assert len(result) > 0
|
76
|
+
|
77
|
+
counter = NodeCounter()
|
78
|
+
count = counter.visit(ast)
|
79
|
+
assert count > 10
|
80
|
+
|
81
|
+
collector = VariableCollector()
|
82
|
+
variables = collector.visit(ast)
|
83
|
+
assert "motor1" in variables
|
84
|
+
assert "speed" in variables
|
85
|
+
|
86
|
+
finally:
|
87
|
+
# Clean up the temporary file
|
88
|
+
os.unlink(temp_file_path)
|
89
|
+
|
90
|
+
def test_error_handling_integration(self):
|
91
|
+
"""Test error handling integration with parsing and visitors."""
|
92
|
+
# Create an error handler that doesn't raise exceptions
|
93
|
+
error_handler = ErrorHandler(raise_on_error=False)
|
94
|
+
|
95
|
+
# Parse code with syntax errors
|
96
|
+
code_with_errors = """
|
97
|
+
def invalid_function(
|
98
|
+
# Missing closing parenthesis
|
99
|
+
print("This will cause an error")
|
100
|
+
"""
|
101
|
+
|
102
|
+
# This should not raise an exception due to raise_on_error=False
|
103
|
+
ast = parse_string(code_with_errors, error_handler=error_handler)
|
104
|
+
|
105
|
+
# Verify that errors were collected
|
106
|
+
assert error_handler.has_errors()
|
107
|
+
errors = error_handler.get_errors()
|
108
|
+
assert len(errors) > 0
|
109
|
+
|
110
|
+
# Try with a different error handler that raises exceptions
|
111
|
+
error_handler_strict = ErrorHandler(raise_on_error=True)
|
112
|
+
|
113
|
+
# This should raise a VexSyntaxError
|
114
|
+
with pytest.raises(VexSyntaxError):
|
115
|
+
parse_string(code_with_errors, error_handler=error_handler_strict)
|
116
|
+
|
117
|
+
# Test error handling with valid code but invalid VEX API calls
|
118
|
+
invalid_vex_code = """
|
119
|
+
motor1 = Motor(PORT1)
|
120
|
+
# Invalid: wrong arguments to spin
|
121
|
+
motor1.spin(INVALID_DIRECTION, "not_a_number", "not_a_unit")
|
122
|
+
"""
|
123
|
+
|
124
|
+
# Parse the code (should succeed syntactically)
|
125
|
+
ast = parse_string(invalid_vex_code)
|
126
|
+
|
127
|
+
# Validate VEX functions (should find errors)
|
128
|
+
validation_errors = validate_vex_functions(ast)
|
129
|
+
assert len(validation_errors) > 0
|
130
|
+
|
131
|
+
def test_end_to_end_workflow(self):
|
132
|
+
"""Test an end-to-end workflow combining multiple API functions."""
|
133
|
+
# 1. Parse a complex program
|
134
|
+
code = """
|
135
|
+
# Initialize motors
|
136
|
+
left_motor = Motor(PORT1)
|
137
|
+
right_motor = Motor(PORT2)
|
138
|
+
|
139
|
+
# Define a function to drive forward
|
140
|
+
def drive_forward(speed, time_ms):
|
141
|
+
# Set both motors to the specified speed
|
142
|
+
left_motor.set_velocity(speed, PERCENT)
|
143
|
+
right_motor.set_velocity(speed, PERCENT)
|
144
|
+
|
145
|
+
# Start the motors
|
146
|
+
left_motor.spin(FORWARD)
|
147
|
+
right_motor.spin(FORWARD)
|
148
|
+
|
149
|
+
# Wait for the specified time
|
150
|
+
wait(time_ms, MSEC)
|
151
|
+
|
152
|
+
# Stop the motors
|
153
|
+
left_motor.stop()
|
154
|
+
right_motor.stop()
|
155
|
+
|
156
|
+
return True
|
157
|
+
|
158
|
+
# Define a function to turn
|
159
|
+
def turn(direction, speed, angle):
|
160
|
+
if direction == LEFT:
|
161
|
+
left_motor.set_velocity(-speed, PERCENT)
|
162
|
+
right_motor.set_velocity(speed, PERCENT)
|
163
|
+
else:
|
164
|
+
left_motor.set_velocity(speed, PERCENT)
|
165
|
+
right_motor.set_velocity(-speed, PERCENT)
|
166
|
+
|
167
|
+
left_motor.spin(FORWARD)
|
168
|
+
right_motor.spin(FORWARD)
|
169
|
+
|
170
|
+
# Wait until the robot has turned the specified angle
|
171
|
+
wait_until(gyro.rotation() >= angle)
|
172
|
+
|
173
|
+
left_motor.stop()
|
174
|
+
right_motor.stop()
|
175
|
+
|
176
|
+
return True
|
177
|
+
|
178
|
+
# Main program
|
179
|
+
def main():
|
180
|
+
# Display welcome message
|
181
|
+
brain.screen.print("Robot starting...")
|
182
|
+
wait(1, SECONDS)
|
183
|
+
|
184
|
+
# Drive forward at 50% speed for 2 seconds
|
185
|
+
success = drive_forward(50, 2000)
|
186
|
+
|
187
|
+
if success:
|
188
|
+
# Turn right 90 degrees
|
189
|
+
turn(RIGHT, 30, 90)
|
190
|
+
|
191
|
+
# Drive forward again
|
192
|
+
drive_forward(50, 1000)
|
193
|
+
|
194
|
+
brain.screen.print("Mission completed!")
|
195
|
+
else:
|
196
|
+
brain.screen.print("Drive failed!")
|
197
|
+
|
198
|
+
return 0
|
199
|
+
"""
|
200
|
+
|
201
|
+
# 2. Parse the code
|
202
|
+
ast = parse_string(code)
|
203
|
+
|
204
|
+
# 3. Use a visitor to analyze the AST
|
205
|
+
counter = NodeCounter()
|
206
|
+
node_count = counter.visit(ast)
|
207
|
+
assert node_count > 50 # Complex program should have many nodes
|
208
|
+
|
209
|
+
# 4. Use the navigator to find specific nodes
|
210
|
+
navigator = create_navigator(ast)
|
211
|
+
|
212
|
+
# Find all function definitions
|
213
|
+
func_defs = navigator.find_function_definitions()
|
214
|
+
assert len(func_defs) == 3
|
215
|
+
func_names = [func.name for func in func_defs]
|
216
|
+
assert "drive_forward" in func_names
|
217
|
+
assert "turn" in func_names
|
218
|
+
assert "main" in func_names
|
219
|
+
|
220
|
+
# Find all VEX API calls
|
221
|
+
vex_calls = navigator.find_vex_api_calls()
|
222
|
+
assert len(vex_calls) > 5
|
223
|
+
|
224
|
+
# 5. Serialize the AST to JSON
|
225
|
+
json_str = serialize_ast_to_json(ast)
|
226
|
+
assert isinstance(json_str, str)
|
227
|
+
|
228
|
+
# 6. Deserialize back to an AST
|
229
|
+
deserialized_ast = deserialize_ast_from_json(json_str)
|
230
|
+
|
231
|
+
# 7. Verify the deserialized AST has the same structure
|
232
|
+
deserialized_func_defs = [node for node in deserialized_ast.body
|
233
|
+
if node.__class__.__name__ == "FunctionDefinition"]
|
234
|
+
assert len(deserialized_func_defs) == 3
|
235
|
+
deserialized_func_names = [func.name for func in deserialized_func_defs]
|
236
|
+
assert set(deserialized_func_names) == set(func_names)
|
237
|
+
|
238
|
+
# 8. Use a visitor on the deserialized AST
|
239
|
+
collector = VariableCollector()
|
240
|
+
variables = collector.visit(deserialized_ast)
|
241
|
+
assert "left_motor" in variables
|
242
|
+
assert "right_motor" in variables
|
243
|
+
assert "speed" in variables
|
244
|
+
assert "direction" in variables
|
245
|
+
|
246
|
+
def test_schema_export_functionality(self):
|
247
|
+
"""Test the schema export functionality."""
|
248
|
+
# Generate the schema
|
249
|
+
schema = generate_ast_schema()
|
250
|
+
assert isinstance(schema, dict)
|
251
|
+
|
252
|
+
# Export the schema to a temporary file
|
253
|
+
with tempfile.NamedTemporaryFile(suffix='.json', delete=False) as temp_file:
|
254
|
+
temp_file_path = temp_file.name
|
255
|
+
|
256
|
+
try:
|
257
|
+
# Export the schema
|
258
|
+
export_schema_to_file(schema, temp_file_path)
|
259
|
+
|
260
|
+
# Verify the file exists and contains valid JSON
|
261
|
+
assert os.path.exists(temp_file_path)
|
262
|
+
|
263
|
+
with open(temp_file_path, 'r') as f:
|
264
|
+
loaded_schema = json.load(f)
|
265
|
+
|
266
|
+
# Check that it's the same schema
|
267
|
+
assert loaded_schema["$schema"] == schema["$schema"]
|
268
|
+
assert "definitions" in loaded_schema
|
269
|
+
assert set(loaded_schema["definitions"].keys()) == set(schema["definitions"].keys())
|
270
|
+
|
271
|
+
finally:
|
272
|
+
# Clean up
|
273
|
+
if os.path.exists(temp_file_path):
|
274
|
+
os.unlink(temp_file_path)
|
275
|
+
|
276
|
+
def test_edge_cases(self):
|
277
|
+
"""Test edge cases in parsing and serialization."""
|
278
|
+
# Test parsing an empty program
|
279
|
+
empty_ast = parse_string("")
|
280
|
+
assert empty_ast is not None
|
281
|
+
assert len(empty_ast.body) == 0
|
282
|
+
|
283
|
+
# Test parsing a program with only comments
|
284
|
+
comments_ast = parse_string("# This is a comment\n# Another comment")
|
285
|
+
assert comments_ast is not None
|
286
|
+
assert len(comments_ast.body) == 0
|
287
|
+
|
288
|
+
# Test parsing a program with unusual whitespace
|
289
|
+
whitespace_code = """
|
290
|
+
|
291
|
+
|
292
|
+
x = 42
|
293
|
+
|
294
|
+
|
295
|
+
y = x + 10
|
296
|
+
|
297
|
+
|
298
|
+
"""
|
299
|
+
whitespace_ast = parse_string(whitespace_code)
|
300
|
+
assert whitespace_ast is not None
|
301
|
+
assert len(whitespace_ast.body) == 2
|
302
|
+
|
303
|
+
# Test serializing and deserializing an empty program
|
304
|
+
empty_json = serialize_ast_to_json(empty_ast)
|
305
|
+
deserialized_empty = deserialize_ast_from_json(empty_json)
|
306
|
+
assert deserialized_empty is not None
|
307
|
+
assert len(deserialized_empty.body) == 0
|
308
|
+
|
309
|
+
# Test with a program containing all types of literals
|
310
|
+
literals_code = """
|
311
|
+
# Number literals
|
312
|
+
int_val = 42
|
313
|
+
float_val = 3.14
|
314
|
+
|
315
|
+
# String literals
|
316
|
+
str_val = "Hello, world!"
|
317
|
+
str_val2 = 'Single quotes'
|
318
|
+
|
319
|
+
# Boolean literals
|
320
|
+
bool_val1 = True
|
321
|
+
bool_val2 = False
|
322
|
+
|
323
|
+
# None literal
|
324
|
+
none_val = None
|
325
|
+
"""
|
326
|
+
|
327
|
+
literals_ast = parse_string(literals_code)
|
328
|
+
assert literals_ast is not None
|
329
|
+
assert len(literals_ast.body) > 0
|
330
|
+
|
331
|
+
# Serialize and deserialize
|
332
|
+
literals_json = serialize_ast_to_json(literals_ast)
|
333
|
+
deserialized_literals = deserialize_ast_from_json(literals_json)
|
334
|
+
|
335
|
+
# Verify the literals were preserved
|
336
|
+
collector = VariableCollector()
|
337
|
+
variables = collector.visit(deserialized_literals)
|
338
|
+
assert len(variables) == 0 # All are assignments, no references
|
@@ -58,9 +58,10 @@ class TestParser:
|
|
58
58
|
code = "if x ==" # Incomplete if statement
|
59
59
|
error_handler = ErrorHandler(raise_on_error=False)
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
# Should not raise an exception when raise_on_error=False
|
62
|
+
ast = parse_string(code, error_handler=error_handler)
|
63
|
+
|
64
|
+
# But should collect errors
|
64
65
|
assert error_handler.has_errors()
|
65
66
|
errors = error_handler.get_errors()
|
66
|
-
assert len(errors) >= 1
|
67
|
+
assert len(errors) >= 1
|
@@ -30,7 +30,11 @@ from .serialization.schema import generate_ast_schema, export_schema_to_file
|
|
30
30
|
__version__ = "0.2.0"
|
31
31
|
|
32
32
|
# Initialize the registry with default functions
|
33
|
-
|
33
|
+
try:
|
34
|
+
initialize()
|
35
|
+
print("VEX function registry initialized successfully")
|
36
|
+
except Exception as e:
|
37
|
+
print(f"Error initializing VEX function registry: {e}")
|
34
38
|
|
35
39
|
__all__ = [
|
36
40
|
# Core functionality
|
@@ -65,4 +69,4 @@ __all__ = [
|
|
65
69
|
"deserialize_ast_from_json",
|
66
70
|
"generate_ast_schema",
|
67
71
|
"export_schema_to_file"
|
68
|
-
]
|
72
|
+
]
|
@@ -81,11 +81,17 @@ class VexFunctionValidator(AstVisitor[List[Tuple[VexAPICall, str]]]):
|
|
81
81
|
# Check if this is a method call on a known object type
|
82
82
|
if '.' in function_name:
|
83
83
|
obj_name, method_name = function_name.split('.', 1)
|
84
|
-
|
85
|
-
#
|
86
|
-
# Just check if the method name exists in the registry API
|
84
|
+
|
85
|
+
# First check if the method exists in the registry
|
87
86
|
if registry_api.get_function(method_name):
|
88
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
|
89
95
|
# Or check if it's a direct function
|
90
96
|
elif registry_api.get_function(function_name):
|
91
97
|
is_vex_function = True
|
@@ -59,7 +59,20 @@ class VexAPICall(FunctionCall):
|
|
59
59
|
if '.' in function_name:
|
60
60
|
# For method calls like "motor1.spin", extract the method name
|
61
61
|
obj_name, method_name = function_name.split('.', 1)
|
62
|
+
|
63
|
+
# First try to get the method signature directly
|
62
64
|
self._signature = registry_api.get_function(method_name)
|
65
|
+
|
66
|
+
# If that fails, try to get it as a method of a specific object type
|
67
|
+
# This is a fallback since we don't know the actual type at parse time
|
68
|
+
if not self._signature:
|
69
|
+
# Try common object types
|
70
|
+
from ..types.objects import MOTOR, TIMER, BRAIN, CONTROLLER
|
71
|
+
for obj_type in [MOTOR, TIMER, BRAIN, CONTROLLER]:
|
72
|
+
method_sig = registry_api.get_method(obj_type, method_name)
|
73
|
+
if method_sig:
|
74
|
+
self._signature = method_sig
|
75
|
+
break
|
63
76
|
else:
|
64
77
|
# For direct function calls
|
65
78
|
self._signature = registry_api.get_function(function_name)
|
@@ -78,8 +91,21 @@ class VexAPICall(FunctionCall):
|
|
78
91
|
return False, self._validation_error
|
79
92
|
|
80
93
|
# Convert args and kwargs to appropriate format
|
81
|
-
arg_values = [
|
82
|
-
|
94
|
+
arg_values = []
|
95
|
+
for arg in self.args:
|
96
|
+
# For string literals, use their actual string value for validation
|
97
|
+
if hasattr(arg, 'value') and hasattr(arg, '__class__') and arg.__class__.__name__ == 'StringLiteral':
|
98
|
+
arg_values.append(arg.value)
|
99
|
+
else:
|
100
|
+
arg_values.append(arg)
|
101
|
+
|
102
|
+
kwarg_values = {}
|
103
|
+
for kw in (self.keywords or []):
|
104
|
+
# For string literals, use their actual string value for validation
|
105
|
+
if hasattr(kw.value, 'value') and hasattr(kw.value, '__class__') and kw.value.__class__.__name__ == 'StringLiteral':
|
106
|
+
kwarg_values[kw.name] = kw.value.value
|
107
|
+
else:
|
108
|
+
kwarg_values[kw.name] = kw.value
|
83
109
|
|
84
110
|
# Validate against the signature
|
85
111
|
valid, error = signature.validate_arguments(arg_values, kwarg_values)
|
@@ -205,7 +231,19 @@ def create_vex_api_call(function: IExpression, args: List[IExpression],
|
|
205
231
|
if '.' in function_name:
|
206
232
|
# For method calls like "motor1.spin", extract the method name
|
207
233
|
obj_name, method_name = function_name.split('.', 1)
|
234
|
+
|
235
|
+
# First try to get the method signature directly
|
208
236
|
signature = registry_api.get_function(method_name)
|
237
|
+
|
238
|
+
# If that fails, try to get it as a method of a specific object type
|
239
|
+
if not signature:
|
240
|
+
# Try common object types
|
241
|
+
from ..types.objects import MOTOR, TIMER, BRAIN, CONTROLLER
|
242
|
+
for obj_type in [MOTOR, TIMER, BRAIN, CONTROLLER]:
|
243
|
+
method_sig = registry_api.get_method(obj_type, method_name)
|
244
|
+
if method_sig:
|
245
|
+
signature = method_sig
|
246
|
+
break
|
209
247
|
else:
|
210
248
|
# For direct function calls
|
211
249
|
signature = registry_api.get_function(function_name)
|
@@ -123,6 +123,12 @@ class NodeFactory:
|
|
123
123
|
"""Create a function definition node."""
|
124
124
|
return FunctionDefinition(name, args, body, return_annotation, location)
|
125
125
|
|
126
|
+
def create_argument(self, name: str, annotation: Optional[Expression] = None,
|
127
|
+
default: Optional[Expression] = None,
|
128
|
+
location: Optional[SourceLocation] = None) -> Argument:
|
129
|
+
"""Create an argument node."""
|
130
|
+
return Argument(name, annotation, default, location)
|
131
|
+
|
126
132
|
def create_return_statement(self, value: Optional[Expression] = None,
|
127
133
|
location: Optional[SourceLocation] = None) -> ReturnStatement:
|
128
134
|
"""Create a return statement node."""
|
@@ -176,4 +182,4 @@ class NodeFactory:
|
|
176
182
|
return Program(body, location)
|
177
183
|
|
178
184
|
# Global factory instance for simple use cases
|
179
|
-
default_factory = NodeFactory()
|
185
|
+
default_factory = NodeFactory()
|
@@ -692,7 +692,13 @@ class PythonParser(BaseParser):
|
|
692
692
|
f"Syntax error: {e.msg}",
|
693
693
|
loc
|
694
694
|
)
|
695
|
-
|
695
|
+
|
696
|
+
# Only raise if the error handler is configured to do so
|
697
|
+
if self.error_handler._raise_on_error:
|
698
|
+
raise VexSyntaxError(f"Syntax error: {e.msg}", loc) from e
|
699
|
+
|
700
|
+
# Return an empty program if we're not raising
|
701
|
+
return self.factory.create_program([])
|
696
702
|
|
697
703
|
except Exception as e:
|
698
704
|
# Handle other parsing errors
|
@@ -138,6 +138,49 @@ class VexFunctionSignature:
|
|
138
138
|
if param_name in kwargs:
|
139
139
|
return False, f"Duplicate argument '{param_name}' for {self.name}"
|
140
140
|
|
141
|
-
#
|
141
|
+
# Type checking for arguments
|
142
|
+
from ..types.type_checker import type_checker
|
143
|
+
from ..types.enums import EnumType
|
144
|
+
|
145
|
+
# Check positional arguments
|
146
|
+
for i, arg in enumerate(args):
|
147
|
+
if i >= len(self.parameters):
|
148
|
+
break
|
149
|
+
|
150
|
+
param = self.parameters[i]
|
151
|
+
expected_type = param.type
|
152
|
+
|
153
|
+
# Handle string literals for enum types
|
154
|
+
if isinstance(expected_type, EnumType) and isinstance(arg, str):
|
155
|
+
if arg not in expected_type.values:
|
156
|
+
return False, f"Invalid enum value '{arg}' for parameter '{param.name}' in {self.name}"
|
157
|
+
continue
|
158
|
+
|
159
|
+
# Handle other types
|
160
|
+
if hasattr(arg, 'get_type'):
|
161
|
+
arg_type = arg.get_type()
|
162
|
+
if arg_type and not type_checker.is_compatible(arg_type, expected_type):
|
163
|
+
return False, f"Type mismatch for parameter '{param.name}' in {self.name}: expected {expected_type}, got {arg_type}"
|
164
|
+
|
165
|
+
# Check keyword arguments
|
166
|
+
for kwarg_name, kwarg_value in kwargs.items():
|
167
|
+
# Find the parameter
|
168
|
+
param = next((p for p in self.parameters if p.name == kwarg_name), None)
|
169
|
+
if not param:
|
170
|
+
continue # Already checked for unknown kwargs above
|
171
|
+
|
172
|
+
expected_type = param.type
|
173
|
+
|
174
|
+
# Handle string literals for enum types
|
175
|
+
if isinstance(expected_type, EnumType) and isinstance(kwarg_value, str):
|
176
|
+
if kwarg_value not in expected_type.values:
|
177
|
+
return False, f"Invalid enum value '{kwarg_value}' for parameter '{param.name}' in {self.name}"
|
178
|
+
continue
|
179
|
+
|
180
|
+
# Handle other types
|
181
|
+
if hasattr(kwarg_value, 'get_type'):
|
182
|
+
kwarg_type = kwarg_value.get_type()
|
183
|
+
if kwarg_type and not type_checker.is_compatible(kwarg_type, expected_type):
|
184
|
+
return False, f"Type mismatch for parameter '{param.name}' in {self.name}: expected {expected_type}, got {kwarg_type}"
|
142
185
|
|
143
186
|
return True, None
|
@@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional, Type, Union, cast
|
|
10
10
|
from ..ast.interfaces import IAstNode
|
11
11
|
from ..parser.factory import NodeFactory
|
12
12
|
from ..utils.source_location import SourceLocation
|
13
|
-
from ..utils.errors import ErrorHandler
|
13
|
+
from ..utils.errors import ErrorHandler, ErrorType
|
14
14
|
|
15
15
|
|
16
16
|
class DeserializationFactory:
|
@@ -153,6 +153,7 @@ class DeserializationFactory:
|
|
153
153
|
"WhileLoop": self.node_factory.create_while_loop,
|
154
154
|
"ForLoop": self.node_factory.create_for_loop,
|
155
155
|
"FunctionDefinition": self.node_factory.create_function_definition,
|
156
|
+
"Argument": self.node_factory.create_argument,
|
156
157
|
"ReturnStatement": self.node_factory.create_return_statement,
|
157
158
|
"BreakStatement": self.node_factory.create_break_statement,
|
158
159
|
"ContinueStatement": self.node_factory.create_continue_statement,
|
@@ -198,11 +199,21 @@ class DeserializationFactory:
|
|
198
199
|
except TypeError as e:
|
199
200
|
# If the factory method doesn't accept the kwargs, report an error
|
200
201
|
if self.error_handler:
|
201
|
-
self.error_handler.
|
202
|
+
self.error_handler.add_error(
|
203
|
+
error_type=ErrorType.INTERNAL_ERROR,
|
204
|
+
message=f"Failed to create {node_type}: {str(e)}"
|
205
|
+
)
|
202
206
|
raise ValueError(f"Failed to deserialize {node_type}: {str(e)}")
|
203
207
|
|
204
208
|
# Specific node creation methods for complex cases
|
205
209
|
|
210
|
+
def _create_attributeaccess(self, data: Dict[str, Any],
|
211
|
+
location: Optional[SourceLocation]) -> IAstNode:
|
212
|
+
"""Create an AttributeAccess node from serialized data."""
|
213
|
+
object_expr = self._deserialize_value(data.get("object"))
|
214
|
+
attribute = data.get("attribute", "")
|
215
|
+
return self.node_factory.create_attribute_access(object_expr, attribute, location)
|
216
|
+
|
206
217
|
def _create_program(self, data: Dict[str, Any],
|
207
218
|
location: Optional[SourceLocation]) -> IAstNode:
|
208
219
|
"""Create a Program node from serialized data."""
|
@@ -453,16 +453,15 @@ def _generate_definitions() -> Dict[str, Any]:
|
|
453
453
|
return definitions
|
454
454
|
|
455
455
|
|
456
|
-
def export_schema_to_file(filepath: str, indent: int = 2) -> None:
|
456
|
+
def export_schema_to_file(schema: Dict[str, Any], filepath: str, indent: int = 2) -> None:
|
457
457
|
"""
|
458
|
-
Save the
|
458
|
+
Save the schema to a file.
|
459
459
|
|
460
460
|
Args:
|
461
|
+
schema: The schema to save
|
461
462
|
filepath: The path to save the schema to
|
462
463
|
indent: The indentation level for pretty-printing
|
463
464
|
"""
|
464
|
-
schema = generate_ast_schema()
|
465
|
-
|
466
465
|
# Ensure the directory exists
|
467
466
|
os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True)
|
468
467
|
|
@@ -50,7 +50,7 @@ class ErrorObserver(Protocol[T_Error]):
|
|
50
50
|
class ErrorHandler:
|
51
51
|
"""Manages error collection and notification."""
|
52
52
|
|
53
|
-
def __init__(self, raise_on_error: bool =
|
53
|
+
def __init__(self, raise_on_error: bool = True):
|
54
54
|
self._errors: List[Error] = []
|
55
55
|
self._raise_on_error = raise_on_error
|
56
56
|
self._observers: List[ErrorObserver] = []
|
@@ -109,4 +109,4 @@ class VexSyntaxError(VexAstError):
|
|
109
109
|
|
110
110
|
def __init__(self, message: str, location: Optional[SourceLocation] = None):
|
111
111
|
self.location = location
|
112
|
-
super().__init__(message)
|
112
|
+
super().__init__(message)
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|