machine-dialect 0.1.0a1__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.
- machine_dialect/__main__.py +667 -0
- machine_dialect/agent/__init__.py +5 -0
- machine_dialect/agent/agent.py +360 -0
- machine_dialect/ast/__init__.py +95 -0
- machine_dialect/ast/ast_node.py +35 -0
- machine_dialect/ast/call_expression.py +82 -0
- machine_dialect/ast/dict_extraction.py +60 -0
- machine_dialect/ast/expressions.py +439 -0
- machine_dialect/ast/literals.py +309 -0
- machine_dialect/ast/program.py +35 -0
- machine_dialect/ast/statements.py +1433 -0
- machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
- machine_dialect/ast/tests/test_boolean_literal.py +29 -0
- machine_dialect/ast/tests/test_collection_hir.py +138 -0
- machine_dialect/ast/tests/test_define_statement.py +142 -0
- machine_dialect/ast/tests/test_desugar.py +541 -0
- machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
- machine_dialect/cfg/__init__.py +6 -0
- machine_dialect/cfg/config.py +156 -0
- machine_dialect/cfg/examples.py +221 -0
- machine_dialect/cfg/generate_with_ai.py +187 -0
- machine_dialect/cfg/openai_generation.py +200 -0
- machine_dialect/cfg/parser.py +94 -0
- machine_dialect/cfg/tests/__init__.py +1 -0
- machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
- machine_dialect/cfg/tests/test_config.py +188 -0
- machine_dialect/cfg/tests/test_examples.py +391 -0
- machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
- machine_dialect/cfg/tests/test_openai_generation.py +256 -0
- machine_dialect/codegen/__init__.py +5 -0
- machine_dialect/codegen/bytecode_module.py +89 -0
- machine_dialect/codegen/bytecode_serializer.py +300 -0
- machine_dialect/codegen/opcodes.py +101 -0
- machine_dialect/codegen/register_codegen.py +1996 -0
- machine_dialect/codegen/symtab.py +208 -0
- machine_dialect/codegen/tests/__init__.py +1 -0
- machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
- machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
- machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
- machine_dialect/codegen/tests/test_symtab.py +418 -0
- machine_dialect/codegen/vm_serializer.py +621 -0
- machine_dialect/compiler/__init__.py +18 -0
- machine_dialect/compiler/compiler.py +197 -0
- machine_dialect/compiler/config.py +149 -0
- machine_dialect/compiler/context.py +149 -0
- machine_dialect/compiler/phases/__init__.py +19 -0
- machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
- machine_dialect/compiler/phases/codegen.py +40 -0
- machine_dialect/compiler/phases/hir_generation.py +39 -0
- machine_dialect/compiler/phases/mir_generation.py +86 -0
- machine_dialect/compiler/phases/optimization.py +110 -0
- machine_dialect/compiler/phases/parsing.py +39 -0
- machine_dialect/compiler/pipeline.py +143 -0
- machine_dialect/compiler/tests/__init__.py +1 -0
- machine_dialect/compiler/tests/test_compiler.py +568 -0
- machine_dialect/compiler/vm_runner.py +173 -0
- machine_dialect/errors/__init__.py +32 -0
- machine_dialect/errors/exceptions.py +369 -0
- machine_dialect/errors/messages.py +82 -0
- machine_dialect/errors/tests/__init__.py +0 -0
- machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
- machine_dialect/errors/tests/test_name_errors.py +118 -0
- machine_dialect/helpers/__init__.py +0 -0
- machine_dialect/helpers/stopwords.py +225 -0
- machine_dialect/helpers/validators.py +30 -0
- machine_dialect/lexer/__init__.py +9 -0
- machine_dialect/lexer/constants.py +23 -0
- machine_dialect/lexer/lexer.py +907 -0
- machine_dialect/lexer/tests/__init__.py +0 -0
- machine_dialect/lexer/tests/helpers.py +86 -0
- machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
- machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
- machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
- machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
- machine_dialect/lexer/tests/test_comments.py +200 -0
- machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
- machine_dialect/lexer/tests/test_lexer_position.py +113 -0
- machine_dialect/lexer/tests/test_list_tokens.py +282 -0
- machine_dialect/lexer/tests/test_stopwords.py +80 -0
- machine_dialect/lexer/tests/test_strict_equality.py +129 -0
- machine_dialect/lexer/tests/test_token.py +41 -0
- machine_dialect/lexer/tests/test_tokenization.py +294 -0
- machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
- machine_dialect/lexer/tests/test_url_literals.py +169 -0
- machine_dialect/lexer/tokens.py +487 -0
- machine_dialect/linter/__init__.py +10 -0
- machine_dialect/linter/__main__.py +144 -0
- machine_dialect/linter/linter.py +154 -0
- machine_dialect/linter/rules/__init__.py +8 -0
- machine_dialect/linter/rules/base.py +112 -0
- machine_dialect/linter/rules/statement_termination.py +99 -0
- machine_dialect/linter/tests/__init__.py +1 -0
- machine_dialect/linter/tests/mdrules/__init__.py +0 -0
- machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
- machine_dialect/linter/tests/test_linter.py +81 -0
- machine_dialect/linter/tests/test_rules.py +110 -0
- machine_dialect/linter/tests/test_violations.py +71 -0
- machine_dialect/linter/violations.py +51 -0
- machine_dialect/mir/__init__.py +69 -0
- machine_dialect/mir/analyses/__init__.py +20 -0
- machine_dialect/mir/analyses/alias_analysis.py +315 -0
- machine_dialect/mir/analyses/dominance_analysis.py +49 -0
- machine_dialect/mir/analyses/escape_analysis.py +286 -0
- machine_dialect/mir/analyses/loop_analysis.py +272 -0
- machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
- machine_dialect/mir/analyses/type_analysis.py +448 -0
- machine_dialect/mir/analyses/use_def_chains.py +232 -0
- machine_dialect/mir/basic_block.py +385 -0
- machine_dialect/mir/dataflow.py +445 -0
- machine_dialect/mir/debug_info.py +208 -0
- machine_dialect/mir/hir_to_mir.py +1738 -0
- machine_dialect/mir/mir_dumper.py +366 -0
- machine_dialect/mir/mir_function.py +167 -0
- machine_dialect/mir/mir_instructions.py +1877 -0
- machine_dialect/mir/mir_interpreter.py +556 -0
- machine_dialect/mir/mir_module.py +225 -0
- machine_dialect/mir/mir_printer.py +480 -0
- machine_dialect/mir/mir_transformer.py +410 -0
- machine_dialect/mir/mir_types.py +367 -0
- machine_dialect/mir/mir_validation.py +455 -0
- machine_dialect/mir/mir_values.py +268 -0
- machine_dialect/mir/optimization_config.py +233 -0
- machine_dialect/mir/optimization_pass.py +251 -0
- machine_dialect/mir/optimization_pipeline.py +355 -0
- machine_dialect/mir/optimizations/__init__.py +84 -0
- machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
- machine_dialect/mir/optimizations/branch_prediction.py +372 -0
- machine_dialect/mir/optimizations/constant_propagation.py +634 -0
- machine_dialect/mir/optimizations/cse.py +398 -0
- machine_dialect/mir/optimizations/dce.py +288 -0
- machine_dialect/mir/optimizations/inlining.py +551 -0
- machine_dialect/mir/optimizations/jump_threading.py +487 -0
- machine_dialect/mir/optimizations/licm.py +405 -0
- machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
- machine_dialect/mir/optimizations/strength_reduction.py +422 -0
- machine_dialect/mir/optimizations/tail_call.py +207 -0
- machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
- machine_dialect/mir/optimizations/type_narrowing.py +397 -0
- machine_dialect/mir/optimizations/type_specialization.py +447 -0
- machine_dialect/mir/optimizations/type_specific.py +906 -0
- machine_dialect/mir/optimize_mir.py +89 -0
- machine_dialect/mir/pass_manager.py +391 -0
- machine_dialect/mir/profiling/__init__.py +26 -0
- machine_dialect/mir/profiling/profile_collector.py +318 -0
- machine_dialect/mir/profiling/profile_data.py +372 -0
- machine_dialect/mir/profiling/profile_reader.py +272 -0
- machine_dialect/mir/profiling/profile_writer.py +226 -0
- machine_dialect/mir/register_allocation.py +302 -0
- machine_dialect/mir/reporting/__init__.py +17 -0
- machine_dialect/mir/reporting/optimization_reporter.py +314 -0
- machine_dialect/mir/reporting/report_formatter.py +289 -0
- machine_dialect/mir/ssa_construction.py +342 -0
- machine_dialect/mir/tests/__init__.py +1 -0
- machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
- machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
- machine_dialect/mir/tests/test_algebraic_division.py +126 -0
- machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
- machine_dialect/mir/tests/test_basic_block.py +425 -0
- machine_dialect/mir/tests/test_branch_prediction.py +459 -0
- machine_dialect/mir/tests/test_call_lowering.py +168 -0
- machine_dialect/mir/tests/test_collection_lowering.py +604 -0
- machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
- machine_dialect/mir/tests/test_custom_passes.py +166 -0
- machine_dialect/mir/tests/test_debug_info.py +285 -0
- machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
- machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
- machine_dialect/mir/tests/test_double_negation.py +231 -0
- machine_dialect/mir/tests/test_escape_analysis.py +233 -0
- machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
- machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
- machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
- machine_dialect/mir/tests/test_inlining.py +435 -0
- machine_dialect/mir/tests/test_licm.py +472 -0
- machine_dialect/mir/tests/test_mir_dumper.py +313 -0
- machine_dialect/mir/tests/test_mir_instructions.py +445 -0
- machine_dialect/mir/tests/test_mir_module.py +860 -0
- machine_dialect/mir/tests/test_mir_printer.py +387 -0
- machine_dialect/mir/tests/test_mir_types.py +123 -0
- machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
- machine_dialect/mir/tests/test_mir_validation.py +378 -0
- machine_dialect/mir/tests/test_mir_values.py +168 -0
- machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
- machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
- machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
- machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
- machine_dialect/mir/tests/test_pass_manager.py +294 -0
- machine_dialect/mir/tests/test_pass_registration.py +64 -0
- machine_dialect/mir/tests/test_profiling.py +356 -0
- machine_dialect/mir/tests/test_register_allocation.py +307 -0
- machine_dialect/mir/tests/test_report_formatters.py +372 -0
- machine_dialect/mir/tests/test_ssa_construction.py +433 -0
- machine_dialect/mir/tests/test_tail_call.py +236 -0
- machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
- machine_dialect/mir/tests/test_type_narrowing.py +277 -0
- machine_dialect/mir/tests/test_type_specialization.py +421 -0
- machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
- machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
- machine_dialect/mir/type_inference.py +368 -0
- machine_dialect/parser/__init__.py +12 -0
- machine_dialect/parser/enums.py +45 -0
- machine_dialect/parser/parser.py +3655 -0
- machine_dialect/parser/protocols.py +11 -0
- machine_dialect/parser/symbol_table.py +169 -0
- machine_dialect/parser/tests/__init__.py +0 -0
- machine_dialect/parser/tests/helper_functions.py +193 -0
- machine_dialect/parser/tests/test_action_statements.py +334 -0
- machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
- machine_dialect/parser/tests/test_call_statements.py +154 -0
- machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
- machine_dialect/parser/tests/test_collection_mutations.py +264 -0
- machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
- machine_dialect/parser/tests/test_define_integration.py +468 -0
- machine_dialect/parser/tests/test_define_statements.py +311 -0
- machine_dialect/parser/tests/test_dict_extraction.py +115 -0
- machine_dialect/parser/tests/test_empty_literal.py +155 -0
- machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
- machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
- machine_dialect/parser/tests/test_if_empty_block.py +61 -0
- machine_dialect/parser/tests/test_if_statements.py +299 -0
- machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
- machine_dialect/parser/tests/test_infix_expressions.py +680 -0
- machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
- machine_dialect/parser/tests/test_interaction_statements.py +269 -0
- machine_dialect/parser/tests/test_list_literals.py +277 -0
- machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
- machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
- machine_dialect/parser/tests/test_parse_errors.py +114 -0
- machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
- machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
- machine_dialect/parser/tests/test_program.py +13 -0
- machine_dialect/parser/tests/test_return_statements.py +89 -0
- machine_dialect/parser/tests/test_set_statements.py +152 -0
- machine_dialect/parser/tests/test_strict_equality.py +258 -0
- machine_dialect/parser/tests/test_symbol_table.py +217 -0
- machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
- machine_dialect/parser/tests/test_utility_statements.py +423 -0
- machine_dialect/parser/token_buffer.py +159 -0
- machine_dialect/repl/__init__.py +3 -0
- machine_dialect/repl/repl.py +426 -0
- machine_dialect/repl/tests/__init__.py +0 -0
- machine_dialect/repl/tests/test_repl.py +606 -0
- machine_dialect/semantic/__init__.py +12 -0
- machine_dialect/semantic/analyzer.py +906 -0
- machine_dialect/semantic/error_messages.py +189 -0
- machine_dialect/semantic/tests/__init__.py +1 -0
- machine_dialect/semantic/tests/test_analyzer.py +364 -0
- machine_dialect/semantic/tests/test_error_messages.py +104 -0
- machine_dialect/tests/edge_cases/__init__.py +10 -0
- machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
- machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
- machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
- machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
- machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
- machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
- machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
- machine_dialect/tests/integration/test_list_compilation.py +395 -0
- machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
- machine_dialect/type_checking/__init__.py +21 -0
- machine_dialect/type_checking/tests/__init__.py +1 -0
- machine_dialect/type_checking/tests/test_type_system.py +230 -0
- machine_dialect/type_checking/type_system.py +270 -0
- machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
- machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
- machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
- machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
- machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
- machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
- machine_dialect_vm/__init__.pyi +15 -0
@@ -0,0 +1,541 @@
|
|
1
|
+
"""Tests for AST desugaring functionality."""
|
2
|
+
|
3
|
+
from machine_dialect.ast import (
|
4
|
+
ActionStatement,
|
5
|
+
Arguments,
|
6
|
+
BlockStatement,
|
7
|
+
CallStatement,
|
8
|
+
ConditionalExpression,
|
9
|
+
EmptyLiteral,
|
10
|
+
ExpressionStatement,
|
11
|
+
FloatLiteral,
|
12
|
+
FunctionStatement,
|
13
|
+
FunctionVisibility,
|
14
|
+
Identifier,
|
15
|
+
IfStatement,
|
16
|
+
InfixExpression,
|
17
|
+
InteractionStatement,
|
18
|
+
PrefixExpression,
|
19
|
+
ReturnStatement,
|
20
|
+
SetStatement,
|
21
|
+
StringLiteral,
|
22
|
+
UtilityStatement,
|
23
|
+
WholeNumberLiteral,
|
24
|
+
YesNoLiteral,
|
25
|
+
)
|
26
|
+
from machine_dialect.lexer import Token, TokenType
|
27
|
+
|
28
|
+
|
29
|
+
class TestExpressionDesugaring:
|
30
|
+
"""Test desugaring of expression nodes."""
|
31
|
+
|
32
|
+
def test_literal_desugaring(self) -> None:
|
33
|
+
"""Test that literals remain unchanged during desugaring."""
|
34
|
+
# Whole Number literal
|
35
|
+
int_lit = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "42", 1, 1), 42)
|
36
|
+
assert int_lit.desugar() is int_lit
|
37
|
+
|
38
|
+
# Float literal
|
39
|
+
float_lit = FloatLiteral(Token(TokenType.LIT_FLOAT, "3.14", 1, 1), 3.14)
|
40
|
+
assert float_lit.desugar() is float_lit
|
41
|
+
|
42
|
+
# String literal
|
43
|
+
str_lit = StringLiteral(Token(TokenType.LIT_TEXT, '"hello"', 1, 1), '"hello"')
|
44
|
+
assert str_lit.desugar() is str_lit
|
45
|
+
|
46
|
+
# Boolean literal
|
47
|
+
bool_lit = YesNoLiteral(Token(TokenType.LIT_YES, "True", 1, 1), True)
|
48
|
+
assert bool_lit.desugar() is bool_lit
|
49
|
+
|
50
|
+
# Empty literal
|
51
|
+
empty_lit = EmptyLiteral(Token(TokenType.KW_EMPTY, "empty", 1, 1))
|
52
|
+
assert empty_lit.desugar() is empty_lit
|
53
|
+
|
54
|
+
def test_identifier_desugaring(self) -> None:
|
55
|
+
"""Test that identifiers remain unchanged during desugaring."""
|
56
|
+
ident = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 1), "x")
|
57
|
+
assert ident.desugar() is ident
|
58
|
+
|
59
|
+
def test_prefix_expression_desugaring(self) -> None:
|
60
|
+
"""Test desugaring of prefix expressions."""
|
61
|
+
# Create a prefix expression: -42
|
62
|
+
token = Token(TokenType.OP_MINUS, "-", 1, 1)
|
63
|
+
prefix = PrefixExpression(token, "-")
|
64
|
+
prefix.right = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "42", 1, 2), 42)
|
65
|
+
|
66
|
+
desugared = prefix.desugar()
|
67
|
+
|
68
|
+
# Should be a new PrefixExpression
|
69
|
+
assert isinstance(desugared, PrefixExpression)
|
70
|
+
assert desugared is not prefix
|
71
|
+
assert desugared.operator == "-"
|
72
|
+
# Right should be the same literal (literals don't change)
|
73
|
+
assert desugared.right is prefix.right
|
74
|
+
|
75
|
+
def test_infix_expression_operator_normalization(self) -> None:
|
76
|
+
"""Test that natural language operators are normalized during desugaring."""
|
77
|
+
left = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 1), "x")
|
78
|
+
right = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "5", 1, 10), 5)
|
79
|
+
|
80
|
+
# Test equality operators - include correct token types
|
81
|
+
test_cases = [
|
82
|
+
(TokenType.OP_EQ, "equals", "=="),
|
83
|
+
(TokenType.OP_EQ, "is equal to", "=="),
|
84
|
+
(TokenType.OP_EQ, "is the same as", "=="),
|
85
|
+
(TokenType.OP_NOT_EQ, "is not equal to", "!="),
|
86
|
+
(TokenType.OP_NOT_EQ, "does not equal", "!="),
|
87
|
+
(TokenType.OP_NOT_EQ, "is different from", "!="),
|
88
|
+
(TokenType.OP_STRICT_EQ, "is strictly equal to", "==="),
|
89
|
+
(TokenType.OP_STRICT_EQ, "is exactly equal to", "==="),
|
90
|
+
(TokenType.OP_STRICT_EQ, "is identical to", "==="),
|
91
|
+
(TokenType.OP_STRICT_NOT_EQ, "is not strictly equal to", "!=="),
|
92
|
+
(TokenType.OP_STRICT_NOT_EQ, "is not exactly equal to", "!=="),
|
93
|
+
(TokenType.OP_STRICT_NOT_EQ, "is not identical to", "!=="),
|
94
|
+
(TokenType.OP_GT, "is greater than", ">"),
|
95
|
+
(TokenType.OP_LT, "is less than", "<"),
|
96
|
+
(TokenType.OP_GTE, "is greater than or equal to", ">="),
|
97
|
+
(TokenType.OP_LTE, "is less than or equal to", "<="),
|
98
|
+
]
|
99
|
+
|
100
|
+
for token_type, natural, normalized in test_cases:
|
101
|
+
token = Token(token_type, natural, 1, 5)
|
102
|
+
infix = InfixExpression(token, natural, left)
|
103
|
+
infix.right = right
|
104
|
+
|
105
|
+
desugared = infix.desugar()
|
106
|
+
|
107
|
+
assert isinstance(desugared, InfixExpression)
|
108
|
+
assert desugared.operator == normalized, f"Failed to normalize '{natural}' to '{normalized}'"
|
109
|
+
# Check that operands are also desugared
|
110
|
+
assert desugared.left is left # Identifiers return self
|
111
|
+
assert desugared.right is right # Literals return self
|
112
|
+
|
113
|
+
def test_infix_expression_already_normalized(self) -> None:
|
114
|
+
"""Test that already normalized operators remain unchanged."""
|
115
|
+
left = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 1), "x")
|
116
|
+
right = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "5", 1, 5), 5)
|
117
|
+
|
118
|
+
# Use correct token types for each operator
|
119
|
+
operators = [
|
120
|
+
(TokenType.OP_PLUS, "+"),
|
121
|
+
(TokenType.OP_MINUS, "-"),
|
122
|
+
(TokenType.OP_STAR, "*"),
|
123
|
+
(TokenType.OP_DIVISION, "/"),
|
124
|
+
(TokenType.OP_EQ, "=="),
|
125
|
+
(TokenType.OP_NOT_EQ, "!="),
|
126
|
+
(TokenType.OP_STRICT_EQ, "==="),
|
127
|
+
(TokenType.OP_STRICT_NOT_EQ, "!=="),
|
128
|
+
(TokenType.OP_GT, ">"),
|
129
|
+
(TokenType.OP_LT, "<"),
|
130
|
+
(TokenType.OP_GTE, ">="),
|
131
|
+
(TokenType.OP_LTE, "<="),
|
132
|
+
(TokenType.OP_CARET, "^"),
|
133
|
+
]
|
134
|
+
|
135
|
+
for token_type, op in operators:
|
136
|
+
token = Token(token_type, op, 1, 3)
|
137
|
+
infix = InfixExpression(token, op, left)
|
138
|
+
infix.right = right
|
139
|
+
|
140
|
+
desugared = infix.desugar()
|
141
|
+
|
142
|
+
assert isinstance(desugared, InfixExpression)
|
143
|
+
assert desugared.operator == op, f"Operator '{op}' should remain unchanged"
|
144
|
+
|
145
|
+
def test_conditional_expression_desugaring(self) -> None:
|
146
|
+
"""Test desugaring of conditional expressions."""
|
147
|
+
consequence = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "1", 1, 1), 1)
|
148
|
+
condition = YesNoLiteral(Token(TokenType.LIT_YES, "True", 1, 5), True)
|
149
|
+
alternative = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "2", 1, 15), 2)
|
150
|
+
|
151
|
+
token = Token(TokenType.KW_IF, "if", 1, 3)
|
152
|
+
cond_expr = ConditionalExpression(token, consequence)
|
153
|
+
cond_expr.condition = condition
|
154
|
+
cond_expr.alternative = alternative
|
155
|
+
|
156
|
+
desugared = cond_expr.desugar()
|
157
|
+
|
158
|
+
assert isinstance(desugared, ConditionalExpression)
|
159
|
+
assert desugared is not cond_expr
|
160
|
+
# Literals should return self
|
161
|
+
assert desugared.consequence is consequence
|
162
|
+
assert desugared.condition is condition
|
163
|
+
assert desugared.alternative is alternative
|
164
|
+
|
165
|
+
def test_arguments_desugaring(self) -> None:
|
166
|
+
"""Test desugaring of arguments."""
|
167
|
+
token = Token(TokenType.KW_WITH, "with", 1, 10)
|
168
|
+
args = Arguments(token)
|
169
|
+
|
170
|
+
# Add positional arguments
|
171
|
+
args.positional.append(WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "1", 1, 15), 1))
|
172
|
+
args.positional.append(StringLiteral(Token(TokenType.LIT_TEXT, '"test"', 1, 18), '"test"'))
|
173
|
+
|
174
|
+
# Add named arguments
|
175
|
+
name = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 25), "x")
|
176
|
+
value = YesNoLiteral(Token(TokenType.LIT_YES, "True", 1, 28), True)
|
177
|
+
args.named.append((name, value))
|
178
|
+
|
179
|
+
desugared = args.desugar()
|
180
|
+
|
181
|
+
assert isinstance(desugared, Arguments)
|
182
|
+
assert desugared is not args
|
183
|
+
assert len(desugared.positional) == 2
|
184
|
+
assert len(desugared.named) == 1
|
185
|
+
# Literals should be the same
|
186
|
+
assert desugared.positional[0] is args.positional[0]
|
187
|
+
assert desugared.positional[1] is args.positional[1]
|
188
|
+
|
189
|
+
|
190
|
+
class TestStatementDesugaring:
|
191
|
+
"""Test desugaring of statement nodes."""
|
192
|
+
|
193
|
+
def test_return_statement_normalization(self) -> None:
|
194
|
+
"""Test that return statements normalize 'give back' and 'gives back' to 'return'."""
|
195
|
+
return_value = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "42", 1, 11), 42)
|
196
|
+
|
197
|
+
# Test "give back"
|
198
|
+
token1 = Token(TokenType.KW_RETURN, "give back", 1, 1)
|
199
|
+
ret1 = ReturnStatement(token1, return_value)
|
200
|
+
desugared1 = ret1.desugar()
|
201
|
+
|
202
|
+
assert isinstance(desugared1, ReturnStatement)
|
203
|
+
assert desugared1.token.literal == "return"
|
204
|
+
assert desugared1.return_value is return_value
|
205
|
+
|
206
|
+
# Test "gives back"
|
207
|
+
token2 = Token(TokenType.KW_RETURN, "gives back", 1, 1)
|
208
|
+
ret2 = ReturnStatement(token2, return_value)
|
209
|
+
desugared2 = ret2.desugar()
|
210
|
+
|
211
|
+
assert isinstance(desugared2, ReturnStatement)
|
212
|
+
assert desugared2.token.literal == "return"
|
213
|
+
assert desugared2.return_value is return_value
|
214
|
+
|
215
|
+
def test_set_statement_desugaring(self) -> None:
|
216
|
+
"""Test desugaring of set statements."""
|
217
|
+
token = Token(TokenType.KW_SET, "Set", 1, 1)
|
218
|
+
name = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 5), "x")
|
219
|
+
value = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "42", 1, 10), 42)
|
220
|
+
|
221
|
+
set_stmt = SetStatement(token, name, value)
|
222
|
+
desugared = set_stmt.desugar()
|
223
|
+
|
224
|
+
assert isinstance(desugared, SetStatement)
|
225
|
+
assert desugared is not set_stmt
|
226
|
+
assert desugared.name is name # Identifiers return self
|
227
|
+
assert desugared.value is value # Literals return self
|
228
|
+
|
229
|
+
def test_call_statement_desugaring(self) -> None:
|
230
|
+
"""Test desugaring of call statements."""
|
231
|
+
token = Token(TokenType.KW_USE, "use", 1, 1)
|
232
|
+
func_name = StringLiteral(Token(TokenType.LIT_TEXT, '"print"', 1, 6), '"print"')
|
233
|
+
|
234
|
+
args = Arguments(Token(TokenType.KW_WITH, "with", 1, 14))
|
235
|
+
args.positional.append(StringLiteral(Token(TokenType.LIT_TEXT, '"hello"', 1, 20), '"hello"'))
|
236
|
+
|
237
|
+
call_stmt = CallStatement(token, func_name, args)
|
238
|
+
desugared = call_stmt.desugar()
|
239
|
+
|
240
|
+
assert isinstance(desugared, CallStatement)
|
241
|
+
assert desugared is not call_stmt
|
242
|
+
assert desugared.function_name is func_name # Literals return self
|
243
|
+
assert isinstance(desugared.arguments, Arguments)
|
244
|
+
assert desugared.arguments is not args
|
245
|
+
|
246
|
+
def test_block_statement_flattening(self) -> None:
|
247
|
+
"""Test that blocks preserve scope (no longer flatten)."""
|
248
|
+
token = Token(TokenType.PUNCT_COLON, ":", 1, 10)
|
249
|
+
|
250
|
+
# Test single statement block - now preserves block for scope
|
251
|
+
block1 = BlockStatement(token, depth=1)
|
252
|
+
single_stmt = ReturnStatement(
|
253
|
+
Token(TokenType.KW_RETURN, "return", 2, 3),
|
254
|
+
WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "42", 2, 10), 42),
|
255
|
+
)
|
256
|
+
block1.statements = [single_stmt]
|
257
|
+
|
258
|
+
desugared1 = block1.desugar()
|
259
|
+
assert isinstance(desugared1, BlockStatement) # Block is preserved
|
260
|
+
assert len(desugared1.statements) == 1
|
261
|
+
|
262
|
+
# Test multi-statement block - should remain a block
|
263
|
+
block2 = BlockStatement(token, depth=1)
|
264
|
+
stmt1 = SetStatement(
|
265
|
+
Token(TokenType.KW_SET, "Set", 2, 3),
|
266
|
+
Identifier(Token(TokenType.MISC_IDENT, "x", 2, 7), "x"),
|
267
|
+
WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "1", 2, 12), 1),
|
268
|
+
)
|
269
|
+
stmt2 = ReturnStatement(
|
270
|
+
Token(TokenType.KW_RETURN, "return", 3, 3), Identifier(Token(TokenType.MISC_IDENT, "x", 3, 10), "x")
|
271
|
+
)
|
272
|
+
block2.statements = [stmt1, stmt2]
|
273
|
+
|
274
|
+
desugared2 = block2.desugar()
|
275
|
+
assert isinstance(desugared2, BlockStatement)
|
276
|
+
assert len(desugared2.statements) == 2
|
277
|
+
|
278
|
+
# Test nested block with single statement - blocks are preserved
|
279
|
+
block3 = BlockStatement(token, depth=1)
|
280
|
+
inner_block = BlockStatement(token, depth=2)
|
281
|
+
inner_block.statements = [single_stmt]
|
282
|
+
block3.statements = [inner_block]
|
283
|
+
|
284
|
+
desugared3 = block3.desugar()
|
285
|
+
assert isinstance(desugared3, BlockStatement) # Outer block preserved
|
286
|
+
assert len(desugared3.statements) == 1
|
287
|
+
assert isinstance(desugared3.statements[0], BlockStatement) # Inner block preserved
|
288
|
+
|
289
|
+
def test_if_statement_desugaring(self) -> None:
|
290
|
+
"""Test desugaring of if statements."""
|
291
|
+
token = Token(TokenType.KW_IF, "if", 1, 1)
|
292
|
+
condition = YesNoLiteral(Token(TokenType.LIT_YES, "True", 1, 4), True)
|
293
|
+
|
294
|
+
# Create consequence block
|
295
|
+
consequence = BlockStatement(Token(TokenType.PUNCT_COLON, ":", 1, 9), depth=1)
|
296
|
+
consequence.statements = [
|
297
|
+
ReturnStatement(
|
298
|
+
Token(TokenType.KW_RETURN, "give back", 2, 3),
|
299
|
+
WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "1", 2, 13), 1),
|
300
|
+
)
|
301
|
+
]
|
302
|
+
|
303
|
+
# Create alternative block
|
304
|
+
alternative = BlockStatement(Token(TokenType.PUNCT_COLON, ":", 3, 5), depth=1)
|
305
|
+
alternative.statements = [
|
306
|
+
ReturnStatement(
|
307
|
+
Token(TokenType.KW_RETURN, "gives back", 4, 3),
|
308
|
+
WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "2", 4, 14), 2),
|
309
|
+
)
|
310
|
+
]
|
311
|
+
|
312
|
+
if_stmt = IfStatement(token, condition)
|
313
|
+
if_stmt.consequence = consequence
|
314
|
+
if_stmt.alternative = alternative
|
315
|
+
|
316
|
+
desugared = if_stmt.desugar()
|
317
|
+
|
318
|
+
assert isinstance(desugared, IfStatement)
|
319
|
+
assert desugared.condition is condition # Literals return self
|
320
|
+
|
321
|
+
# Even though blocks have single statements, IfStatement keeps them as blocks
|
322
|
+
assert isinstance(desugared.consequence, BlockStatement)
|
323
|
+
assert isinstance(desugared.alternative, BlockStatement)
|
324
|
+
|
325
|
+
# Check that the return statements inside were normalized
|
326
|
+
assert desugared.consequence is not None # Type guard
|
327
|
+
cons_stmt = desugared.consequence.statements[0]
|
328
|
+
assert isinstance(cons_stmt, ReturnStatement)
|
329
|
+
assert cons_stmt.token.literal == "return"
|
330
|
+
|
331
|
+
assert desugared.alternative is not None # Type guard
|
332
|
+
alt_stmt = desugared.alternative.statements[0]
|
333
|
+
assert isinstance(alt_stmt, ReturnStatement)
|
334
|
+
assert alt_stmt.token.literal == "return"
|
335
|
+
|
336
|
+
def test_expression_statement_desugaring(self) -> None:
|
337
|
+
"""Test desugaring of expression statements."""
|
338
|
+
# Create an infix expression that needs normalization
|
339
|
+
left = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 1), "x")
|
340
|
+
right = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "5", 1, 15), 5)
|
341
|
+
|
342
|
+
infix = InfixExpression(Token(TokenType.OP_EQ, "is equal to", 1, 3), "is equal to", left)
|
343
|
+
infix.right = right
|
344
|
+
|
345
|
+
expr_stmt = ExpressionStatement(Token(TokenType.MISC_IDENT, "x", 1, 1), infix)
|
346
|
+
desugared = expr_stmt.desugar()
|
347
|
+
|
348
|
+
assert isinstance(desugared, ExpressionStatement)
|
349
|
+
assert desugared is not expr_stmt
|
350
|
+
assert isinstance(desugared.expression, InfixExpression)
|
351
|
+
assert desugared.expression.operator == "==" # Should be normalized
|
352
|
+
|
353
|
+
|
354
|
+
class TestFunctionStatementDesugaring:
|
355
|
+
"""Test desugaring of function-like statements."""
|
356
|
+
|
357
|
+
def test_action_statement_desugaring(self) -> None:
|
358
|
+
"""Test that ActionStatement desugars to FunctionStatement with PRIVATE visibility."""
|
359
|
+
token = Token(TokenType.KW_ACTION, "action", 1, 1)
|
360
|
+
name = Identifier(Token(TokenType.MISC_IDENT, "doSomething", 1, 8), "doSomething")
|
361
|
+
|
362
|
+
body = BlockStatement(Token(TokenType.PUNCT_COLON, ":", 1, 20), depth=1)
|
363
|
+
body.statements = [
|
364
|
+
ReturnStatement(
|
365
|
+
Token(TokenType.KW_RETURN, "give back", 2, 3),
|
366
|
+
WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "42", 2, 13), 42),
|
367
|
+
)
|
368
|
+
]
|
369
|
+
|
370
|
+
action = ActionStatement(token, name, body=body)
|
371
|
+
desugared = action.desugar()
|
372
|
+
|
373
|
+
assert isinstance(desugared, FunctionStatement)
|
374
|
+
assert desugared.visibility == FunctionVisibility.PRIVATE
|
375
|
+
assert desugared.name is name
|
376
|
+
# Body might be flattened if it has single statement
|
377
|
+
if isinstance(desugared.body, BlockStatement):
|
378
|
+
# Check that body was desugared (return statement normalized)
|
379
|
+
ret_stmt = desugared.body.statements[0]
|
380
|
+
else:
|
381
|
+
# Single statement was flattened
|
382
|
+
ret_stmt = desugared.body
|
383
|
+
assert isinstance(ret_stmt, ReturnStatement)
|
384
|
+
assert ret_stmt.token.literal == "return"
|
385
|
+
|
386
|
+
def test_interaction_statement_desugaring(self) -> None:
|
387
|
+
"""Test that InteractionStatement desugars to FunctionStatement with PUBLIC visibility."""
|
388
|
+
token = Token(TokenType.KW_INTERACTION, "interaction", 1, 1)
|
389
|
+
name = Identifier(Token(TokenType.MISC_IDENT, "handleRequest", 1, 13), "handleRequest")
|
390
|
+
|
391
|
+
body = BlockStatement(Token(TokenType.PUNCT_COLON, ":", 1, 27), depth=1)
|
392
|
+
body.statements = [
|
393
|
+
SetStatement(
|
394
|
+
Token(TokenType.KW_SET, "Set", 2, 3),
|
395
|
+
Identifier(Token(TokenType.MISC_IDENT, "x", 2, 7), "x"),
|
396
|
+
WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "10", 2, 12), 10),
|
397
|
+
)
|
398
|
+
]
|
399
|
+
|
400
|
+
interaction = InteractionStatement(token, name, body=body)
|
401
|
+
desugared = interaction.desugar()
|
402
|
+
|
403
|
+
assert isinstance(desugared, FunctionStatement)
|
404
|
+
assert desugared.visibility == FunctionVisibility.PUBLIC
|
405
|
+
assert desugared.name is name
|
406
|
+
|
407
|
+
def test_utility_statement_desugaring(self) -> None:
|
408
|
+
"""Test that UtilityStatement desugars to FunctionStatement with FUNCTION visibility."""
|
409
|
+
token = Token(TokenType.KW_UTILITY, "utility", 1, 1)
|
410
|
+
name = Identifier(Token(TokenType.MISC_IDENT, "calculate", 1, 9), "calculate")
|
411
|
+
|
412
|
+
body = BlockStatement(Token(TokenType.PUNCT_COLON, ":", 1, 19), depth=1)
|
413
|
+
body.statements = [
|
414
|
+
ReturnStatement(
|
415
|
+
Token(TokenType.KW_RETURN, "gives back", 2, 3),
|
416
|
+
WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "100", 2, 14), 100),
|
417
|
+
)
|
418
|
+
]
|
419
|
+
|
420
|
+
utility = UtilityStatement(token, name, body=body)
|
421
|
+
desugared = utility.desugar()
|
422
|
+
|
423
|
+
assert isinstance(desugared, FunctionStatement)
|
424
|
+
assert desugared.visibility == FunctionVisibility.FUNCTION
|
425
|
+
assert desugared.name is name
|
426
|
+
# Check return normalization
|
427
|
+
if isinstance(desugared.body, BlockStatement):
|
428
|
+
ret_stmt = desugared.body.statements[0]
|
429
|
+
else:
|
430
|
+
# Single statement was flattened
|
431
|
+
ret_stmt = desugared.body
|
432
|
+
assert isinstance(ret_stmt, ReturnStatement)
|
433
|
+
assert ret_stmt.token.literal == "return"
|
434
|
+
|
435
|
+
def test_function_statement_str_representation(self) -> None:
|
436
|
+
"""Test string representation of FunctionStatement."""
|
437
|
+
token = Token(TokenType.KW_ACTION, "action", 1, 1)
|
438
|
+
name = Identifier(Token(TokenType.MISC_IDENT, "test", 1, 8), "test")
|
439
|
+
body = BlockStatement(Token(TokenType.PUNCT_COLON, ":", 1, 13), depth=1)
|
440
|
+
|
441
|
+
# Test PRIVATE (action)
|
442
|
+
func1 = FunctionStatement(token, FunctionVisibility.PRIVATE, name, body=body)
|
443
|
+
func1_str = str(func1)
|
444
|
+
assert "action" in func1_str
|
445
|
+
assert "`test`" in func1_str # Identifier is wrapped in backticks
|
446
|
+
|
447
|
+
# Test PUBLIC (interaction)
|
448
|
+
func2 = FunctionStatement(token, FunctionVisibility.PUBLIC, name, body=body)
|
449
|
+
func2_str = str(func2)
|
450
|
+
assert "interaction" in func2_str
|
451
|
+
assert "`test`" in func2_str
|
452
|
+
|
453
|
+
# Test FUNCTION (utility)
|
454
|
+
func3 = FunctionStatement(token, FunctionVisibility.FUNCTION, name, body=body)
|
455
|
+
func3_str = str(func3)
|
456
|
+
assert "utility" in func3_str
|
457
|
+
assert "`test`" in func3_str
|
458
|
+
|
459
|
+
|
460
|
+
class TestComplexDesugaring:
|
461
|
+
"""Test desugaring of complex nested structures."""
|
462
|
+
|
463
|
+
def test_nested_expression_desugaring(self) -> None:
|
464
|
+
"""Test desugaring of deeply nested expressions."""
|
465
|
+
# Create: (x is equal to 5) and (y is greater than 10)
|
466
|
+
x = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 1), "x")
|
467
|
+
five = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "5", 1, 15), 5)
|
468
|
+
|
469
|
+
left_expr = InfixExpression(Token(TokenType.OP_EQ, "is equal to", 1, 3), "is equal to", x)
|
470
|
+
left_expr.right = five
|
471
|
+
|
472
|
+
y = Identifier(Token(TokenType.MISC_IDENT, "y", 1, 20), "y")
|
473
|
+
ten = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "10", 1, 38), 10)
|
474
|
+
|
475
|
+
right_expr = InfixExpression(Token(TokenType.OP_GT, "is greater than", 1, 22), "is greater than", y)
|
476
|
+
right_expr.right = ten
|
477
|
+
|
478
|
+
and_expr = InfixExpression(Token(TokenType.KW_AND, "and", 1, 17), "and", left_expr)
|
479
|
+
and_expr.right = right_expr
|
480
|
+
|
481
|
+
desugared = and_expr.desugar()
|
482
|
+
|
483
|
+
assert isinstance(desugared, InfixExpression)
|
484
|
+
assert desugared.operator == "and"
|
485
|
+
|
486
|
+
# Check left side normalization
|
487
|
+
assert isinstance(desugared.left, InfixExpression)
|
488
|
+
assert desugared.left.operator == "=="
|
489
|
+
|
490
|
+
# Check right side normalization
|
491
|
+
assert desugared.right is not None # Type guard
|
492
|
+
assert isinstance(desugared.right, InfixExpression)
|
493
|
+
assert desugared.right.operator == ">"
|
494
|
+
|
495
|
+
def test_complex_statement_desugaring(self) -> None:
|
496
|
+
"""Test desugaring of complex statement structures."""
|
497
|
+
# Create an if statement with nested blocks and expressions
|
498
|
+
token = Token(TokenType.KW_IF, "if", 1, 1)
|
499
|
+
|
500
|
+
# Condition: x is strictly equal to 5
|
501
|
+
x = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 4), "x")
|
502
|
+
five = WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "5", 1, 27), 5)
|
503
|
+
condition = InfixExpression(
|
504
|
+
Token(TokenType.OP_STRICT_EQ, "is strictly equal to", 1, 6), "is strictly equal to", x
|
505
|
+
)
|
506
|
+
condition.right = five
|
507
|
+
|
508
|
+
# Consequence: nested block with give back
|
509
|
+
consequence = BlockStatement(Token(TokenType.PUNCT_COLON, ":", 1, 32), depth=1)
|
510
|
+
inner_block = BlockStatement(Token(TokenType.PUNCT_COLON, ":", 2, 5), depth=2)
|
511
|
+
inner_block.statements = [
|
512
|
+
ReturnStatement(
|
513
|
+
Token(TokenType.KW_RETURN, "give back", 3, 7),
|
514
|
+
WholeNumberLiteral(Token(TokenType.LIT_WHOLE_NUMBER, "100", 3, 17), 100),
|
515
|
+
)
|
516
|
+
]
|
517
|
+
consequence.statements = [inner_block]
|
518
|
+
|
519
|
+
if_stmt = IfStatement(token, condition)
|
520
|
+
if_stmt.consequence = consequence
|
521
|
+
|
522
|
+
desugared = if_stmt.desugar()
|
523
|
+
|
524
|
+
assert isinstance(desugared, IfStatement)
|
525
|
+
|
526
|
+
# Check condition normalization
|
527
|
+
assert desugared.condition is not None # Type guard
|
528
|
+
assert isinstance(desugared.condition, InfixExpression)
|
529
|
+
assert desugared.condition.operator == "==="
|
530
|
+
|
531
|
+
# Check that nested single-statement blocks are preserved in if statement
|
532
|
+
assert desugared.consequence is not None # Type guard
|
533
|
+
assert isinstance(desugared.consequence, BlockStatement)
|
534
|
+
# Blocks are now preserved for scope
|
535
|
+
assert len(desugared.consequence.statements) == 1
|
536
|
+
inner_block2 = desugared.consequence.statements[0]
|
537
|
+
assert isinstance(inner_block2, BlockStatement) # Block is preserved
|
538
|
+
assert len(inner_block2.statements) == 1
|
539
|
+
ret_stmt = inner_block2.statements[0]
|
540
|
+
assert isinstance(ret_stmt, ReturnStatement)
|
541
|
+
assert ret_stmt.token.literal == "return"
|