machine-dialect 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. machine_dialect/__main__.py +667 -0
  2. machine_dialect/agent/__init__.py +5 -0
  3. machine_dialect/agent/agent.py +360 -0
  4. machine_dialect/ast/__init__.py +95 -0
  5. machine_dialect/ast/ast_node.py +35 -0
  6. machine_dialect/ast/call_expression.py +82 -0
  7. machine_dialect/ast/dict_extraction.py +60 -0
  8. machine_dialect/ast/expressions.py +439 -0
  9. machine_dialect/ast/literals.py +309 -0
  10. machine_dialect/ast/program.py +35 -0
  11. machine_dialect/ast/statements.py +1433 -0
  12. machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
  13. machine_dialect/ast/tests/test_boolean_literal.py +29 -0
  14. machine_dialect/ast/tests/test_collection_hir.py +138 -0
  15. machine_dialect/ast/tests/test_define_statement.py +142 -0
  16. machine_dialect/ast/tests/test_desugar.py +541 -0
  17. machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
  18. machine_dialect/cfg/__init__.py +6 -0
  19. machine_dialect/cfg/config.py +156 -0
  20. machine_dialect/cfg/examples.py +221 -0
  21. machine_dialect/cfg/generate_with_ai.py +187 -0
  22. machine_dialect/cfg/openai_generation.py +200 -0
  23. machine_dialect/cfg/parser.py +94 -0
  24. machine_dialect/cfg/tests/__init__.py +1 -0
  25. machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
  26. machine_dialect/cfg/tests/test_config.py +188 -0
  27. machine_dialect/cfg/tests/test_examples.py +391 -0
  28. machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
  29. machine_dialect/cfg/tests/test_openai_generation.py +256 -0
  30. machine_dialect/codegen/__init__.py +5 -0
  31. machine_dialect/codegen/bytecode_module.py +89 -0
  32. machine_dialect/codegen/bytecode_serializer.py +300 -0
  33. machine_dialect/codegen/opcodes.py +101 -0
  34. machine_dialect/codegen/register_codegen.py +1996 -0
  35. machine_dialect/codegen/symtab.py +208 -0
  36. machine_dialect/codegen/tests/__init__.py +1 -0
  37. machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
  38. machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
  39. machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
  40. machine_dialect/codegen/tests/test_symtab.py +418 -0
  41. machine_dialect/codegen/vm_serializer.py +621 -0
  42. machine_dialect/compiler/__init__.py +18 -0
  43. machine_dialect/compiler/compiler.py +197 -0
  44. machine_dialect/compiler/config.py +149 -0
  45. machine_dialect/compiler/context.py +149 -0
  46. machine_dialect/compiler/phases/__init__.py +19 -0
  47. machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
  48. machine_dialect/compiler/phases/codegen.py +40 -0
  49. machine_dialect/compiler/phases/hir_generation.py +39 -0
  50. machine_dialect/compiler/phases/mir_generation.py +86 -0
  51. machine_dialect/compiler/phases/optimization.py +110 -0
  52. machine_dialect/compiler/phases/parsing.py +39 -0
  53. machine_dialect/compiler/pipeline.py +143 -0
  54. machine_dialect/compiler/tests/__init__.py +1 -0
  55. machine_dialect/compiler/tests/test_compiler.py +568 -0
  56. machine_dialect/compiler/vm_runner.py +173 -0
  57. machine_dialect/errors/__init__.py +32 -0
  58. machine_dialect/errors/exceptions.py +369 -0
  59. machine_dialect/errors/messages.py +82 -0
  60. machine_dialect/errors/tests/__init__.py +0 -0
  61. machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
  62. machine_dialect/errors/tests/test_name_errors.py +118 -0
  63. machine_dialect/helpers/__init__.py +0 -0
  64. machine_dialect/helpers/stopwords.py +225 -0
  65. machine_dialect/helpers/validators.py +30 -0
  66. machine_dialect/lexer/__init__.py +9 -0
  67. machine_dialect/lexer/constants.py +23 -0
  68. machine_dialect/lexer/lexer.py +907 -0
  69. machine_dialect/lexer/tests/__init__.py +0 -0
  70. machine_dialect/lexer/tests/helpers.py +86 -0
  71. machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
  72. machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
  73. machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
  74. machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
  75. machine_dialect/lexer/tests/test_comments.py +200 -0
  76. machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
  77. machine_dialect/lexer/tests/test_lexer_position.py +113 -0
  78. machine_dialect/lexer/tests/test_list_tokens.py +282 -0
  79. machine_dialect/lexer/tests/test_stopwords.py +80 -0
  80. machine_dialect/lexer/tests/test_strict_equality.py +129 -0
  81. machine_dialect/lexer/tests/test_token.py +41 -0
  82. machine_dialect/lexer/tests/test_tokenization.py +294 -0
  83. machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
  84. machine_dialect/lexer/tests/test_url_literals.py +169 -0
  85. machine_dialect/lexer/tokens.py +487 -0
  86. machine_dialect/linter/__init__.py +10 -0
  87. machine_dialect/linter/__main__.py +144 -0
  88. machine_dialect/linter/linter.py +154 -0
  89. machine_dialect/linter/rules/__init__.py +8 -0
  90. machine_dialect/linter/rules/base.py +112 -0
  91. machine_dialect/linter/rules/statement_termination.py +99 -0
  92. machine_dialect/linter/tests/__init__.py +1 -0
  93. machine_dialect/linter/tests/mdrules/__init__.py +0 -0
  94. machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
  95. machine_dialect/linter/tests/test_linter.py +81 -0
  96. machine_dialect/linter/tests/test_rules.py +110 -0
  97. machine_dialect/linter/tests/test_violations.py +71 -0
  98. machine_dialect/linter/violations.py +51 -0
  99. machine_dialect/mir/__init__.py +69 -0
  100. machine_dialect/mir/analyses/__init__.py +20 -0
  101. machine_dialect/mir/analyses/alias_analysis.py +315 -0
  102. machine_dialect/mir/analyses/dominance_analysis.py +49 -0
  103. machine_dialect/mir/analyses/escape_analysis.py +286 -0
  104. machine_dialect/mir/analyses/loop_analysis.py +272 -0
  105. machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
  106. machine_dialect/mir/analyses/type_analysis.py +448 -0
  107. machine_dialect/mir/analyses/use_def_chains.py +232 -0
  108. machine_dialect/mir/basic_block.py +385 -0
  109. machine_dialect/mir/dataflow.py +445 -0
  110. machine_dialect/mir/debug_info.py +208 -0
  111. machine_dialect/mir/hir_to_mir.py +1738 -0
  112. machine_dialect/mir/mir_dumper.py +366 -0
  113. machine_dialect/mir/mir_function.py +167 -0
  114. machine_dialect/mir/mir_instructions.py +1877 -0
  115. machine_dialect/mir/mir_interpreter.py +556 -0
  116. machine_dialect/mir/mir_module.py +225 -0
  117. machine_dialect/mir/mir_printer.py +480 -0
  118. machine_dialect/mir/mir_transformer.py +410 -0
  119. machine_dialect/mir/mir_types.py +367 -0
  120. machine_dialect/mir/mir_validation.py +455 -0
  121. machine_dialect/mir/mir_values.py +268 -0
  122. machine_dialect/mir/optimization_config.py +233 -0
  123. machine_dialect/mir/optimization_pass.py +251 -0
  124. machine_dialect/mir/optimization_pipeline.py +355 -0
  125. machine_dialect/mir/optimizations/__init__.py +84 -0
  126. machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
  127. machine_dialect/mir/optimizations/branch_prediction.py +372 -0
  128. machine_dialect/mir/optimizations/constant_propagation.py +634 -0
  129. machine_dialect/mir/optimizations/cse.py +398 -0
  130. machine_dialect/mir/optimizations/dce.py +288 -0
  131. machine_dialect/mir/optimizations/inlining.py +551 -0
  132. machine_dialect/mir/optimizations/jump_threading.py +487 -0
  133. machine_dialect/mir/optimizations/licm.py +405 -0
  134. machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
  135. machine_dialect/mir/optimizations/strength_reduction.py +422 -0
  136. machine_dialect/mir/optimizations/tail_call.py +207 -0
  137. machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
  138. machine_dialect/mir/optimizations/type_narrowing.py +397 -0
  139. machine_dialect/mir/optimizations/type_specialization.py +447 -0
  140. machine_dialect/mir/optimizations/type_specific.py +906 -0
  141. machine_dialect/mir/optimize_mir.py +89 -0
  142. machine_dialect/mir/pass_manager.py +391 -0
  143. machine_dialect/mir/profiling/__init__.py +26 -0
  144. machine_dialect/mir/profiling/profile_collector.py +318 -0
  145. machine_dialect/mir/profiling/profile_data.py +372 -0
  146. machine_dialect/mir/profiling/profile_reader.py +272 -0
  147. machine_dialect/mir/profiling/profile_writer.py +226 -0
  148. machine_dialect/mir/register_allocation.py +302 -0
  149. machine_dialect/mir/reporting/__init__.py +17 -0
  150. machine_dialect/mir/reporting/optimization_reporter.py +314 -0
  151. machine_dialect/mir/reporting/report_formatter.py +289 -0
  152. machine_dialect/mir/ssa_construction.py +342 -0
  153. machine_dialect/mir/tests/__init__.py +1 -0
  154. machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
  155. machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
  156. machine_dialect/mir/tests/test_algebraic_division.py +126 -0
  157. machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
  158. machine_dialect/mir/tests/test_basic_block.py +425 -0
  159. machine_dialect/mir/tests/test_branch_prediction.py +459 -0
  160. machine_dialect/mir/tests/test_call_lowering.py +168 -0
  161. machine_dialect/mir/tests/test_collection_lowering.py +604 -0
  162. machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
  163. machine_dialect/mir/tests/test_custom_passes.py +166 -0
  164. machine_dialect/mir/tests/test_debug_info.py +285 -0
  165. machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
  166. machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
  167. machine_dialect/mir/tests/test_double_negation.py +231 -0
  168. machine_dialect/mir/tests/test_escape_analysis.py +233 -0
  169. machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
  170. machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
  171. machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
  172. machine_dialect/mir/tests/test_inlining.py +435 -0
  173. machine_dialect/mir/tests/test_licm.py +472 -0
  174. machine_dialect/mir/tests/test_mir_dumper.py +313 -0
  175. machine_dialect/mir/tests/test_mir_instructions.py +445 -0
  176. machine_dialect/mir/tests/test_mir_module.py +860 -0
  177. machine_dialect/mir/tests/test_mir_printer.py +387 -0
  178. machine_dialect/mir/tests/test_mir_types.py +123 -0
  179. machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
  180. machine_dialect/mir/tests/test_mir_validation.py +378 -0
  181. machine_dialect/mir/tests/test_mir_values.py +168 -0
  182. machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
  183. machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
  184. machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
  185. machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
  186. machine_dialect/mir/tests/test_pass_manager.py +294 -0
  187. machine_dialect/mir/tests/test_pass_registration.py +64 -0
  188. machine_dialect/mir/tests/test_profiling.py +356 -0
  189. machine_dialect/mir/tests/test_register_allocation.py +307 -0
  190. machine_dialect/mir/tests/test_report_formatters.py +372 -0
  191. machine_dialect/mir/tests/test_ssa_construction.py +433 -0
  192. machine_dialect/mir/tests/test_tail_call.py +236 -0
  193. machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
  194. machine_dialect/mir/tests/test_type_narrowing.py +277 -0
  195. machine_dialect/mir/tests/test_type_specialization.py +421 -0
  196. machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
  197. machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
  198. machine_dialect/mir/type_inference.py +368 -0
  199. machine_dialect/parser/__init__.py +12 -0
  200. machine_dialect/parser/enums.py +45 -0
  201. machine_dialect/parser/parser.py +3655 -0
  202. machine_dialect/parser/protocols.py +11 -0
  203. machine_dialect/parser/symbol_table.py +169 -0
  204. machine_dialect/parser/tests/__init__.py +0 -0
  205. machine_dialect/parser/tests/helper_functions.py +193 -0
  206. machine_dialect/parser/tests/test_action_statements.py +334 -0
  207. machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
  208. machine_dialect/parser/tests/test_call_statements.py +154 -0
  209. machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
  210. machine_dialect/parser/tests/test_collection_mutations.py +264 -0
  211. machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
  212. machine_dialect/parser/tests/test_define_integration.py +468 -0
  213. machine_dialect/parser/tests/test_define_statements.py +311 -0
  214. machine_dialect/parser/tests/test_dict_extraction.py +115 -0
  215. machine_dialect/parser/tests/test_empty_literal.py +155 -0
  216. machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
  217. machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
  218. machine_dialect/parser/tests/test_if_empty_block.py +61 -0
  219. machine_dialect/parser/tests/test_if_statements.py +299 -0
  220. machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
  221. machine_dialect/parser/tests/test_infix_expressions.py +680 -0
  222. machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
  223. machine_dialect/parser/tests/test_interaction_statements.py +269 -0
  224. machine_dialect/parser/tests/test_list_literals.py +277 -0
  225. machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
  226. machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
  227. machine_dialect/parser/tests/test_parse_errors.py +114 -0
  228. machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
  229. machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
  230. machine_dialect/parser/tests/test_program.py +13 -0
  231. machine_dialect/parser/tests/test_return_statements.py +89 -0
  232. machine_dialect/parser/tests/test_set_statements.py +152 -0
  233. machine_dialect/parser/tests/test_strict_equality.py +258 -0
  234. machine_dialect/parser/tests/test_symbol_table.py +217 -0
  235. machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
  236. machine_dialect/parser/tests/test_utility_statements.py +423 -0
  237. machine_dialect/parser/token_buffer.py +159 -0
  238. machine_dialect/repl/__init__.py +3 -0
  239. machine_dialect/repl/repl.py +426 -0
  240. machine_dialect/repl/tests/__init__.py +0 -0
  241. machine_dialect/repl/tests/test_repl.py +606 -0
  242. machine_dialect/semantic/__init__.py +12 -0
  243. machine_dialect/semantic/analyzer.py +906 -0
  244. machine_dialect/semantic/error_messages.py +189 -0
  245. machine_dialect/semantic/tests/__init__.py +1 -0
  246. machine_dialect/semantic/tests/test_analyzer.py +364 -0
  247. machine_dialect/semantic/tests/test_error_messages.py +104 -0
  248. machine_dialect/tests/edge_cases/__init__.py +10 -0
  249. machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
  250. machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
  251. machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
  252. machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
  253. machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
  254. machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
  255. machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
  256. machine_dialect/tests/integration/test_list_compilation.py +395 -0
  257. machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
  258. machine_dialect/type_checking/__init__.py +21 -0
  259. machine_dialect/type_checking/tests/__init__.py +1 -0
  260. machine_dialect/type_checking/tests/test_type_system.py +230 -0
  261. machine_dialect/type_checking/type_system.py +270 -0
  262. machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
  263. machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
  264. machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
  265. machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
  266. machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
  267. machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
  268. machine_dialect_vm/__init__.pyi +15 -0
@@ -0,0 +1,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"]