machine-dialect 0.1.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- machine_dialect/__main__.py +667 -0
- machine_dialect/agent/__init__.py +5 -0
- machine_dialect/agent/agent.py +360 -0
- machine_dialect/ast/__init__.py +95 -0
- machine_dialect/ast/ast_node.py +35 -0
- machine_dialect/ast/call_expression.py +82 -0
- machine_dialect/ast/dict_extraction.py +60 -0
- machine_dialect/ast/expressions.py +439 -0
- machine_dialect/ast/literals.py +309 -0
- machine_dialect/ast/program.py +35 -0
- machine_dialect/ast/statements.py +1433 -0
- machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
- machine_dialect/ast/tests/test_boolean_literal.py +29 -0
- machine_dialect/ast/tests/test_collection_hir.py +138 -0
- machine_dialect/ast/tests/test_define_statement.py +142 -0
- machine_dialect/ast/tests/test_desugar.py +541 -0
- machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
- machine_dialect/cfg/__init__.py +6 -0
- machine_dialect/cfg/config.py +156 -0
- machine_dialect/cfg/examples.py +221 -0
- machine_dialect/cfg/generate_with_ai.py +187 -0
- machine_dialect/cfg/openai_generation.py +200 -0
- machine_dialect/cfg/parser.py +94 -0
- machine_dialect/cfg/tests/__init__.py +1 -0
- machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
- machine_dialect/cfg/tests/test_config.py +188 -0
- machine_dialect/cfg/tests/test_examples.py +391 -0
- machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
- machine_dialect/cfg/tests/test_openai_generation.py +256 -0
- machine_dialect/codegen/__init__.py +5 -0
- machine_dialect/codegen/bytecode_module.py +89 -0
- machine_dialect/codegen/bytecode_serializer.py +300 -0
- machine_dialect/codegen/opcodes.py +101 -0
- machine_dialect/codegen/register_codegen.py +1996 -0
- machine_dialect/codegen/symtab.py +208 -0
- machine_dialect/codegen/tests/__init__.py +1 -0
- machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
- machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
- machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
- machine_dialect/codegen/tests/test_symtab.py +418 -0
- machine_dialect/codegen/vm_serializer.py +621 -0
- machine_dialect/compiler/__init__.py +18 -0
- machine_dialect/compiler/compiler.py +197 -0
- machine_dialect/compiler/config.py +149 -0
- machine_dialect/compiler/context.py +149 -0
- machine_dialect/compiler/phases/__init__.py +19 -0
- machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
- machine_dialect/compiler/phases/codegen.py +40 -0
- machine_dialect/compiler/phases/hir_generation.py +39 -0
- machine_dialect/compiler/phases/mir_generation.py +86 -0
- machine_dialect/compiler/phases/optimization.py +110 -0
- machine_dialect/compiler/phases/parsing.py +39 -0
- machine_dialect/compiler/pipeline.py +143 -0
- machine_dialect/compiler/tests/__init__.py +1 -0
- machine_dialect/compiler/tests/test_compiler.py +568 -0
- machine_dialect/compiler/vm_runner.py +173 -0
- machine_dialect/errors/__init__.py +32 -0
- machine_dialect/errors/exceptions.py +369 -0
- machine_dialect/errors/messages.py +82 -0
- machine_dialect/errors/tests/__init__.py +0 -0
- machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
- machine_dialect/errors/tests/test_name_errors.py +118 -0
- machine_dialect/helpers/__init__.py +0 -0
- machine_dialect/helpers/stopwords.py +225 -0
- machine_dialect/helpers/validators.py +30 -0
- machine_dialect/lexer/__init__.py +9 -0
- machine_dialect/lexer/constants.py +23 -0
- machine_dialect/lexer/lexer.py +907 -0
- machine_dialect/lexer/tests/__init__.py +0 -0
- machine_dialect/lexer/tests/helpers.py +86 -0
- machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
- machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
- machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
- machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
- machine_dialect/lexer/tests/test_comments.py +200 -0
- machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
- machine_dialect/lexer/tests/test_lexer_position.py +113 -0
- machine_dialect/lexer/tests/test_list_tokens.py +282 -0
- machine_dialect/lexer/tests/test_stopwords.py +80 -0
- machine_dialect/lexer/tests/test_strict_equality.py +129 -0
- machine_dialect/lexer/tests/test_token.py +41 -0
- machine_dialect/lexer/tests/test_tokenization.py +294 -0
- machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
- machine_dialect/lexer/tests/test_url_literals.py +169 -0
- machine_dialect/lexer/tokens.py +487 -0
- machine_dialect/linter/__init__.py +10 -0
- machine_dialect/linter/__main__.py +144 -0
- machine_dialect/linter/linter.py +154 -0
- machine_dialect/linter/rules/__init__.py +8 -0
- machine_dialect/linter/rules/base.py +112 -0
- machine_dialect/linter/rules/statement_termination.py +99 -0
- machine_dialect/linter/tests/__init__.py +1 -0
- machine_dialect/linter/tests/mdrules/__init__.py +0 -0
- machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
- machine_dialect/linter/tests/test_linter.py +81 -0
- machine_dialect/linter/tests/test_rules.py +110 -0
- machine_dialect/linter/tests/test_violations.py +71 -0
- machine_dialect/linter/violations.py +51 -0
- machine_dialect/mir/__init__.py +69 -0
- machine_dialect/mir/analyses/__init__.py +20 -0
- machine_dialect/mir/analyses/alias_analysis.py +315 -0
- machine_dialect/mir/analyses/dominance_analysis.py +49 -0
- machine_dialect/mir/analyses/escape_analysis.py +286 -0
- machine_dialect/mir/analyses/loop_analysis.py +272 -0
- machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
- machine_dialect/mir/analyses/type_analysis.py +448 -0
- machine_dialect/mir/analyses/use_def_chains.py +232 -0
- machine_dialect/mir/basic_block.py +385 -0
- machine_dialect/mir/dataflow.py +445 -0
- machine_dialect/mir/debug_info.py +208 -0
- machine_dialect/mir/hir_to_mir.py +1738 -0
- machine_dialect/mir/mir_dumper.py +366 -0
- machine_dialect/mir/mir_function.py +167 -0
- machine_dialect/mir/mir_instructions.py +1877 -0
- machine_dialect/mir/mir_interpreter.py +556 -0
- machine_dialect/mir/mir_module.py +225 -0
- machine_dialect/mir/mir_printer.py +480 -0
- machine_dialect/mir/mir_transformer.py +410 -0
- machine_dialect/mir/mir_types.py +367 -0
- machine_dialect/mir/mir_validation.py +455 -0
- machine_dialect/mir/mir_values.py +268 -0
- machine_dialect/mir/optimization_config.py +233 -0
- machine_dialect/mir/optimization_pass.py +251 -0
- machine_dialect/mir/optimization_pipeline.py +355 -0
- machine_dialect/mir/optimizations/__init__.py +84 -0
- machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
- machine_dialect/mir/optimizations/branch_prediction.py +372 -0
- machine_dialect/mir/optimizations/constant_propagation.py +634 -0
- machine_dialect/mir/optimizations/cse.py +398 -0
- machine_dialect/mir/optimizations/dce.py +288 -0
- machine_dialect/mir/optimizations/inlining.py +551 -0
- machine_dialect/mir/optimizations/jump_threading.py +487 -0
- machine_dialect/mir/optimizations/licm.py +405 -0
- machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
- machine_dialect/mir/optimizations/strength_reduction.py +422 -0
- machine_dialect/mir/optimizations/tail_call.py +207 -0
- machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
- machine_dialect/mir/optimizations/type_narrowing.py +397 -0
- machine_dialect/mir/optimizations/type_specialization.py +447 -0
- machine_dialect/mir/optimizations/type_specific.py +906 -0
- machine_dialect/mir/optimize_mir.py +89 -0
- machine_dialect/mir/pass_manager.py +391 -0
- machine_dialect/mir/profiling/__init__.py +26 -0
- machine_dialect/mir/profiling/profile_collector.py +318 -0
- machine_dialect/mir/profiling/profile_data.py +372 -0
- machine_dialect/mir/profiling/profile_reader.py +272 -0
- machine_dialect/mir/profiling/profile_writer.py +226 -0
- machine_dialect/mir/register_allocation.py +302 -0
- machine_dialect/mir/reporting/__init__.py +17 -0
- machine_dialect/mir/reporting/optimization_reporter.py +314 -0
- machine_dialect/mir/reporting/report_formatter.py +289 -0
- machine_dialect/mir/ssa_construction.py +342 -0
- machine_dialect/mir/tests/__init__.py +1 -0
- machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
- machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
- machine_dialect/mir/tests/test_algebraic_division.py +126 -0
- machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
- machine_dialect/mir/tests/test_basic_block.py +425 -0
- machine_dialect/mir/tests/test_branch_prediction.py +459 -0
- machine_dialect/mir/tests/test_call_lowering.py +168 -0
- machine_dialect/mir/tests/test_collection_lowering.py +604 -0
- machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
- machine_dialect/mir/tests/test_custom_passes.py +166 -0
- machine_dialect/mir/tests/test_debug_info.py +285 -0
- machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
- machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
- machine_dialect/mir/tests/test_double_negation.py +231 -0
- machine_dialect/mir/tests/test_escape_analysis.py +233 -0
- machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
- machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
- machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
- machine_dialect/mir/tests/test_inlining.py +435 -0
- machine_dialect/mir/tests/test_licm.py +472 -0
- machine_dialect/mir/tests/test_mir_dumper.py +313 -0
- machine_dialect/mir/tests/test_mir_instructions.py +445 -0
- machine_dialect/mir/tests/test_mir_module.py +860 -0
- machine_dialect/mir/tests/test_mir_printer.py +387 -0
- machine_dialect/mir/tests/test_mir_types.py +123 -0
- machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
- machine_dialect/mir/tests/test_mir_validation.py +378 -0
- machine_dialect/mir/tests/test_mir_values.py +168 -0
- machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
- machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
- machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
- machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
- machine_dialect/mir/tests/test_pass_manager.py +294 -0
- machine_dialect/mir/tests/test_pass_registration.py +64 -0
- machine_dialect/mir/tests/test_profiling.py +356 -0
- machine_dialect/mir/tests/test_register_allocation.py +307 -0
- machine_dialect/mir/tests/test_report_formatters.py +372 -0
- machine_dialect/mir/tests/test_ssa_construction.py +433 -0
- machine_dialect/mir/tests/test_tail_call.py +236 -0
- machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
- machine_dialect/mir/tests/test_type_narrowing.py +277 -0
- machine_dialect/mir/tests/test_type_specialization.py +421 -0
- machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
- machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
- machine_dialect/mir/type_inference.py +368 -0
- machine_dialect/parser/__init__.py +12 -0
- machine_dialect/parser/enums.py +45 -0
- machine_dialect/parser/parser.py +3655 -0
- machine_dialect/parser/protocols.py +11 -0
- machine_dialect/parser/symbol_table.py +169 -0
- machine_dialect/parser/tests/__init__.py +0 -0
- machine_dialect/parser/tests/helper_functions.py +193 -0
- machine_dialect/parser/tests/test_action_statements.py +334 -0
- machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
- machine_dialect/parser/tests/test_call_statements.py +154 -0
- machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
- machine_dialect/parser/tests/test_collection_mutations.py +264 -0
- machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
- machine_dialect/parser/tests/test_define_integration.py +468 -0
- machine_dialect/parser/tests/test_define_statements.py +311 -0
- machine_dialect/parser/tests/test_dict_extraction.py +115 -0
- machine_dialect/parser/tests/test_empty_literal.py +155 -0
- machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
- machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
- machine_dialect/parser/tests/test_if_empty_block.py +61 -0
- machine_dialect/parser/tests/test_if_statements.py +299 -0
- machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
- machine_dialect/parser/tests/test_infix_expressions.py +680 -0
- machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
- machine_dialect/parser/tests/test_interaction_statements.py +269 -0
- machine_dialect/parser/tests/test_list_literals.py +277 -0
- machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
- machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
- machine_dialect/parser/tests/test_parse_errors.py +114 -0
- machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
- machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
- machine_dialect/parser/tests/test_program.py +13 -0
- machine_dialect/parser/tests/test_return_statements.py +89 -0
- machine_dialect/parser/tests/test_set_statements.py +152 -0
- machine_dialect/parser/tests/test_strict_equality.py +258 -0
- machine_dialect/parser/tests/test_symbol_table.py +217 -0
- machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
- machine_dialect/parser/tests/test_utility_statements.py +423 -0
- machine_dialect/parser/token_buffer.py +159 -0
- machine_dialect/repl/__init__.py +3 -0
- machine_dialect/repl/repl.py +426 -0
- machine_dialect/repl/tests/__init__.py +0 -0
- machine_dialect/repl/tests/test_repl.py +606 -0
- machine_dialect/semantic/__init__.py +12 -0
- machine_dialect/semantic/analyzer.py +906 -0
- machine_dialect/semantic/error_messages.py +189 -0
- machine_dialect/semantic/tests/__init__.py +1 -0
- machine_dialect/semantic/tests/test_analyzer.py +364 -0
- machine_dialect/semantic/tests/test_error_messages.py +104 -0
- machine_dialect/tests/edge_cases/__init__.py +10 -0
- machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
- machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
- machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
- machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
- machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
- machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
- machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
- machine_dialect/tests/integration/test_list_compilation.py +395 -0
- machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
- machine_dialect/type_checking/__init__.py +21 -0
- machine_dialect/type_checking/tests/__init__.py +1 -0
- machine_dialect/type_checking/tests/test_type_system.py +230 -0
- machine_dialect/type_checking/type_system.py +270 -0
- machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
- machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
- machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
- machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
- machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
- machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
- machine_dialect_vm/__init__.pyi +15 -0
@@ -0,0 +1,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
|