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,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"
|