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,137 @@
1
+ """Tests for parsing integer 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 TestWholeNumberLiteralExpressions:
14
+ """Test cases for parsing integer literal expressions."""
15
+
16
+ @pytest.mark.parametrize(
17
+ "source,expected_value",
18
+ [
19
+ # Single digit integers
20
+ ("0", 0),
21
+ ("1", 1),
22
+ ("5", 5),
23
+ ("9", 9),
24
+ # Multi-digit integers
25
+ ("10", 10),
26
+ ("42", 42),
27
+ ("123", 123),
28
+ ("999", 999),
29
+ # Large integers
30
+ ("1000", 1000),
31
+ ("12345", 12345),
32
+ ("99999", 99999),
33
+ ("1000000", 1000000),
34
+ # Edge cases
35
+ ("2147483647", 2147483647), # Max 32-bit signed int
36
+ ("9223372036854775807", 9223372036854775807), # Max 64-bit signed int
37
+ # Integers with underscores (one on each side)
38
+ ("_42_", 42),
39
+ ("_123_", 123),
40
+ ("_1_", 1),
41
+ ("_999_", 999),
42
+ ("_12345_", 12345),
43
+ ("_0_", 0),
44
+ ],
45
+ )
46
+ def test_integer_literal_expression(self, source: str, expected_value: int) -> None:
47
+ """Test parsing various integer literal expressions.
48
+
49
+ Args:
50
+ source: The source code to parse.
51
+ expected_value: The expected integer value.
52
+ """
53
+ parser = Parser()
54
+
55
+ program = parser.parse(source)
56
+
57
+ assert_program_statements(parser, program)
58
+
59
+ statement = program.statements[0]
60
+ assert isinstance(statement, ExpressionStatement)
61
+ assert statement.expression is not None
62
+
63
+ assert_literal_expression(statement.expression, expected_value)
64
+
65
+ def test_integer_with_period(self) -> None:
66
+ """Test parsing integer literal followed by period."""
67
+ source = "42."
68
+ parser = Parser()
69
+
70
+ program = parser.parse(source)
71
+
72
+ assert_program_statements(parser, program)
73
+
74
+ statement = program.statements[0]
75
+ assert isinstance(statement, ExpressionStatement)
76
+ assert statement.expression is not None
77
+
78
+ assert_literal_expression(statement.expression, 42)
79
+
80
+ def test_multiple_integer_statements(self) -> None:
81
+ """Test parsing multiple integer literal statements."""
82
+ source = "1. 2. 3."
83
+ parser = Parser()
84
+
85
+ program = parser.parse(source)
86
+
87
+ assert len(parser.errors) == 0
88
+ assert len(program.statements) == 3
89
+
90
+ # Check each statement
91
+ for i, expected_value in enumerate([1, 2, 3]):
92
+ statement = program.statements[i]
93
+ assert isinstance(statement, ExpressionStatement)
94
+ assert statement.expression is not None
95
+ assert_literal_expression(statement.expression, expected_value)
96
+
97
+ @pytest.mark.parametrize(
98
+ "source,error_substring",
99
+ [
100
+ # Underscore only on left side
101
+ ("_42", "_42"),
102
+ ("_123", "_123"),
103
+ ("_1", "_1"),
104
+ # Underscore only on right side
105
+ ("42_", "42_"),
106
+ ("123_", "123_"),
107
+ ("1_", "1_"),
108
+ # Multiple underscores
109
+ ("__42__", "__42__"),
110
+ ("___123___", "___123___"),
111
+ ("__1__", "__1__"),
112
+ # Mixed multiple and single
113
+ ("__42_", "__42_"),
114
+ ("_42__", "_42__"),
115
+ ],
116
+ )
117
+ def test_invalid_underscore_formats_produce_errors(self, source: str, error_substring: str) -> None:
118
+ """Test that invalid underscore formats produce lexer errors.
119
+
120
+ Args:
121
+ source: The source code with invalid underscore format.
122
+ error_substring: Expected substring in the error message.
123
+ """
124
+ # Lexer instantiation moved to Parser.parse()
125
+ parser = Parser()
126
+
127
+ # Parse the program (parser collects lexer errors)
128
+ _ = parser.parse(source)
129
+
130
+ # Should have at least one error
131
+ assert len(parser.errors) >= 1, f"Expected error for invalid format: {source}"
132
+
133
+ # Check that the error mentions the invalid token
134
+ error_messages = [str(error) for error in parser.errors]
135
+ assert any(error_substring in msg for msg in error_messages), (
136
+ f"Expected error to mention '{error_substring}', got errors: {error_messages}"
137
+ )
@@ -0,0 +1,269 @@
1
+ """Tests for Interaction statements (public methods) in Machine Dialect™."""
2
+
3
+ from machine_dialect.ast import (
4
+ BlockStatement,
5
+ EmptyLiteral,
6
+ IfStatement,
7
+ InteractionStatement,
8
+ Output,
9
+ )
10
+ from machine_dialect.parser import Parser
11
+
12
+
13
+ class TestInteractionStatements:
14
+ """Test parsing of Interaction statements (public methods)."""
15
+
16
+ def test_simple_interaction_without_parameters(self) -> None:
17
+ """Test parsing a simple interaction without parameters."""
18
+ source = """### **Interaction**: `turn alarm off`
19
+
20
+ <details>
21
+ <summary>Turns off the alarm when it is on.</summary>
22
+
23
+ > Define `alarm is on` as Yes/No.
24
+ > Set `alarm is on` to _Yes_.
25
+ > **if** `alarm is on` **then**:
26
+ > >
27
+ > > **Set** `alarm is on` **to** _No_.
28
+ > > Say _"Alarm has been turned off"_.
29
+
30
+ </details>"""
31
+
32
+ parser = Parser()
33
+ program = parser.parse(source, check_semantics=False)
34
+
35
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
36
+ assert len(program.statements) == 1
37
+
38
+ interaction_stmt = program.statements[0]
39
+ assert isinstance(interaction_stmt, InteractionStatement)
40
+ assert interaction_stmt.name.value == "turn alarm off"
41
+ assert len(interaction_stmt.inputs) == 0
42
+ assert len(interaction_stmt.outputs) == 0
43
+ assert isinstance(interaction_stmt.body, BlockStatement)
44
+
45
+ # The body should contain define + set + if statement
46
+ assert len(interaction_stmt.body.statements) == 3
47
+ if_stmt = interaction_stmt.body.statements[2]
48
+ assert isinstance(if_stmt, IfStatement)
49
+
50
+ # The if statement should have a consequence block with 2 statements
51
+ assert isinstance(if_stmt.consequence, BlockStatement)
52
+ assert len(if_stmt.consequence.statements) == 2
53
+
54
+ def test_interaction_with_heading_level(self) -> None:
55
+ """Test that interaction heading level (###) is parsed correctly."""
56
+ source = """### **Interaction**: `get status`
57
+
58
+ <details>
59
+ <summary>Returns the current status.</summary>
60
+
61
+ > Say _"Status: OK"_.
62
+
63
+ </details>"""
64
+
65
+ parser = Parser()
66
+ program = parser.parse(source, check_semantics=False)
67
+
68
+ assert len(parser.errors) == 0
69
+ assert len(program.statements) == 1
70
+
71
+ interaction_stmt = program.statements[0]
72
+ assert isinstance(interaction_stmt, InteractionStatement)
73
+ assert interaction_stmt.name.value == "get status"
74
+
75
+ def test_interaction_with_multi_word_name(self) -> None:
76
+ """Test interaction with multi-word name in backticks."""
77
+ source = """### **Interaction**: `check system health`
78
+
79
+ <details>
80
+ <summary>Checks if the system is healthy.</summary>
81
+
82
+ > Define `health` as Text.
83
+ > Set `health` to _"Good"_.
84
+ > Say `health`.
85
+
86
+ </details>"""
87
+
88
+ parser = Parser()
89
+ program = parser.parse(source, check_semantics=False)
90
+
91
+ assert len(parser.errors) == 0
92
+ assert len(program.statements) == 1
93
+
94
+ interaction_stmt = program.statements[0]
95
+ assert isinstance(interaction_stmt, InteractionStatement)
96
+ assert interaction_stmt.name.value == "check system health"
97
+
98
+ def test_interaction_plural_form(self) -> None:
99
+ """Test that 'Interactions' keyword also works."""
100
+ source = """### **Interactions**: `say hello`
101
+
102
+ <details>
103
+ <summary>Greets the user.</summary>
104
+
105
+ > Say _"Hello!"_.
106
+
107
+ </details>"""
108
+
109
+ parser = Parser()
110
+ program = parser.parse(source, check_semantics=False)
111
+
112
+ assert len(parser.errors) == 0
113
+ assert len(program.statements) == 1
114
+
115
+ interaction_stmt = program.statements[0]
116
+ assert isinstance(interaction_stmt, InteractionStatement)
117
+ assert interaction_stmt.name.value == "say hello"
118
+
119
+ def test_interaction_with_empty_body(self) -> None:
120
+ """Test interaction with no statements in body."""
121
+ source = """### **Interaction**: `noop`
122
+
123
+ <details>
124
+ <summary>No operation.</summary>
125
+
126
+ </details>"""
127
+
128
+ parser = Parser()
129
+ program = parser.parse(source, check_semantics=False)
130
+
131
+ assert len(parser.errors) == 0
132
+ assert len(program.statements) == 1
133
+
134
+ interaction_stmt = program.statements[0]
135
+ assert isinstance(interaction_stmt, InteractionStatement)
136
+ assert interaction_stmt.name.value == "noop"
137
+ assert len(interaction_stmt.body.statements) == 0
138
+
139
+ def test_multiple_interactions(self) -> None:
140
+ """Test parsing multiple interactions in one program."""
141
+ source = """### **Interaction**: `start process`
142
+
143
+ <details>
144
+ <summary>Starts the process.</summary>
145
+
146
+ > Define `running` as Yes/No.
147
+ > Set `running` to _Yes_.
148
+
149
+ </details>
150
+
151
+ ### **Interaction**: `stop process`
152
+
153
+ <details>
154
+ <summary>Stops the process.</summary>
155
+
156
+ > Define `is_running` as Yes/No.
157
+ > Set `is_running` to _No_.
158
+
159
+ </details>"""
160
+
161
+ parser = Parser()
162
+ program = parser.parse(source, check_semantics=False)
163
+
164
+ assert len(parser.errors) == 0
165
+ assert len(program.statements) == 2
166
+
167
+ first_interaction = program.statements[0]
168
+ assert isinstance(first_interaction, InteractionStatement)
169
+ assert first_interaction.name.value == "start process"
170
+
171
+ second_interaction = program.statements[1]
172
+ assert isinstance(second_interaction, InteractionStatement)
173
+ assert second_interaction.name.value == "stop process"
174
+
175
+ def test_interaction_with_parameters(self) -> None:
176
+ """Test parsing an interaction with input and output parameters."""
177
+ source = """### **Interaction**: `get user info`
178
+
179
+ <details>
180
+ <summary>Gets user information.</summary>
181
+
182
+ > Define `user` as Text.
183
+ > Define `age` as Number.
184
+ > Set `user` to _"John Doe"_.
185
+ > Set `age` to _25_.
186
+ > Give back `user`.
187
+
188
+ </details>
189
+
190
+ #### Inputs:
191
+ - `userId` **as** Text (required)
192
+
193
+ #### Outputs:
194
+ - `user` **as** Text
195
+ - `age` **as** Number"""
196
+
197
+ parser = Parser()
198
+ program = parser.parse(source, check_semantics=False)
199
+
200
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
201
+ assert len(program.statements) == 1
202
+
203
+ interaction_stmt = program.statements[0]
204
+ assert isinstance(interaction_stmt, InteractionStatement)
205
+ assert interaction_stmt.name.value == "get user info"
206
+
207
+ # Check inputs
208
+ assert len(interaction_stmt.inputs) == 1
209
+ input_param = interaction_stmt.inputs[0]
210
+ assert input_param.name.value == "userId"
211
+ assert input_param.type_name == "Text"
212
+ assert input_param.is_required is True
213
+
214
+ # Check outputs
215
+ assert len(interaction_stmt.outputs) == 2
216
+
217
+ user_param = interaction_stmt.outputs[0]
218
+ assert isinstance(user_param, Output)
219
+ assert user_param.name.value == "user"
220
+ assert user_param.type_name == "Text"
221
+ # All outputs have Empty as default when not specified
222
+ assert isinstance(user_param.default_value, EmptyLiteral)
223
+
224
+ age_param = interaction_stmt.outputs[1]
225
+ assert isinstance(age_param, Output)
226
+ assert age_param.name.value == "age"
227
+ assert age_param.type_name == "Number"
228
+ # All outputs have Empty as default when not specified
229
+ assert isinstance(age_param.default_value, EmptyLiteral)
230
+
231
+ def test_mixed_actions_and_interactions(self) -> None:
232
+ """Test parsing both actions and interactions in same program."""
233
+ source = """### **Action**: `internal process`
234
+
235
+ <details>
236
+ <summary>Internal processing.</summary>
237
+
238
+ > Define `data` as Text.
239
+ > Set `data` to _"processed"_.
240
+
241
+ </details>
242
+
243
+ ### **Interaction**: `get data`
244
+
245
+ <details>
246
+ <summary>Returns processed data.</summary>
247
+
248
+ > Define `current_data` as Text.
249
+ > Say `current_data`.
250
+
251
+ </details>"""
252
+
253
+ parser = Parser()
254
+ program = parser.parse(source, check_semantics=False)
255
+
256
+ assert len(parser.errors) == 0
257
+ assert len(program.statements) == 2
258
+
259
+ # First should be an Action
260
+ from machine_dialect.ast import ActionStatement
261
+
262
+ action = program.statements[0]
263
+ assert isinstance(action, ActionStatement)
264
+ assert action.name.value == "internal process"
265
+
266
+ # Second should be an Interaction
267
+ interaction = program.statements[1]
268
+ assert isinstance(interaction, InteractionStatement)
269
+ assert interaction.name.value == "get data"
@@ -0,0 +1,277 @@
1
+ """Test parsing of list literals (unordered, ordered, and named)."""
2
+
3
+ import pytest
4
+
5
+ from machine_dialect.ast import (
6
+ FloatLiteral,
7
+ NamedListLiteral,
8
+ OrderedListLiteral,
9
+ SetStatement,
10
+ StringLiteral,
11
+ UnorderedListLiteral,
12
+ WholeNumberLiteral,
13
+ )
14
+ from machine_dialect.parser import Parser
15
+
16
+
17
+ class TestUnorderedLists:
18
+ """Test parsing of unordered lists (dash-prefixed)."""
19
+
20
+ def test_simple_unordered_list(self) -> None:
21
+ """Test parsing a simple unordered list."""
22
+ source = """
23
+ Define `fruits` as an unordered list.
24
+ Set `fruits` to:
25
+ - _"apple"_.
26
+ - _"banana"_.
27
+ - _"cherry"_.
28
+ """
29
+ parser = Parser()
30
+ program = parser.parse(source)
31
+
32
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
33
+ assert len(program.statements) == 2 # Define and Set statements
34
+
35
+ set_stmt = program.statements[1] # Second statement is the Set
36
+ assert isinstance(set_stmt, SetStatement)
37
+ assert set_stmt.name and set_stmt.name.value == "fruits"
38
+
39
+ # Check the list literal
40
+ assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, UnorderedListLiteral)
41
+ list_literal = set_stmt.value
42
+ assert len(list_literal.elements) == 3
43
+
44
+ # Check elements
45
+ assert isinstance(list_literal.elements[0], StringLiteral)
46
+ assert list_literal.elements[0].value == "apple"
47
+ assert isinstance(list_literal.elements[1], StringLiteral)
48
+ assert list_literal.elements[1].value == "banana"
49
+ assert isinstance(list_literal.elements[2], StringLiteral)
50
+ assert list_literal.elements[2].value == "cherry"
51
+
52
+ def test_mixed_type_unordered_list(self) -> None:
53
+ """Test parsing an unordered list with mixed types."""
54
+ source = """
55
+ Define `mixed` as unordered list.
56
+ Set `mixed` to:
57
+ - _"text"_.
58
+ - _42_.
59
+ - _3.14_.
60
+ """
61
+ parser = Parser()
62
+ program = parser.parse(source)
63
+
64
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
65
+ assert len(program.statements) == 2
66
+
67
+ set_stmt = program.statements[1]
68
+ assert isinstance(set_stmt, SetStatement)
69
+
70
+ # Check the list literal
71
+ assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, UnorderedListLiteral)
72
+ list_literal = set_stmt.value
73
+ assert len(list_literal.elements) == 3
74
+
75
+ # Check mixed types
76
+ assert isinstance(list_literal.elements[0], StringLiteral)
77
+ assert list_literal.elements[0].value == "text"
78
+ assert isinstance(list_literal.elements[1], WholeNumberLiteral)
79
+ assert list_literal.elements[1].value == 42
80
+ assert isinstance(list_literal.elements[2], FloatLiteral)
81
+ assert list_literal.elements[2].value == 3.14
82
+
83
+ def test_list_with_negative_numbers(self) -> None:
84
+ """Test that lists can contain negative numbers."""
85
+ source = """
86
+ Define `numbers` as unordered list.
87
+ Set `numbers` to:
88
+ - _-5_.
89
+ - _10_.
90
+ - _-3.14_.
91
+ """
92
+ parser = Parser()
93
+ program = parser.parse(source)
94
+
95
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
96
+ assert len(program.statements) == 2
97
+
98
+ set_stmt = program.statements[1]
99
+ assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, UnorderedListLiteral)
100
+ list_literal = set_stmt.value
101
+ assert len(list_literal.elements) == 3
102
+
103
+ # Check negative numbers
104
+ assert isinstance(list_literal.elements[0], WholeNumberLiteral)
105
+ assert list_literal.elements[0].value == -5
106
+ assert isinstance(list_literal.elements[1], WholeNumberLiteral)
107
+ assert list_literal.elements[1].value == 10
108
+ assert isinstance(list_literal.elements[2], FloatLiteral)
109
+ assert list_literal.elements[2].value == -3.14
110
+
111
+
112
+ class TestOrderedLists:
113
+ """Test parsing of ordered lists (numbered)."""
114
+
115
+ def test_simple_ordered_list(self) -> None:
116
+ """Test parsing a simple ordered list."""
117
+ source = """
118
+ Define `steps` as ordered list.
119
+ Set `steps` to:
120
+ 1. _"First step"_.
121
+ 2. _"Second step"_.
122
+ 3. _"Third step"_.
123
+ """
124
+ parser = Parser()
125
+ program = parser.parse(source)
126
+
127
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
128
+ assert len(program.statements) == 2
129
+
130
+ set_stmt = program.statements[1]
131
+ assert isinstance(set_stmt, SetStatement)
132
+ assert set_stmt.name and set_stmt.name.value == "steps"
133
+
134
+ # Check the list literal
135
+ assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, OrderedListLiteral)
136
+ list_literal = set_stmt.value
137
+ assert len(list_literal.elements) == 3
138
+
139
+ # Check elements
140
+ assert isinstance(list_literal.elements[0], StringLiteral)
141
+ assert list_literal.elements[0].value == "First step"
142
+ assert isinstance(list_literal.elements[1], StringLiteral)
143
+ assert list_literal.elements[1].value == "Second step"
144
+ assert isinstance(list_literal.elements[2], StringLiteral)
145
+ assert list_literal.elements[2].value == "Third step"
146
+
147
+ def test_non_sequential_ordered_list(self) -> None:
148
+ """Test that ordered lists can have non-sequential numbers."""
149
+ source = """
150
+ Define `priorities` as ordered list.
151
+ Set `priorities` to:
152
+ 1. _"High priority"_.
153
+ 5. _"Medium priority"_.
154
+ 10. _"Low priority"_.
155
+ """
156
+ parser = Parser()
157
+ program = parser.parse(source)
158
+
159
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
160
+ assert len(program.statements) == 2
161
+
162
+ set_stmt = program.statements[1]
163
+ assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, OrderedListLiteral)
164
+ list_literal = set_stmt.value
165
+ assert len(list_literal.elements) == 3
166
+
167
+
168
+ class TestNamedLists:
169
+ """Test parsing of named lists (dictionaries)."""
170
+
171
+ def test_simple_named_list(self) -> None:
172
+ """Test parsing a simple named list."""
173
+ source = """
174
+ Define `person` as named list.
175
+ Set `person` to:
176
+ - _"name"_: _"Alice"_.
177
+ - _"profession"_: _"Software Engineer"_.
178
+ - _"age"_: _30_.
179
+ """
180
+ parser = Parser()
181
+ program = parser.parse(source)
182
+
183
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
184
+ assert len(program.statements) == 2
185
+
186
+ set_stmt = program.statements[1]
187
+ assert isinstance(set_stmt, SetStatement)
188
+ assert set_stmt.name and set_stmt.name.value == "person"
189
+
190
+ # Check the list literal
191
+ assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, NamedListLiteral)
192
+ list_literal = set_stmt.value
193
+ assert len(list_literal.entries) == 3
194
+
195
+ # Check name-value pairs
196
+ assert list_literal.entries[0][0] == "name"
197
+ assert isinstance(list_literal.entries[0][1], StringLiteral)
198
+ assert list_literal.entries[0][1].value == "Alice"
199
+
200
+ assert list_literal.entries[1][0] == "profession"
201
+ assert isinstance(list_literal.entries[1][1], StringLiteral)
202
+ assert list_literal.entries[1][1].value == "Software Engineer"
203
+
204
+ assert list_literal.entries[2][0] == "age"
205
+ assert isinstance(list_literal.entries[2][1], WholeNumberLiteral)
206
+ assert list_literal.entries[2][1].value == 30
207
+
208
+
209
+ class TestListParsingErrors:
210
+ """Test error handling in list parsing."""
211
+
212
+ @pytest.mark.parametrize(
213
+ "source",
214
+ [
215
+ """
216
+ Define `mixed` as list.
217
+ Set `mixed` to:
218
+ - _"unordered item"_.
219
+ 1. _"ordered item"_.
220
+ """,
221
+ """
222
+ Define `mixed` as list.
223
+ Set `mixed` to:
224
+ 1. _"unordered item"_.
225
+ - _"ordered item"_.
226
+ """,
227
+ ],
228
+ )
229
+ def test_mixed_list_types_not_allowed(self, source: str) -> None:
230
+ """Test that mixing list types is not allowed."""
231
+ parser = Parser()
232
+ program = parser.parse(source)
233
+
234
+ # The parser should handle mixed list types gracefully
235
+ # It may produce an error expression or a partial list
236
+ assert len(program.statements) >= 2 # At least Define and Set
237
+
238
+ set_stmt = program.statements[1]
239
+ assert isinstance(set_stmt, SetStatement)
240
+
241
+ # Check what the parser produced
242
+ if hasattr(set_stmt, "value"):
243
+ # If it's an ErrorExpression, that's acceptable
244
+ from machine_dialect.ast import ErrorExpression
245
+
246
+ if isinstance(set_stmt.value, ErrorExpression):
247
+ # Parser correctly identified an error
248
+ pass
249
+ elif isinstance(set_stmt.value, UnorderedListLiteral | OrderedListLiteral):
250
+ # Parser created a list with only the consistent items
251
+ list_literal = set_stmt.value
252
+ # Should have at most the first type's items
253
+ assert len(list_literal.elements) <= 1
254
+
255
+ # The main requirement is that the parser doesn't crash
256
+ # and produces some reasonable output
257
+ assert program is not None
258
+
259
+ def test_incomplete_named_list(self) -> None:
260
+ """Test parsing incomplete named list elements."""
261
+ source = """
262
+ Define `incomplete` as named list.
263
+ Set `incomplete` to:
264
+ - "name": _"test"_.
265
+ """
266
+ parser = Parser()
267
+ program = parser.parse(source)
268
+
269
+ # Should still create a named list
270
+ assert len(program.statements) == 2
271
+ set_stmt = program.statements[1]
272
+ assert isinstance(set_stmt, SetStatement)
273
+
274
+ # Should be a named list with error for missing content
275
+ assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, NamedListLiteral)
276
+ list_literal = set_stmt.value
277
+ assert len(list_literal.entries) == 1