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,606 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""Comprehensive unit tests for the Machine Dialectâ„¢ REPL."""
|
3
|
+
|
4
|
+
from unittest.mock import Mock, patch
|
5
|
+
|
6
|
+
import pytest
|
7
|
+
|
8
|
+
from machine_dialect.repl.repl import REPL
|
9
|
+
|
10
|
+
|
11
|
+
class TestREPLInitialization:
|
12
|
+
"""Test REPL initialization and configuration."""
|
13
|
+
|
14
|
+
def test_init_default_settings(self) -> None:
|
15
|
+
"""Test REPL initialization with default settings."""
|
16
|
+
repl = REPL()
|
17
|
+
|
18
|
+
assert repl.prompt == "md> "
|
19
|
+
assert repl.running is True
|
20
|
+
assert repl.debug_tokens is False
|
21
|
+
assert repl.show_ast is False
|
22
|
+
assert repl.accumulated_source == ""
|
23
|
+
assert repl.multiline_buffer == ""
|
24
|
+
assert repl.in_multiline_mode is False
|
25
|
+
assert repl.hir_phase is not None
|
26
|
+
assert repl.vm_runner is not None # Should try to initialize VM
|
27
|
+
|
28
|
+
def test_init_debug_tokens_mode(self) -> None:
|
29
|
+
"""Test REPL initialization with debug tokens mode."""
|
30
|
+
repl = REPL(debug_tokens=True)
|
31
|
+
|
32
|
+
assert repl.debug_tokens is True
|
33
|
+
assert repl.show_ast is False
|
34
|
+
assert repl.vm_runner is None # Should not initialize VM in debug mode
|
35
|
+
|
36
|
+
def test_init_ast_mode(self) -> None:
|
37
|
+
"""Test REPL initialization with AST display mode."""
|
38
|
+
repl = REPL(show_ast=True)
|
39
|
+
|
40
|
+
assert repl.debug_tokens is False
|
41
|
+
assert repl.show_ast is True
|
42
|
+
assert repl.vm_runner is None # Should not initialize VM in AST mode
|
43
|
+
|
44
|
+
@patch("machine_dialect.compiler.vm_runner.VMRunner")
|
45
|
+
def test_init_vm_runner_import_error(self, mock_vm_runner: Mock) -> None:
|
46
|
+
"""Test REPL gracefully handles VM import errors."""
|
47
|
+
mock_vm_runner.side_effect = ImportError("VM not available")
|
48
|
+
|
49
|
+
with patch("builtins.print") as mock_print:
|
50
|
+
repl = REPL()
|
51
|
+
|
52
|
+
assert repl.show_ast is True # Should fall back to AST mode
|
53
|
+
assert repl.vm_runner is None
|
54
|
+
mock_print.assert_any_call("Warning: Rust VM not available: VM not available")
|
55
|
+
mock_print.assert_any_call("Falling back to AST display mode.")
|
56
|
+
|
57
|
+
@patch("machine_dialect.compiler.vm_runner.VMRunner")
|
58
|
+
def test_init_vm_runner_runtime_error(self, mock_vm_runner: Mock) -> None:
|
59
|
+
"""Test REPL handles VM runtime errors during initialization."""
|
60
|
+
mock_vm_runner.side_effect = RuntimeError("VM initialization failed")
|
61
|
+
|
62
|
+
with patch("builtins.print") as mock_print:
|
63
|
+
repl = REPL()
|
64
|
+
|
65
|
+
assert repl.show_ast is True
|
66
|
+
assert repl.vm_runner is None
|
67
|
+
mock_print.assert_any_call("Warning: Rust VM not available: VM initialization failed")
|
68
|
+
|
69
|
+
|
70
|
+
class TestREPLDisplay:
|
71
|
+
"""Test REPL display methods."""
|
72
|
+
|
73
|
+
@patch("builtins.print")
|
74
|
+
def test_print_welcome_vm_mode(self, mock_print: Mock) -> None:
|
75
|
+
"""Test welcome message in VM execution mode."""
|
76
|
+
with patch("machine_dialect.compiler.vm_runner.VMRunner"):
|
77
|
+
repl = REPL()
|
78
|
+
repl.print_welcome()
|
79
|
+
|
80
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
81
|
+
assert "Machine Dialectâ„¢ REPL v0.1.0" in calls
|
82
|
+
assert "Mode: Rust VM Execution Mode" in calls
|
83
|
+
assert "Type 'exit' to exit, 'help' for help" in calls
|
84
|
+
|
85
|
+
@patch("builtins.print")
|
86
|
+
def test_print_welcome_debug_tokens_mode(self, mock_print: Mock) -> None:
|
87
|
+
"""Test welcome message in token debug mode."""
|
88
|
+
repl = REPL(debug_tokens=True)
|
89
|
+
repl.print_welcome()
|
90
|
+
|
91
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
92
|
+
assert "Mode: Token Debug Mode" in calls
|
93
|
+
|
94
|
+
@patch("builtins.print")
|
95
|
+
def test_print_welcome_ast_mode(self, mock_print: Mock) -> None:
|
96
|
+
"""Test welcome message in AST display mode."""
|
97
|
+
repl = REPL(show_ast=True)
|
98
|
+
repl.print_welcome()
|
99
|
+
|
100
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
101
|
+
assert "Mode: HIR/AST Display Mode" in calls
|
102
|
+
|
103
|
+
@patch("builtins.print")
|
104
|
+
def test_print_help_vm_mode(self, mock_print: Mock) -> None:
|
105
|
+
"""Test help message in VM mode."""
|
106
|
+
with patch("machine_dialect.compiler.vm_runner.VMRunner"):
|
107
|
+
repl = REPL()
|
108
|
+
repl.print_help()
|
109
|
+
|
110
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
111
|
+
assert any("Enter Machine Dialectâ„¢ code to execute it on the Rust VM." in call for call in calls)
|
112
|
+
assert any("reset - Clear accumulated source" in call for call in calls)
|
113
|
+
|
114
|
+
@patch("builtins.print")
|
115
|
+
def test_print_help_debug_tokens_mode(self, mock_print: Mock) -> None:
|
116
|
+
"""Test help message in debug tokens mode."""
|
117
|
+
repl = REPL(debug_tokens=True)
|
118
|
+
repl.print_help()
|
119
|
+
|
120
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
121
|
+
assert any("Enter any text to see its tokens." in call for call in calls)
|
122
|
+
# Should not show reset command in token debug mode
|
123
|
+
assert not any("reset" in call for call in calls)
|
124
|
+
|
125
|
+
@patch("os.system")
|
126
|
+
def test_clear_screen_unix(self, mock_system: Mock) -> None:
|
127
|
+
"""Test screen clearing on Unix systems."""
|
128
|
+
with patch("os.name", "posix"):
|
129
|
+
repl = REPL()
|
130
|
+
repl.clear_screen()
|
131
|
+
mock_system.assert_called_once_with("clear")
|
132
|
+
|
133
|
+
@patch("os.system")
|
134
|
+
def test_clear_screen_windows(self, mock_system: Mock) -> None:
|
135
|
+
"""Test screen clearing on Windows systems."""
|
136
|
+
with patch("os.name", "nt"):
|
137
|
+
repl = REPL()
|
138
|
+
repl.clear_screen()
|
139
|
+
mock_system.assert_called_once_with("cls")
|
140
|
+
|
141
|
+
|
142
|
+
class TestREPLTokenFormatting:
|
143
|
+
"""Test token formatting functionality."""
|
144
|
+
|
145
|
+
def test_format_token(self) -> None:
|
146
|
+
"""Test token formatting for display."""
|
147
|
+
from machine_dialect.lexer.tokens import Token, TokenType
|
148
|
+
|
149
|
+
repl = REPL()
|
150
|
+
token = Token(TokenType.KW_SET, "Set", line=1, position=1)
|
151
|
+
|
152
|
+
formatted = repl.format_token(token)
|
153
|
+
expected = " KW_SET | 'Set'"
|
154
|
+
|
155
|
+
assert formatted == expected
|
156
|
+
|
157
|
+
def test_format_token_with_special_characters(self) -> None:
|
158
|
+
"""Test token formatting with special characters."""
|
159
|
+
from machine_dialect.lexer.tokens import Token, TokenType
|
160
|
+
|
161
|
+
repl = REPL()
|
162
|
+
token = Token(TokenType.LIT_TEXT, '"hello\nworld"', line=1, position=1)
|
163
|
+
|
164
|
+
formatted = repl.format_token(token)
|
165
|
+
# Should properly escape the newline in repr
|
166
|
+
assert "LIT_TEXT" in formatted
|
167
|
+
assert "'\"hello\\nworld\"'" in formatted
|
168
|
+
|
169
|
+
|
170
|
+
class TestREPLMultilineHandling:
|
171
|
+
"""Test multi-line input handling."""
|
172
|
+
|
173
|
+
def test_should_continue_multiline_with_colon(self) -> None:
|
174
|
+
"""Test multi-line continuation with colon ending."""
|
175
|
+
repl = REPL()
|
176
|
+
|
177
|
+
assert repl.should_continue_multiline("If _5_ > _3_ then:")
|
178
|
+
assert repl.should_continue_multiline(" something:")
|
179
|
+
assert not repl.should_continue_multiline("Set x to _10_.")
|
180
|
+
|
181
|
+
def test_should_continue_multiline_with_greater_than(self) -> None:
|
182
|
+
"""Test multi-line continuation with > marker."""
|
183
|
+
repl = REPL()
|
184
|
+
|
185
|
+
assert repl.should_continue_multiline("> _42_.")
|
186
|
+
assert repl.should_continue_multiline(" > something else.")
|
187
|
+
assert not repl.should_continue_multiline("Regular line.")
|
188
|
+
|
189
|
+
def test_get_multiline_prompt_basic(self) -> None:
|
190
|
+
"""Test multi-line prompt generation."""
|
191
|
+
repl = REPL()
|
192
|
+
|
193
|
+
# No depth should return basic prompt
|
194
|
+
repl.multiline_buffer = ""
|
195
|
+
assert repl.get_multiline_prompt() == "... "
|
196
|
+
|
197
|
+
def test_get_multiline_prompt_with_depth(self) -> None:
|
198
|
+
"""Test multi-line prompt with nested blocks."""
|
199
|
+
repl = REPL()
|
200
|
+
|
201
|
+
# Set up buffer with > markers
|
202
|
+
repl.multiline_buffer = "> _42_."
|
203
|
+
prompt = repl.get_multiline_prompt()
|
204
|
+
assert prompt == "... " # Current implementation returns "... " regardless of depth
|
205
|
+
|
206
|
+
|
207
|
+
class TestREPLTokenization:
|
208
|
+
"""Test tokenization functionality."""
|
209
|
+
|
210
|
+
@patch("builtins.print")
|
211
|
+
def test_tokenize_and_print_simple_input(self, mock_print: Mock) -> None:
|
212
|
+
"""Test tokenizing and printing simple input."""
|
213
|
+
repl = REPL(debug_tokens=True)
|
214
|
+
|
215
|
+
repl.tokenize_and_print("Set `x` to _10_.")
|
216
|
+
|
217
|
+
# Check that tokens were printed
|
218
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
219
|
+
token_output = [call for call in calls if "KW_SET" in call or "Tokens" in call]
|
220
|
+
assert len(token_output) > 0
|
221
|
+
|
222
|
+
@patch("builtins.print")
|
223
|
+
def test_tokenize_and_print_error_handling(self, mock_print: Mock) -> None:
|
224
|
+
"""Test tokenization error handling."""
|
225
|
+
repl = REPL(debug_tokens=True)
|
226
|
+
|
227
|
+
# Mock lexer to raise an exception
|
228
|
+
with patch("machine_dialect.repl.repl.Lexer") as mock_lexer_class:
|
229
|
+
mock_lexer = Mock()
|
230
|
+
mock_lexer.next_token.side_effect = Exception("Lexer error")
|
231
|
+
mock_lexer_class.return_value = mock_lexer
|
232
|
+
|
233
|
+
repl.tokenize_and_print("invalid input")
|
234
|
+
|
235
|
+
# Should print error message
|
236
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
237
|
+
error_calls = [call for call in calls if "Error:" in call]
|
238
|
+
assert len(error_calls) > 0
|
239
|
+
|
240
|
+
|
241
|
+
class TestREPLParsing:
|
242
|
+
"""Test parsing functionality."""
|
243
|
+
|
244
|
+
def test_parse_and_print_accumulates_source(self) -> None:
|
245
|
+
"""Test that parsing accumulates successful source."""
|
246
|
+
repl = REPL(show_ast=True)
|
247
|
+
|
248
|
+
with patch("machine_dialect.repl.repl.Parser") as mock_parser_class, patch("builtins.print"):
|
249
|
+
# Mock successful parsing
|
250
|
+
mock_parser = Mock()
|
251
|
+
mock_parser.parse.return_value = Mock()
|
252
|
+
mock_parser.has_errors.return_value = False
|
253
|
+
mock_parser_class.return_value = mock_parser
|
254
|
+
|
255
|
+
# Mock HIR phase
|
256
|
+
with patch.object(repl.hir_phase, "run") as mock_hir_run:
|
257
|
+
mock_program = Mock()
|
258
|
+
mock_program.statements = []
|
259
|
+
mock_hir_run.return_value = mock_program
|
260
|
+
|
261
|
+
repl.parse_and_print("Set `x` to _10_.")
|
262
|
+
|
263
|
+
# Source should be accumulated
|
264
|
+
assert repl.accumulated_source == "Set `x` to _10_."
|
265
|
+
|
266
|
+
@patch("builtins.print")
|
267
|
+
def test_parse_and_print_handles_parser_errors(self, mock_print: Mock) -> None:
|
268
|
+
"""Test that parsing handles parser errors correctly."""
|
269
|
+
repl = REPL(show_ast=True)
|
270
|
+
|
271
|
+
with patch("machine_dialect.repl.repl.Parser") as mock_parser_class:
|
272
|
+
# Mock parser with errors
|
273
|
+
mock_parser = Mock()
|
274
|
+
mock_parser.parse.return_value = Mock()
|
275
|
+
mock_parser.has_errors.return_value = True
|
276
|
+
mock_parser.errors = ["Parse error: unexpected token"]
|
277
|
+
mock_parser_class.return_value = mock_parser
|
278
|
+
|
279
|
+
original_source = repl.accumulated_source
|
280
|
+
repl.parse_and_print("invalid syntax")
|
281
|
+
|
282
|
+
# Source should not be updated on error
|
283
|
+
assert repl.accumulated_source == original_source
|
284
|
+
|
285
|
+
# Should print error
|
286
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
287
|
+
error_calls = [call for call in calls if "Errors found:" in call]
|
288
|
+
assert len(error_calls) > 0
|
289
|
+
|
290
|
+
@patch("builtins.print")
|
291
|
+
def test_parse_and_print_vm_execution(self, mock_print: Mock) -> None:
|
292
|
+
"""Test parsing with VM execution."""
|
293
|
+
with patch("machine_dialect.compiler.vm_runner.VMRunner") as mock_vm_runner_class:
|
294
|
+
mock_vm_runner = Mock()
|
295
|
+
mock_vm_runner.execute.return_value = "42"
|
296
|
+
mock_vm_runner_class.return_value = mock_vm_runner
|
297
|
+
|
298
|
+
repl = REPL() # VM mode
|
299
|
+
|
300
|
+
with patch("machine_dialect.repl.repl.Parser") as mock_parser_class:
|
301
|
+
# Mock successful parsing
|
302
|
+
mock_parser = Mock()
|
303
|
+
mock_parser.parse.return_value = Mock()
|
304
|
+
mock_parser.has_errors.return_value = False
|
305
|
+
mock_parser_class.return_value = mock_parser
|
306
|
+
|
307
|
+
# Mock HIR phase
|
308
|
+
with patch.object(repl.hir_phase, "run"):
|
309
|
+
repl.parse_and_print("Set `x` to _10_.")
|
310
|
+
|
311
|
+
# Should execute code
|
312
|
+
mock_vm_runner.execute.assert_called_once()
|
313
|
+
|
314
|
+
# Should print result
|
315
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
316
|
+
result_calls = [call for call in calls if "Execution Result:" in call or "42" in call]
|
317
|
+
assert len(result_calls) > 0
|
318
|
+
|
319
|
+
@patch("builtins.print")
|
320
|
+
def test_parse_and_print_vm_execution_error(self, mock_print: Mock) -> None:
|
321
|
+
"""Test parsing handles VM execution errors."""
|
322
|
+
with patch("machine_dialect.compiler.vm_runner.VMRunner") as mock_vm_runner_class:
|
323
|
+
mock_vm_runner = Mock()
|
324
|
+
mock_vm_runner.execute.side_effect = Exception("Execution failed")
|
325
|
+
mock_vm_runner_class.return_value = mock_vm_runner
|
326
|
+
|
327
|
+
repl = REPL() # VM mode
|
328
|
+
|
329
|
+
with patch("machine_dialect.repl.repl.Parser") as mock_parser_class:
|
330
|
+
# Mock successful parsing
|
331
|
+
mock_parser = Mock()
|
332
|
+
mock_parser.parse.return_value = Mock()
|
333
|
+
mock_parser.has_errors.return_value = False
|
334
|
+
mock_parser_class.return_value = mock_parser
|
335
|
+
|
336
|
+
# Mock HIR phase
|
337
|
+
with patch.object(repl.hir_phase, "run"):
|
338
|
+
repl.parse_and_print("Set `x` to _10_.")
|
339
|
+
|
340
|
+
# Should print execution error
|
341
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
342
|
+
error_calls = [call for call in calls if "Execution Error:" in call]
|
343
|
+
assert len(error_calls) > 0
|
344
|
+
|
345
|
+
|
346
|
+
class TestREPLMainLoop:
|
347
|
+
"""Test the main REPL loop functionality."""
|
348
|
+
|
349
|
+
@patch("builtins.input")
|
350
|
+
@patch("builtins.print")
|
351
|
+
def test_run_exit_command(self, mock_print: Mock, mock_input: Mock) -> None:
|
352
|
+
"""Test REPL exits on 'exit' command."""
|
353
|
+
mock_input.return_value = "exit"
|
354
|
+
|
355
|
+
repl = REPL(debug_tokens=True) # Use debug mode to avoid VM setup
|
356
|
+
exit_code = repl.run()
|
357
|
+
|
358
|
+
assert exit_code == 0
|
359
|
+
assert repl.running is False
|
360
|
+
|
361
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
362
|
+
assert "Goodbye!" in calls
|
363
|
+
|
364
|
+
@patch("builtins.input")
|
365
|
+
@patch("builtins.print")
|
366
|
+
def test_run_help_command(self, mock_print: Mock, mock_input: Mock) -> None:
|
367
|
+
"""Test REPL shows help on 'help' command."""
|
368
|
+
mock_input.side_effect = ["help", "exit"]
|
369
|
+
|
370
|
+
repl = REPL(debug_tokens=True)
|
371
|
+
exit_code = repl.run()
|
372
|
+
|
373
|
+
assert exit_code == 0
|
374
|
+
|
375
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
376
|
+
help_calls = [call for call in calls if "Available commands:" in call]
|
377
|
+
assert len(help_calls) > 0
|
378
|
+
|
379
|
+
@patch("builtins.input")
|
380
|
+
@patch("builtins.print")
|
381
|
+
@patch("os.system")
|
382
|
+
def test_run_clear_command(self, mock_system: Mock, mock_print: Mock, mock_input: Mock) -> None:
|
383
|
+
"""Test REPL clears screen on 'clear' command."""
|
384
|
+
mock_input.side_effect = ["clear", "exit"]
|
385
|
+
|
386
|
+
repl = REPL(debug_tokens=True)
|
387
|
+
exit_code = repl.run()
|
388
|
+
|
389
|
+
assert exit_code == 0
|
390
|
+
mock_system.assert_called() # Screen should be cleared
|
391
|
+
|
392
|
+
@patch("builtins.input")
|
393
|
+
@patch("builtins.print")
|
394
|
+
def test_run_reset_command(self, mock_print: Mock, mock_input: Mock) -> None:
|
395
|
+
"""Test REPL resets accumulated source on 'reset' command."""
|
396
|
+
mock_input.side_effect = ["reset", "exit"]
|
397
|
+
|
398
|
+
repl = REPL(show_ast=True) # Non-debug mode to enable reset
|
399
|
+
repl.accumulated_source = "some code"
|
400
|
+
|
401
|
+
exit_code = repl.run()
|
402
|
+
|
403
|
+
assert exit_code == 0
|
404
|
+
assert repl.accumulated_source == ""
|
405
|
+
|
406
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
407
|
+
reset_calls = [call for call in calls if "Accumulated source cleared." in call]
|
408
|
+
assert len(reset_calls) > 0
|
409
|
+
|
410
|
+
@patch("builtins.input")
|
411
|
+
@patch("builtins.print")
|
412
|
+
def test_run_keyboard_interrupt_normal_mode(self, mock_print: Mock, mock_input: Mock) -> None:
|
413
|
+
"""Test REPL handles KeyboardInterrupt in normal mode."""
|
414
|
+
mock_input.side_effect = KeyboardInterrupt()
|
415
|
+
|
416
|
+
repl = REPL(debug_tokens=True)
|
417
|
+
exit_code = repl.run()
|
418
|
+
|
419
|
+
assert exit_code == 0
|
420
|
+
assert repl.running is False
|
421
|
+
|
422
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
423
|
+
goodbye_calls = [call for call in calls if "Goodbye!" in call]
|
424
|
+
assert len(goodbye_calls) > 0
|
425
|
+
|
426
|
+
@patch("builtins.input")
|
427
|
+
@patch("builtins.print")
|
428
|
+
def test_run_keyboard_interrupt_multiline_mode(self, mock_print: Mock, mock_input: Mock) -> None:
|
429
|
+
"""Test REPL handles KeyboardInterrupt in multiline mode."""
|
430
|
+
repl = REPL(debug_tokens=True)
|
431
|
+
repl.in_multiline_mode = True
|
432
|
+
repl.multiline_buffer = "some input"
|
433
|
+
|
434
|
+
mock_input.side_effect = KeyboardInterrupt()
|
435
|
+
|
436
|
+
exit_code = repl.run()
|
437
|
+
|
438
|
+
assert exit_code == 0
|
439
|
+
assert repl.in_multiline_mode is False
|
440
|
+
assert repl.multiline_buffer == ""
|
441
|
+
|
442
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
443
|
+
cancel_calls = [call for call in calls if "Multiline input cancelled." in call]
|
444
|
+
assert len(cancel_calls) > 0
|
445
|
+
|
446
|
+
@patch("builtins.input")
|
447
|
+
@patch("builtins.print")
|
448
|
+
def test_run_eof_error(self, mock_print: Mock, mock_input: Mock) -> None:
|
449
|
+
"""Test REPL handles EOFError (Ctrl+D)."""
|
450
|
+
mock_input.side_effect = EOFError()
|
451
|
+
|
452
|
+
repl = REPL(debug_tokens=True)
|
453
|
+
exit_code = repl.run()
|
454
|
+
|
455
|
+
assert exit_code == 0
|
456
|
+
assert repl.running is False
|
457
|
+
|
458
|
+
@patch("builtins.input")
|
459
|
+
@patch("builtins.print")
|
460
|
+
def test_run_unexpected_error(self, mock_print: Mock, mock_input: Mock) -> None:
|
461
|
+
"""Test REPL handles unexpected errors."""
|
462
|
+
mock_input.side_effect = RuntimeError("Unexpected error")
|
463
|
+
|
464
|
+
repl = REPL(debug_tokens=True)
|
465
|
+
exit_code = repl.run()
|
466
|
+
|
467
|
+
assert exit_code == 1 # Error exit code
|
468
|
+
|
469
|
+
calls = [str(call.args[0]) if call.args else "" for call in mock_print.call_args_list]
|
470
|
+
error_calls = [call for call in calls if "Unexpected error:" in call]
|
471
|
+
assert len(error_calls) > 0
|
472
|
+
|
473
|
+
@patch("builtins.input")
|
474
|
+
def test_run_multiline_input_collection(self, mock_input: Mock) -> None:
|
475
|
+
"""Test REPL collects multi-line input correctly."""
|
476
|
+
mock_input.side_effect = [
|
477
|
+
"If _5_ > _3_ then:", # Start multiline
|
478
|
+
"> _42_.", # Continue multiline
|
479
|
+
"", # End multiline (empty line)
|
480
|
+
"exit", # Exit
|
481
|
+
]
|
482
|
+
|
483
|
+
repl = REPL(show_ast=True)
|
484
|
+
|
485
|
+
with patch.object(repl, "parse_and_print") as mock_parse:
|
486
|
+
exit_code = repl.run()
|
487
|
+
|
488
|
+
assert exit_code == 0
|
489
|
+
|
490
|
+
# Should have called parse_and_print with combined input
|
491
|
+
mock_parse.assert_called()
|
492
|
+
call_args = mock_parse.call_args[0][0]
|
493
|
+
assert "If _5_ > _3_ then:" in call_args
|
494
|
+
assert "> _42_." in call_args
|
495
|
+
|
496
|
+
@patch("builtins.input")
|
497
|
+
def test_run_auto_period_addition(self, mock_input: Mock) -> None:
|
498
|
+
"""Test REPL automatically adds periods to statements."""
|
499
|
+
mock_input.side_effect = [
|
500
|
+
"Set `x` to _10_", # Missing period
|
501
|
+
"exit",
|
502
|
+
]
|
503
|
+
|
504
|
+
repl = REPL(show_ast=True)
|
505
|
+
|
506
|
+
with patch.object(repl, "parse_and_print") as mock_parse:
|
507
|
+
exit_code = repl.run()
|
508
|
+
|
509
|
+
assert exit_code == 0
|
510
|
+
|
511
|
+
# Should have added period
|
512
|
+
mock_parse.assert_called_with("Set `x` to _10_.")
|
513
|
+
|
514
|
+
@patch("builtins.input")
|
515
|
+
def test_run_no_auto_period_in_debug_mode(self, mock_input: Mock) -> None:
|
516
|
+
"""Test REPL doesn't add periods in debug token mode."""
|
517
|
+
mock_input.side_effect = ["Set x to 10", "exit"]
|
518
|
+
|
519
|
+
repl = REPL(debug_tokens=True)
|
520
|
+
|
521
|
+
with patch.object(repl, "tokenize_and_print") as mock_tokenize:
|
522
|
+
exit_code = repl.run()
|
523
|
+
|
524
|
+
assert exit_code == 0
|
525
|
+
|
526
|
+
# Should not have added period
|
527
|
+
mock_tokenize.assert_called_with("Set x to 10")
|
528
|
+
|
529
|
+
|
530
|
+
class TestREPLMainFunction:
|
531
|
+
"""Test the main function and argument parsing."""
|
532
|
+
|
533
|
+
@patch("sys.argv", ["repl.py"])
|
534
|
+
@patch("sys.exit")
|
535
|
+
def test_main_default_arguments(self, mock_exit: Mock) -> None:
|
536
|
+
"""Test main function with default arguments."""
|
537
|
+
with patch("machine_dialect.repl.repl.REPL") as mock_repl_class:
|
538
|
+
mock_repl = Mock()
|
539
|
+
mock_repl.run.return_value = 0
|
540
|
+
mock_repl_class.return_value = mock_repl
|
541
|
+
|
542
|
+
from machine_dialect.repl.repl import main
|
543
|
+
|
544
|
+
main()
|
545
|
+
|
546
|
+
mock_repl_class.assert_called_once_with(debug_tokens=False, show_ast=False)
|
547
|
+
mock_repl.run.assert_called_once()
|
548
|
+
mock_exit.assert_called_once_with(0)
|
549
|
+
|
550
|
+
@patch("sys.argv", ["repl.py", "--debug-tokens"])
|
551
|
+
@patch("sys.exit")
|
552
|
+
def test_main_debug_tokens_flag(self, mock_exit: Mock) -> None:
|
553
|
+
"""Test main function with debug tokens flag."""
|
554
|
+
with patch("machine_dialect.repl.repl.REPL") as mock_repl_class:
|
555
|
+
mock_repl = Mock()
|
556
|
+
mock_repl.run.return_value = 0
|
557
|
+
mock_repl_class.return_value = mock_repl
|
558
|
+
|
559
|
+
from machine_dialect.repl.repl import main
|
560
|
+
|
561
|
+
main()
|
562
|
+
|
563
|
+
mock_repl_class.assert_called_once_with(debug_tokens=True, show_ast=False)
|
564
|
+
|
565
|
+
@patch("sys.argv", ["repl.py", "--ast"])
|
566
|
+
@patch("sys.exit")
|
567
|
+
def test_main_ast_flag(self, mock_exit: Mock) -> None:
|
568
|
+
"""Test main function with AST flag."""
|
569
|
+
with patch("machine_dialect.repl.repl.REPL") as mock_repl_class:
|
570
|
+
mock_repl = Mock()
|
571
|
+
mock_repl.run.return_value = 0
|
572
|
+
mock_repl_class.return_value = mock_repl
|
573
|
+
|
574
|
+
from machine_dialect.repl.repl import main
|
575
|
+
|
576
|
+
main()
|
577
|
+
|
578
|
+
mock_repl_class.assert_called_once_with(debug_tokens=False, show_ast=True)
|
579
|
+
|
580
|
+
@patch("sys.argv", ["repl.py", "--debug-tokens", "--ast"])
|
581
|
+
@patch("sys.exit", side_effect=SystemExit(1))
|
582
|
+
@patch("builtins.print")
|
583
|
+
def test_main_incompatible_flags(self, mock_print: Mock, mock_exit: Mock) -> None:
|
584
|
+
"""Test main function handles incompatible flags."""
|
585
|
+
from machine_dialect.repl.repl import main
|
586
|
+
|
587
|
+
with pytest.raises(SystemExit):
|
588
|
+
main()
|
589
|
+
|
590
|
+
mock_print.assert_called_with("Error: --debug-tokens and --ast flags are not compatible")
|
591
|
+
mock_exit.assert_called_with(1)
|
592
|
+
|
593
|
+
@patch("sys.argv", ["repl.py"])
|
594
|
+
@patch("sys.exit")
|
595
|
+
def test_main_repl_error_exit_code(self, mock_exit: Mock) -> None:
|
596
|
+
"""Test main function propagates REPL exit codes."""
|
597
|
+
with patch("machine_dialect.repl.repl.REPL") as mock_repl_class:
|
598
|
+
mock_repl = Mock()
|
599
|
+
mock_repl.run.return_value = 1 # Error exit code
|
600
|
+
mock_repl_class.return_value = mock_repl
|
601
|
+
|
602
|
+
from machine_dialect.repl.repl import main
|
603
|
+
|
604
|
+
main()
|
605
|
+
|
606
|
+
mock_exit.assert_called_once_with(1)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"""Semantic analysis for Machine Dialectâ„¢.
|
2
|
+
|
3
|
+
This package provides semantic analysis capabilities including:
|
4
|
+
- Type checking and inference
|
5
|
+
- Variable usage validation
|
6
|
+
- Scope analysis
|
7
|
+
- Error reporting with helpful messages
|
8
|
+
"""
|
9
|
+
|
10
|
+
from machine_dialect.semantic.analyzer import SemanticAnalyzer, TypeInfo
|
11
|
+
|
12
|
+
__all__ = ["SemanticAnalyzer", "TypeInfo"]
|