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,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
|
vex_ast/utils/errors.py
ADDED
@@ -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
|