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,468 @@
1
+ """Integration tests for Define statement with symbol table tracking.
2
+
3
+ These tests verify that the parser properly integrates with the symbol table
4
+ to track variable definitions and validate variable usage.
5
+ """
6
+
7
+ from machine_dialect.ast import DefineStatement, SetStatement
8
+ from machine_dialect.errors.exceptions import MDNameError, MDTypeError
9
+ from machine_dialect.parser import Parser
10
+
11
+
12
+ class TestDefineIntegration:
13
+ """Test integration between Define statements and symbol table."""
14
+
15
+ def test_define_then_set_valid(self) -> None:
16
+ """Test that defining a variable allows it to be set."""
17
+ source = """
18
+ Define `count` as Whole Number.
19
+ Set `count` to _42_.
20
+ """
21
+ parser = Parser()
22
+ program = parser.parse(source)
23
+
24
+ # Should parse successfully
25
+ assert len(parser.errors) == 0
26
+ assert len(program.statements) == 2
27
+ assert isinstance(program.statements[0], DefineStatement)
28
+ assert isinstance(program.statements[1], SetStatement)
29
+
30
+ def test_set_undefined_variable_error(self) -> None:
31
+ """Test that using undefined variable generates error."""
32
+ source = """
33
+ Set `undefined_var` to _10_.
34
+ """
35
+ parser = Parser()
36
+ parser.parse(source)
37
+
38
+ # Should have exactly one error
39
+ assert len(parser.errors) == 1
40
+ error = parser.errors[0]
41
+ assert isinstance(error, MDNameError)
42
+ assert "undefined_var" in str(error)
43
+ assert "not defined" in str(error)
44
+
45
+ def test_define_with_default_then_set(self) -> None:
46
+ """Test defining variable with default value then setting it."""
47
+ source = """
48
+ Define `message` as Text (default: _"Hello"_).
49
+ Set `message` to _"World"_.
50
+ """
51
+ parser = Parser()
52
+ program = parser.parse(source)
53
+
54
+ # Should parse successfully
55
+ assert len(parser.errors) == 0
56
+ assert len(program.statements) == 2
57
+
58
+ def test_multiple_defines_then_sets(self) -> None:
59
+ """Test multiple variable definitions and uses."""
60
+ source = """
61
+ Define `x` as Whole Number.
62
+ Define `y` as Float.
63
+ Define `name` as Text.
64
+ Set `x` to _10_.
65
+ Set `y` to _3.14_.
66
+ Set `name` to _"Alice"_.
67
+ """
68
+ parser = Parser()
69
+ program = parser.parse(source)
70
+
71
+ # Should parse successfully
72
+ assert len(parser.errors) == 0
73
+ assert len(program.statements) == 6
74
+
75
+ def test_redefinition_error(self) -> None:
76
+ """Test that redefining a variable generates error."""
77
+ source = """
78
+ Define `x` as Whole Number.
79
+ Define `x` as Text.
80
+ """
81
+ parser = Parser()
82
+ parser.parse(source)
83
+
84
+ # Should have exactly one error for redefinition
85
+ assert len(parser.errors) == 1
86
+ error = parser.errors[0]
87
+ assert isinstance(error, MDNameError)
88
+ assert "already defined" in str(error)
89
+
90
+ def test_define_in_different_scopes(self) -> None:
91
+ """Test that variables can be defined in different scopes (future feature)."""
92
+ # This test is a placeholder for when we implement scope handling
93
+ # Currently all variables are in global scope
94
+ source = """
95
+ Define `global_var` as Whole Number.
96
+ Set `global_var` to _1_.
97
+ """
98
+ parser = Parser()
99
+ parser.parse(source)
100
+
101
+ assert len(parser.errors) == 0
102
+
103
+ def test_use_before_define_error(self) -> None:
104
+ """Test that using variable before definition generates error."""
105
+ source = """
106
+ Set `x` to _5_.
107
+ Define `x` as Whole Number.
108
+ """
109
+ parser = Parser()
110
+ parser.parse(source)
111
+
112
+ # Should have error for using undefined variable
113
+ assert len(parser.errors) == 1
114
+ error = parser.errors[0]
115
+ assert isinstance(error, MDNameError)
116
+ assert "not defined" in str(error)
117
+
118
+ def test_complex_program_with_defines_and_sets(self) -> None:
119
+ """Test a more complex program with multiple defines and sets."""
120
+ source = """
121
+ Define `user_name` as Text.
122
+ Define `user_age` as Whole Number.
123
+ Define `is_admin` as Yes/No (default: _no_).
124
+
125
+ Set `user_name` to _"John Doe"_.
126
+ Set `user_age` to _25_.
127
+ Set `is_admin` to _yes_.
128
+
129
+ Define `score` as Float.
130
+ Set `score` to _98.5_.
131
+ """
132
+ parser = Parser()
133
+ program = parser.parse(source)
134
+
135
+ # Should parse successfully
136
+ assert len(parser.errors) == 0
137
+ assert len(program.statements) == 8
138
+
139
+ def test_undefined_variable_in_expression(self) -> None:
140
+ """Test that undefined variables in expressions generate errors."""
141
+ source = """
142
+ Define `x` as Whole Number.
143
+ Set `x` to _10_.
144
+ Set `y` to `x` + _5_.
145
+ """
146
+ parser = Parser()
147
+ parser.parse(source)
148
+
149
+ # Should have error for undefined variable y
150
+ assert len(parser.errors) == 1
151
+ error = parser.errors[0]
152
+ assert isinstance(error, MDNameError)
153
+ assert "y" in str(error)
154
+
155
+ def test_define_with_union_types(self) -> None:
156
+ """Test defining variable with union types."""
157
+ source = """
158
+ Define `flexible` as Whole Number or Text.
159
+ Set `flexible` to _42_.
160
+ Define `flexible` as Float.
161
+ """
162
+ parser = Parser()
163
+ parser.parse(source)
164
+
165
+ # Should have error for redefinition
166
+ assert len(parser.errors) == 1
167
+ error = parser.errors[0]
168
+ assert isinstance(error, MDNameError)
169
+ assert "already defined" in str(error)
170
+
171
+ def test_error_recovery_continues_parsing(self) -> None:
172
+ """Test that parser continues after encountering errors."""
173
+ source = """
174
+ Set `undefined1` to _1_.
175
+ Define `valid` as Whole Number.
176
+ Set `undefined2` to _2_.
177
+ Set `valid` to _100_.
178
+ Set `undefined3` to _3_.
179
+ """
180
+ parser = Parser()
181
+ program = parser.parse(source)
182
+
183
+ # Should have 3 errors for undefined variables
184
+ assert len(parser.errors) == 3
185
+ # But should still parse all 5 statements
186
+ assert len(program.statements) == 5
187
+
188
+ def test_define_without_type_error(self) -> None:
189
+ """Test that Define without type generates appropriate error."""
190
+ source = """
191
+ Define `x` as.
192
+ """
193
+ parser = Parser()
194
+ parser.parse(source)
195
+
196
+ # Should have syntax error
197
+ assert len(parser.errors) > 0
198
+
199
+ def test_define_with_invalid_syntax_error(self) -> None:
200
+ """Test that invalid Define syntax generates appropriate error."""
201
+ source = """
202
+ Define as Whole Number.
203
+ """
204
+ parser = Parser()
205
+ parser.parse(source)
206
+
207
+ # Should have syntax error
208
+ assert len(parser.errors) > 0
209
+
210
+ def test_multiple_errors_collected(self) -> None:
211
+ """Test that multiple errors are collected in one pass."""
212
+ source = """
213
+ Set `a` to _1_.
214
+ Set `b` to _2_.
215
+ Define `a` as Whole Number.
216
+ Define `a` as Text.
217
+ Set `c` to _3_.
218
+ """
219
+ parser = Parser()
220
+ parser.parse(source)
221
+
222
+ # Should have errors for:
223
+ # 1. 'a' not defined (first Set)
224
+ # 2. 'b' not defined (second Set)
225
+ # 3. 'a' already defined (second Define)
226
+ # 4. 'c' not defined (last Set)
227
+ assert len(parser.errors) == 4
228
+
229
+
230
+ class TestDefineTypeChecking:
231
+ """Test type checking integration with Define and Set statements."""
232
+
233
+ def test_valid_type_assignments(self) -> None:
234
+ """Test that valid type assignments work correctly."""
235
+ source = """
236
+ Define `count` as Whole Number.
237
+ Set `count` to _42_.
238
+
239
+ Define `price` as Float.
240
+ Set `price` to _19.99_.
241
+
242
+ Define `name` as Text.
243
+ Set `name` to _"Alice"_.
244
+
245
+ Define `active` as Yes/No.
246
+ Set `active` to _yes_.
247
+ """
248
+ parser = Parser()
249
+ program = parser.parse(source)
250
+
251
+ # Should parse without type errors
252
+ assert len(parser.errors) == 0
253
+ assert len(program.statements) == 8
254
+
255
+ def test_type_mismatch_whole_number_to_text(self) -> None:
256
+ """Test type mismatch when assigning text to whole number."""
257
+ source = """
258
+ Define `count` as Whole Number.
259
+ Set `count` to _"not a number"_.
260
+ """
261
+ parser = Parser()
262
+ parser.parse(source)
263
+
264
+ # Should have a type error
265
+ assert len(parser.errors) > 0
266
+ # Find type-related errors
267
+ from machine_dialect.errors import MDTypeError
268
+
269
+ type_errors = [e for e in parser.errors if isinstance(e, MDTypeError)]
270
+ assert len(type_errors) > 0
271
+
272
+ def test_type_mismatch_text_to_whole_number(self) -> None:
273
+ """Test type mismatch when assigning number to text."""
274
+ source = """
275
+ Define `message` as Text.
276
+ Set `message` to _42_.
277
+ """
278
+ parser = Parser()
279
+ parser.parse(source)
280
+
281
+ # Should have a type error
282
+ assert len(parser.errors) > 0
283
+
284
+ def test_union_type_valid_assignments(self) -> None:
285
+ """Test that union types accept values of any specified type."""
286
+ source = """
287
+ Define `flexible` as Whole Number or Text.
288
+ Set `flexible` to _42_.
289
+ Set `flexible` to _"hello"_.
290
+ """
291
+ parser = Parser()
292
+ parser.parse(source)
293
+
294
+ # Both assignments should be valid
295
+ assert len(parser.errors) == 0
296
+
297
+ def test_union_type_invalid_assignment(self) -> None:
298
+ """Test that union types reject values not in the union."""
299
+ source = """
300
+ Define `flexible` as Whole Number or Text.
301
+ Set `flexible` to _yes_.
302
+ """
303
+ parser = Parser()
304
+ parser.parse(source)
305
+
306
+ # Should have type error (Yes/No not in union)
307
+ assert len(parser.errors) > 0
308
+
309
+ def test_number_type_accepts_whole_and_float(self) -> None:
310
+ """Test that Number type accepts both Whole Number and Float."""
311
+ source = """
312
+ Define `num` as Number.
313
+ Set `num` to _42_.
314
+ Set `num` to _3.14_.
315
+ """
316
+ parser = Parser()
317
+ parser.parse(source)
318
+
319
+ # Both assignments should be valid
320
+ assert len(parser.errors) == 0
321
+
322
+ def test_number_type_rejects_non_numeric(self) -> None:
323
+ """Test that Number type rejects non-numeric values."""
324
+ source = """
325
+ Define `num` as Number.
326
+ Set `num` to _"text"_.
327
+ """
328
+ parser = Parser()
329
+ parser.parse(source)
330
+
331
+ # Should have type error
332
+ assert len(parser.errors) > 0
333
+
334
+ def test_empty_assignable_to_any_type(self) -> None:
335
+ """Test that empty requires explicit type declaration for strict type checking."""
336
+ # In strict mode, empty is NOT assignable to non-nullable types
337
+ source = """
338
+ Define `maybe_text` as Text.
339
+ Set `maybe_text` to empty.
340
+
341
+ Define `maybe_num` as Whole Number.
342
+ Set `maybe_num` to empty.
343
+
344
+ Define `maybe_bool` as Yes/No.
345
+ Set `maybe_bool` to empty.
346
+ """
347
+ parser = Parser()
348
+ parser.parse(source)
349
+
350
+ # Should have type errors for all empty assignments to non-nullable types
351
+ assert len(parser.errors) == 3
352
+ for error in parser.errors:
353
+ assert isinstance(error, MDTypeError)
354
+ assert "Empty" in str(error)
355
+
356
+ # Test that explicit nullable types work
357
+ source_nullable = """
358
+ Define `maybe_text` as Text or Empty.
359
+ Set `maybe_text` to empty.
360
+
361
+ Define `maybe_num` as Whole Number or Empty.
362
+ Set `maybe_num` to empty.
363
+
364
+ Define `maybe_bool` as Yes/No or Empty.
365
+ Set `maybe_bool` to empty.
366
+ """
367
+ parser2 = Parser()
368
+ parser2.parse(source_nullable)
369
+
370
+ # All empty assignments to nullable types should be valid
371
+ assert len(parser2.errors) == 0
372
+
373
+ def test_url_type_assignment(self) -> None:
374
+ """Test URL type assignments."""
375
+ source = """
376
+ Define `website` as URL.
377
+ Set `website` to _"https://example.com"_.
378
+ """
379
+ parser = Parser()
380
+ parser.parse(source)
381
+
382
+ # URL assignment should be valid
383
+ assert len(parser.errors) == 0
384
+
385
+ def test_multiple_type_errors_collected(self) -> None:
386
+ """Test that multiple type errors are collected."""
387
+ source = """
388
+ Define `a` as Whole Number.
389
+ Define `b` as Text.
390
+ Define `c` as Yes/No.
391
+
392
+ Set `a` to _"wrong"_.
393
+ Set `b` to _42_.
394
+ Set `c` to _"also wrong"_.
395
+ """
396
+ parser = Parser()
397
+ parser.parse(source)
398
+
399
+ # Should have 3 type errors
400
+ assert len(parser.errors) == 3
401
+
402
+ def test_type_checking_with_expressions(self) -> None:
403
+ """Test type checking with complex expressions."""
404
+ source = """
405
+ Define `x` as Whole Number.
406
+ Define `y` as Whole Number.
407
+ Set `x` to _10_.
408
+ Set `y` to _20_.
409
+ """
410
+ parser = Parser()
411
+ parser.parse(source)
412
+
413
+ # Should parse without errors
414
+ assert len(parser.errors) == 0
415
+
416
+ def test_float_not_assignable_to_whole_number(self) -> None:
417
+ """Test that Float cannot be assigned to Whole Number (no implicit conversion)."""
418
+ source = """
419
+ Define `int_only` as Whole Number.
420
+ Set `int_only` to _3.14_.
421
+ """
422
+ parser = Parser()
423
+ parser.parse(source)
424
+
425
+ # Should have type error (no implicit conversions per spec)
426
+ assert len(parser.errors) > 0
427
+
428
+ def test_whole_number_assignable_to_number(self) -> None:
429
+ """Test that Whole Number can be assigned to Number type."""
430
+ source = """
431
+ Define `num` as Number.
432
+ Set `num` to _42_.
433
+ """
434
+ parser = Parser()
435
+ parser.parse(source)
436
+
437
+ # Should be valid (Number accepts Whole Number)
438
+ assert len(parser.errors) == 0
439
+
440
+ def test_complex_union_types(self) -> None:
441
+ """Test complex union types with multiple alternatives."""
442
+ source = """
443
+ Define `complex` as Whole Number or Text or Yes/No or Empty.
444
+ Set `complex` to _42_.
445
+ Set `complex` to _"text"_.
446
+ Set `complex` to _yes_.
447
+ Set `complex` to empty.
448
+ """
449
+ parser = Parser()
450
+ parser.parse(source)
451
+
452
+ # All assignments should be valid
453
+ assert len(parser.errors) == 0
454
+
455
+ def test_type_error_message_content(self) -> None:
456
+ """Test that type error messages are informative."""
457
+ source = """
458
+ Define `num` as Whole Number.
459
+ Set `num` to _"text"_.
460
+ """
461
+ parser = Parser()
462
+ parser.parse(source)
463
+
464
+ # Should have meaningful error message
465
+ assert len(parser.errors) > 0
466
+ error_str = str(parser.errors[0])
467
+ # Error should mention the variable name and types involved
468
+ assert "num" in error_str or "Whole Number" in error_str.lower() or "text" in error_str.lower()