vex-ast 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vex_ast-0.2.3/vex_ast.egg-info → vex_ast-0.2.4}/PKG-INFO +1 -1
- {vex_ast-0.2.3 → vex_ast-0.2.4}/pyproject.toml +1 -1
- {vex_ast-0.2.3 → vex_ast-0.2.4}/setup.py +1 -1
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/expressions.py +44 -1
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/interfaces.py +16 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/parser/factory.py +6 -1
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/parser/python_parser.py +81 -26
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/signature.py +5 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/visitors/base.py +4 -1
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/visitors/printer.py +2 -1
- {vex_ast-0.2.3 → vex_ast-0.2.4/vex_ast.egg-info}/PKG-INFO +1 -1
- {vex_ast-0.2.3 → vex_ast-0.2.4}/LICENSE +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/MANIFEST.in +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/pytest.ini +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/requirements.txt +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/setup.cfg +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/conftest.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_comprehensive_integration.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_core.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_integration.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_literals.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_navigator.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_parser.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_registry.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_serialization.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_statements.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_vex_nodes.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/tests/test_visitors.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/READMEAPI.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/core.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/literals.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/navigator.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/operators.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/statements.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/validators.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/ast/vex_nodes.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/parser/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/parser/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/parser/interfaces.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/parser/strategies.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/api.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/categories.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/functions/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/functions/display.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/functions/drivetrain.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/functions/initialize.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/functions/motor.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/functions/sensors.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/functions/timing.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/language_map.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/registry.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/simulation_behavior.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/registry/validation.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/serialization/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/serialization/json_deserializer.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/serialization/json_serializer.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/serialization/schema.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/types/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/types/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/types/base.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/types/enums.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/types/objects.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/types/primitives.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/types/type_checker.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/utils/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/utils/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/utils/errors.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/utils/source_location.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/utils/type_definitions.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/visitors/README.md +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/visitors/__init__.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/visitors/analyzer.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast/visitors/transformer.py +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast.egg-info/SOURCES.txt +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast.egg-info/dependency_links.txt +0 -0
- {vex_ast-0.2.3 → vex_ast-0.2.4}/vex_ast.egg-info/top_level.txt +0 -0
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='vex_ast',
|
5
|
-
version='0.2.
|
5
|
+
version='0.2.4',
|
6
6
|
description='A Python package for generating Abstract Syntax Trees for VEX V5 code.',
|
7
7
|
long_description=open('README.md').read(),
|
8
8
|
long_description_content_type='text/markdown',
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from typing import Dict, List, Optional, Union, cast, Any
|
4
4
|
|
5
|
-
from .interfaces import IAstNode, IExpression, IVisitor, T_VisitorResult, IIdentifier, IFunctionCall
|
5
|
+
from .interfaces import IAstNode, IExpression, IVisitor, T_VisitorResult, IIdentifier, IFunctionCall, IConditionalExpression
|
6
6
|
from .core import Expression
|
7
7
|
from .operators import Operator
|
8
8
|
from ..utils.source_location import SourceLocation
|
@@ -173,6 +173,49 @@ class KeywordArgument(Expression):
|
|
173
173
|
"""Get the keyword value."""
|
174
174
|
return self.value
|
175
175
|
|
176
|
+
class ConditionalExpression(Expression, IConditionalExpression):
|
177
|
+
"""A conditional expression (ternary operator, e.g., a if condition else b)."""
|
178
|
+
|
179
|
+
_fields = ('condition', 'true_expr', 'false_expr')
|
180
|
+
|
181
|
+
def __init__(self, condition: IExpression, true_expr: IExpression, false_expr: IExpression,
|
182
|
+
location: Optional[SourceLocation] = None):
|
183
|
+
super().__init__(location)
|
184
|
+
self.condition = condition
|
185
|
+
self.true_expr = true_expr
|
186
|
+
self.false_expr = false_expr
|
187
|
+
|
188
|
+
# Set parent references
|
189
|
+
if isinstance(condition, Expression):
|
190
|
+
condition.set_parent(self)
|
191
|
+
if isinstance(true_expr, Expression):
|
192
|
+
true_expr.set_parent(self)
|
193
|
+
if isinstance(false_expr, Expression):
|
194
|
+
false_expr.set_parent(self)
|
195
|
+
|
196
|
+
def get_children(self) -> List[IAstNode]:
|
197
|
+
"""Get child nodes."""
|
198
|
+
return [
|
199
|
+
cast(IAstNode, self.condition),
|
200
|
+
cast(IAstNode, self.true_expr),
|
201
|
+
cast(IAstNode, self.false_expr)
|
202
|
+
]
|
203
|
+
|
204
|
+
def accept(self, visitor: IVisitor[T_VisitorResult]) -> T_VisitorResult:
|
205
|
+
return visitor.visit_conditionalexpression(self)
|
206
|
+
|
207
|
+
def get_condition(self) -> IExpression:
|
208
|
+
"""Get the condition expression."""
|
209
|
+
return self.condition
|
210
|
+
|
211
|
+
def get_true_expression(self) -> IExpression:
|
212
|
+
"""Get the expression to evaluate if condition is true."""
|
213
|
+
return self.true_expr
|
214
|
+
|
215
|
+
def get_false_expression(self) -> IExpression:
|
216
|
+
"""Get the expression to evaluate if condition is false."""
|
217
|
+
return self.false_expr
|
218
|
+
|
176
219
|
class FunctionCall(Expression, IFunctionCall):
|
177
220
|
"""A function call."""
|
178
221
|
|
@@ -84,6 +84,22 @@ class IIdentifier(IExpression, Protocol):
|
|
84
84
|
"""Get the identifier name."""
|
85
85
|
...
|
86
86
|
|
87
|
+
@runtime_checkable
|
88
|
+
class IConditionalExpression(IExpression, Protocol):
|
89
|
+
"""Protocol for conditional expression (ternary operator) nodes."""
|
90
|
+
|
91
|
+
def get_condition(self) -> IExpression:
|
92
|
+
"""Get the condition expression."""
|
93
|
+
...
|
94
|
+
|
95
|
+
def get_true_expression(self) -> IExpression:
|
96
|
+
"""Get the expression to evaluate if condition is true."""
|
97
|
+
...
|
98
|
+
|
99
|
+
def get_false_expression(self) -> IExpression:
|
100
|
+
"""Get the expression to evaluate if condition is false."""
|
101
|
+
...
|
102
|
+
|
87
103
|
@runtime_checkable
|
88
104
|
class IFunctionCall(IExpression, Protocol):
|
89
105
|
"""Protocol for function call nodes."""
|
@@ -4,7 +4,7 @@ from typing import Any, Dict, Optional, Type, Union, cast, List
|
|
4
4
|
|
5
5
|
from ..ast.core import Expression, Program, Statement
|
6
6
|
from ..ast.expressions import (
|
7
|
-
AttributeAccess, BinaryOperation, FunctionCall, Identifier, KeywordArgument,
|
7
|
+
AttributeAccess, BinaryOperation, ConditionalExpression, FunctionCall, Identifier, KeywordArgument,
|
8
8
|
UnaryOperation, VariableReference
|
9
9
|
)
|
10
10
|
from ..ast.literals import (
|
@@ -77,6 +77,11 @@ class NodeFactory:
|
|
77
77
|
"""Create a unary operation node."""
|
78
78
|
return UnaryOperation(op, operand, location)
|
79
79
|
|
80
|
+
def create_conditional_expression(self, condition: Expression, true_expr: Expression, false_expr: Expression,
|
81
|
+
location: Optional[SourceLocation] = None) -> ConditionalExpression:
|
82
|
+
"""Create a conditional expression (ternary operator) node."""
|
83
|
+
return ConditionalExpression(condition, true_expr, false_expr, location)
|
84
|
+
|
80
85
|
def create_function_call(self, function: Expression, args: List[Expression] = None,
|
81
86
|
keywords: List[KeywordArgument] = None,
|
82
87
|
location: Optional[SourceLocation] = None) -> FunctionCall:
|
@@ -294,21 +294,12 @@ class PythonParser(BaseParser):
|
|
294
294
|
op_name = op_type.__name__
|
295
295
|
|
296
296
|
op_map = {
|
297
|
-
'And':
|
298
|
-
'Or':
|
297
|
+
'And': Operator.LOGICAL_AND,
|
298
|
+
'Or': Operator.LOGICAL_OR
|
299
299
|
}
|
300
300
|
|
301
301
|
if op_name in op_map:
|
302
|
-
|
303
|
-
vex_op = PYTHON_COMP_OP_MAP.get(op_str)
|
304
|
-
|
305
|
-
if not vex_op:
|
306
|
-
self.error_handler.add_error(
|
307
|
-
ErrorType.PARSER_ERROR,
|
308
|
-
f"Unsupported boolean operator: {op_name}",
|
309
|
-
loc
|
310
|
-
)
|
311
|
-
vex_op = Operator.LOGICAL_AND # Fallback
|
302
|
+
vex_op = op_map[op_name]
|
312
303
|
else:
|
313
304
|
self.error_handler.add_error(
|
314
305
|
ErrorType.PARSER_ERROR,
|
@@ -329,6 +320,15 @@ class PythonParser(BaseParser):
|
|
329
320
|
|
330
321
|
return result
|
331
322
|
|
323
|
+
# Conditional expressions (ternary operators)
|
324
|
+
elif isinstance(node, ast.IfExp):
|
325
|
+
loc = self._get_location(node)
|
326
|
+
test = self._convert_expression(node.test)
|
327
|
+
body = self._convert_expression(node.body)
|
328
|
+
orelse = self._convert_expression(node.orelse)
|
329
|
+
|
330
|
+
return self.factory.create_conditional_expression(test, body, orelse, loc)
|
331
|
+
|
332
332
|
# List literals
|
333
333
|
elif isinstance(node, ast.List) or isinstance(node, ast.Tuple):
|
334
334
|
# We don't have a dedicated list/tuple node, so use function call
|
@@ -607,22 +607,77 @@ class PythonParser(BaseParser):
|
|
607
607
|
self._get_location(node)
|
608
608
|
)
|
609
609
|
|
610
|
-
# Import statements
|
611
|
-
elif isinstance(node,
|
610
|
+
# Import statements
|
611
|
+
elif isinstance(node, ast.Import):
|
612
612
|
loc = self._get_location(node)
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
613
|
+
# Create a list of assignments for each imported name
|
614
|
+
statements = []
|
615
|
+
|
616
|
+
for name in node.names:
|
617
|
+
# Create an identifier for the module
|
618
|
+
module_name = name.name
|
619
|
+
as_name = name.asname or module_name
|
620
|
+
|
621
|
+
# Create an assignment: as_name = module_name
|
622
|
+
target = self.factory.create_identifier(as_name, loc)
|
623
|
+
value = self.factory.create_identifier(f"<import:{module_name}>", loc)
|
624
|
+
|
625
|
+
statements.append(self.factory.create_assignment(target, value, loc))
|
626
|
+
|
627
|
+
# If there's only one statement, return it
|
628
|
+
if len(statements) == 1:
|
629
|
+
return statements[0]
|
630
|
+
|
631
|
+
# Otherwise, return the first one and add a warning
|
632
|
+
if len(statements) > 1:
|
633
|
+
self.error_handler.add_error(
|
634
|
+
ErrorType.PARSER_ERROR,
|
635
|
+
"Multiple imports in a single statement are not fully supported",
|
622
636
|
loc
|
623
|
-
)
|
624
|
-
|
625
|
-
|
637
|
+
)
|
638
|
+
|
639
|
+
return statements[0]
|
640
|
+
|
641
|
+
# Import from statements
|
642
|
+
elif isinstance(node, ast.ImportFrom):
|
643
|
+
loc = self._get_location(node)
|
644
|
+
module_name = node.module or ""
|
645
|
+
|
646
|
+
# Special case for "from vex import *"
|
647
|
+
if module_name == "vex" and any(name.name == "*" for name in node.names):
|
648
|
+
# Create a special identifier that represents "from vex import *"
|
649
|
+
return self.factory.create_expression_statement(
|
650
|
+
self.factory.create_identifier("<import:vex:*>", loc),
|
651
|
+
loc
|
652
|
+
)
|
653
|
+
|
654
|
+
# For other import from statements, create assignments
|
655
|
+
statements = []
|
656
|
+
|
657
|
+
for name in node.names:
|
658
|
+
# Create an identifier for the imported name
|
659
|
+
imported_name = name.name
|
660
|
+
as_name = name.asname or imported_name
|
661
|
+
|
662
|
+
# Create an assignment: as_name = module_name.imported_name
|
663
|
+
target = self.factory.create_identifier(as_name, loc)
|
664
|
+
value = self.factory.create_identifier(f"<import:{module_name}.{imported_name}>", loc)
|
665
|
+
|
666
|
+
statements.append(self.factory.create_assignment(target, value, loc))
|
667
|
+
|
668
|
+
# If there's only one statement, return it
|
669
|
+
if len(statements) == 1:
|
670
|
+
return statements[0]
|
671
|
+
|
672
|
+
# Otherwise, return the first one and add a warning
|
673
|
+
if len(statements) > 1:
|
674
|
+
self.error_handler.add_error(
|
675
|
+
ErrorType.PARSER_ERROR,
|
676
|
+
"Multiple imports in a single statement are not fully supported",
|
677
|
+
loc
|
678
|
+
)
|
679
|
+
|
680
|
+
return statements[0]
|
626
681
|
|
627
682
|
# Class definitions - not supported yet
|
628
683
|
elif isinstance(node, ast.ClassDef):
|
@@ -24,6 +24,11 @@ class VexFunctionParameter:
|
|
24
24
|
self.description = description
|
25
25
|
self.is_optional = default_value is not None
|
26
26
|
|
27
|
+
@property
|
28
|
+
def optional(self) -> bool:
|
29
|
+
"""Alias for is_optional for compatibility."""
|
30
|
+
return self.is_optional
|
31
|
+
|
27
32
|
def __str__(self) -> str:
|
28
33
|
mode_str = ""
|
29
34
|
if self.mode == ParameterMode.REFERENCE:
|
@@ -47,6 +47,9 @@ class AstVisitor(Generic[T_VisitorResult], ABC):
|
|
47
47
|
def visit_unaryoperation(self, node: Any) -> T_VisitorResult:
|
48
48
|
return self.generic_visit(node)
|
49
49
|
|
50
|
+
def visit_conditionalexpression(self, node: Any) -> T_VisitorResult:
|
51
|
+
return self.generic_visit(node)
|
52
|
+
|
50
53
|
def visit_functioncall(self, node: Any) -> T_VisitorResult:
|
51
54
|
return self.generic_visit(node)
|
52
55
|
|
@@ -127,4 +130,4 @@ class TypedVisitorMixin:
|
|
127
130
|
method_name = self.node_type_to_method_name(type(node))
|
128
131
|
if hasattr(self, method_name):
|
129
132
|
return getattr(self, method_name)(node)
|
130
|
-
return self.generic_visit(node)
|
133
|
+
return self.generic_visit(node)
|
@@ -126,6 +126,7 @@ class PrintVisitor(AstVisitor[str]):
|
|
126
126
|
visit_variablereference = generic_visit
|
127
127
|
visit_binaryoperation = generic_visit
|
128
128
|
visit_unaryoperation = generic_visit
|
129
|
+
visit_conditionalexpression = generic_visit
|
129
130
|
visit_functioncall = generic_visit
|
130
131
|
visit_keywordargument = generic_visit
|
131
132
|
visit_expressionstatement = generic_visit
|
@@ -142,4 +143,4 @@ class PrintVisitor(AstVisitor[str]):
|
|
142
143
|
visit_motorcontrol = generic_visit
|
143
144
|
visit_sensorreading = generic_visit
|
144
145
|
visit_timingcontrol = generic_visit
|
145
|
-
visit_displayoutput = generic_visit
|
146
|
+
visit_displayoutput = generic_visit
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|