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.
Files changed (41) hide show
  1. vex_ast/__init__.py +65 -0
  2. vex_ast/ast/__init__.py +75 -0
  3. vex_ast/ast/core.py +71 -0
  4. vex_ast/ast/expressions.py +233 -0
  5. vex_ast/ast/interfaces.py +192 -0
  6. vex_ast/ast/literals.py +80 -0
  7. vex_ast/ast/navigator.py +213 -0
  8. vex_ast/ast/operators.py +136 -0
  9. vex_ast/ast/statements.py +351 -0
  10. vex_ast/ast/validators.py +114 -0
  11. vex_ast/ast/vex_nodes.py +241 -0
  12. vex_ast/parser/__init__.py +0 -0
  13. vex_ast/parser/factory.py +179 -0
  14. vex_ast/parser/interfaces.py +35 -0
  15. vex_ast/parser/python_parser.py +725 -0
  16. vex_ast/parser/strategies.py +0 -0
  17. vex_ast/registry/__init__.py +51 -0
  18. vex_ast/registry/api.py +155 -0
  19. vex_ast/registry/categories.py +136 -0
  20. vex_ast/registry/language_map.py +78 -0
  21. vex_ast/registry/registry.py +153 -0
  22. vex_ast/registry/signature.py +143 -0
  23. vex_ast/registry/simulation_behavior.py +9 -0
  24. vex_ast/registry/validation.py +44 -0
  25. vex_ast/serialization/__init__.py +37 -0
  26. vex_ast/serialization/json_deserializer.py +264 -0
  27. vex_ast/serialization/json_serializer.py +148 -0
  28. vex_ast/serialization/schema.py +471 -0
  29. vex_ast/utils/__init__.py +0 -0
  30. vex_ast/utils/errors.py +112 -0
  31. vex_ast/utils/source_location.py +39 -0
  32. vex_ast/utils/type_definitions.py +0 -0
  33. vex_ast/visitors/__init__.py +0 -0
  34. vex_ast/visitors/analyzer.py +103 -0
  35. vex_ast/visitors/base.py +130 -0
  36. vex_ast/visitors/printer.py +145 -0
  37. vex_ast/visitors/transformer.py +0 -0
  38. vex_ast-0.1.0.dist-info/METADATA +176 -0
  39. vex_ast-0.1.0.dist-info/RECORD +41 -0
  40. vex_ast-0.1.0.dist-info/WHEEL +5 -0
  41. 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)