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.
Files changed (268) hide show
  1. machine_dialect/__main__.py +667 -0
  2. machine_dialect/agent/__init__.py +5 -0
  3. machine_dialect/agent/agent.py +360 -0
  4. machine_dialect/ast/__init__.py +95 -0
  5. machine_dialect/ast/ast_node.py +35 -0
  6. machine_dialect/ast/call_expression.py +82 -0
  7. machine_dialect/ast/dict_extraction.py +60 -0
  8. machine_dialect/ast/expressions.py +439 -0
  9. machine_dialect/ast/literals.py +309 -0
  10. machine_dialect/ast/program.py +35 -0
  11. machine_dialect/ast/statements.py +1433 -0
  12. machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
  13. machine_dialect/ast/tests/test_boolean_literal.py +29 -0
  14. machine_dialect/ast/tests/test_collection_hir.py +138 -0
  15. machine_dialect/ast/tests/test_define_statement.py +142 -0
  16. machine_dialect/ast/tests/test_desugar.py +541 -0
  17. machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
  18. machine_dialect/cfg/__init__.py +6 -0
  19. machine_dialect/cfg/config.py +156 -0
  20. machine_dialect/cfg/examples.py +221 -0
  21. machine_dialect/cfg/generate_with_ai.py +187 -0
  22. machine_dialect/cfg/openai_generation.py +200 -0
  23. machine_dialect/cfg/parser.py +94 -0
  24. machine_dialect/cfg/tests/__init__.py +1 -0
  25. machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
  26. machine_dialect/cfg/tests/test_config.py +188 -0
  27. machine_dialect/cfg/tests/test_examples.py +391 -0
  28. machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
  29. machine_dialect/cfg/tests/test_openai_generation.py +256 -0
  30. machine_dialect/codegen/__init__.py +5 -0
  31. machine_dialect/codegen/bytecode_module.py +89 -0
  32. machine_dialect/codegen/bytecode_serializer.py +300 -0
  33. machine_dialect/codegen/opcodes.py +101 -0
  34. machine_dialect/codegen/register_codegen.py +1996 -0
  35. machine_dialect/codegen/symtab.py +208 -0
  36. machine_dialect/codegen/tests/__init__.py +1 -0
  37. machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
  38. machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
  39. machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
  40. machine_dialect/codegen/tests/test_symtab.py +418 -0
  41. machine_dialect/codegen/vm_serializer.py +621 -0
  42. machine_dialect/compiler/__init__.py +18 -0
  43. machine_dialect/compiler/compiler.py +197 -0
  44. machine_dialect/compiler/config.py +149 -0
  45. machine_dialect/compiler/context.py +149 -0
  46. machine_dialect/compiler/phases/__init__.py +19 -0
  47. machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
  48. machine_dialect/compiler/phases/codegen.py +40 -0
  49. machine_dialect/compiler/phases/hir_generation.py +39 -0
  50. machine_dialect/compiler/phases/mir_generation.py +86 -0
  51. machine_dialect/compiler/phases/optimization.py +110 -0
  52. machine_dialect/compiler/phases/parsing.py +39 -0
  53. machine_dialect/compiler/pipeline.py +143 -0
  54. machine_dialect/compiler/tests/__init__.py +1 -0
  55. machine_dialect/compiler/tests/test_compiler.py +568 -0
  56. machine_dialect/compiler/vm_runner.py +173 -0
  57. machine_dialect/errors/__init__.py +32 -0
  58. machine_dialect/errors/exceptions.py +369 -0
  59. machine_dialect/errors/messages.py +82 -0
  60. machine_dialect/errors/tests/__init__.py +0 -0
  61. machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
  62. machine_dialect/errors/tests/test_name_errors.py +118 -0
  63. machine_dialect/helpers/__init__.py +0 -0
  64. machine_dialect/helpers/stopwords.py +225 -0
  65. machine_dialect/helpers/validators.py +30 -0
  66. machine_dialect/lexer/__init__.py +9 -0
  67. machine_dialect/lexer/constants.py +23 -0
  68. machine_dialect/lexer/lexer.py +907 -0
  69. machine_dialect/lexer/tests/__init__.py +0 -0
  70. machine_dialect/lexer/tests/helpers.py +86 -0
  71. machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
  72. machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
  73. machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
  74. machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
  75. machine_dialect/lexer/tests/test_comments.py +200 -0
  76. machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
  77. machine_dialect/lexer/tests/test_lexer_position.py +113 -0
  78. machine_dialect/lexer/tests/test_list_tokens.py +282 -0
  79. machine_dialect/lexer/tests/test_stopwords.py +80 -0
  80. machine_dialect/lexer/tests/test_strict_equality.py +129 -0
  81. machine_dialect/lexer/tests/test_token.py +41 -0
  82. machine_dialect/lexer/tests/test_tokenization.py +294 -0
  83. machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
  84. machine_dialect/lexer/tests/test_url_literals.py +169 -0
  85. machine_dialect/lexer/tokens.py +487 -0
  86. machine_dialect/linter/__init__.py +10 -0
  87. machine_dialect/linter/__main__.py +144 -0
  88. machine_dialect/linter/linter.py +154 -0
  89. machine_dialect/linter/rules/__init__.py +8 -0
  90. machine_dialect/linter/rules/base.py +112 -0
  91. machine_dialect/linter/rules/statement_termination.py +99 -0
  92. machine_dialect/linter/tests/__init__.py +1 -0
  93. machine_dialect/linter/tests/mdrules/__init__.py +0 -0
  94. machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
  95. machine_dialect/linter/tests/test_linter.py +81 -0
  96. machine_dialect/linter/tests/test_rules.py +110 -0
  97. machine_dialect/linter/tests/test_violations.py +71 -0
  98. machine_dialect/linter/violations.py +51 -0
  99. machine_dialect/mir/__init__.py +69 -0
  100. machine_dialect/mir/analyses/__init__.py +20 -0
  101. machine_dialect/mir/analyses/alias_analysis.py +315 -0
  102. machine_dialect/mir/analyses/dominance_analysis.py +49 -0
  103. machine_dialect/mir/analyses/escape_analysis.py +286 -0
  104. machine_dialect/mir/analyses/loop_analysis.py +272 -0
  105. machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
  106. machine_dialect/mir/analyses/type_analysis.py +448 -0
  107. machine_dialect/mir/analyses/use_def_chains.py +232 -0
  108. machine_dialect/mir/basic_block.py +385 -0
  109. machine_dialect/mir/dataflow.py +445 -0
  110. machine_dialect/mir/debug_info.py +208 -0
  111. machine_dialect/mir/hir_to_mir.py +1738 -0
  112. machine_dialect/mir/mir_dumper.py +366 -0
  113. machine_dialect/mir/mir_function.py +167 -0
  114. machine_dialect/mir/mir_instructions.py +1877 -0
  115. machine_dialect/mir/mir_interpreter.py +556 -0
  116. machine_dialect/mir/mir_module.py +225 -0
  117. machine_dialect/mir/mir_printer.py +480 -0
  118. machine_dialect/mir/mir_transformer.py +410 -0
  119. machine_dialect/mir/mir_types.py +367 -0
  120. machine_dialect/mir/mir_validation.py +455 -0
  121. machine_dialect/mir/mir_values.py +268 -0
  122. machine_dialect/mir/optimization_config.py +233 -0
  123. machine_dialect/mir/optimization_pass.py +251 -0
  124. machine_dialect/mir/optimization_pipeline.py +355 -0
  125. machine_dialect/mir/optimizations/__init__.py +84 -0
  126. machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
  127. machine_dialect/mir/optimizations/branch_prediction.py +372 -0
  128. machine_dialect/mir/optimizations/constant_propagation.py +634 -0
  129. machine_dialect/mir/optimizations/cse.py +398 -0
  130. machine_dialect/mir/optimizations/dce.py +288 -0
  131. machine_dialect/mir/optimizations/inlining.py +551 -0
  132. machine_dialect/mir/optimizations/jump_threading.py +487 -0
  133. machine_dialect/mir/optimizations/licm.py +405 -0
  134. machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
  135. machine_dialect/mir/optimizations/strength_reduction.py +422 -0
  136. machine_dialect/mir/optimizations/tail_call.py +207 -0
  137. machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
  138. machine_dialect/mir/optimizations/type_narrowing.py +397 -0
  139. machine_dialect/mir/optimizations/type_specialization.py +447 -0
  140. machine_dialect/mir/optimizations/type_specific.py +906 -0
  141. machine_dialect/mir/optimize_mir.py +89 -0
  142. machine_dialect/mir/pass_manager.py +391 -0
  143. machine_dialect/mir/profiling/__init__.py +26 -0
  144. machine_dialect/mir/profiling/profile_collector.py +318 -0
  145. machine_dialect/mir/profiling/profile_data.py +372 -0
  146. machine_dialect/mir/profiling/profile_reader.py +272 -0
  147. machine_dialect/mir/profiling/profile_writer.py +226 -0
  148. machine_dialect/mir/register_allocation.py +302 -0
  149. machine_dialect/mir/reporting/__init__.py +17 -0
  150. machine_dialect/mir/reporting/optimization_reporter.py +314 -0
  151. machine_dialect/mir/reporting/report_formatter.py +289 -0
  152. machine_dialect/mir/ssa_construction.py +342 -0
  153. machine_dialect/mir/tests/__init__.py +1 -0
  154. machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
  155. machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
  156. machine_dialect/mir/tests/test_algebraic_division.py +126 -0
  157. machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
  158. machine_dialect/mir/tests/test_basic_block.py +425 -0
  159. machine_dialect/mir/tests/test_branch_prediction.py +459 -0
  160. machine_dialect/mir/tests/test_call_lowering.py +168 -0
  161. machine_dialect/mir/tests/test_collection_lowering.py +604 -0
  162. machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
  163. machine_dialect/mir/tests/test_custom_passes.py +166 -0
  164. machine_dialect/mir/tests/test_debug_info.py +285 -0
  165. machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
  166. machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
  167. machine_dialect/mir/tests/test_double_negation.py +231 -0
  168. machine_dialect/mir/tests/test_escape_analysis.py +233 -0
  169. machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
  170. machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
  171. machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
  172. machine_dialect/mir/tests/test_inlining.py +435 -0
  173. machine_dialect/mir/tests/test_licm.py +472 -0
  174. machine_dialect/mir/tests/test_mir_dumper.py +313 -0
  175. machine_dialect/mir/tests/test_mir_instructions.py +445 -0
  176. machine_dialect/mir/tests/test_mir_module.py +860 -0
  177. machine_dialect/mir/tests/test_mir_printer.py +387 -0
  178. machine_dialect/mir/tests/test_mir_types.py +123 -0
  179. machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
  180. machine_dialect/mir/tests/test_mir_validation.py +378 -0
  181. machine_dialect/mir/tests/test_mir_values.py +168 -0
  182. machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
  183. machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
  184. machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
  185. machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
  186. machine_dialect/mir/tests/test_pass_manager.py +294 -0
  187. machine_dialect/mir/tests/test_pass_registration.py +64 -0
  188. machine_dialect/mir/tests/test_profiling.py +356 -0
  189. machine_dialect/mir/tests/test_register_allocation.py +307 -0
  190. machine_dialect/mir/tests/test_report_formatters.py +372 -0
  191. machine_dialect/mir/tests/test_ssa_construction.py +433 -0
  192. machine_dialect/mir/tests/test_tail_call.py +236 -0
  193. machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
  194. machine_dialect/mir/tests/test_type_narrowing.py +277 -0
  195. machine_dialect/mir/tests/test_type_specialization.py +421 -0
  196. machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
  197. machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
  198. machine_dialect/mir/type_inference.py +368 -0
  199. machine_dialect/parser/__init__.py +12 -0
  200. machine_dialect/parser/enums.py +45 -0
  201. machine_dialect/parser/parser.py +3655 -0
  202. machine_dialect/parser/protocols.py +11 -0
  203. machine_dialect/parser/symbol_table.py +169 -0
  204. machine_dialect/parser/tests/__init__.py +0 -0
  205. machine_dialect/parser/tests/helper_functions.py +193 -0
  206. machine_dialect/parser/tests/test_action_statements.py +334 -0
  207. machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
  208. machine_dialect/parser/tests/test_call_statements.py +154 -0
  209. machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
  210. machine_dialect/parser/tests/test_collection_mutations.py +264 -0
  211. machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
  212. machine_dialect/parser/tests/test_define_integration.py +468 -0
  213. machine_dialect/parser/tests/test_define_statements.py +311 -0
  214. machine_dialect/parser/tests/test_dict_extraction.py +115 -0
  215. machine_dialect/parser/tests/test_empty_literal.py +155 -0
  216. machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
  217. machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
  218. machine_dialect/parser/tests/test_if_empty_block.py +61 -0
  219. machine_dialect/parser/tests/test_if_statements.py +299 -0
  220. machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
  221. machine_dialect/parser/tests/test_infix_expressions.py +680 -0
  222. machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
  223. machine_dialect/parser/tests/test_interaction_statements.py +269 -0
  224. machine_dialect/parser/tests/test_list_literals.py +277 -0
  225. machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
  226. machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
  227. machine_dialect/parser/tests/test_parse_errors.py +114 -0
  228. machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
  229. machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
  230. machine_dialect/parser/tests/test_program.py +13 -0
  231. machine_dialect/parser/tests/test_return_statements.py +89 -0
  232. machine_dialect/parser/tests/test_set_statements.py +152 -0
  233. machine_dialect/parser/tests/test_strict_equality.py +258 -0
  234. machine_dialect/parser/tests/test_symbol_table.py +217 -0
  235. machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
  236. machine_dialect/parser/tests/test_utility_statements.py +423 -0
  237. machine_dialect/parser/token_buffer.py +159 -0
  238. machine_dialect/repl/__init__.py +3 -0
  239. machine_dialect/repl/repl.py +426 -0
  240. machine_dialect/repl/tests/__init__.py +0 -0
  241. machine_dialect/repl/tests/test_repl.py +606 -0
  242. machine_dialect/semantic/__init__.py +12 -0
  243. machine_dialect/semantic/analyzer.py +906 -0
  244. machine_dialect/semantic/error_messages.py +189 -0
  245. machine_dialect/semantic/tests/__init__.py +1 -0
  246. machine_dialect/semantic/tests/test_analyzer.py +364 -0
  247. machine_dialect/semantic/tests/test_error_messages.py +104 -0
  248. machine_dialect/tests/edge_cases/__init__.py +10 -0
  249. machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
  250. machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
  251. machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
  252. machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
  253. machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
  254. machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
  255. machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
  256. machine_dialect/tests/integration/test_list_compilation.py +395 -0
  257. machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
  258. machine_dialect/type_checking/__init__.py +21 -0
  259. machine_dialect/type_checking/tests/__init__.py +1 -0
  260. machine_dialect/type_checking/tests/test_type_system.py +230 -0
  261. machine_dialect/type_checking/type_system.py +270 -0
  262. machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
  263. machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
  264. machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
  265. machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
  266. machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
  267. machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
  268. 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"