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,187 @@
1
+ """Tests for error handling in use statements."""
2
+
3
+ from machine_dialect.ast import Arguments, CallStatement
4
+ from machine_dialect.parser import Parser
5
+
6
+
7
+ class TestCallStatementErrors:
8
+ """Test error handling for use statements."""
9
+
10
+ def test_call_missing_function_name(self) -> None:
11
+ """Test that use without function name produces an error."""
12
+ source = 'use with _"test"_.'
13
+
14
+ parser = Parser()
15
+ parser.parse(source, check_semantics=False)
16
+
17
+ assert len(parser.errors) > 0
18
+ error_messages = [str(err).lower() for err in parser.errors]
19
+ assert any(
20
+ ("expected identifier" in msg and "function name" in msg) or ("identifier" in msg and "got" in msg)
21
+ for msg in error_messages
22
+ ), f"Expected error about missing function name, got: {parser.errors}"
23
+
24
+ def test_call_with_invalid_function_name_type(self) -> None:
25
+ """Test that use with non-identifier function name produces an error."""
26
+ source = 'use _"not_an_identifier"_ with _"test"_.'
27
+
28
+ parser = Parser()
29
+ parser.parse(source, check_semantics=False)
30
+
31
+ assert len(parser.errors) > 0
32
+ error_messages = [str(err).lower() for err in parser.errors]
33
+ assert any("expected identifier" in msg or "function name" in msg for msg in error_messages), (
34
+ f"Expected error about invalid function name type, got: {parser.errors}"
35
+ )
36
+
37
+ def test_call_without_period(self) -> None:
38
+ """Test that use statement without period produces an error."""
39
+ source = "use `my_function`"
40
+
41
+ parser = Parser()
42
+ parser.parse(source, check_semantics=False)
43
+
44
+ assert len(parser.errors) > 0
45
+ error_messages = [str(err).lower() for err in parser.errors]
46
+ assert any("period" in msg or "punct_period" in msg for msg in error_messages), (
47
+ f"Expected error about missing period, got: {parser.errors}"
48
+ )
49
+
50
+ def test_call_with_invalid_argument_value(self) -> None:
51
+ """Test that use with truly invalid argument value produces an error."""
52
+ source = "use `my_function` with @#$."
53
+
54
+ parser = Parser()
55
+ parser.parse(source, check_semantics=False)
56
+
57
+ # Should have an error about invalid argument
58
+ assert len(parser.errors) > 0
59
+ error_messages = [str(err).lower() for err in parser.errors]
60
+ assert any("invalid" in msg or "no suitable parse function" in msg for msg in error_messages), (
61
+ f"Expected error about invalid argument, got: {parser.errors}"
62
+ )
63
+
64
+ def test_call_with_missing_comma_between_positional_args(self) -> None:
65
+ """Test that missing comma between arguments produces an error."""
66
+ source = 'use `my_function` with `param` _"value"_.'
67
+
68
+ parser = Parser()
69
+ program = parser.parse(source, check_semantics=False)
70
+
71
+ # Should have an error about missing comma
72
+ assert len(parser.errors) > 0
73
+ error_messages = [str(err).lower() for err in parser.errors]
74
+ assert any("comma" in msg or "unexpected" in msg for msg in error_messages), (
75
+ f"Expected error about missing comma, got: {parser.errors}"
76
+ )
77
+
78
+ # Should still parse both arguments (error recovery)
79
+ assert len(program.statements) == 1
80
+ call_stmt = program.statements[0]
81
+ assert isinstance(call_stmt, CallStatement)
82
+ if call_stmt.arguments:
83
+ # Should have parsed both as positional arguments despite the error
84
+ assert isinstance(call_stmt.arguments, Arguments)
85
+ assert len(call_stmt.arguments.positional) == 2
86
+
87
+ def test_call_with_empty_arguments(self) -> None:
88
+ """Test that use with 'with' but no arguments produces reasonable behavior."""
89
+ source = "use `my_function` with ."
90
+
91
+ parser = Parser()
92
+ program = parser.parse(source, check_semantics=False)
93
+
94
+ # This should either produce an error or create an empty arguments list
95
+ if len(parser.errors) > 0:
96
+ # If errors, should be about missing arguments
97
+ error_messages = [str(err).lower() for err in parser.errors]
98
+ assert any("invalid" in msg or "expected" in msg for msg in error_messages)
99
+ else:
100
+ # If no errors, should have empty arguments
101
+ assert len(program.statements) == 1
102
+ call_stmt = program.statements[0]
103
+ assert isinstance(call_stmt, CallStatement)
104
+ assert call_stmt.arguments is not None
105
+ assert isinstance(call_stmt.arguments, Arguments)
106
+ assert len(call_stmt.arguments.positional) == 0
107
+ assert len(call_stmt.arguments.named) == 0
108
+
109
+ def test_call_with_duplicate_named_arguments(self) -> None:
110
+ """Test behavior with duplicate named argument keys."""
111
+ source = 'use `my_function` where `param` is _"value1"_, `param` is _"value2"_.'
112
+
113
+ parser = Parser()
114
+ program = parser.parse(source, check_semantics=False)
115
+
116
+ # Parser currently doesn't check for duplicates, so this should parse successfully
117
+ # but both values should be present
118
+ assert len(parser.errors) == 0, f"Unexpected errors: {parser.errors}"
119
+ assert len(program.statements) == 1
120
+ call_stmt = program.statements[0]
121
+ assert isinstance(call_stmt, CallStatement)
122
+ assert call_stmt.arguments is not None
123
+ assert isinstance(call_stmt.arguments, Arguments)
124
+ assert len(call_stmt.arguments.named) == 2
125
+ # Both should have the same name
126
+ name1, _ = call_stmt.arguments.named[0]
127
+ name2, _ = call_stmt.arguments.named[1]
128
+ assert name1.value == "param"
129
+ assert name2.value == "param"
130
+
131
+ def test_call_with_missing_comma_between_arguments(self) -> None:
132
+ """Test that missing comma between arguments produces an error."""
133
+ source = 'use `my_function` with _"arg1"_ _"arg2"_.'
134
+
135
+ parser = Parser()
136
+ program = parser.parse(source, check_semantics=False)
137
+
138
+ # Should produce an error about missing comma
139
+ assert len(parser.errors) > 0
140
+ error_messages = [str(err).lower() for err in parser.errors]
141
+ assert any("comma" in msg or "unexpected" in msg for msg in error_messages), (
142
+ f"Expected error about missing comma, got: {parser.errors}"
143
+ )
144
+
145
+ # With error recovery, it should still parse both arguments
146
+ assert len(program.statements) == 1
147
+ call_stmt = program.statements[0]
148
+ assert isinstance(call_stmt, CallStatement)
149
+ if call_stmt.arguments:
150
+ # Should have parsed both arguments (with error recovery)
151
+ assert isinstance(call_stmt.arguments, Arguments)
152
+ assert len(call_stmt.arguments.positional) == 2
153
+
154
+ def test_call_with_trailing_comma(self) -> None:
155
+ """Test that trailing comma in arguments is handled gracefully."""
156
+ source = 'use `my_function` with _"arg1"_, _"arg2"_,.'
157
+
158
+ parser = Parser()
159
+ program = parser.parse(source, check_semantics=False)
160
+
161
+ # Trailing comma should be acceptable or produce a clear error
162
+ if len(parser.errors) == 0:
163
+ assert len(program.statements) == 1
164
+ call_stmt = program.statements[0]
165
+ assert isinstance(call_stmt, CallStatement)
166
+ assert call_stmt.arguments is not None
167
+ assert isinstance(call_stmt.arguments, Arguments)
168
+ assert len(call_stmt.arguments.positional) == 2
169
+
170
+ def test_call_with_mixed_valid_and_invalid_arguments(self) -> None:
171
+ """Test error recovery with mixed valid and invalid arguments."""
172
+ # Note: 'invalid' without backticks is actually a valid identifier argument
173
+ # To test truly invalid syntax, we need something that's not a valid token
174
+ source = 'use `my_function` with _"valid"_, , _42_.'
175
+
176
+ parser = Parser()
177
+ program = parser.parse(source, check_semantics=False)
178
+
179
+ # The double comma should cause parsing issues
180
+ # Parser should handle this gracefully
181
+ assert len(program.statements) == 1
182
+ call_stmt = program.statements[0]
183
+ assert isinstance(call_stmt, CallStatement)
184
+ # Should have parsed the valid arguments
185
+ if call_stmt.arguments:
186
+ assert isinstance(call_stmt.arguments, Arguments)
187
+ assert len(call_stmt.arguments.positional) >= 1 # At least the first valid one
@@ -0,0 +1,264 @@
1
+ """Test collection mutation statements in Machine Dialect™."""
2
+
3
+ from machine_dialect.ast import (
4
+ Identifier,
5
+ SetStatement,
6
+ UnorderedListLiteral,
7
+ WholeNumberLiteral,
8
+ )
9
+ from machine_dialect.ast.statements import CollectionMutationStatement
10
+ from machine_dialect.parser import Parser
11
+
12
+
13
+ class TestCollectionMutations:
14
+ """Test parsing of collection mutation operations."""
15
+
16
+ def test_add_to_list(self) -> None:
17
+ """Test Add operation on lists."""
18
+ source = """
19
+ Define `items` as Unordered List.
20
+ Set `items` to blank.
21
+ Add _4_ to `items`.
22
+ """
23
+ parser = Parser()
24
+ program = parser.parse(source)
25
+
26
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
27
+ assert len(program.statements) == 3
28
+
29
+ # Check Add statement
30
+ add_stmt = program.statements[2]
31
+ assert isinstance(add_stmt, CollectionMutationStatement)
32
+ assert add_stmt.operation == "add"
33
+ assert isinstance(add_stmt.collection, Identifier)
34
+ assert add_stmt.collection.value == "items"
35
+ assert isinstance(add_stmt.value, WholeNumberLiteral)
36
+ assert add_stmt.value.value == 4
37
+
38
+ def test_remove_from_list(self) -> None:
39
+ """Test Remove operation on lists."""
40
+ source = """
41
+ Define `items` as Unordered List.
42
+ Set `items` to:
43
+ - _2_.
44
+ Remove _2_ from `items`.
45
+ """
46
+ parser = Parser()
47
+ program = parser.parse(source)
48
+
49
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
50
+ assert len(program.statements) == 3
51
+
52
+ # Check Remove statement
53
+ remove_stmt = program.statements[2]
54
+ assert isinstance(remove_stmt, CollectionMutationStatement)
55
+ assert remove_stmt.operation == "remove"
56
+ assert isinstance(remove_stmt.collection, Identifier)
57
+ assert remove_stmt.collection.value == "items"
58
+ assert isinstance(remove_stmt.value, WholeNumberLiteral)
59
+ assert remove_stmt.value.value == 2
60
+
61
+ def test_set_item_with_ordinal(self) -> None:
62
+ """Test Set item using ordinal (first, second, third)."""
63
+ source = """
64
+ Define `items` as Unordered List.
65
+ Set `items` to:
66
+ - _1_.
67
+ - _2_.
68
+ Set the first item of `items` to _10_.
69
+ """
70
+ parser = Parser()
71
+ program = parser.parse(source)
72
+
73
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
74
+ assert len(program.statements) == 3
75
+
76
+ # Check Set item statement
77
+ set_stmt = program.statements[2]
78
+ assert isinstance(set_stmt, CollectionMutationStatement)
79
+ assert set_stmt.operation == "set"
80
+ assert isinstance(set_stmt.collection, Identifier)
81
+ assert set_stmt.collection.value == "items"
82
+ assert isinstance(set_stmt.value, WholeNumberLiteral)
83
+ assert set_stmt.value.value == 10
84
+ assert set_stmt.position == "first"
85
+ assert set_stmt.position_type == "ordinal"
86
+
87
+ def test_set_item_with_numeric_index(self) -> None:
88
+ """Test Set item using numeric index."""
89
+ source = """
90
+ Define `items` as Unordered List.
91
+ Set `items` to:
92
+ - _1_.
93
+ - _2_.
94
+ - _3_.
95
+ Set item _2_ of `items` to _20_.
96
+ """
97
+ parser = Parser()
98
+ program = parser.parse(source)
99
+
100
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
101
+ assert len(program.statements) == 3
102
+
103
+ # Check Set item statement
104
+ set_stmt = program.statements[2]
105
+ assert isinstance(set_stmt, CollectionMutationStatement)
106
+ assert set_stmt.operation == "set"
107
+ assert isinstance(set_stmt.collection, Identifier)
108
+ assert set_stmt.collection.value == "items"
109
+ assert isinstance(set_stmt.value, WholeNumberLiteral)
110
+ assert set_stmt.value.value == 20
111
+ # Position should be the parsed expression
112
+ assert isinstance(set_stmt.position, WholeNumberLiteral)
113
+ assert set_stmt.position.value == 2
114
+ assert set_stmt.position_type == "numeric"
115
+
116
+ def test_insert_at_position(self) -> None:
117
+ """Test Insert operation at specific position."""
118
+ source = """
119
+ Define `items` as Unordered List.
120
+ Set `items` to:
121
+ - _1_.
122
+ - _3_.
123
+ Insert _15_ at position _2_ in `items`.
124
+ """
125
+ parser = Parser()
126
+ program = parser.parse(source)
127
+
128
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
129
+ assert len(program.statements) == 3
130
+
131
+ # Check Insert statement
132
+ insert_stmt = program.statements[2]
133
+ assert isinstance(insert_stmt, CollectionMutationStatement)
134
+ assert insert_stmt.operation == "insert"
135
+ assert isinstance(insert_stmt.collection, Identifier)
136
+ assert insert_stmt.collection.value == "items"
137
+ assert isinstance(insert_stmt.value, WholeNumberLiteral)
138
+ assert insert_stmt.value.value == 15
139
+ assert isinstance(insert_stmt.position, WholeNumberLiteral)
140
+ assert insert_stmt.position.value == 2
141
+ assert insert_stmt.position_type == "numeric"
142
+
143
+ def test_multiple_ordinals(self) -> None:
144
+ """Test multiple ordinal Set operations."""
145
+ source = """
146
+ Define `items` as Ordered List.
147
+ Set `items` to:
148
+ 1. _1_.
149
+ 2. _2_.
150
+ 3. _3_.
151
+ 4. _4_.
152
+ Set the first item of `items` to _100_.
153
+ Set the second item of `items` to _200_.
154
+ Set the third item of `items` to _300_.
155
+ Set the last item of `items` to _999_.
156
+ """
157
+ parser = Parser()
158
+ program = parser.parse(source)
159
+
160
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
161
+ assert len(program.statements) == 6
162
+
163
+ # Check each Set statement
164
+ ordinals = ["first", "second", "third", "last"]
165
+ values = [100, 200, 300, 999]
166
+
167
+ for i, (ordinal, value) in enumerate(zip(ordinals, values, strict=True), 2):
168
+ set_stmt = program.statements[i]
169
+ assert isinstance(set_stmt, CollectionMutationStatement)
170
+ assert set_stmt.operation == "set"
171
+ assert set_stmt.position == ordinal
172
+ assert set_stmt.position_type == "ordinal"
173
+ assert isinstance(set_stmt.value, WholeNumberLiteral)
174
+ assert isinstance(set_stmt.value, WholeNumberLiteral)
175
+ assert set_stmt.value.value == value
176
+
177
+ def test_keywords_as_identifiers(self) -> None:
178
+ """Test that keywords can be used as variable names in collection contexts."""
179
+ source = """
180
+ Define `first` as Unordered List.
181
+ Set `first` to blank.
182
+ Add _1_ to `first`.
183
+ Define `items` as Unordered List.
184
+ Set `items` to blank.
185
+ Add _2_ to `items`.
186
+ """
187
+ parser = Parser()
188
+ program = parser.parse(source)
189
+
190
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
191
+ assert len(program.statements) == 6
192
+
193
+ # Check that both 'first' (a keyword) and 'items' work as identifiers
194
+ add_stmt1 = program.statements[2]
195
+ assert isinstance(add_stmt1, CollectionMutationStatement)
196
+ assert isinstance(add_stmt1.collection, Identifier)
197
+ assert add_stmt1.collection.value == "first"
198
+
199
+ add_stmt2 = program.statements[5]
200
+ assert isinstance(add_stmt2, CollectionMutationStatement)
201
+ assert isinstance(add_stmt2.collection, Identifier)
202
+ assert add_stmt2.collection.value == "items"
203
+
204
+ def test_set_without_the(self) -> None:
205
+ """Test that 'Set first item of' works without 'the'."""
206
+ source = """
207
+ Define `items` as Unordered List.
208
+ Set `items` to:
209
+ - _1_.
210
+ - _2_.
211
+ Set first item of `items` to _10_.
212
+ """
213
+ parser = Parser()
214
+ program = parser.parse(source)
215
+
216
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
217
+ assert len(program.statements) == 3
218
+
219
+ # Should parse the same as "Set the first item of"
220
+ set_stmt = program.statements[2]
221
+ assert isinstance(set_stmt, CollectionMutationStatement)
222
+ assert set_stmt.operation == "set"
223
+ assert set_stmt.position == "first"
224
+ assert set_stmt.position_type == "ordinal"
225
+
226
+ def test_complex_mutation_sequence(self) -> None:
227
+ """Test a complex sequence of list mutations."""
228
+ source = """
229
+ Define `shopping` as Unordered List.
230
+ Set `shopping` to:
231
+ - _"milk"_.
232
+ - _"bread"_.
233
+ - _"eggs"_.
234
+
235
+ Add _"butter"_ to `shopping`.
236
+ Remove _"bread"_ from `shopping`.
237
+ Set the first item of `shopping` to _"oat milk"_.
238
+ Insert _"cheese"_ at position _2_ in `shopping`.
239
+ """
240
+ parser = Parser()
241
+ program = parser.parse(source)
242
+
243
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
244
+ assert len(program.statements) == 6
245
+
246
+ # Verify the initial list
247
+ set_stmt = program.statements[1]
248
+ assert isinstance(set_stmt, SetStatement)
249
+ assert isinstance(set_stmt.value, UnorderedListLiteral)
250
+ assert len(set_stmt.value.elements) == 3
251
+
252
+ # Verify each mutation
253
+ mutations = program.statements[2:]
254
+ assert all(isinstance(stmt, CollectionMutationStatement) for stmt in mutations)
255
+
256
+ # Check operations
257
+ assert isinstance(mutations[0], CollectionMutationStatement)
258
+ assert isinstance(mutations[1], CollectionMutationStatement)
259
+ assert isinstance(mutations[2], CollectionMutationStatement)
260
+ assert isinstance(mutations[3], CollectionMutationStatement)
261
+ assert mutations[0].operation == "add"
262
+ assert mutations[1].operation == "remove"
263
+ assert mutations[2].operation == "set"
264
+ assert mutations[3].operation == "insert"