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,604 @@
1
+ """Tests for collection operation lowering from HIR to MIR."""
2
+
3
+ from machine_dialect.ast import (
4
+ CollectionAccessExpression,
5
+ CollectionMutationStatement,
6
+ DefineStatement,
7
+ Expression,
8
+ Identifier,
9
+ OrderedListLiteral,
10
+ Program,
11
+ SetStatement,
12
+ StringLiteral,
13
+ UnorderedListLiteral,
14
+ WholeNumberLiteral,
15
+ )
16
+ from machine_dialect.lexer import Token, TokenType
17
+ from machine_dialect.mir.hir_to_mir import lower_to_mir
18
+ from machine_dialect.mir.mir_instructions import (
19
+ ArrayAppend,
20
+ ArrayClear,
21
+ ArrayCreate,
22
+ ArrayFindIndex,
23
+ ArrayGet,
24
+ ArrayInsert,
25
+ ArrayLength,
26
+ ArrayRemove,
27
+ ArraySet,
28
+ BinaryOp,
29
+ LoadConst,
30
+ )
31
+
32
+
33
+ class TestCollectionMutationLowering:
34
+ """Test lowering of CollectionMutationStatement to MIR."""
35
+
36
+ def test_add_to_list(self) -> None:
37
+ """Test Add operation generates ArrayAppend."""
38
+ # Create AST for: Add _"cherry"_ to `fruits`
39
+ token = Token(TokenType.KW_ADD, "Add", 1, 1)
40
+ collection = Identifier(token, "fruits")
41
+ value = StringLiteral(token, "cherry")
42
+
43
+ stmt = CollectionMutationStatement(
44
+ token=token,
45
+ operation="add",
46
+ collection=collection,
47
+ value=value,
48
+ )
49
+
50
+ # Create a program with variable definition
51
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
52
+ define_stmt = DefineStatement(
53
+ token=define_token,
54
+ name=Identifier(token, "fruits"),
55
+ type_spec=["unordered", "list"],
56
+ )
57
+
58
+ program = Program([define_stmt, stmt])
59
+
60
+ # Lower to MIR
61
+ mir_module = lower_to_mir(program)
62
+
63
+ # Check that ArrayAppend was generated
64
+ main_func = mir_module.get_function("__main__")
65
+ assert main_func is not None
66
+
67
+ # Find ArrayAppend instruction
68
+ found_append = False
69
+ for block in main_func.cfg.blocks.values():
70
+ for inst in block.instructions:
71
+ if isinstance(inst, ArrayAppend):
72
+ found_append = True
73
+ break
74
+
75
+ assert found_append, "ArrayAppend instruction not found"
76
+
77
+ def test_set_list_item_numeric(self) -> None:
78
+ """Test Set operation with numeric index generates ArraySet."""
79
+ # Create AST for: Set item _2_ of `numbers` to _99_
80
+ token = Token(TokenType.KW_SET, "Set", 1, 1)
81
+ collection = Identifier(token, "numbers")
82
+ value = WholeNumberLiteral(token, 99)
83
+
84
+ # Position is already 0-based after HIR transformation
85
+ stmt = CollectionMutationStatement(
86
+ token=token,
87
+ operation="set",
88
+ collection=collection,
89
+ value=value,
90
+ position=1, # Index 1 (second item, 0-based)
91
+ position_type="numeric",
92
+ )
93
+
94
+ # Create a program with variable definition
95
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
96
+ define_stmt = DefineStatement(
97
+ token=define_token,
98
+ name=Identifier(token, "numbers"),
99
+ type_spec=["ordered", "list"],
100
+ )
101
+
102
+ program = Program([define_stmt, stmt])
103
+
104
+ # Lower to MIR
105
+ mir_module = lower_to_mir(program)
106
+
107
+ # Check that ArraySet was generated
108
+ main_func = mir_module.get_function("__main__")
109
+ assert main_func is not None
110
+
111
+ # Find ArraySet instruction
112
+ found_set = False
113
+ for block in main_func.cfg.blocks.values():
114
+ for inst in block.instructions:
115
+ if isinstance(inst, ArraySet):
116
+ found_set = True
117
+ break
118
+
119
+ assert found_set, "ArraySet instruction not found"
120
+
121
+ def test_set_last_item(self) -> None:
122
+ """Test Set operation with 'last' position."""
123
+ # Create AST for: Set the last item of `numbers` to _999_
124
+ token = Token(TokenType.KW_SET, "Set", 1, 1)
125
+ collection = Identifier(token, "numbers")
126
+ value = WholeNumberLiteral(token, 999)
127
+
128
+ stmt = CollectionMutationStatement(
129
+ token=token,
130
+ operation="set",
131
+ collection=collection,
132
+ value=value,
133
+ position="last",
134
+ position_type="ordinal",
135
+ )
136
+
137
+ # Create a program with variable definition
138
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
139
+ define_stmt = DefineStatement(
140
+ token=define_token,
141
+ name=Identifier(token, "numbers"),
142
+ type_spec=["ordered", "list"],
143
+ )
144
+
145
+ program = Program([define_stmt, stmt])
146
+
147
+ # Lower to MIR
148
+ mir_module = lower_to_mir(program)
149
+
150
+ # Check that ArrayLength and BinaryOp (subtract) were generated
151
+ main_func = mir_module.get_function("__main__")
152
+ assert main_func is not None
153
+
154
+ # Find ArrayLength and BinaryOp instructions
155
+ found_length = False
156
+ found_subtract = False
157
+ found_set = False
158
+
159
+ for block in main_func.cfg.blocks.values():
160
+ for inst in block.instructions:
161
+ if isinstance(inst, ArrayLength):
162
+ found_length = True
163
+ elif isinstance(inst, BinaryOp) and inst.op == "-":
164
+ found_subtract = True
165
+ elif isinstance(inst, ArraySet):
166
+ found_set = True
167
+
168
+ assert found_length, "ArrayLength instruction not found"
169
+ assert found_subtract, "BinaryOp subtract instruction not found"
170
+ assert found_set, "ArraySet instruction not found"
171
+
172
+ def test_remove_from_list(self) -> None:
173
+ """Test Remove operation generates ArrayRemove."""
174
+ # Create AST for: Remove the second item from `numbers`
175
+ token = Token(TokenType.KW_REMOVE, "Remove", 1, 1)
176
+ collection = Identifier(token, "numbers")
177
+
178
+ stmt = CollectionMutationStatement(
179
+ token=token,
180
+ operation="remove",
181
+ collection=collection,
182
+ position=1, # Index 1 (second item, 0-based)
183
+ position_type="numeric",
184
+ )
185
+
186
+ # Create a program with variable definition
187
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
188
+ define_stmt = DefineStatement(
189
+ token=define_token,
190
+ name=Identifier(token, "numbers"),
191
+ type_spec=["ordered", "list"],
192
+ )
193
+
194
+ program = Program([define_stmt, stmt])
195
+
196
+ # Lower to MIR
197
+ mir_module = lower_to_mir(program)
198
+
199
+ # Check that ArrayRemove was generated
200
+ main_func = mir_module.get_function("__main__")
201
+ assert main_func is not None
202
+
203
+ # Find ArrayRemove instruction
204
+ found_remove = False
205
+ for block in main_func.cfg.blocks.values():
206
+ for inst in block.instructions:
207
+ if isinstance(inst, ArrayRemove):
208
+ found_remove = True
209
+ break
210
+
211
+ assert found_remove, "ArrayRemove instruction not found"
212
+
213
+ def test_remove_by_value_from_list(self) -> None:
214
+ """Test Remove by value generates ArrayFindIndex and ArrayRemove."""
215
+ # Create AST for: Remove _"banana"_ from `fruits`
216
+ token = Token(TokenType.KW_REMOVE, "Remove", 1, 1)
217
+ collection = Identifier(token, "fruits")
218
+ value = StringLiteral(token, "banana")
219
+
220
+ stmt = CollectionMutationStatement(
221
+ token=token,
222
+ operation="remove",
223
+ collection=collection,
224
+ value=value,
225
+ )
226
+
227
+ # Create a program with variable definition
228
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
229
+ define_stmt = DefineStatement(
230
+ token=define_token,
231
+ name=Identifier(token, "fruits"),
232
+ type_spec=["unordered", "list"],
233
+ )
234
+
235
+ program = Program([define_stmt, stmt])
236
+
237
+ # Lower to MIR
238
+ mir_module = lower_to_mir(program)
239
+
240
+ # Check that ArrayFindIndex and ArrayRemove were generated
241
+ main_func = mir_module.get_function("__main__")
242
+ assert main_func is not None
243
+
244
+ # Find ArrayFindIndex and ArrayRemove instructions
245
+ found_find = False
246
+ found_remove = False
247
+ for block in main_func.cfg.blocks.values():
248
+ for inst in block.instructions:
249
+ if isinstance(inst, ArrayFindIndex):
250
+ found_find = True
251
+ elif isinstance(inst, ArrayRemove):
252
+ found_remove = True
253
+
254
+ assert found_find, "ArrayFindIndex instruction not found"
255
+ assert found_remove, "ArrayRemove instruction not found"
256
+
257
+ def test_insert_into_list(self) -> None:
258
+ """Test Insert operation generates ArrayInsert."""
259
+ # Create AST for: Insert _50_ at position 2 in `numbers`
260
+ token = Token(TokenType.KW_INSERT, "Insert", 1, 1)
261
+ collection = Identifier(token, "numbers")
262
+ value = WholeNumberLiteral(token, 50)
263
+
264
+ stmt = CollectionMutationStatement(
265
+ token=token,
266
+ operation="insert",
267
+ collection=collection,
268
+ value=value,
269
+ position=2, # Index 2 (third position, 0-based)
270
+ position_type="numeric",
271
+ )
272
+
273
+ # Create a program with variable definition
274
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
275
+ define_stmt = DefineStatement(
276
+ token=define_token,
277
+ name=Identifier(token, "numbers"),
278
+ type_spec=["ordered", "list"],
279
+ )
280
+
281
+ program = Program([define_stmt, stmt])
282
+
283
+ # Lower to MIR
284
+ mir_module = lower_to_mir(program)
285
+
286
+ # Check that ArrayInsert was generated
287
+ main_func = mir_module.get_function("__main__")
288
+ assert main_func is not None
289
+
290
+ # Find ArrayInsert instruction
291
+ found_insert = False
292
+ for block in main_func.cfg.blocks.values():
293
+ for inst in block.instructions:
294
+ if isinstance(inst, ArrayInsert):
295
+ found_insert = True
296
+ break
297
+
298
+ assert found_insert, "ArrayInsert instruction not found"
299
+
300
+ def test_empty_list_operation(self) -> None:
301
+ """Test Clear operation generates ArrayClear."""
302
+ # Create AST for: Clear `numbers`
303
+ token = Token(TokenType.KW_CLEAR, "Clear", 1, 1)
304
+ collection = Identifier(token, "numbers")
305
+
306
+ stmt = CollectionMutationStatement(
307
+ token=token,
308
+ operation="clear",
309
+ collection=collection,
310
+ )
311
+
312
+ # Create a program with variable definition
313
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
314
+ define_stmt = DefineStatement(
315
+ token=define_token,
316
+ name=Identifier(token, "numbers"),
317
+ type_spec=["ordered", "list"],
318
+ )
319
+
320
+ program = Program([define_stmt, stmt])
321
+
322
+ # Lower to MIR
323
+ mir_module = lower_to_mir(program)
324
+
325
+ # Check that ArrayClear was generated
326
+ main_func = mir_module.get_function("__main__")
327
+ assert main_func is not None
328
+
329
+ # Find ArrayClear instruction
330
+ found_clear = False
331
+ for block in main_func.cfg.blocks.values():
332
+ for inst in block.instructions:
333
+ if isinstance(inst, ArrayClear):
334
+ found_clear = True
335
+ break
336
+
337
+ assert found_clear, "ArrayClear instruction not found"
338
+
339
+
340
+ class TestCollectionAccessLowering:
341
+ """Test lowering of CollectionAccessExpression to MIR."""
342
+
343
+ def test_numeric_access(self) -> None:
344
+ """Test numeric array access generates ArrayGet."""
345
+ # Create AST for: item _1_ of `numbers` (0-based after HIR)
346
+ token = Token(TokenType.KW_ITEM, "item", 1, 1)
347
+ collection = Identifier(token, "numbers")
348
+
349
+ expr = CollectionAccessExpression(
350
+ token=token,
351
+ collection=collection,
352
+ accessor=0, # Already 0-based after HIR
353
+ access_type="numeric",
354
+ )
355
+
356
+ # Create a SetStatement using this expression
357
+ set_token = Token(TokenType.KW_SET, "Set", 1, 1)
358
+ stmt = SetStatement(
359
+ token=set_token,
360
+ name=Identifier(token, "value"),
361
+ value=expr,
362
+ )
363
+
364
+ # Create a program with variable definitions
365
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
366
+ define_numbers = DefineStatement(
367
+ token=define_token,
368
+ name=Identifier(token, "numbers"),
369
+ type_spec=["ordered", "list"],
370
+ )
371
+ define_value = DefineStatement(
372
+ token=define_token,
373
+ name=Identifier(token, "value"),
374
+ type_spec=["whole", "number"],
375
+ )
376
+
377
+ program = Program([define_numbers, define_value, stmt])
378
+
379
+ # Lower to MIR
380
+ mir_module = lower_to_mir(program)
381
+
382
+ # Check that ArrayGet was generated
383
+ main_func = mir_module.get_function("__main__")
384
+ assert main_func is not None
385
+
386
+ # Find ArrayGet instruction
387
+ found_get = False
388
+ for block in main_func.cfg.blocks.values():
389
+ for inst in block.instructions:
390
+ if isinstance(inst, ArrayGet):
391
+ found_get = True
392
+ break
393
+
394
+ assert found_get, "ArrayGet instruction not found"
395
+
396
+ def test_last_item_access(self) -> None:
397
+ """Test 'last' item access generates proper MIR."""
398
+ # Create AST for: the last item of `numbers`
399
+ token = Token(TokenType.KW_LAST, "last", 1, 1)
400
+ collection = Identifier(token, "numbers")
401
+
402
+ expr = CollectionAccessExpression(
403
+ token=token,
404
+ collection=collection,
405
+ accessor="last",
406
+ access_type="ordinal",
407
+ )
408
+
409
+ # Create a SetStatement using this expression
410
+ set_token = Token(TokenType.KW_SET, "Set", 1, 1)
411
+ stmt = SetStatement(
412
+ token=set_token,
413
+ name=Identifier(token, "value"),
414
+ value=expr,
415
+ )
416
+
417
+ # Create a program with variable definitions
418
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
419
+ define_numbers = DefineStatement(
420
+ token=define_token,
421
+ name=Identifier(token, "numbers"),
422
+ type_spec=["ordered", "list"],
423
+ )
424
+ define_value = DefineStatement(
425
+ token=define_token,
426
+ name=Identifier(token, "value"),
427
+ type_spec=["whole", "number"],
428
+ )
429
+
430
+ program = Program([define_numbers, define_value, stmt])
431
+
432
+ # Lower to MIR
433
+ mir_module = lower_to_mir(program)
434
+
435
+ # Check that ArrayLength, BinaryOp (subtract), and ArrayGet were generated
436
+ main_func = mir_module.get_function("__main__")
437
+ assert main_func is not None
438
+
439
+ found_length = False
440
+ found_subtract = False
441
+ found_get = False
442
+
443
+ for block in main_func.cfg.blocks.values():
444
+ for inst in block.instructions:
445
+ if isinstance(inst, ArrayLength):
446
+ found_length = True
447
+ elif isinstance(inst, BinaryOp) and inst.op == "-":
448
+ found_subtract = True
449
+ elif isinstance(inst, ArrayGet):
450
+ found_get = True
451
+
452
+ assert found_length, "ArrayLength instruction not found"
453
+ assert found_subtract, "BinaryOp subtract instruction not found"
454
+ assert found_get, "ArrayGet instruction not found"
455
+
456
+
457
+ class TestListLiteralLowering:
458
+ """Test lowering of list literals to MIR."""
459
+
460
+ def test_ordered_list_literal(self) -> None:
461
+ """Test ordered list literal generates ArrayCreate and ArraySet."""
462
+ # Create AST for ordered list with elements
463
+ token = Token(TokenType.LIT_WHOLE_NUMBER, "1", 1, 1)
464
+ elements: list[Expression] = [
465
+ WholeNumberLiteral(token, 10),
466
+ WholeNumberLiteral(token, 20),
467
+ WholeNumberLiteral(token, 30),
468
+ ]
469
+
470
+ list_literal = OrderedListLiteral(token, elements)
471
+
472
+ # Create a SetStatement with this list
473
+ set_token = Token(TokenType.KW_SET, "Set", 1, 1)
474
+ stmt = SetStatement(
475
+ token=set_token,
476
+ name=Identifier(token, "numbers"),
477
+ value=list_literal,
478
+ )
479
+
480
+ # Create a program with variable definition
481
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
482
+ define_stmt = DefineStatement(
483
+ token=define_token,
484
+ name=Identifier(token, "numbers"),
485
+ type_spec=["ordered", "list"],
486
+ )
487
+
488
+ program = Program([define_stmt, stmt])
489
+
490
+ # Lower to MIR
491
+ mir_module = lower_to_mir(program)
492
+
493
+ # Check that ArrayCreate and ArraySet were generated
494
+ main_func = mir_module.get_function("__main__")
495
+ assert main_func is not None
496
+
497
+ found_create = False
498
+ set_count = 0
499
+
500
+ for block in main_func.cfg.blocks.values():
501
+ for inst in block.instructions:
502
+ if isinstance(inst, ArrayCreate):
503
+ found_create = True
504
+ elif isinstance(inst, ArraySet):
505
+ set_count += 1
506
+
507
+ assert found_create, "ArrayCreate instruction not found"
508
+ assert set_count == 3, f"Expected 3 ArraySet instructions, found {set_count}"
509
+
510
+ def test_unordered_list_literal(self) -> None:
511
+ """Test unordered list literal generates ArrayCreate and ArraySet."""
512
+ # Create AST for unordered list with elements
513
+ token = Token(TokenType.PUNCT_DASH, "-", 1, 1)
514
+ elements: list[Expression] = [
515
+ StringLiteral(token, "apple"),
516
+ StringLiteral(token, "banana"),
517
+ StringLiteral(token, "cherry"),
518
+ ]
519
+
520
+ list_literal = UnorderedListLiteral(token, elements)
521
+
522
+ # Create a SetStatement with this list
523
+ set_token = Token(TokenType.KW_SET, "Set", 1, 1)
524
+ stmt = SetStatement(
525
+ token=set_token,
526
+ name=Identifier(token, "fruits"),
527
+ value=list_literal,
528
+ )
529
+
530
+ # Create a program with variable definition
531
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
532
+ define_stmt = DefineStatement(
533
+ token=define_token,
534
+ name=Identifier(token, "fruits"),
535
+ type_spec=["unordered", "list"],
536
+ )
537
+
538
+ program = Program([define_stmt, stmt])
539
+
540
+ # Lower to MIR
541
+ mir_module = lower_to_mir(program)
542
+
543
+ # Check that ArrayCreate and ArraySet were generated
544
+ main_func = mir_module.get_function("__main__")
545
+ assert main_func is not None
546
+
547
+ found_create = False
548
+ set_count = 0
549
+
550
+ for block in main_func.cfg.blocks.values():
551
+ for inst in block.instructions:
552
+ if isinstance(inst, ArrayCreate):
553
+ found_create = True
554
+ elif isinstance(inst, ArraySet):
555
+ set_count += 1
556
+
557
+ assert found_create, "ArrayCreate instruction not found"
558
+ assert set_count == 3, f"Expected 3 ArraySet instructions, found {set_count}"
559
+
560
+ def test_empty_list(self) -> None:
561
+ """Test empty list generates ArrayCreate with size 0."""
562
+ # Create AST for empty ordered list
563
+ token = Token(TokenType.LIT_WHOLE_NUMBER, "1", 1, 1)
564
+ list_literal = OrderedListLiteral(token, [])
565
+
566
+ # Create a SetStatement with this list
567
+ set_token = Token(TokenType.KW_SET, "Set", 1, 1)
568
+ stmt = SetStatement(
569
+ token=set_token,
570
+ name=Identifier(token, "empty"),
571
+ value=list_literal,
572
+ )
573
+
574
+ # Create a program with variable definition
575
+ define_token = Token(TokenType.KW_DEFINE, "Define", 1, 1)
576
+ define_stmt = DefineStatement(
577
+ token=define_token,
578
+ name=Identifier(token, "empty"),
579
+ type_spec=["ordered", "list"],
580
+ )
581
+
582
+ program = Program([define_stmt, stmt])
583
+
584
+ # Lower to MIR
585
+ mir_module = lower_to_mir(program)
586
+
587
+ # Check that ArrayCreate was generated with size 0
588
+ main_func = mir_module.get_function("__main__")
589
+ assert main_func is not None
590
+
591
+ found_create = False
592
+ found_zero_const = False
593
+
594
+ for block in main_func.cfg.blocks.values():
595
+ for inst in block.instructions:
596
+ if isinstance(inst, ArrayCreate):
597
+ found_create = True
598
+ elif isinstance(inst, LoadConst):
599
+ # Check if we're loading a constant 0
600
+ if hasattr(inst.constant, "value") and inst.constant.value == 0:
601
+ found_zero_const = True
602
+
603
+ assert found_create, "ArrayCreate instruction not found"
604
+ assert found_zero_const, "LoadConst with value 0 not found"