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,163 @@
1
+ """Tests for parsing float literal expressions."""
2
+
3
+ import pytest
4
+
5
+ from machine_dialect.ast import ExpressionStatement
6
+ from machine_dialect.parser import Parser
7
+ from machine_dialect.parser.tests.helper_functions import (
8
+ assert_literal_expression,
9
+ assert_program_statements,
10
+ )
11
+
12
+
13
+ class TestFloatLiteralExpressions:
14
+ """Test cases for parsing float literal expressions."""
15
+
16
+ @pytest.mark.parametrize(
17
+ "source,expected_value",
18
+ [
19
+ # Simple floats
20
+ ("0.0", 0.0),
21
+ ("1.0", 1.0),
22
+ ("3.14", 3.14),
23
+ ("2.71", 2.71),
24
+ # Floats without leading zero
25
+ (".5", 0.5),
26
+ (".25", 0.25),
27
+ (".125", 0.125),
28
+ # Floats with many decimal places
29
+ ("3.14159", 3.14159),
30
+ ("2.71828", 2.71828),
31
+ ("0.123456789", 0.123456789),
32
+ # Large floats
33
+ ("123.456", 123.456),
34
+ ("9999.9999", 9999.9999),
35
+ ("1000000.0", 1000000.0),
36
+ # Small floats
37
+ ("0.001", 0.001),
38
+ ("0.0001", 0.0001),
39
+ ("0.00001", 0.00001),
40
+ # Edge cases
41
+ ("999999999.999999999", 999999999.999999999),
42
+ # Floats with underscores (one on each side)
43
+ ("_3.14_", 3.14),
44
+ ("_0.5_", 0.5),
45
+ ("_1.0_", 1.0),
46
+ ("_123.456_", 123.456),
47
+ ("_.25_", 0.25),
48
+ ("_0.001_", 0.001),
49
+ ("_0.0_", 0.0),
50
+ ],
51
+ )
52
+ def test_float_literal_expression(self, source: str, expected_value: float) -> None:
53
+ """Test parsing various float literal expressions.
54
+
55
+ Args:
56
+ source: The source code to parse.
57
+ expected_value: The expected float value.
58
+ """
59
+ parser = Parser()
60
+
61
+ program = parser.parse(source)
62
+
63
+ assert_program_statements(parser, program)
64
+
65
+ statement = program.statements[0]
66
+ assert isinstance(statement, ExpressionStatement)
67
+ assert statement.expression is not None
68
+
69
+ assert_literal_expression(statement.expression, expected_value)
70
+
71
+ def test_float_with_period(self) -> None:
72
+ """Test parsing float literal followed by period."""
73
+ source = "3.14."
74
+ parser = Parser()
75
+
76
+ program = parser.parse(source)
77
+
78
+ assert_program_statements(parser, program)
79
+
80
+ statement = program.statements[0]
81
+ assert isinstance(statement, ExpressionStatement)
82
+ assert statement.expression is not None
83
+
84
+ assert_literal_expression(statement.expression, 3.14)
85
+
86
+ def test_multiple_float_statements(self) -> None:
87
+ """Test parsing multiple float literal statements."""
88
+ source = "1.1. 2.2. 3.3."
89
+ parser = Parser()
90
+
91
+ program = parser.parse(source)
92
+
93
+ assert len(parser.errors) == 0
94
+ assert len(program.statements) == 3
95
+
96
+ # Check each statement
97
+ for i, expected_value in enumerate([1.1, 2.2, 3.3]):
98
+ statement = program.statements[i]
99
+ assert isinstance(statement, ExpressionStatement)
100
+ assert statement.expression is not None
101
+ assert_literal_expression(statement.expression, expected_value)
102
+
103
+ @pytest.mark.parametrize(
104
+ "source,error_substring",
105
+ [
106
+ # Underscore only on left side
107
+ ("_3.14", "_3.14"),
108
+ ("_0.5", "_0.5"),
109
+ ("_123.456", "_123.456"),
110
+ ("_.25", "_.25"),
111
+ # Underscore only on right side
112
+ ("3.14_", "3.14_"),
113
+ ("0.5_", "0.5_"),
114
+ ("123.456_", "123.456_"),
115
+ (".25_", ".25_"),
116
+ # Multiple underscores
117
+ ("__3.14__", "__3.14__"),
118
+ ("___0.5___", "___0.5___"),
119
+ ("__123.456__", "__123.456__"),
120
+ # Mixed multiple and single
121
+ ("__3.14_", "__3.14_"),
122
+ ("_3.14__", "_3.14__"),
123
+ ],
124
+ )
125
+ def test_invalid_underscore_formats_produce_errors(self, source: str, error_substring: str) -> None:
126
+ """Test that invalid underscore formats produce lexer errors.
127
+
128
+ Args:
129
+ source: The source code with invalid underscore format.
130
+ error_substring: Expected substring in the error message.
131
+ """
132
+ # Lexer instantiation moved to Parser.parse()
133
+ parser = Parser()
134
+
135
+ # Parse the program (parser collects lexer errors)
136
+ _ = parser.parse(source)
137
+
138
+ # Should have at least one error
139
+ assert len(parser.errors) >= 1, f"Expected error for invalid format: {source}"
140
+
141
+ # Check that the error mentions the invalid token
142
+ error_messages = [str(error) for error in parser.errors]
143
+ assert any(error_substring in msg for msg in error_messages), (
144
+ f"Expected error to mention '{error_substring}', got errors: {error_messages}"
145
+ )
146
+
147
+ def test_mixed_integer_and_float_statements(self) -> None:
148
+ """Test parsing mixed integer and float literal statements."""
149
+ source = "42. 3.14. 100. 0.5."
150
+ parser = Parser()
151
+
152
+ program = parser.parse(source)
153
+
154
+ assert len(parser.errors) == 0
155
+ assert len(program.statements) == 4
156
+
157
+ # Check each statement with expected values and types
158
+ expected_values = [42, 3.14, 100, 0.5]
159
+ for i, expected_value in enumerate(expected_values):
160
+ statement = program.statements[i]
161
+ assert isinstance(statement, ExpressionStatement)
162
+ assert statement.expression is not None
163
+ assert_literal_expression(statement.expression, expected_value)
@@ -0,0 +1,57 @@
1
+ """Tests for parsing identifier expressions."""
2
+
3
+ import pytest
4
+
5
+ from machine_dialect.ast import ExpressionStatement
6
+ from machine_dialect.parser import Parser
7
+ from machine_dialect.parser.tests.helper_functions import (
8
+ assert_literal_expression,
9
+ assert_program_statements,
10
+ )
11
+
12
+
13
+ class TestIdentifierExpressions:
14
+ """Test cases for parsing identifier expressions."""
15
+
16
+ @pytest.mark.parametrize(
17
+ "source,expected_value",
18
+ [
19
+ # Simple identifiers
20
+ ("foobar", "foobar"),
21
+ ("x", "x"),
22
+ ("myVariableName", "myVariableName"),
23
+ ("test_variable_name", "test_variable_name"),
24
+ # Backtick identifiers
25
+ ("`myVariable`", "myVariable"),
26
+ ("`x`", "x"),
27
+ # Multi-word identifiers
28
+ ("`email address`", "email address"),
29
+ ("`user name`", "user name"),
30
+ ("`first name`", "first name"),
31
+ ("`shopping cart total`", "shopping cart total"),
32
+ ("`is logged in`", "is logged in"),
33
+ ("`has been processed`", "has been processed"),
34
+ # Complex multi-word identifiers
35
+ ("`customer email address`", "customer email address"),
36
+ ("`total order amount`", "total order amount"),
37
+ ("`user account status`", "user account status"),
38
+ ],
39
+ )
40
+ def test_identifier_expression(self, source: str, expected_value: str) -> None:
41
+ """Test parsing various identifier expressions.
42
+
43
+ Args:
44
+ source: The source code to parse.
45
+ expected_value: The expected identifier value.
46
+ """
47
+ parser = Parser()
48
+
49
+ program = parser.parse(source, check_semantics=False)
50
+
51
+ assert_program_statements(parser, program)
52
+
53
+ statement = program.statements[0]
54
+ assert isinstance(statement, ExpressionStatement)
55
+ assert statement.expression is not None
56
+
57
+ assert_literal_expression(statement.expression, expected_value)
@@ -0,0 +1,61 @@
1
+ """Tests for if statements with empty blocks."""
2
+
3
+ from machine_dialect.parser import Parser
4
+
5
+
6
+ class TestIfEmptyBlock:
7
+ """Test that if statements with empty blocks generate appropriate errors."""
8
+
9
+ def test_if_with_empty_consequence_block(self) -> None:
10
+ """Test that an if statement with an empty consequence block generates an error."""
11
+ source = """if True then:
12
+ >
13
+ """
14
+ parser = Parser()
15
+ _ = parser.parse(source)
16
+
17
+ # Should have an error about empty consequence block
18
+ assert len(parser.errors) > 0
19
+ assert any("must have a non-empty consequence block" in str(error) for error in parser.errors)
20
+
21
+ def test_if_with_empty_else_block(self) -> None:
22
+ """Test that an else block that is empty generates an error."""
23
+ source = """if True then:
24
+ > Set x to 1.
25
+ else:
26
+ >
27
+ """
28
+ parser = Parser()
29
+ _ = parser.parse(source)
30
+
31
+ # Should have an error about empty else block
32
+ assert len(parser.errors) > 0
33
+ assert any("must not be empty" in str(error) for error in parser.errors)
34
+
35
+ def test_nested_if_with_empty_block(self) -> None:
36
+ """Test that nested if statements also require non-empty blocks."""
37
+ source = """if True then:
38
+ > if False then:
39
+ > >
40
+ > Set x to 1."""
41
+
42
+ parser = Parser()
43
+ _ = parser.parse(source)
44
+
45
+ # Should have an error about the nested if's empty block
46
+ assert len(parser.errors) > 0
47
+ assert any("must have a non-empty consequence block" in str(error) for error in parser.errors)
48
+
49
+ def test_if_with_only_empty_lines_in_block(self) -> None:
50
+ """Test that a block with only empty lines is still considered empty."""
51
+ source = """if True then:
52
+ >
53
+ >
54
+ >
55
+ """
56
+ parser = Parser()
57
+ _ = parser.parse(source)
58
+
59
+ # Should have an error about empty consequence block
60
+ assert len(parser.errors) > 0
61
+ assert any("must have a non-empty consequence block" in str(error) for error in parser.errors)
@@ -0,0 +1,299 @@
1
+ """Tests for if statements with block statements in the parser.
2
+
3
+ This module tests the parsing of if statements which support blocks of statements
4
+ marked by '>' symbols for depth tracking.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from machine_dialect.ast import (
10
+ BlockStatement,
11
+ Identifier,
12
+ IfStatement,
13
+ InfixExpression,
14
+ SetStatement,
15
+ WholeNumberLiteral,
16
+ YesNoLiteral,
17
+ )
18
+ from machine_dialect.parser import Parser
19
+
20
+
21
+ class TestIfStatements:
22
+ """Test parsing of if statements with block statements."""
23
+
24
+ def test_parse_basic_if_statement(self) -> None:
25
+ """Test parsing of basic if statement with single statement in block."""
26
+ source = """
27
+ if Yes then:
28
+ > Set x to 1.
29
+ """
30
+ parser = Parser()
31
+ program = parser.parse(source)
32
+
33
+ assert len(program.statements) == 1
34
+ assert isinstance(program.statements[0], IfStatement)
35
+
36
+ if_stmt = program.statements[0]
37
+ assert isinstance(if_stmt.condition, YesNoLiteral)
38
+ assert if_stmt.condition.value is True
39
+
40
+ assert isinstance(if_stmt.consequence, BlockStatement)
41
+ assert if_stmt.consequence.depth == 1
42
+ assert len(if_stmt.consequence.statements) == 1
43
+
44
+ set_stmt = if_stmt.consequence.statements[0]
45
+ assert isinstance(set_stmt, SetStatement)
46
+ assert isinstance(set_stmt.name, Identifier)
47
+ assert set_stmt.name.value == "x"
48
+ assert isinstance(set_stmt.value, WholeNumberLiteral)
49
+ assert set_stmt.value.value == 1
50
+
51
+ assert if_stmt.alternative is None
52
+
53
+ def test_parse_if_else_statement(self) -> None:
54
+ """Test parsing of if-else statement with blocks."""
55
+ source = """
56
+ if Yes then:
57
+ > Set x to 1.
58
+ > Set y to 2.
59
+ else:
60
+ > Set x to 3.
61
+ > Set y to 4.
62
+ """
63
+ parser = Parser()
64
+ program = parser.parse(source)
65
+
66
+ assert len(program.statements) == 1
67
+ assert isinstance(program.statements[0], IfStatement)
68
+
69
+ if_stmt = program.statements[0]
70
+ assert isinstance(if_stmt.condition, YesNoLiteral)
71
+ assert if_stmt.condition.value is True
72
+
73
+ # Check consequence block
74
+ assert isinstance(if_stmt.consequence, BlockStatement)
75
+ assert if_stmt.consequence.depth == 1
76
+ assert len(if_stmt.consequence.statements) == 2
77
+
78
+ # Check alternative block
79
+ assert if_stmt.alternative is not None
80
+ assert isinstance(if_stmt.alternative, BlockStatement)
81
+ assert if_stmt.alternative.depth == 1
82
+ assert len(if_stmt.alternative.statements) == 2
83
+
84
+ def test_parse_nested_if_statements(self) -> None:
85
+ """Test parsing of nested if statements with proper depth."""
86
+ source = """
87
+ if Yes then:
88
+ >
89
+ > Set foo to 1.
90
+ >
91
+ > if No then:
92
+ > >
93
+ > > Set bar to 2.
94
+ > > Set baz to 3.
95
+ >
96
+ > Set bax to 4.
97
+ """
98
+ parser = Parser()
99
+ program = parser.parse(source)
100
+
101
+ assert len(program.statements) == 1
102
+ outer_if = program.statements[0]
103
+ assert isinstance(outer_if, IfStatement)
104
+
105
+ assert isinstance(outer_if.consequence, BlockStatement)
106
+ assert outer_if.consequence.depth == 1
107
+ assert len(outer_if.consequence.statements) == 3
108
+
109
+ # First statement in outer block
110
+ assert isinstance(outer_if.consequence.statements[0], SetStatement)
111
+
112
+ # Nested if statement
113
+ inner_if = outer_if.consequence.statements[1]
114
+ assert isinstance(inner_if, IfStatement)
115
+ assert isinstance(inner_if.consequence, BlockStatement)
116
+ assert inner_if.consequence.depth == 2
117
+ assert len(inner_if.consequence.statements) == 2
118
+
119
+ # Last statement in outer block
120
+ assert isinstance(outer_if.consequence.statements[2], SetStatement)
121
+
122
+ @pytest.mark.parametrize(
123
+ "keyword,else_keyword",
124
+ [
125
+ ("if", "else"),
126
+ ("if", "otherwise"),
127
+ ("when", "else"),
128
+ ("when", "otherwise"),
129
+ ("whenever", "else"),
130
+ ("whenever", "otherwise"),
131
+ ],
132
+ )
133
+ def test_parse_if_keywords_variations(self, keyword: str, else_keyword: str) -> None:
134
+ """Test parsing of if statements with different keyword variations."""
135
+ source = f"""
136
+ {keyword} Yes then:
137
+ > Set x to 1.
138
+ {else_keyword}:
139
+ > Set x to 2.
140
+ """
141
+ parser = Parser()
142
+ program = parser.parse(source)
143
+
144
+ assert len(program.statements) == 1
145
+ assert isinstance(program.statements[0], IfStatement)
146
+
147
+ if_stmt = program.statements[0]
148
+ assert isinstance(if_stmt.condition, YesNoLiteral)
149
+ assert if_stmt.condition.value is True
150
+
151
+ assert isinstance(if_stmt.consequence, BlockStatement)
152
+ assert len(if_stmt.consequence.statements) == 1
153
+
154
+ assert if_stmt.alternative is not None
155
+ assert isinstance(if_stmt.alternative, BlockStatement)
156
+ assert len(if_stmt.alternative.statements) == 1
157
+
158
+ def test_parse_if_with_complex_condition(self) -> None:
159
+ """Test parsing of if statement with complex boolean expression."""
160
+ source = """
161
+ if x > 5 and y < 10 then:
162
+ > Set result to True.
163
+ """
164
+ parser = Parser()
165
+ program = parser.parse(source)
166
+
167
+ assert len(program.statements) == 1
168
+ assert isinstance(program.statements[0], IfStatement)
169
+
170
+ if_stmt = program.statements[0]
171
+ assert isinstance(if_stmt.condition, InfixExpression)
172
+ assert if_stmt.condition.operator == "and"
173
+
174
+ assert isinstance(if_stmt.consequence, BlockStatement)
175
+ assert len(if_stmt.consequence.statements) == 1
176
+
177
+ def test_parse_empty_blocks(self) -> None:
178
+ """Test parsing of if statement with empty blocks."""
179
+ source = """
180
+ if Yes then:
181
+ >
182
+ else:
183
+ >
184
+ """
185
+ parser = Parser()
186
+ program = parser.parse(source)
187
+
188
+ assert len(program.statements) == 1
189
+ assert isinstance(program.statements[0], IfStatement)
190
+
191
+ if_stmt = program.statements[0]
192
+ assert isinstance(if_stmt.consequence, BlockStatement)
193
+ assert len(if_stmt.consequence.statements) == 0
194
+
195
+ assert if_stmt.alternative is not None
196
+ assert isinstance(if_stmt.alternative, BlockStatement)
197
+ assert len(if_stmt.alternative.statements) == 0
198
+
199
+ def test_parse_if_without_then_keyword(self) -> None:
200
+ """Test parsing of if statement with colon directly after condition."""
201
+ source = """
202
+ if Yes:
203
+ > Set x to 1.
204
+ """
205
+ parser = Parser()
206
+ program = parser.parse(source)
207
+
208
+ assert len(program.statements) == 1
209
+ assert isinstance(program.statements[0], IfStatement)
210
+
211
+ if_stmt = program.statements[0]
212
+ assert isinstance(if_stmt.condition, YesNoLiteral)
213
+ assert isinstance(if_stmt.consequence, BlockStatement)
214
+ assert len(if_stmt.consequence.statements) == 1
215
+
216
+ def test_block_depth_tracking(self) -> None:
217
+ """Test that block depth is properly tracked and validated."""
218
+ source = """
219
+ if Yes then:
220
+ > Set x to 1.
221
+ > > Set y to 2.
222
+ """
223
+ parser = Parser()
224
+ parser.parse(source)
225
+
226
+ # This should produce an error - depth suddenly increases
227
+ assert len(parser.errors) > 0
228
+ assert any("depth" in str(error).lower() for error in parser.errors)
229
+
230
+ def test_missing_period_error(self) -> None:
231
+ """Test that missing periods generate appropriate errors."""
232
+ source = """
233
+ if Yes then:
234
+ > Set x to 1
235
+
236
+ x
237
+ """
238
+ parser = Parser()
239
+ parser.parse(source)
240
+
241
+ # Should have errors about missing period
242
+ assert len(parser.errors) > 0
243
+ assert any("period" in str(error).lower() or "TokenType.PUNCT_PERIOD" in str(error) for error in parser.errors)
244
+
245
+ def test_multiple_if_statements(self) -> None:
246
+ """Test parsing multiple if statements in sequence."""
247
+ source = """
248
+ if Yes then:
249
+ > Set x to 1.
250
+
251
+ if No then:
252
+ > Set y to 2.
253
+ else:
254
+ > Set y to 3.
255
+ """
256
+ parser = Parser()
257
+ program = parser.parse(source)
258
+
259
+ assert len(program.statements) == 2
260
+ assert all(isinstance(stmt, IfStatement) for stmt in program.statements)
261
+
262
+ # First if statement
263
+ if_stmt1 = program.statements[0]
264
+ assert isinstance(if_stmt1, IfStatement)
265
+ assert isinstance(if_stmt1.condition, YesNoLiteral)
266
+ assert if_stmt1.condition.value is True
267
+ assert if_stmt1.alternative is None
268
+
269
+ # Second if statement
270
+ if_stmt2 = program.statements[1]
271
+ assert isinstance(if_stmt2, IfStatement)
272
+ assert isinstance(if_stmt2.condition, YesNoLiteral)
273
+ assert if_stmt2.condition.value is False
274
+ assert if_stmt2.alternative is not None
275
+
276
+ def test_if_statements_with_empty_lines(self) -> None:
277
+ """Test parsing multiple if statements in sequence."""
278
+ source = """
279
+ if No then:
280
+ >
281
+ > Set y to 2.
282
+ >
283
+ else:
284
+ >
285
+ > Set y to 3.
286
+ >
287
+ """
288
+ parser = Parser()
289
+ program = parser.parse(source)
290
+
291
+ assert len(program.statements) == 1
292
+ assert all(isinstance(stmt, IfStatement) for stmt in program.statements)
293
+
294
+ # If statement
295
+ if_stmt = program.statements[0]
296
+ assert isinstance(if_stmt, IfStatement)
297
+ assert isinstance(if_stmt.condition, YesNoLiteral)
298
+ assert if_stmt.condition.value is False
299
+ assert if_stmt.alternative is not None
@@ -0,0 +1,86 @@
1
+ """Tests for parser handling of illegal tokens.
2
+
3
+ This module tests that the parser correctly reports MDSyntaxError
4
+ (not MDNameError) when encountering MISC_ILLEGAL tokens from the lexer.
5
+ """
6
+
7
+ from machine_dialect.errors.exceptions import MDSyntaxError
8
+ from machine_dialect.parser import Parser
9
+
10
+
11
+ class TestIllegalTokenHandling:
12
+ """Test that illegal tokens are reported as syntax errors."""
13
+
14
+ def test_malformed_underscore_literal_syntax_error(self) -> None:
15
+ """Test that malformed underscore literals produce syntax errors."""
16
+ source = 'Define `x` as Whole Number. Set `x` to _"unclosed.'
17
+ parser = Parser()
18
+
19
+ parser.parse(source)
20
+
21
+ # Should have one syntax error for the illegal token
22
+ assert len(parser.errors) == 1
23
+ assert isinstance(parser.errors[0], MDSyntaxError)
24
+ assert "Illegal token" in str(parser.errors[0])
25
+ assert '_"unclosed.' in str(parser.errors[0])
26
+
27
+ def test_multiple_underscores_with_number(self) -> None:
28
+ """Test that invalid underscore patterns produce syntax errors."""
29
+ source = "Define `x` as Whole Number. Set `x` to __42."
30
+ parser = Parser()
31
+
32
+ parser.parse(source)
33
+
34
+ # Should have one syntax error for the illegal token
35
+ assert len(parser.errors) == 1
36
+ assert isinstance(parser.errors[0], MDSyntaxError)
37
+ assert "Illegal token" in str(parser.errors[0])
38
+ assert "__42" in str(parser.errors[0])
39
+
40
+ def test_trailing_underscore_after_number(self) -> None:
41
+ """Test that numbers with trailing underscores produce syntax errors."""
42
+ source = "Define `x` as Whole Number. Set `x` to 42_."
43
+ parser = Parser()
44
+
45
+ parser.parse(source)
46
+
47
+ # Should have one syntax error for the illegal token
48
+ assert len(parser.errors) == 1
49
+ assert isinstance(parser.errors[0], MDSyntaxError)
50
+ assert "Illegal token" in str(parser.errors[0])
51
+ assert "42_" in str(parser.errors[0])
52
+
53
+ def test_incomplete_underscore_wrapped_number(self) -> None:
54
+ """Test that incomplete underscore-wrapped numbers produce syntax errors."""
55
+ source = "Define `x` as Whole Number. Set `x` to _42."
56
+ parser = Parser()
57
+
58
+ parser.parse(source)
59
+
60
+ # Should have one syntax error for the illegal token
61
+ assert len(parser.errors) == 1
62
+ assert isinstance(parser.errors[0], MDSyntaxError)
63
+ assert "Illegal token" in str(parser.errors[0])
64
+ assert "_42" in str(parser.errors[0])
65
+
66
+ def test_recovery_after_illegal_token(self) -> None:
67
+ """Test that parser recovers and continues after illegal tokens."""
68
+ source = "Define `x` as Whole Number. Define `y` as Whole Number. Set `x` to _42. Set `y` to _10_."
69
+ parser = Parser()
70
+
71
+ program = parser.parse(source)
72
+
73
+ # Should have one syntax error for the first illegal token
74
+ assert len(parser.errors) == 1
75
+ assert isinstance(parser.errors[0], MDSyntaxError)
76
+ assert "_42" in str(parser.errors[0])
77
+
78
+ # Should have parsed four statements (2 Define + 2 Set)
79
+ assert len(program.statements) == 4
80
+ # First two are Define statements
81
+ assert program.statements[0] is not None
82
+ assert program.statements[1] is not None
83
+ # Third statement (Set with _42) has an error
84
+ assert program.statements[2] is not None
85
+ # Fourth statement should be complete (10 is properly wrapped)
86
+ assert program.statements[3] is not None