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,471 @@
1
+ """
2
+ JSON Schema generation for AST nodes.
3
+
4
+ This module provides functionality to generate JSON Schema definitions
5
+ for the AST structure, which can be used for validation and documentation.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from typing import Any, Dict, List, Optional, Set, Type
11
+
12
+ from ..ast.core import Program, Expression, Statement
13
+ from ..ast.expressions import (
14
+ AttributeAccess, BinaryOperation, FunctionCall, Identifier, KeywordArgument,
15
+ UnaryOperation, VariableReference
16
+ )
17
+ from ..ast.literals import (
18
+ BooleanLiteral, NoneLiteral, NumberLiteral, StringLiteral
19
+ )
20
+ from ..ast.statements import (
21
+ Assignment, BreakStatement, ContinueStatement, ExpressionStatement,
22
+ ForLoop, FunctionDefinition, IfStatement, ReturnStatement, WhileLoop, Argument
23
+ )
24
+ from ..ast.vex_nodes import (
25
+ DisplayOutput, MotorControl, SensorReading, TimingControl, VexAPICall
26
+ )
27
+
28
+
29
+ def generate_ast_schema() -> Dict[str, Any]:
30
+ """
31
+ Generate a JSON Schema describing the AST node structure.
32
+
33
+ Returns:
34
+ A JSON Schema object as a dictionary
35
+ """
36
+ schema = {
37
+ "$schema": "http://json-schema.org/draft-07/schema#",
38
+ "title": "VEX AST Schema",
39
+ "description": "JSON Schema for VEX AST nodes",
40
+ "type": "object",
41
+ "required": ["type"],
42
+ "properties": {
43
+ "type": {
44
+ "type": "string",
45
+ "description": "The type of AST node"
46
+ },
47
+ "location": {
48
+ "$ref": "#/definitions/SourceLocation"
49
+ }
50
+ },
51
+ "oneOf": [
52
+ {"$ref": f"#/definitions/{node_type}"}
53
+ for node_type in _get_all_node_types()
54
+ ],
55
+ "definitions": _generate_definitions()
56
+ }
57
+
58
+ return schema
59
+
60
+
61
+ def _get_all_node_types() -> List[str]:
62
+ """
63
+ Get a list of all AST node type names.
64
+
65
+ Returns:
66
+ A list of node type names
67
+ """
68
+ # Core types
69
+ node_types = ["Program"]
70
+
71
+ # Expression types
72
+ node_types.extend([
73
+ "Identifier", "VariableReference", "AttributeAccess",
74
+ "BinaryOperation", "UnaryOperation", "FunctionCall",
75
+ "KeywordArgument"
76
+ ])
77
+
78
+ # Literal types
79
+ node_types.extend([
80
+ "NumberLiteral", "StringLiteral", "BooleanLiteral", "NoneLiteral"
81
+ ])
82
+
83
+ # Statement types
84
+ node_types.extend([
85
+ "ExpressionStatement", "Assignment", "IfStatement",
86
+ "WhileLoop", "ForLoop", "FunctionDefinition", "Argument",
87
+ "ReturnStatement", "BreakStatement", "ContinueStatement"
88
+ ])
89
+
90
+ # VEX-specific types
91
+ node_types.extend([
92
+ "VexAPICall", "MotorControl", "SensorReading",
93
+ "TimingControl", "DisplayOutput"
94
+ ])
95
+
96
+ return node_types
97
+
98
+
99
+ def _generate_definitions() -> Dict[str, Any]:
100
+ """
101
+ Generate schema definitions for all AST node types.
102
+
103
+ Returns:
104
+ A dictionary of schema definitions
105
+ """
106
+ definitions = {}
107
+
108
+ # Add SourceLocation definition
109
+ definitions["SourceLocation"] = {
110
+ "type": "object",
111
+ "required": ["line", "column"],
112
+ "properties": {
113
+ "line": {
114
+ "type": "integer",
115
+ "description": "The line number (1-based)"
116
+ },
117
+ "column": {
118
+ "type": "integer",
119
+ "description": "The column number (1-based)"
120
+ },
121
+ "end_line": {
122
+ "type": "integer",
123
+ "description": "The ending line number"
124
+ },
125
+ "end_column": {
126
+ "type": "integer",
127
+ "description": "The ending column number"
128
+ },
129
+ "filename": {
130
+ "type": "string",
131
+ "description": "The source filename"
132
+ }
133
+ }
134
+ }
135
+
136
+ # Add node type definitions
137
+
138
+ # Program
139
+ definitions["Program"] = {
140
+ "type": "object",
141
+ "required": ["type", "body"],
142
+ "properties": {
143
+ "type": {"enum": ["Program"]},
144
+ "body": {
145
+ "type": "array",
146
+ "description": "List of statements in the program",
147
+ "items": {"$ref": "#/definitions/Statement"}
148
+ },
149
+ "location": {"$ref": "#/definitions/SourceLocation"}
150
+ }
151
+ }
152
+
153
+ # Expression base
154
+ definitions["Expression"] = {
155
+ "type": "object",
156
+ "required": ["type"],
157
+ "properties": {
158
+ "type": {"type": "string"},
159
+ "location": {"$ref": "#/definitions/SourceLocation"}
160
+ },
161
+ "oneOf": [
162
+ {"$ref": f"#/definitions/{expr_type}"}
163
+ for expr_type in [
164
+ "Identifier", "VariableReference", "AttributeAccess",
165
+ "BinaryOperation", "UnaryOperation", "FunctionCall",
166
+ "NumberLiteral", "StringLiteral", "BooleanLiteral", "NoneLiteral"
167
+ ]
168
+ ]
169
+ }
170
+
171
+ # Statement base
172
+ definitions["Statement"] = {
173
+ "type": "object",
174
+ "required": ["type"],
175
+ "properties": {
176
+ "type": {"type": "string"},
177
+ "location": {"$ref": "#/definitions/SourceLocation"}
178
+ },
179
+ "oneOf": [
180
+ {"$ref": f"#/definitions/{stmt_type}"}
181
+ for stmt_type in [
182
+ "ExpressionStatement", "Assignment", "IfStatement",
183
+ "WhileLoop", "ForLoop", "FunctionDefinition",
184
+ "ReturnStatement", "BreakStatement", "ContinueStatement"
185
+ ]
186
+ ]
187
+ }
188
+
189
+ # Literals
190
+ definitions["NumberLiteral"] = {
191
+ "type": "object",
192
+ "required": ["type", "value"],
193
+ "properties": {
194
+ "type": {"enum": ["NumberLiteral"]},
195
+ "value": {
196
+ "oneOf": [
197
+ {"type": "number"},
198
+ {"type": "integer"}
199
+ ],
200
+ "description": "The numeric value"
201
+ },
202
+ "location": {"$ref": "#/definitions/SourceLocation"}
203
+ }
204
+ }
205
+
206
+ definitions["StringLiteral"] = {
207
+ "type": "object",
208
+ "required": ["type", "value"],
209
+ "properties": {
210
+ "type": {"enum": ["StringLiteral"]},
211
+ "value": {
212
+ "type": "string",
213
+ "description": "The string value"
214
+ },
215
+ "location": {"$ref": "#/definitions/SourceLocation"}
216
+ }
217
+ }
218
+
219
+ definitions["BooleanLiteral"] = {
220
+ "type": "object",
221
+ "required": ["type", "value"],
222
+ "properties": {
223
+ "type": {"enum": ["BooleanLiteral"]},
224
+ "value": {
225
+ "type": "boolean",
226
+ "description": "The boolean value"
227
+ },
228
+ "location": {"$ref": "#/definitions/SourceLocation"}
229
+ }
230
+ }
231
+
232
+ definitions["NoneLiteral"] = {
233
+ "type": "object",
234
+ "required": ["type"],
235
+ "properties": {
236
+ "type": {"enum": ["NoneLiteral"]},
237
+ "location": {"$ref": "#/definitions/SourceLocation"}
238
+ }
239
+ }
240
+
241
+ # Expressions
242
+ definitions["Identifier"] = {
243
+ "type": "object",
244
+ "required": ["type", "name"],
245
+ "properties": {
246
+ "type": {"enum": ["Identifier"]},
247
+ "name": {
248
+ "type": "string",
249
+ "description": "The identifier name"
250
+ },
251
+ "location": {"$ref": "#/definitions/SourceLocation"}
252
+ }
253
+ }
254
+
255
+ definitions["VariableReference"] = {
256
+ "type": "object",
257
+ "required": ["type", "identifier"],
258
+ "properties": {
259
+ "type": {"enum": ["VariableReference"]},
260
+ "identifier": {
261
+ "$ref": "#/definitions/Identifier",
262
+ "description": "The referenced identifier"
263
+ },
264
+ "location": {"$ref": "#/definitions/SourceLocation"}
265
+ }
266
+ }
267
+
268
+ definitions["AttributeAccess"] = {
269
+ "type": "object",
270
+ "required": ["type", "object", "attribute"],
271
+ "properties": {
272
+ "type": {"enum": ["AttributeAccess"]},
273
+ "object": {
274
+ "$ref": "#/definitions/Expression",
275
+ "description": "The object expression"
276
+ },
277
+ "attribute": {
278
+ "type": "string",
279
+ "description": "The attribute name"
280
+ },
281
+ "location": {"$ref": "#/definitions/SourceLocation"}
282
+ }
283
+ }
284
+
285
+ definitions["BinaryOperation"] = {
286
+ "type": "object",
287
+ "required": ["type", "left", "op", "right"],
288
+ "properties": {
289
+ "type": {"enum": ["BinaryOperation"]},
290
+ "left": {
291
+ "$ref": "#/definitions/Expression",
292
+ "description": "The left operand"
293
+ },
294
+ "op": {
295
+ "type": "string",
296
+ "description": "The operator"
297
+ },
298
+ "right": {
299
+ "$ref": "#/definitions/Expression",
300
+ "description": "The right operand"
301
+ },
302
+ "location": {"$ref": "#/definitions/SourceLocation"}
303
+ }
304
+ }
305
+
306
+ definitions["UnaryOperation"] = {
307
+ "type": "object",
308
+ "required": ["type", "op", "operand"],
309
+ "properties": {
310
+ "type": {"enum": ["UnaryOperation"]},
311
+ "op": {
312
+ "type": "string",
313
+ "description": "The operator"
314
+ },
315
+ "operand": {
316
+ "$ref": "#/definitions/Expression",
317
+ "description": "The operand"
318
+ },
319
+ "location": {"$ref": "#/definitions/SourceLocation"}
320
+ }
321
+ }
322
+
323
+ definitions["FunctionCall"] = {
324
+ "type": "object",
325
+ "required": ["type", "function", "args"],
326
+ "properties": {
327
+ "type": {"enum": ["FunctionCall"]},
328
+ "function": {
329
+ "$ref": "#/definitions/Expression",
330
+ "description": "The function expression"
331
+ },
332
+ "args": {
333
+ "type": "array",
334
+ "description": "The positional arguments",
335
+ "items": {"$ref": "#/definitions/Expression"}
336
+ },
337
+ "keywords": {
338
+ "type": "array",
339
+ "description": "The keyword arguments",
340
+ "items": {"$ref": "#/definitions/KeywordArgument"}
341
+ },
342
+ "location": {"$ref": "#/definitions/SourceLocation"}
343
+ }
344
+ }
345
+
346
+ definitions["KeywordArgument"] = {
347
+ "type": "object",
348
+ "required": ["type", "name", "value"],
349
+ "properties": {
350
+ "type": {"enum": ["KeywordArgument"]},
351
+ "name": {
352
+ "type": "string",
353
+ "description": "The argument name"
354
+ },
355
+ "value": {
356
+ "$ref": "#/definitions/Expression",
357
+ "description": "The argument value"
358
+ },
359
+ "location": {"$ref": "#/definitions/SourceLocation"}
360
+ }
361
+ }
362
+
363
+ # Statements
364
+ definitions["ExpressionStatement"] = {
365
+ "type": "object",
366
+ "required": ["type", "expression"],
367
+ "properties": {
368
+ "type": {"enum": ["ExpressionStatement"]},
369
+ "expression": {
370
+ "$ref": "#/definitions/Expression",
371
+ "description": "The expression"
372
+ },
373
+ "location": {"$ref": "#/definitions/SourceLocation"}
374
+ }
375
+ }
376
+
377
+ definitions["Assignment"] = {
378
+ "type": "object",
379
+ "required": ["type", "target", "value"],
380
+ "properties": {
381
+ "type": {"enum": ["Assignment"]},
382
+ "target": {
383
+ "$ref": "#/definitions/Expression",
384
+ "description": "The assignment target"
385
+ },
386
+ "value": {
387
+ "$ref": "#/definitions/Expression",
388
+ "description": "The assigned value"
389
+ },
390
+ "location": {"$ref": "#/definitions/SourceLocation"}
391
+ }
392
+ }
393
+
394
+ definitions["IfStatement"] = {
395
+ "type": "object",
396
+ "required": ["type", "test", "body"],
397
+ "properties": {
398
+ "type": {"enum": ["IfStatement"]},
399
+ "test": {
400
+ "$ref": "#/definitions/Expression",
401
+ "description": "The condition expression"
402
+ },
403
+ "body": {
404
+ "type": "array",
405
+ "description": "The if-body statements",
406
+ "items": {"$ref": "#/definitions/Statement"}
407
+ },
408
+ "orelse": {
409
+ "oneOf": [
410
+ {
411
+ "type": "array",
412
+ "description": "The else-body statements",
413
+ "items": {"$ref": "#/definitions/Statement"}
414
+ },
415
+ {
416
+ "$ref": "#/definitions/IfStatement",
417
+ "description": "An elif statement"
418
+ }
419
+ ]
420
+ },
421
+ "location": {"$ref": "#/definitions/SourceLocation"}
422
+ }
423
+ }
424
+
425
+ # Add more definitions for other node types...
426
+
427
+ # VEX-specific nodes
428
+ definitions["VexAPICall"] = {
429
+ "type": "object",
430
+ "required": ["type", "function", "args"],
431
+ "properties": {
432
+ "type": {"enum": ["VexAPICall"]},
433
+ "function": {
434
+ "$ref": "#/definitions/Expression",
435
+ "description": "The function expression"
436
+ },
437
+ "args": {
438
+ "type": "array",
439
+ "description": "The positional arguments",
440
+ "items": {"$ref": "#/definitions/Expression"}
441
+ },
442
+ "keywords": {
443
+ "type": "array",
444
+ "description": "The keyword arguments",
445
+ "items": {"$ref": "#/definitions/KeywordArgument"}
446
+ },
447
+ "location": {"$ref": "#/definitions/SourceLocation"}
448
+ }
449
+ }
450
+
451
+ # Add more VEX-specific node definitions...
452
+
453
+ return definitions
454
+
455
+
456
+ def export_schema_to_file(filepath: str, indent: int = 2) -> None:
457
+ """
458
+ Save the generated schema to a file.
459
+
460
+ Args:
461
+ filepath: The path to save the schema to
462
+ indent: The indentation level for pretty-printing
463
+ """
464
+ schema = generate_ast_schema()
465
+
466
+ # Ensure the directory exists
467
+ os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True)
468
+
469
+ # Write the schema to the file
470
+ with open(filepath, 'w') as f:
471
+ json.dump(schema, f, indent=indent)
File without changes
@@ -0,0 +1,112 @@
1
+ """Error handling framework for the VEX AST."""
2
+
3
+ import logging
4
+ from enum import Enum, auto
5
+ from typing import List, Optional, Callable, Protocol, Any, TypeVar, cast
6
+
7
+ from .source_location import SourceLocation
8
+
9
+ # Configure basic logging
10
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class ErrorType(Enum):
14
+ """Types of errors that can occur during AST processing."""
15
+ LEXER_ERROR = auto()
16
+ PARSER_ERROR = auto()
17
+ TYPE_ERROR = auto()
18
+ SEMANTIC_ERROR = auto()
19
+ INTERNAL_ERROR = auto()
20
+
21
+ class Error:
22
+ """Represents a single error detected during processing."""
23
+
24
+ def __init__(self,
25
+ error_type: ErrorType,
26
+ message: str,
27
+ location: Optional[SourceLocation] = None,
28
+ suggestion: Optional[str] = None):
29
+ self.error_type = error_type
30
+ self.message = message
31
+ self.location = location
32
+ self.suggestion = suggestion
33
+
34
+ def __str__(self) -> str:
35
+ """Format the error for display."""
36
+ loc_str = f" at {self.location}" if self.location else ""
37
+ sugg_str = f"\n Suggestion: {self.suggestion}" if self.suggestion else ""
38
+ return f"[{self.error_type.name}]{loc_str}: {self.message}{sugg_str}"
39
+
40
+ # Error handler callback protocol
41
+ T_Error = TypeVar('T_Error', bound=Error)
42
+
43
+ class ErrorObserver(Protocol[T_Error]):
44
+ """Protocol for objects that can observe errors."""
45
+
46
+ def on_error(self, error: T_Error) -> None:
47
+ """Handle an error notification."""
48
+ ...
49
+
50
+ class ErrorHandler:
51
+ """Manages error collection and notification."""
52
+
53
+ def __init__(self, raise_on_error: bool = False):
54
+ self._errors: List[Error] = []
55
+ self._raise_on_error = raise_on_error
56
+ self._observers: List[ErrorObserver] = []
57
+
58
+ def add_error(self,
59
+ error_type: ErrorType,
60
+ message: str,
61
+ location: Optional[SourceLocation] = None,
62
+ suggestion: Optional[str] = None) -> None:
63
+ """Add an error to the collection."""
64
+ error = Error(error_type, message, location, suggestion)
65
+ self._errors.append(error)
66
+ logger.error(str(error))
67
+
68
+ # Notify observers
69
+ for observer in self._observers:
70
+ try:
71
+ observer.on_error(error)
72
+ except Exception as e:
73
+ logger.exception(f"Error observer failed: {e}")
74
+
75
+ if self._raise_on_error:
76
+ if error_type == ErrorType.PARSER_ERROR:
77
+ raise VexSyntaxError(message, location)
78
+ else:
79
+ raise VexAstError(str(error))
80
+
81
+ def get_errors(self) -> List[Error]:
82
+ """Get a copy of all collected errors."""
83
+ return self._errors.copy()
84
+
85
+ def has_errors(self) -> bool:
86
+ """Check if any errors have been collected."""
87
+ return bool(self._errors)
88
+
89
+ def clear_errors(self) -> None:
90
+ """Clear all collected errors."""
91
+ self._errors.clear()
92
+
93
+ def add_observer(self, observer: ErrorObserver) -> None:
94
+ """Add an observer to be notified of errors."""
95
+ if observer not in self._observers:
96
+ self._observers.append(observer)
97
+
98
+ def remove_observer(self, observer: ErrorObserver) -> None:
99
+ """Remove an error observer."""
100
+ if observer in self._observers:
101
+ self._observers.remove(observer)
102
+
103
+ class VexAstError(Exception):
104
+ """Base exception class for VEX AST errors."""
105
+ pass
106
+
107
+ class VexSyntaxError(VexAstError):
108
+ """Exception raised for syntax errors during parsing."""
109
+
110
+ def __init__(self, message: str, location: Optional[SourceLocation] = None):
111
+ self.location = location
112
+ super().__init__(message)
@@ -0,0 +1,39 @@
1
+ """Source location tracking for AST nodes."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ @dataclass
7
+ class SourceLocation:
8
+ """Represents a location in source code."""
9
+
10
+ # Start position
11
+ line: int
12
+ column: int
13
+
14
+ # End position (optional)
15
+ end_line: Optional[int] = None
16
+ end_column: Optional[int] = None
17
+
18
+ # Source file
19
+ filename: Optional[str] = None
20
+
21
+ def __post_init__(self):
22
+ """Validate and normalize the location data."""
23
+ # Ensure end line is at least start line if not specified
24
+ if self.end_line is None:
25
+ self.end_line = self.line
26
+
27
+ def __str__(self) -> str:
28
+ """Format the location for display."""
29
+ file_prefix = f"{self.filename}:" if self.filename else ""
30
+
31
+ # Just a point or single line span
32
+ if self.end_line == self.line:
33
+ if self.end_column is None or self.end_column == self.column:
34
+ return f"{file_prefix}L{self.line}:{self.column}"
35
+ return f"{file_prefix}L{self.line}:{self.column}-{self.end_column}"
36
+
37
+ # Multi-line span
38
+ end_col = f":{self.end_column}" if self.end_column is not None else ""
39
+ return f"{file_prefix}L{self.line}:{self.column}-L{self.end_line}{end_col}"
File without changes
File without changes