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,233 @@
1
+ """Tests for escape analysis."""
2
+
3
+ from machine_dialect.mir.analyses.escape_analysis import EscapeAnalysis, EscapeState
4
+ from machine_dialect.mir.mir_function import MIRFunction
5
+ from machine_dialect.mir.mir_instructions import (
6
+ BinaryOp,
7
+ Call,
8
+ Copy,
9
+ LoadConst,
10
+ Return,
11
+ SetAttr,
12
+ )
13
+ from machine_dialect.mir.mir_types import MIRType
14
+ from machine_dialect.mir.mir_values import Constant, FunctionRef, Variable
15
+
16
+
17
+ def create_simple_function() -> MIRFunction:
18
+ """Create a simple function for testing.
19
+
20
+ def simple(x):
21
+ a = 10
22
+ b = a + x
23
+ return b
24
+ """
25
+ func = MIRFunction("simple", [Variable("x", MIRType.INT)])
26
+ func.locals["a"] = Variable("a", MIRType.INT)
27
+ func.locals["b"] = Variable("b", MIRType.INT)
28
+
29
+ entry = func.cfg.get_or_create_block("entry")
30
+ func.cfg.entry_block = entry
31
+
32
+ x_var = Variable("x", MIRType.INT)
33
+ a_var = Variable("a", MIRType.INT)
34
+ b_var = Variable("b", MIRType.INT)
35
+
36
+ entry.instructions = [
37
+ LoadConst(a_var, Constant(10), (1, 1)),
38
+ BinaryOp(b_var, "+", a_var, x_var, (1, 1)),
39
+ Return((1, 1), b_var),
40
+ ]
41
+
42
+ return func
43
+
44
+
45
+ def create_escaping_function() -> MIRFunction:
46
+ """Create a function with escaping variables.
47
+
48
+ def escaping(x):
49
+ a = 10
50
+ b = foo(a) # a escapes as argument
51
+ return b
52
+ """
53
+ func = MIRFunction("escaping", [Variable("x", MIRType.INT)])
54
+ func.locals["a"] = Variable("a", MIRType.INT)
55
+ func.locals["b"] = Variable("b", MIRType.INT)
56
+
57
+ entry = func.cfg.get_or_create_block("entry")
58
+ func.cfg.entry_block = entry
59
+
60
+ a_var = Variable("a", MIRType.INT)
61
+ b_var = Variable("b", MIRType.INT)
62
+
63
+ entry.instructions = [
64
+ LoadConst(a_var, Constant(10), (1, 1)),
65
+ Call(b_var, FunctionRef("foo"), [a_var], (1, 1)),
66
+ Return((1, 1), b_var),
67
+ ]
68
+
69
+ return func
70
+
71
+
72
+ def create_aliasing_function() -> MIRFunction:
73
+ """Create a function with aliasing variables.
74
+
75
+ def aliasing():
76
+ a = 10
77
+ b = a # b aliases a
78
+ c = b # c aliases b and a
79
+ return c
80
+ """
81
+ func = MIRFunction("aliasing")
82
+ func.locals["a"] = Variable("a", MIRType.INT)
83
+ func.locals["b"] = Variable("b", MIRType.INT)
84
+ func.locals["c"] = Variable("c", MIRType.INT)
85
+
86
+ entry = func.cfg.get_or_create_block("entry")
87
+ func.cfg.entry_block = entry
88
+
89
+ a_var = Variable("a", MIRType.INT)
90
+ b_var = Variable("b", MIRType.INT)
91
+ c_var = Variable("c", MIRType.INT)
92
+
93
+ entry.instructions = [
94
+ LoadConst(a_var, Constant(10), (1, 1)),
95
+ Copy(b_var, a_var, (1, 1)), # b = a
96
+ Copy(c_var, b_var, (1, 1)), # c = b
97
+ Return((1, 1), c_var),
98
+ ]
99
+
100
+ return func
101
+
102
+
103
+ def create_heap_escape_function() -> MIRFunction:
104
+ """Create a function where variable escapes to heap.
105
+
106
+ def heap_escape(obj):
107
+ a = 10
108
+ obj.field = a # a escapes to heap
109
+ return obj
110
+ """
111
+ func = MIRFunction("heap_escape", [Variable("obj", MIRType.INT)])
112
+ func.locals["a"] = Variable("a", MIRType.INT)
113
+
114
+ entry = func.cfg.get_or_create_block("entry")
115
+ func.cfg.entry_block = entry
116
+
117
+ obj_var = Variable("obj", MIRType.INT) # Use INT as placeholder
118
+ a_var = Variable("a", MIRType.INT)
119
+
120
+ entry.instructions = [
121
+ LoadConst(a_var, Constant(10), (1, 1)),
122
+ SetAttr(obj_var, "field", a_var),
123
+ Return((1, 1), obj_var),
124
+ ]
125
+
126
+ return func
127
+
128
+
129
+ class TestEscapeAnalysis:
130
+ """Test escape analysis."""
131
+
132
+ def setup_method(self) -> None:
133
+ """Set up test fixtures."""
134
+ self.analysis = EscapeAnalysis()
135
+
136
+ def test_no_escape(self) -> None:
137
+ """Test variables that don't escape."""
138
+ func = create_simple_function()
139
+ escape_info = self.analysis.run_on_function(func)
140
+
141
+ # 'a' should not escape (only used locally)
142
+ a_var = Variable("a", MIRType.INT)
143
+ assert not escape_info.does_escape(a_var)
144
+ assert escape_info.is_stack_eligible(a_var)
145
+
146
+ # 'b' escapes via return
147
+ b_var = Variable("b", MIRType.INT)
148
+ assert escape_info.does_escape(b_var)
149
+ assert not escape_info.is_stack_eligible(b_var)
150
+
151
+ def test_argument_escape(self) -> None:
152
+ """Test variables escaping as function arguments."""
153
+ func = create_escaping_function()
154
+ escape_info = self.analysis.run_on_function(func)
155
+
156
+ # 'a' escapes as argument to foo()
157
+ a_var = Variable("a", MIRType.INT)
158
+ assert escape_info.does_escape(a_var)
159
+ assert not escape_info.is_stack_eligible(a_var)
160
+
161
+ info = escape_info.get_info(a_var)
162
+ assert info is not None
163
+ if info:
164
+ assert info.state == EscapeState.ARG_ESCAPE
165
+
166
+ def test_alias_propagation(self) -> None:
167
+ """Test escape propagation through aliases."""
168
+ func = create_aliasing_function()
169
+ escape_info = self.analysis.run_on_function(func)
170
+
171
+ # 'c' escapes via return
172
+ c_var = Variable("c", MIRType.INT)
173
+ assert escape_info.does_escape(c_var)
174
+
175
+ # 'b' aliases 'c', so it should also escape
176
+ b_var = Variable("b", MIRType.INT)
177
+ assert escape_info.does_escape(b_var)
178
+
179
+ # 'a' aliases 'b' which aliases 'c', so it should also escape
180
+ a_var = Variable("a", MIRType.INT)
181
+ assert escape_info.does_escape(a_var)
182
+
183
+ def test_heap_escape(self) -> None:
184
+ """Test variables escaping to heap."""
185
+ func = create_heap_escape_function()
186
+ escape_info = self.analysis.run_on_function(func)
187
+
188
+ # 'a' escapes to heap via SetAttr
189
+ a_var = Variable("a", MIRType.INT)
190
+ assert escape_info.does_escape(a_var)
191
+ assert not escape_info.is_stack_eligible(a_var)
192
+
193
+ info = escape_info.get_info(a_var)
194
+ assert info is not None
195
+ if info:
196
+ assert info.state == EscapeState.HEAP_ESCAPE
197
+
198
+ def test_parameter_handling(self) -> None:
199
+ """Test handling of function parameters."""
200
+ func = create_simple_function()
201
+ escape_info = self.analysis.run_on_function(func)
202
+
203
+ # Parameters are tracked but not considered escaping just by existing
204
+ x_var = Variable("x", MIRType.INT)
205
+ # 'x' is used in computation but doesn't escape further
206
+ assert not escape_info.does_escape(x_var)
207
+
208
+ def test_escape_sites(self) -> None:
209
+ """Test tracking of escape sites."""
210
+ func = create_escaping_function()
211
+ escape_info = self.analysis.run_on_function(func)
212
+
213
+ a_var = Variable("a", MIRType.INT)
214
+ info = escape_info.get_info(a_var)
215
+ assert info is not None
216
+
217
+ # Should have one escape site (the Call instruction)
218
+ if info:
219
+ assert len(info.escape_sites) == 1
220
+ assert isinstance(info.escape_sites[0], Call)
221
+
222
+ def test_stack_eligible_collection(self) -> None:
223
+ """Test collection of stack-eligible variables."""
224
+ func = create_simple_function()
225
+ escape_info = self.analysis.run_on_function(func)
226
+
227
+ # 'a' should be stack eligible
228
+ a_var = Variable("a", MIRType.INT)
229
+ assert a_var in escape_info.stack_eligible
230
+
231
+ # 'b' should not be stack eligible (escapes via return)
232
+ b_var = Variable("b", MIRType.INT)
233
+ assert b_var in escape_info.escaping_vars
@@ -0,0 +1,465 @@
1
+ """Tests for HIR to MIR lowering."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from machine_dialect.ast import (
6
+ Arguments,
7
+ BlockStatement,
8
+ CallStatement,
9
+ EmptyLiteral,
10
+ Expression,
11
+ FloatLiteral,
12
+ FunctionStatement,
13
+ FunctionVisibility,
14
+ Identifier,
15
+ IfStatement,
16
+ InfixExpression,
17
+ Parameter,
18
+ PrefixExpression,
19
+ Program,
20
+ ReturnStatement,
21
+ SetStatement,
22
+ Statement,
23
+ StringLiteral,
24
+ WholeNumberLiteral,
25
+ YesNoLiteral,
26
+ )
27
+ from machine_dialect.lexer import Token, TokenType
28
+ from machine_dialect.mir.hir_to_mir import lower_to_mir
29
+ from machine_dialect.mir.mir_instructions import (
30
+ BinaryOp,
31
+ Call,
32
+ ConditionalJump,
33
+ LoadConst,
34
+ Return,
35
+ StoreVar,
36
+ UnaryOp,
37
+ )
38
+ from machine_dialect.mir.mir_types import MIRType
39
+
40
+
41
+ class TestHIRToMIRLowering:
42
+ """Test HIR to MIR lowering."""
43
+
44
+ def _dummy_token(self, literal: str = "", token_type: TokenType = TokenType.MISC_IDENT) -> Token:
45
+ """Create a dummy token for testing."""
46
+ return Token(token_type, literal, 0, 0)
47
+
48
+ def _create_infix_expr(self, operator: str, left: Expression, right: Expression) -> InfixExpression:
49
+ """Create an infix expression with proper initialization."""
50
+ token_map = {
51
+ "+": TokenType.OP_PLUS,
52
+ "-": TokenType.OP_MINUS,
53
+ "*": TokenType.OP_STAR,
54
+ "/": TokenType.OP_DIVISION,
55
+ }
56
+ token_type = token_map.get(operator, TokenType.MISC_ILLEGAL)
57
+ expr = InfixExpression(token=self._dummy_token(operator, token_type), operator=operator, left=left)
58
+ expr.right = right
59
+ return expr
60
+
61
+ def test_lower_empty_program(self) -> None:
62
+ """Test lowering an empty program."""
63
+ program = Program(statements=[])
64
+ module = lower_to_mir(program)
65
+
66
+ assert module.name == "__main__"
67
+ assert len(module.functions) == 0
68
+ assert module.main_function is None
69
+
70
+ def test_lower_simple_function(self) -> None:
71
+ """Test lowering a simple function."""
72
+ # Create function: function main() { return 42; }
73
+ body = BlockStatement(token=self._dummy_token())
74
+ body.statements = [
75
+ ReturnStatement(
76
+ token=self._dummy_token("return", TokenType.KW_RETURN),
77
+ return_value=WholeNumberLiteral(token=self._dummy_token("42", TokenType.LIT_WHOLE_NUMBER), value=42),
78
+ )
79
+ ]
80
+ func = FunctionStatement(
81
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
82
+ visibility=FunctionVisibility.FUNCTION,
83
+ name=Identifier(self._dummy_token("__main__"), "__main__"),
84
+ body=body,
85
+ )
86
+ program = Program(statements=[func])
87
+ module = lower_to_mir(program)
88
+
89
+ # Check module has main function
90
+ assert len(module.functions) == 1
91
+ assert "__main__" in module.functions
92
+ assert module.main_function == "__main__"
93
+
94
+ # Check function structure
95
+ main_func = module.get_function("__main__")
96
+ assert main_func is not None
97
+ assert main_func.name == "__main__"
98
+ assert len(main_func.params) == 0
99
+
100
+ # Check CFG
101
+ assert main_func.cfg.entry_block is not None
102
+ entry = main_func.cfg.entry_block
103
+ # With Load-Then-Store approach, we generate LoadConst + Return
104
+ assert len(entry.instructions) == 2
105
+ assert isinstance(entry.instructions[0], LoadConst)
106
+ assert isinstance(entry.instructions[1], Return)
107
+
108
+ def test_lower_function_with_parameters(self) -> None:
109
+ """Test lowering a function with parameters."""
110
+ # Create function: function add(a, b) { return a + b; }
111
+ inputs = [
112
+ Parameter(token=self._dummy_token("a"), name=Identifier(self._dummy_token("a"), "a")),
113
+ Parameter(token=self._dummy_token("b"), name=Identifier(self._dummy_token("b"), "b")),
114
+ ]
115
+ body = BlockStatement(token=self._dummy_token())
116
+ body.statements = [
117
+ ReturnStatement(
118
+ token=self._dummy_token("return", TokenType.KW_RETURN),
119
+ return_value=self._create_infix_expr(
120
+ "+", Identifier(self._dummy_token("a"), "a"), Identifier(self._dummy_token("b"), "b")
121
+ ),
122
+ )
123
+ ]
124
+ func = FunctionStatement(
125
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
126
+ visibility=FunctionVisibility.FUNCTION,
127
+ name=Identifier(self._dummy_token("add"), "add"),
128
+ inputs=inputs,
129
+ body=body,
130
+ )
131
+ program = Program(statements=[func])
132
+ module = lower_to_mir(program)
133
+
134
+ # Check function has parameters
135
+ add_func = module.get_function("add")
136
+ assert add_func is not None
137
+ assert len(add_func.params) == 2
138
+ assert add_func.params[0].name == "a"
139
+ assert add_func.params[1].name == "b"
140
+
141
+ def test_lower_set_statement(self) -> None:
142
+ """Test lowering a set statement."""
143
+ # Create: function main() { set x to 10; }
144
+ body = BlockStatement(token=self._dummy_token())
145
+ body.statements = [
146
+ SetStatement(
147
+ token=self._dummy_token("set", TokenType.KW_SET),
148
+ name=Identifier(self._dummy_token("x"), "x"),
149
+ value=WholeNumberLiteral(token=self._dummy_token("10", TokenType.LIT_WHOLE_NUMBER), value=10),
150
+ )
151
+ ]
152
+ func = FunctionStatement(
153
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
154
+ visibility=FunctionVisibility.FUNCTION,
155
+ name=Identifier(self._dummy_token("__main__"), "__main__"),
156
+ body=body,
157
+ )
158
+ program = Program(statements=[func])
159
+ module = lower_to_mir(program)
160
+
161
+ main_func = module.get_function("__main__")
162
+ assert main_func is not None
163
+ assert main_func.cfg.entry_block is not None
164
+
165
+ # Should have StoreVar instruction
166
+ instructions = main_func.cfg.entry_block.instructions
167
+ assert any(isinstance(inst, StoreVar) for inst in instructions)
168
+
169
+ def test_lower_if_statement(self) -> None:
170
+ """Test lowering an if statement."""
171
+ # Create: if (true) { return 1; } else { return 2; }
172
+ consequence_block = BlockStatement(token=self._dummy_token())
173
+ consequence_block.statements = [
174
+ ReturnStatement(
175
+ token=self._dummy_token("return", TokenType.KW_RETURN),
176
+ return_value=WholeNumberLiteral(token=self._dummy_token("1", TokenType.LIT_WHOLE_NUMBER), value=1),
177
+ )
178
+ ]
179
+
180
+ alternative_block = BlockStatement(token=self._dummy_token())
181
+ alternative_block.statements = [
182
+ ReturnStatement(
183
+ token=self._dummy_token("return", TokenType.KW_RETURN),
184
+ return_value=WholeNumberLiteral(token=self._dummy_token("2", TokenType.LIT_WHOLE_NUMBER), value=2),
185
+ )
186
+ ]
187
+
188
+ if_stmt = IfStatement(
189
+ token=self._dummy_token("if", TokenType.KW_IF),
190
+ condition=YesNoLiteral(token=self._dummy_token("true", TokenType.LIT_YES), value=True),
191
+ )
192
+ if_stmt.consequence = consequence_block
193
+ if_stmt.alternative = alternative_block
194
+ body = BlockStatement(token=self._dummy_token())
195
+ body.statements = [if_stmt]
196
+ func = FunctionStatement(
197
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
198
+ visibility=FunctionVisibility.FUNCTION,
199
+ name=Identifier(self._dummy_token("test"), "test"),
200
+ body=body,
201
+ )
202
+ program = Program(statements=[func])
203
+ module = lower_to_mir(program)
204
+
205
+ test_func = module.get_function("test")
206
+ assert test_func is not None
207
+
208
+ # Should have multiple blocks
209
+ assert len(test_func.cfg.blocks) > 1
210
+
211
+ # Should have conditional jump in entry block
212
+ assert test_func.cfg.entry_block is not None
213
+ entry = test_func.cfg.entry_block
214
+ assert any(isinstance(inst, ConditionalJump) for inst in entry.instructions)
215
+
216
+ def test_lower_call_statement(self) -> None:
217
+ """Test lowering a call statement."""
218
+ # Create: call print with "hello";
219
+ args = Arguments(token=self._dummy_token())
220
+ args.positional = [StringLiteral(token=self._dummy_token('"hello"', TokenType.LIT_TEXT), value='"hello"')]
221
+ call_stmt = CallStatement(
222
+ token=self._dummy_token("use", TokenType.KW_USE),
223
+ function_name=StringLiteral(token=self._dummy_token('"print"', TokenType.LIT_TEXT), value='"print"'),
224
+ arguments=args,
225
+ )
226
+ body = BlockStatement(token=self._dummy_token())
227
+ body.statements = [call_stmt]
228
+ func = FunctionStatement(
229
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
230
+ visibility=FunctionVisibility.FUNCTION,
231
+ name=Identifier(self._dummy_token("__main__"), "__main__"),
232
+ body=body,
233
+ )
234
+ program = Program(statements=[func])
235
+ module = lower_to_mir(program)
236
+
237
+ main_func = module.get_function("__main__")
238
+ assert main_func is not None
239
+ assert main_func.cfg.entry_block is not None
240
+
241
+ # Should have Call instruction
242
+ instructions = main_func.cfg.entry_block.instructions
243
+ assert any(isinstance(inst, Call) for inst in instructions)
244
+
245
+ def test_lower_infix_expression(self) -> None:
246
+ """Test lowering infix expressions."""
247
+ # Create: return 2 + 3 * 4;
248
+ expr = self._create_infix_expr(
249
+ "+",
250
+ WholeNumberLiteral(token=self._dummy_token("2", TokenType.LIT_WHOLE_NUMBER), value=2),
251
+ self._create_infix_expr(
252
+ "*",
253
+ WholeNumberLiteral(token=self._dummy_token("3", TokenType.LIT_WHOLE_NUMBER), value=3),
254
+ WholeNumberLiteral(token=self._dummy_token("4", TokenType.LIT_WHOLE_NUMBER), value=4),
255
+ ),
256
+ )
257
+ body = BlockStatement(token=self._dummy_token())
258
+ body.statements = [ReturnStatement(token=self._dummy_token("return", TokenType.KW_RETURN), return_value=expr)]
259
+ func = FunctionStatement(
260
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
261
+ visibility=FunctionVisibility.FUNCTION,
262
+ name=Identifier(self._dummy_token("calc"), "calc"),
263
+ body=body,
264
+ )
265
+ program = Program(statements=[func])
266
+ module = lower_to_mir(program)
267
+
268
+ calc_func = module.get_function("calc")
269
+ assert calc_func is not None
270
+ assert calc_func.cfg.entry_block is not None
271
+
272
+ # Should have BinaryOp instructions
273
+ instructions = calc_func.cfg.entry_block.instructions
274
+ binary_ops = [inst for inst in instructions if isinstance(inst, BinaryOp)]
275
+ assert len(binary_ops) == 2 # One for *, one for +
276
+
277
+ def test_lower_prefix_expression(self) -> None:
278
+ """Test lowering prefix expressions."""
279
+ # Create: return -42;
280
+ expr = PrefixExpression(token=self._dummy_token("-", TokenType.OP_MINUS), operator="-")
281
+ expr.right = WholeNumberLiteral(token=self._dummy_token("42", TokenType.LIT_WHOLE_NUMBER), value=42)
282
+ body = BlockStatement(token=self._dummy_token())
283
+ body.statements = [ReturnStatement(token=self._dummy_token("return", TokenType.KW_RETURN), return_value=expr)]
284
+ func = FunctionStatement(
285
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
286
+ visibility=FunctionVisibility.FUNCTION,
287
+ name=Identifier(self._dummy_token("neg"), "neg"),
288
+ body=body,
289
+ )
290
+ program = Program(statements=[func])
291
+ module = lower_to_mir(program)
292
+
293
+ neg_func = module.get_function("neg")
294
+ assert neg_func is not None
295
+ assert neg_func.cfg.entry_block is not None
296
+
297
+ # Should have UnaryOp instruction
298
+ instructions = neg_func.cfg.entry_block.instructions
299
+ assert any(isinstance(inst, UnaryOp) for inst in instructions)
300
+
301
+ # def test_lower_call_expression(self) -> None:
302
+ # """Test lowering call expressions."""
303
+ # # Note: CallExpression doesn't exist in the current AST
304
+ # # This test would need to be implemented differently
305
+ # pass
306
+
307
+ def test_lower_literals(self) -> None:
308
+ """Test lowering various literal types."""
309
+ # Create function with various literals
310
+ stmts: list[Statement] = [
311
+ SetStatement(
312
+ token=self._dummy_token("set", TokenType.KW_SET),
313
+ name=Identifier(self._dummy_token("i"), "i"),
314
+ value=WholeNumberLiteral(token=self._dummy_token("42", TokenType.LIT_WHOLE_NUMBER), value=42),
315
+ ),
316
+ SetStatement(
317
+ token=self._dummy_token("set", TokenType.KW_SET),
318
+ name=Identifier(self._dummy_token("f"), "f"),
319
+ value=FloatLiteral(token=self._dummy_token("3.14", TokenType.LIT_FLOAT), value=3.14),
320
+ ),
321
+ SetStatement(
322
+ token=self._dummy_token("set", TokenType.KW_SET),
323
+ name=Identifier(self._dummy_token("s"), "s"),
324
+ value=StringLiteral(token=self._dummy_token('"hello"', TokenType.LIT_TEXT), value='"hello"'),
325
+ ),
326
+ SetStatement(
327
+ token=self._dummy_token("set", TokenType.KW_SET),
328
+ name=Identifier(self._dummy_token("b"), "b"),
329
+ value=YesNoLiteral(token=self._dummy_token("true", TokenType.LIT_YES), value=True),
330
+ ),
331
+ SetStatement(
332
+ token=self._dummy_token("set", TokenType.KW_SET),
333
+ name=Identifier(self._dummy_token("e"), "e"),
334
+ value=EmptyLiteral(token=self._dummy_token("empty", TokenType.KW_EMPTY)),
335
+ ),
336
+ ]
337
+ body = BlockStatement(token=self._dummy_token())
338
+ body.statements = stmts
339
+ func = FunctionStatement(
340
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
341
+ visibility=FunctionVisibility.FUNCTION,
342
+ name=Identifier(self._dummy_token("literals"), "literals"),
343
+ body=body,
344
+ )
345
+ program = Program(statements=[func])
346
+ module = lower_to_mir(program)
347
+
348
+ lit_func = module.get_function("literals")
349
+ assert lit_func is not None
350
+
351
+ # Check that we have 5 local variables
352
+ assert len(lit_func.locals) == 5
353
+ assert "i" in lit_func.locals
354
+ assert "f" in lit_func.locals
355
+ assert "s" in lit_func.locals
356
+ assert "b" in lit_func.locals
357
+ assert "e" in lit_func.locals
358
+
359
+ def test_lower_action_and_interaction(self) -> None:
360
+ """Test lowering action and interaction functions."""
361
+ # Create action (private method)
362
+ action = FunctionStatement(
363
+ token=self._dummy_token("action", TokenType.KW_ACTION),
364
+ visibility=FunctionVisibility.PRIVATE,
365
+ name=Identifier(self._dummy_token("helper"), "helper"),
366
+ body=BlockStatement(token=self._dummy_token()),
367
+ )
368
+
369
+ # Create interaction (public method)
370
+ body = BlockStatement(token=self._dummy_token())
371
+ interaction = FunctionStatement(
372
+ token=self._dummy_token("interaction", TokenType.KW_INTERACTION),
373
+ visibility=FunctionVisibility.PUBLIC,
374
+ name=Identifier(self._dummy_token("process"), "process"),
375
+ inputs=[Parameter(token=self._dummy_token("input"), name=Identifier(self._dummy_token("input"), "input"))],
376
+ body=body,
377
+ )
378
+
379
+ program = Program(statements=[action, interaction])
380
+ module = lower_to_mir(program)
381
+
382
+ # Both should be in module
383
+ assert len(module.functions) == 2
384
+ assert "helper" in module.functions
385
+ assert "process" in module.functions
386
+
387
+ # Check return types (both should be void/empty)
388
+ helper = module.get_function("helper")
389
+ process = module.get_function("process")
390
+ assert helper is not None
391
+ assert process is not None
392
+ assert helper.return_type == MIRType.EMPTY
393
+ assert process.return_type == MIRType.EMPTY
394
+
395
+ def test_implicit_return(self) -> None:
396
+ """Test that implicit return is added when needed."""
397
+ # Function without explicit return
398
+ body = BlockStatement(token=self._dummy_token())
399
+ body.statements = [
400
+ SetStatement(
401
+ token=self._dummy_token("set", TokenType.KW_SET),
402
+ name=Identifier(self._dummy_token("x"), "x"),
403
+ value=WholeNumberLiteral(token=self._dummy_token("10", TokenType.LIT_WHOLE_NUMBER), value=10),
404
+ )
405
+ ]
406
+ func = FunctionStatement(
407
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
408
+ visibility=FunctionVisibility.FUNCTION,
409
+ name=Identifier(self._dummy_token("no_return"), "no_return"),
410
+ body=body,
411
+ )
412
+ program = Program(statements=[func])
413
+ module = lower_to_mir(program)
414
+
415
+ no_return = module.get_function("no_return")
416
+ assert no_return is not None
417
+ assert no_return.cfg.entry_block is not None
418
+
419
+ # Should have added implicit return
420
+ instructions = no_return.cfg.entry_block.instructions
421
+ assert any(isinstance(inst, Return) for inst in instructions)
422
+
423
+ def test_nested_if_statements(self) -> None:
424
+ """Test lowering nested if statements."""
425
+ # Create: if (a) { if (b) { return 1; } }
426
+ inner_consequence = BlockStatement(token=self._dummy_token())
427
+ inner_consequence.statements = [
428
+ ReturnStatement(
429
+ token=self._dummy_token("return", TokenType.KW_RETURN),
430
+ return_value=WholeNumberLiteral(token=self._dummy_token("1", TokenType.LIT_WHOLE_NUMBER), value=1),
431
+ )
432
+ ]
433
+
434
+ inner_if = IfStatement(
435
+ token=self._dummy_token("if", TokenType.KW_IF), condition=Identifier(self._dummy_token("b"), "b")
436
+ )
437
+ inner_if.consequence = inner_consequence
438
+
439
+ outer_consequence = BlockStatement(token=self._dummy_token())
440
+ outer_consequence.statements = [inner_if]
441
+
442
+ outer_if = IfStatement(
443
+ token=self._dummy_token("if", TokenType.KW_IF), condition=Identifier(self._dummy_token("a"), "a")
444
+ )
445
+ outer_if.consequence = outer_consequence
446
+ body = BlockStatement(token=self._dummy_token())
447
+ body.statements = [outer_if]
448
+ func = FunctionStatement(
449
+ token=self._dummy_token("utility", TokenType.KW_UTILITY),
450
+ visibility=FunctionVisibility.FUNCTION,
451
+ name=Identifier(self._dummy_token("nested"), "nested"),
452
+ inputs=[
453
+ Parameter(token=self._dummy_token("a"), name=Identifier(self._dummy_token("a"), "a")),
454
+ Parameter(token=self._dummy_token("b"), name=Identifier(self._dummy_token("b"), "b")),
455
+ ],
456
+ body=body,
457
+ )
458
+ program = Program(statements=[func])
459
+ module = lower_to_mir(program)
460
+
461
+ nested = module.get_function("nested")
462
+ assert nested is not None
463
+
464
+ # Should have multiple blocks for nested control flow
465
+ assert len(nested.cfg.blocks) > 3