vex-ast 0.1.0__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.
- vex_ast/__init__.py +65 -0
- vex_ast/ast/__init__.py +75 -0
- vex_ast/ast/core.py +71 -0
- vex_ast/ast/expressions.py +233 -0
- vex_ast/ast/interfaces.py +192 -0
- vex_ast/ast/literals.py +80 -0
- vex_ast/ast/navigator.py +213 -0
- vex_ast/ast/operators.py +136 -0
- vex_ast/ast/statements.py +351 -0
- vex_ast/ast/validators.py +114 -0
- vex_ast/ast/vex_nodes.py +241 -0
- vex_ast/parser/__init__.py +0 -0
- vex_ast/parser/factory.py +179 -0
- vex_ast/parser/interfaces.py +35 -0
- vex_ast/parser/python_parser.py +725 -0
- vex_ast/parser/strategies.py +0 -0
- vex_ast/registry/__init__.py +51 -0
- vex_ast/registry/api.py +155 -0
- vex_ast/registry/categories.py +136 -0
- vex_ast/registry/language_map.py +78 -0
- vex_ast/registry/registry.py +153 -0
- vex_ast/registry/signature.py +143 -0
- vex_ast/registry/simulation_behavior.py +9 -0
- vex_ast/registry/validation.py +44 -0
- vex_ast/serialization/__init__.py +37 -0
- vex_ast/serialization/json_deserializer.py +264 -0
- vex_ast/serialization/json_serializer.py +148 -0
- vex_ast/serialization/schema.py +471 -0
- vex_ast/utils/__init__.py +0 -0
- vex_ast/utils/errors.py +112 -0
- vex_ast/utils/source_location.py +39 -0
- vex_ast/utils/type_definitions.py +0 -0
- vex_ast/visitors/__init__.py +0 -0
- vex_ast/visitors/analyzer.py +103 -0
- vex_ast/visitors/base.py +130 -0
- vex_ast/visitors/printer.py +145 -0
- vex_ast/visitors/transformer.py +0 -0
- vex_ast-0.1.0.dist-info/METADATA +176 -0
- vex_ast-0.1.0.dist-info/RECORD +41 -0
- vex_ast-0.1.0.dist-info/WHEEL +5 -0
- vex_ast-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
from typing import Optional, List, Dict, Any, Union, Callable, Tuple
|
2
|
+
from enum import Enum, auto
|
3
|
+
from ..types.base import VexType, VOID, ANY
|
4
|
+
|
5
|
+
class ParameterMode(Enum):
|
6
|
+
"""Parameter passing modes"""
|
7
|
+
VALUE = auto() # Pass by value
|
8
|
+
REFERENCE = auto() # Pass by reference
|
9
|
+
OUTPUT = auto() # Output parameter
|
10
|
+
|
11
|
+
class VexFunctionParameter:
|
12
|
+
"""Represents a parameter in a VEX function signature"""
|
13
|
+
|
14
|
+
def __init__(self,
|
15
|
+
name: str,
|
16
|
+
type_: VexType,
|
17
|
+
default_value: Optional[Any] = None,
|
18
|
+
mode: ParameterMode = ParameterMode.VALUE,
|
19
|
+
description: str = ""):
|
20
|
+
self.name = name
|
21
|
+
self.type = type_
|
22
|
+
self.default_value = default_value
|
23
|
+
self.mode = mode
|
24
|
+
self.description = description
|
25
|
+
self.is_optional = default_value is not None
|
26
|
+
|
27
|
+
def __str__(self) -> str:
|
28
|
+
mode_str = ""
|
29
|
+
if self.mode == ParameterMode.REFERENCE:
|
30
|
+
mode_str = "&"
|
31
|
+
elif self.mode == ParameterMode.OUTPUT:
|
32
|
+
mode_str = "*"
|
33
|
+
|
34
|
+
default_str = ""
|
35
|
+
if self.is_optional:
|
36
|
+
default_str = f" = {self.default_value}"
|
37
|
+
|
38
|
+
return f"{self.type}{mode_str} {self.name}{default_str}"
|
39
|
+
|
40
|
+
class SimulationCategory(Enum):
|
41
|
+
"""Categories for simulation behavior"""
|
42
|
+
MOTOR_CONTROL = auto()
|
43
|
+
SENSOR_READING = auto()
|
44
|
+
DISPLAY_OUTPUT = auto()
|
45
|
+
TIMING_CONTROL = auto()
|
46
|
+
COMPETITION = auto()
|
47
|
+
CONFIGURATION = auto()
|
48
|
+
CALCULATION = auto()
|
49
|
+
EVENT_HANDLING = auto()
|
50
|
+
OTHER = auto()
|
51
|
+
|
52
|
+
SimulationBehaviorFunc = Callable[..., Any]
|
53
|
+
|
54
|
+
class VexFunctionSignature:
|
55
|
+
"""Represents the signature of a VEX function"""
|
56
|
+
|
57
|
+
def __init__(self,
|
58
|
+
name: str,
|
59
|
+
return_type: VexType = VOID,
|
60
|
+
parameters: List[Union[VexFunctionParameter, Tuple[str, str, Optional[Any]]]] = None,
|
61
|
+
description: str = "",
|
62
|
+
category: SimulationCategory = SimulationCategory.OTHER,
|
63
|
+
simulation_behavior: Optional[SimulationBehaviorFunc] = None,
|
64
|
+
python_name: Optional[str] = None,
|
65
|
+
cpp_name: Optional[str] = None,
|
66
|
+
object_type: Optional[VexType] = None,
|
67
|
+
method_name: Optional[str] = None):
|
68
|
+
self.name = name
|
69
|
+
self.return_type = return_type
|
70
|
+
|
71
|
+
# Convert tuple parameters to VexFunctionParameter objects
|
72
|
+
processed_params = []
|
73
|
+
if parameters:
|
74
|
+
for param in parameters:
|
75
|
+
if isinstance(param, VexFunctionParameter):
|
76
|
+
processed_params.append(param)
|
77
|
+
elif isinstance(param, tuple) and len(param) >= 2:
|
78
|
+
# Extract tuple values
|
79
|
+
param_name = param[0]
|
80
|
+
param_type = param[1]
|
81
|
+
default_value = param[2] if len(param) > 2 else None
|
82
|
+
processed_params.append(VexFunctionParameter(
|
83
|
+
name=param_name,
|
84
|
+
type_=param_type,
|
85
|
+
default_value=default_value
|
86
|
+
))
|
87
|
+
|
88
|
+
self.parameters = processed_params
|
89
|
+
self.description = description
|
90
|
+
self.category = category
|
91
|
+
self.simulation_behavior = simulation_behavior
|
92
|
+
self.python_name = python_name or name
|
93
|
+
self.cpp_name = cpp_name or name
|
94
|
+
self.object_type = object_type # For methods, this is the class type
|
95
|
+
self.method_name = method_name # For methods, this is the method name
|
96
|
+
|
97
|
+
|
98
|
+
# Validate there are no duplicate parameter names
|
99
|
+
param_names = [param.name for param in self.parameters]
|
100
|
+
if len(param_names) != len(set(param_names)):
|
101
|
+
raise ValueError(f"Duplicate parameter names in function {name}")
|
102
|
+
|
103
|
+
# Ensure optional parameters come after required parameters
|
104
|
+
has_optional = False
|
105
|
+
for param in self.parameters:
|
106
|
+
if param.is_optional:
|
107
|
+
has_optional = True
|
108
|
+
elif has_optional:
|
109
|
+
raise ValueError(f"Required parameter after optional parameter in function {name}")
|
110
|
+
|
111
|
+
def __str__(self) -> str:
|
112
|
+
params_str = ", ".join(str(param) for param in self.parameters)
|
113
|
+
return f"{self.return_type} {self.name}({params_str})"
|
114
|
+
|
115
|
+
def validate_arguments(self,
|
116
|
+
args: List[Any],
|
117
|
+
kwargs: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
|
118
|
+
"""Validate function arguments against this signature"""
|
119
|
+
# Check if we have too many positional arguments
|
120
|
+
if len(args) > len(self.parameters):
|
121
|
+
return False, f"Too many positional arguments for {self.name}"
|
122
|
+
|
123
|
+
# Check if we have unknown keyword arguments
|
124
|
+
param_names = {param.name for param in self.parameters}
|
125
|
+
for kwarg_name in kwargs:
|
126
|
+
if kwarg_name not in param_names:
|
127
|
+
return False, f"Unknown keyword argument '{kwarg_name}' for {self.name}"
|
128
|
+
|
129
|
+
# Check if we have the required number of arguments
|
130
|
+
required_params = [p for p in self.parameters if not p.is_optional]
|
131
|
+
if len(args) + len(kwargs) < len(required_params):
|
132
|
+
return False, f"Missing required arguments for {self.name}"
|
133
|
+
|
134
|
+
# Check if we have duplicate arguments
|
135
|
+
args_used = min(len(args), len(self.parameters))
|
136
|
+
for i in range(args_used):
|
137
|
+
param_name = self.parameters[i].name
|
138
|
+
if param_name in kwargs:
|
139
|
+
return False, f"Duplicate argument '{param_name}' for {self.name}"
|
140
|
+
|
141
|
+
# TODO: Add type checking for arguments
|
142
|
+
|
143
|
+
return True, None
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# vex_ast/registry/simulation_behavior.py
|
2
|
+
from enum import Enum, auto
|
3
|
+
|
4
|
+
class SimulationBehavior(Enum):
|
5
|
+
"""Categories of simulation behaviors for VEX functions"""
|
6
|
+
AFFECTS_MOTOR = "AFFECTS_MOTOR"
|
7
|
+
READS_SENSOR = "READS_SENSOR"
|
8
|
+
AFFECTS_TIMING = "AFFECTS_TIMING"
|
9
|
+
AFFECTS_DISPLAY = "AFFECTS_DISPLAY"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from typing import Dict, List, Optional, Set, Tuple, Any, Union
|
2
|
+
from ..types.base import VexType
|
3
|
+
from ..types.type_checker import type_checker
|
4
|
+
from .registry import registry, VexFunctionRegistry
|
5
|
+
from .signature import VexFunctionSignature, VexFunctionParameter
|
6
|
+
|
7
|
+
class FunctionCallValidator:
|
8
|
+
"""Validates function calls against the registry"""
|
9
|
+
|
10
|
+
def __init__(self, registry: VexFunctionRegistry = registry):
|
11
|
+
self.registry = registry
|
12
|
+
|
13
|
+
def validate_call(self,
|
14
|
+
function_name: str,
|
15
|
+
args: List[Any] = None,
|
16
|
+
kwargs: Dict[str, Any] = None,
|
17
|
+
language: str = "python") -> Tuple[bool, Optional[str]]:
|
18
|
+
"""Validate a function call"""
|
19
|
+
args = args or []
|
20
|
+
kwargs = kwargs or {}
|
21
|
+
return self.registry.validate_call(function_name, args, kwargs, language)
|
22
|
+
|
23
|
+
def validate_method_call(self,
|
24
|
+
object_type: Union[VexType, str],
|
25
|
+
method_name: str,
|
26
|
+
args: List[Any] = None,
|
27
|
+
kwargs: Dict[str, Any] = None) -> Tuple[bool, Optional[str]]:
|
28
|
+
"""Validate a method call on an object"""
|
29
|
+
args = args or []
|
30
|
+
kwargs = kwargs or {}
|
31
|
+
return self.registry.validate_method_call(object_type, method_name, args, kwargs)
|
32
|
+
|
33
|
+
def validate_ast_function_call(self, function_call_node: Any) -> Tuple[bool, Optional[str]]:
|
34
|
+
"""Validate a function call AST node"""
|
35
|
+
# This would need to be implemented based on the actual AST node structure
|
36
|
+
# For now, just a placeholder showing the interface
|
37
|
+
function_name = function_call_node.function.name
|
38
|
+
args = [arg.value for arg in function_call_node.args]
|
39
|
+
kwargs = {kw.name: kw.value for kw in function_call_node.keywords}
|
40
|
+
|
41
|
+
return self.validate_call(function_name, args, kwargs)
|
42
|
+
|
43
|
+
# Singleton instance
|
44
|
+
validator = FunctionCallValidator()
|
@@ -0,0 +1,37 @@
|
|
1
|
+
"""
|
2
|
+
Serialization package for VEX AST.
|
3
|
+
|
4
|
+
This package provides functionality for serializing and deserializing AST nodes
|
5
|
+
to and from JSON format, as well as generating JSON schema for the AST structure.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .json_serializer import (
|
9
|
+
SerializationVisitor,
|
10
|
+
serialize_ast_to_dict,
|
11
|
+
serialize_ast_to_json
|
12
|
+
)
|
13
|
+
from .json_deserializer import (
|
14
|
+
DeserializationFactory,
|
15
|
+
deserialize_ast_from_dict,
|
16
|
+
deserialize_ast_from_json
|
17
|
+
)
|
18
|
+
from .schema import (
|
19
|
+
generate_ast_schema,
|
20
|
+
export_schema_to_file
|
21
|
+
)
|
22
|
+
|
23
|
+
__all__ = [
|
24
|
+
# Serialization
|
25
|
+
"SerializationVisitor",
|
26
|
+
"serialize_ast_to_dict",
|
27
|
+
"serialize_ast_to_json",
|
28
|
+
|
29
|
+
# Deserialization
|
30
|
+
"DeserializationFactory",
|
31
|
+
"deserialize_ast_from_dict",
|
32
|
+
"deserialize_ast_from_json",
|
33
|
+
|
34
|
+
# Schema
|
35
|
+
"generate_ast_schema",
|
36
|
+
"export_schema_to_file"
|
37
|
+
]
|
@@ -0,0 +1,264 @@
|
|
1
|
+
"""
|
2
|
+
JSON deserialization for AST nodes.
|
3
|
+
|
4
|
+
This module provides functionality to convert JSON data back to AST nodes.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
from typing import Any, Dict, List, Optional, Type, Union, cast
|
9
|
+
|
10
|
+
from ..ast.interfaces import IAstNode
|
11
|
+
from ..parser.factory import NodeFactory
|
12
|
+
from ..utils.source_location import SourceLocation
|
13
|
+
from ..utils.errors import ErrorHandler
|
14
|
+
|
15
|
+
|
16
|
+
class DeserializationFactory:
|
17
|
+
"""
|
18
|
+
Factory for deserializing JSON data back to AST nodes.
|
19
|
+
|
20
|
+
This class uses the NodeFactory to create AST nodes from serialized data,
|
21
|
+
handling the reconstruction of the node hierarchy and parent-child relationships.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, error_handler: Optional[ErrorHandler] = None):
|
25
|
+
"""
|
26
|
+
Initialize the deserialization factory.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
error_handler: Optional error handler for reporting deserialization issues
|
30
|
+
"""
|
31
|
+
self.node_factory = NodeFactory(error_handler)
|
32
|
+
self.error_handler = error_handler
|
33
|
+
|
34
|
+
def deserialize_node(self, data: Dict[str, Any]) -> IAstNode:
|
35
|
+
"""
|
36
|
+
Deserialize a dictionary representation back to an AST node.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
data: Dictionary representation of an AST node
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
The reconstructed AST node
|
43
|
+
|
44
|
+
Raises:
|
45
|
+
ValueError: If the data is invalid or missing required fields
|
46
|
+
"""
|
47
|
+
# Extract node type
|
48
|
+
if "type" not in data:
|
49
|
+
raise ValueError("Missing 'type' field in node data")
|
50
|
+
|
51
|
+
node_type = data["type"]
|
52
|
+
|
53
|
+
# Create source location if present
|
54
|
+
location = None
|
55
|
+
if "location" in data:
|
56
|
+
location = self._deserialize_location(data["location"])
|
57
|
+
|
58
|
+
# Dispatch to appropriate creation method based on node type
|
59
|
+
method_name = f"_create_{node_type.lower()}"
|
60
|
+
if hasattr(self, method_name):
|
61
|
+
create_method = getattr(self, method_name)
|
62
|
+
node = create_method(data, location)
|
63
|
+
else:
|
64
|
+
# Fallback to generic creation if no specific method exists
|
65
|
+
node = self._create_generic_node(node_type, data, location)
|
66
|
+
|
67
|
+
return node
|
68
|
+
|
69
|
+
def _deserialize_location(self, data: Dict[str, Any]) -> SourceLocation:
|
70
|
+
"""
|
71
|
+
Deserialize a dictionary to a SourceLocation object.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
data: Dictionary representation of a source location
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
A SourceLocation object
|
78
|
+
"""
|
79
|
+
return SourceLocation(
|
80
|
+
line=data["line"],
|
81
|
+
column=data["column"],
|
82
|
+
end_line=data.get("end_line"),
|
83
|
+
end_column=data.get("end_column"),
|
84
|
+
filename=data.get("filename")
|
85
|
+
)
|
86
|
+
|
87
|
+
def _deserialize_value(self, value: Any) -> Any:
|
88
|
+
"""
|
89
|
+
Deserialize a value based on its type.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
value: The value to deserialize
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
The deserialized value
|
96
|
+
"""
|
97
|
+
# Handle None
|
98
|
+
if value is None:
|
99
|
+
return None
|
100
|
+
|
101
|
+
# Handle dictionaries (potentially nested nodes)
|
102
|
+
if isinstance(value, dict) and "type" in value:
|
103
|
+
return self.deserialize_node(value)
|
104
|
+
|
105
|
+
# Handle lists of values
|
106
|
+
if isinstance(value, list):
|
107
|
+
return [self._deserialize_value(item) for item in value]
|
108
|
+
|
109
|
+
# Handle dictionaries (not nodes)
|
110
|
+
if isinstance(value, dict):
|
111
|
+
return {k: self._deserialize_value(v) for k, v in value.items()}
|
112
|
+
|
113
|
+
# Return basic types as-is
|
114
|
+
return value
|
115
|
+
|
116
|
+
def _create_generic_node(self, node_type: str, data: Dict[str, Any],
|
117
|
+
location: Optional[SourceLocation]) -> IAstNode:
|
118
|
+
"""
|
119
|
+
Generic node creation when no specific method exists.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
node_type: The type of node to create
|
123
|
+
data: Dictionary representation of the node
|
124
|
+
location: Optional source location
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
The created AST node
|
128
|
+
|
129
|
+
Raises:
|
130
|
+
ValueError: If the node type is not supported
|
131
|
+
"""
|
132
|
+
# Map of node types to factory methods
|
133
|
+
factory_methods = {
|
134
|
+
# Literals
|
135
|
+
"NumberLiteral": self.node_factory.create_number_literal,
|
136
|
+
"StringLiteral": self.node_factory.create_string_literal,
|
137
|
+
"BooleanLiteral": self.node_factory.create_boolean_literal,
|
138
|
+
"NoneLiteral": self.node_factory.create_none_literal,
|
139
|
+
|
140
|
+
# Expressions
|
141
|
+
"Identifier": self.node_factory.create_identifier,
|
142
|
+
"VariableReference": self.node_factory.create_variable_reference,
|
143
|
+
"AttributeAccess": self.node_factory.create_attribute_access,
|
144
|
+
"BinaryOperation": self.node_factory.create_binary_operation,
|
145
|
+
"UnaryOperation": self.node_factory.create_unary_operation,
|
146
|
+
"FunctionCall": self.node_factory.create_function_call,
|
147
|
+
"KeywordArgument": self.node_factory.create_keyword_argument,
|
148
|
+
|
149
|
+
# Statements
|
150
|
+
"ExpressionStatement": self.node_factory.create_expression_statement,
|
151
|
+
"Assignment": self.node_factory.create_assignment,
|
152
|
+
"IfStatement": self.node_factory.create_if_statement,
|
153
|
+
"WhileLoop": self.node_factory.create_while_loop,
|
154
|
+
"ForLoop": self.node_factory.create_for_loop,
|
155
|
+
"FunctionDefinition": self.node_factory.create_function_definition,
|
156
|
+
"ReturnStatement": self.node_factory.create_return_statement,
|
157
|
+
"BreakStatement": self.node_factory.create_break_statement,
|
158
|
+
"ContinueStatement": self.node_factory.create_continue_statement,
|
159
|
+
|
160
|
+
# VEX-specific nodes
|
161
|
+
"VexAPICall": self.node_factory.create_vex_api_call,
|
162
|
+
"MotorControl": self.node_factory.create_motor_control,
|
163
|
+
"SensorReading": self.node_factory.create_sensor_reading,
|
164
|
+
"TimingControl": self.node_factory.create_timing_control,
|
165
|
+
"DisplayOutput": self.node_factory.create_display_output,
|
166
|
+
|
167
|
+
# Core
|
168
|
+
"Program": self.node_factory.create_program,
|
169
|
+
}
|
170
|
+
|
171
|
+
if node_type not in factory_methods:
|
172
|
+
raise ValueError(f"Unsupported node type: {node_type}")
|
173
|
+
|
174
|
+
# Extract and deserialize attributes
|
175
|
+
kwargs = {}
|
176
|
+
for key, value in data.items():
|
177
|
+
if key not in ["type", "location"]:
|
178
|
+
kwargs[key] = self._deserialize_value(value)
|
179
|
+
|
180
|
+
# Create the node using the appropriate factory method
|
181
|
+
factory_method = factory_methods[node_type]
|
182
|
+
|
183
|
+
# Special handling for certain node types
|
184
|
+
if node_type == "Program":
|
185
|
+
return factory_method(kwargs.get("body", []), location)
|
186
|
+
elif node_type in ["NumberLiteral", "StringLiteral", "BooleanLiteral"]:
|
187
|
+
return factory_method(kwargs.get("value"), location)
|
188
|
+
elif node_type == "NoneLiteral":
|
189
|
+
return factory_method(location)
|
190
|
+
elif node_type == "Identifier":
|
191
|
+
return factory_method(kwargs.get("name", ""), location)
|
192
|
+
|
193
|
+
# For other node types, pass all kwargs and location
|
194
|
+
# This is a simplified approach; in a real implementation,
|
195
|
+
# you would need to handle each node type specifically
|
196
|
+
try:
|
197
|
+
return factory_method(**kwargs, location=location)
|
198
|
+
except TypeError as e:
|
199
|
+
# If the factory method doesn't accept the kwargs, report an error
|
200
|
+
if self.error_handler:
|
201
|
+
self.error_handler.report_error(f"Failed to create {node_type}: {str(e)}")
|
202
|
+
raise ValueError(f"Failed to deserialize {node_type}: {str(e)}")
|
203
|
+
|
204
|
+
# Specific node creation methods for complex cases
|
205
|
+
|
206
|
+
def _create_program(self, data: Dict[str, Any],
|
207
|
+
location: Optional[SourceLocation]) -> IAstNode:
|
208
|
+
"""Create a Program node from serialized data."""
|
209
|
+
body = [self._deserialize_value(stmt) for stmt in data.get("body", [])]
|
210
|
+
return self.node_factory.create_program(body, location)
|
211
|
+
|
212
|
+
def _create_functioncall(self, data: Dict[str, Any],
|
213
|
+
location: Optional[SourceLocation]) -> IAstNode:
|
214
|
+
"""Create a FunctionCall node from serialized data."""
|
215
|
+
function = self._deserialize_value(data.get("function"))
|
216
|
+
args = [self._deserialize_value(arg) for arg in data.get("args", [])]
|
217
|
+
keywords = [self._deserialize_value(kw) for kw in data.get("keywords", [])]
|
218
|
+
return self.node_factory.create_function_call(function, args, keywords, location)
|
219
|
+
|
220
|
+
def _create_ifstatement(self, data: Dict[str, Any],
|
221
|
+
location: Optional[SourceLocation]) -> IAstNode:
|
222
|
+
"""Create an IfStatement node from serialized data."""
|
223
|
+
test = self._deserialize_value(data.get("test"))
|
224
|
+
body = [self._deserialize_value(stmt) for stmt in data.get("body", [])]
|
225
|
+
orelse = None
|
226
|
+
if "orelse" in data:
|
227
|
+
orelse_data = data["orelse"]
|
228
|
+
if isinstance(orelse_data, list):
|
229
|
+
orelse = [self._deserialize_value(stmt) for stmt in orelse_data]
|
230
|
+
else:
|
231
|
+
orelse = self._deserialize_value(orelse_data)
|
232
|
+
return self.node_factory.create_if_statement(test, body, orelse, location)
|
233
|
+
|
234
|
+
|
235
|
+
def deserialize_ast_from_dict(data: Dict[str, Any],
|
236
|
+
error_handler: Optional[ErrorHandler] = None) -> IAstNode:
|
237
|
+
"""
|
238
|
+
Create an AST from a dictionary representation.
|
239
|
+
|
240
|
+
Args:
|
241
|
+
data: Dictionary representation of an AST
|
242
|
+
error_handler: Optional error handler for reporting deserialization issues
|
243
|
+
|
244
|
+
Returns:
|
245
|
+
The reconstructed AST
|
246
|
+
"""
|
247
|
+
factory = DeserializationFactory(error_handler)
|
248
|
+
return factory.deserialize_node(data)
|
249
|
+
|
250
|
+
|
251
|
+
def deserialize_ast_from_json(json_str: str,
|
252
|
+
error_handler: Optional[ErrorHandler] = None) -> IAstNode:
|
253
|
+
"""
|
254
|
+
Create an AST from a JSON string.
|
255
|
+
|
256
|
+
Args:
|
257
|
+
json_str: JSON string representation of an AST
|
258
|
+
error_handler: Optional error handler for reporting deserialization issues
|
259
|
+
|
260
|
+
Returns:
|
261
|
+
The reconstructed AST
|
262
|
+
"""
|
263
|
+
data = json.loads(json_str)
|
264
|
+
return deserialize_ast_from_dict(data, error_handler)
|
@@ -0,0 +1,148 @@
|
|
1
|
+
"""
|
2
|
+
JSON serialization for AST nodes.
|
3
|
+
|
4
|
+
This module provides functionality to convert AST nodes to JSON format.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
from typing import Any, Dict, List, Optional, Union, cast
|
9
|
+
|
10
|
+
from ..ast.interfaces import IAstNode
|
11
|
+
from ..visitors.base import AstVisitor
|
12
|
+
from ..utils.source_location import SourceLocation
|
13
|
+
|
14
|
+
|
15
|
+
class SerializationVisitor(AstVisitor[Dict[str, Any]]):
|
16
|
+
"""
|
17
|
+
Visitor that converts AST nodes to dictionary representations.
|
18
|
+
|
19
|
+
This visitor traverses the AST and converts each node to a dictionary
|
20
|
+
that can be serialized to JSON. The dictionaries include node type
|
21
|
+
information and all relevant attributes.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def generic_visit(self, node: IAstNode) -> Dict[str, Any]:
|
25
|
+
"""
|
26
|
+
Default serialization logic for all node types.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
node: The AST node to serialize
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
A dictionary representation of the node
|
33
|
+
"""
|
34
|
+
# Get the node's class name for type information
|
35
|
+
node_type = node.__class__.__name__
|
36
|
+
|
37
|
+
# Start with basic node information
|
38
|
+
result = {
|
39
|
+
"type": node_type,
|
40
|
+
}
|
41
|
+
|
42
|
+
# Add source location if available
|
43
|
+
if node.location:
|
44
|
+
result["location"] = self._serialize_location(node.location)
|
45
|
+
|
46
|
+
# Add all attributes from the node
|
47
|
+
attributes = node.get_attributes()
|
48
|
+
for name, value in attributes.items():
|
49
|
+
# Skip internal attributes and parent reference
|
50
|
+
if name.startswith('_'):
|
51
|
+
continue
|
52
|
+
|
53
|
+
# Handle different attribute types
|
54
|
+
result[name] = self._serialize_attribute(value)
|
55
|
+
|
56
|
+
return result
|
57
|
+
|
58
|
+
def _serialize_location(self, location: SourceLocation) -> Dict[str, Any]:
|
59
|
+
"""
|
60
|
+
Serialize a source location to a dictionary.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
location: The source location to serialize
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
A dictionary representation of the source location
|
67
|
+
"""
|
68
|
+
result = {
|
69
|
+
"line": location.line,
|
70
|
+
"column": location.column,
|
71
|
+
}
|
72
|
+
|
73
|
+
if location.end_line is not None:
|
74
|
+
result["end_line"] = location.end_line
|
75
|
+
|
76
|
+
if location.end_column is not None:
|
77
|
+
result["end_column"] = location.end_column
|
78
|
+
|
79
|
+
if location.filename:
|
80
|
+
result["filename"] = location.filename
|
81
|
+
|
82
|
+
return result
|
83
|
+
|
84
|
+
def _serialize_attribute(self, value: Any) -> Any:
|
85
|
+
"""
|
86
|
+
Serialize an attribute value based on its type.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
value: The attribute value to serialize
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
A serialized representation of the value
|
93
|
+
"""
|
94
|
+
# Handle None
|
95
|
+
if value is None:
|
96
|
+
return None
|
97
|
+
|
98
|
+
# Handle AST nodes
|
99
|
+
if isinstance(value, IAstNode):
|
100
|
+
return self.visit(value)
|
101
|
+
|
102
|
+
# Handle lists of values
|
103
|
+
if isinstance(value, list):
|
104
|
+
return [self._serialize_attribute(item) for item in value]
|
105
|
+
|
106
|
+
# Handle dictionaries
|
107
|
+
if isinstance(value, dict):
|
108
|
+
return {k: self._serialize_attribute(v) for k, v in value.items()}
|
109
|
+
|
110
|
+
# Handle basic types (strings, numbers, booleans)
|
111
|
+
if isinstance(value, (str, int, float, bool)):
|
112
|
+
return value
|
113
|
+
|
114
|
+
# Handle Operator enum values
|
115
|
+
if hasattr(value, '__module__') and 'operators' in value.__module__ and hasattr(value, 'value'):
|
116
|
+
return value.value
|
117
|
+
|
118
|
+
# For other types, convert to string
|
119
|
+
return str(value)
|
120
|
+
|
121
|
+
|
122
|
+
def serialize_ast_to_dict(ast: IAstNode) -> Dict[str, Any]:
|
123
|
+
"""
|
124
|
+
Convert an AST node to a dictionary representation.
|
125
|
+
|
126
|
+
Args:
|
127
|
+
ast: The AST node to serialize
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
A dictionary representation of the AST
|
131
|
+
"""
|
132
|
+
visitor = SerializationVisitor()
|
133
|
+
return visitor.visit(ast)
|
134
|
+
|
135
|
+
|
136
|
+
def serialize_ast_to_json(ast: IAstNode, indent: Optional[int] = None) -> str:
|
137
|
+
"""
|
138
|
+
Convert an AST node to a JSON string.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
ast: The AST node to serialize
|
142
|
+
indent: Optional indentation level for pretty-printing
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
A JSON string representation of the AST
|
146
|
+
"""
|
147
|
+
data = serialize_ast_to_dict(ast)
|
148
|
+
return json.dumps(data, indent=indent)
|