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,354 @@
1
+ """Tests for the AI-based Machine Dialect™ code generation module."""
2
+
3
+ from unittest.mock import MagicMock, Mock, mock_open, patch
4
+
5
+ import pytest
6
+
7
+ from machine_dialect.cfg.generate_with_ai import generate_code, main
8
+
9
+
10
+ class TestGenerateCode:
11
+ """Test the generate_code function."""
12
+
13
+ @patch("machine_dialect.cfg.generate_with_ai.ConfigLoader")
14
+ @patch("machine_dialect.cfg.generate_with_ai.CFGParser")
15
+ def test_generate_code_with_valid_config(self, mock_parser_class: Mock, mock_loader_class: Mock) -> None:
16
+ """Test successful code generation with valid configuration."""
17
+ # Setup mocks
18
+ mock_loader = mock_loader_class.return_value
19
+ mock_config = MagicMock()
20
+ mock_config.key = "test-api-key"
21
+ mock_config.model = "gpt-3.5-turbo"
22
+ mock_loader.load.return_value = mock_config
23
+
24
+ mock_parser = mock_parser_class.return_value
25
+ mock_parser.validate.return_value = True
26
+
27
+ # Call function
28
+ result = generate_code(
29
+ task="calculate area",
30
+ temperature=0.5,
31
+ max_tokens=300,
32
+ validate=True,
33
+ )
34
+
35
+ # Verify result contains example code
36
+ assert "Set `width` to" in result
37
+ assert "Set `height` to" in result
38
+ assert "Set `area` to" in result
39
+
40
+ # Verify mocks were called
41
+ mock_loader.load.assert_called_once()
42
+ mock_parser.validate.assert_called_once()
43
+
44
+ @patch("machine_dialect.cfg.generate_with_ai.ConfigLoader")
45
+ @patch("machine_dialect.cfg.generate_with_ai.CFGParser")
46
+ def test_generate_code_with_api_key_override(self, mock_parser_class: Mock, mock_loader_class: Mock) -> None:
47
+ """Test that API key parameter overrides config."""
48
+ # Setup mocks
49
+ mock_loader = mock_loader_class.return_value
50
+ mock_config = MagicMock()
51
+ mock_config.key = "config-api-key"
52
+ mock_config.model = "gpt-3.5-turbo"
53
+ mock_loader.load.return_value = mock_config
54
+
55
+ mock_parser = mock_parser_class.return_value
56
+ mock_parser.validate.return_value = True
57
+
58
+ # Call with API key override
59
+ result = generate_code(
60
+ task="test task",
61
+ api_key="override-api-key",
62
+ validate=True,
63
+ )
64
+
65
+ # Verify the config was overridden
66
+ assert mock_config.key == "override-api-key"
67
+ assert result is not None
68
+
69
+ @patch("machine_dialect.cfg.generate_with_ai.ConfigLoader")
70
+ @patch("machine_dialect.cfg.generate_with_ai.CFGParser")
71
+ def test_generate_code_with_model_override(self, mock_parser_class: Mock, mock_loader_class: Mock) -> None:
72
+ """Test that model parameter overrides config."""
73
+ # Setup mocks
74
+ mock_loader = mock_loader_class.return_value
75
+ mock_config = MagicMock()
76
+ mock_config.key = "test-api-key"
77
+ mock_config.model = "gpt-3.5-turbo"
78
+ mock_loader.load.return_value = mock_config
79
+
80
+ mock_parser = mock_parser_class.return_value
81
+ mock_parser.validate.return_value = True
82
+
83
+ # Call with model override
84
+ result = generate_code(
85
+ task="test task",
86
+ model="gpt-4",
87
+ validate=True,
88
+ )
89
+
90
+ # Verify the config was overridden
91
+ assert mock_config.model == "gpt-4"
92
+ assert result is not None
93
+
94
+ @patch("machine_dialect.cfg.generate_with_ai.ConfigLoader")
95
+ def test_generate_code_missing_api_key(self, mock_loader_class: Mock) -> None:
96
+ """Test error when API key is not configured."""
97
+ # Setup mocks - no API key
98
+ mock_loader = mock_loader_class.return_value
99
+ mock_config = MagicMock()
100
+ mock_config.key = None
101
+ mock_config.model = "gpt-3.5-turbo"
102
+ mock_loader.load.return_value = mock_config
103
+ mock_loader.get_error_message.return_value = "Please configure API key"
104
+
105
+ # Should raise ValueError
106
+ with pytest.raises(ValueError, match="Please configure API key"):
107
+ generate_code(task="test task")
108
+
109
+ @patch("machine_dialect.cfg.generate_with_ai.ConfigLoader")
110
+ def test_generate_code_missing_model(self, mock_loader_class: Mock) -> None:
111
+ """Test error when model is not configured."""
112
+ # Setup mocks - no model
113
+ mock_loader = mock_loader_class.return_value
114
+ mock_config = MagicMock()
115
+ mock_config.key = "test-api-key"
116
+ mock_config.model = None
117
+ mock_loader.load.return_value = mock_config
118
+ mock_loader.get_error_message.return_value = "Please configure model"
119
+
120
+ # Should raise ValueError
121
+ with pytest.raises(ValueError, match="No AI model configured"):
122
+ generate_code(task="test task")
123
+
124
+ @patch("machine_dialect.cfg.generate_with_ai.ConfigLoader")
125
+ @patch("machine_dialect.cfg.generate_with_ai.CFGParser")
126
+ @patch("builtins.print")
127
+ def test_generate_code_without_validation(
128
+ self, mock_print: Mock, mock_parser_class: Mock, mock_loader_class: Mock
129
+ ) -> None:
130
+ """Test code generation without validation."""
131
+ # Setup mocks
132
+ mock_loader = mock_loader_class.return_value
133
+ mock_config = MagicMock()
134
+ mock_config.key = "test-api-key"
135
+ mock_config.model = "gpt-3.5-turbo"
136
+ mock_loader.load.return_value = mock_config
137
+
138
+ mock_parser = mock_parser_class.return_value
139
+
140
+ # Call without validation
141
+ result = generate_code(
142
+ task="test task",
143
+ validate=False,
144
+ )
145
+
146
+ # Verify parser was not instantiated/called
147
+ mock_parser.validate.assert_not_called()
148
+ assert result is not None
149
+
150
+ @patch("machine_dialect.cfg.generate_with_ai.ConfigLoader")
151
+ @patch("machine_dialect.cfg.generate_with_ai.CFGParser")
152
+ @patch("builtins.print")
153
+ def test_generate_code_with_invalid_syntax(
154
+ self, mock_print: Mock, mock_parser_class: Mock, mock_loader_class: Mock
155
+ ) -> None:
156
+ """Test code generation when validation fails."""
157
+ # Setup mocks
158
+ mock_loader = mock_loader_class.return_value
159
+ mock_config = MagicMock()
160
+ mock_config.key = "test-api-key"
161
+ mock_config.model = "gpt-3.5-turbo"
162
+ mock_loader.load.return_value = mock_config
163
+
164
+ mock_parser = mock_parser_class.return_value
165
+ mock_parser.validate.return_value = False
166
+
167
+ # Call with validation
168
+ result = generate_code(
169
+ task="test task",
170
+ validate=True,
171
+ )
172
+
173
+ # Verify validation was attempted and failed message printed
174
+ mock_parser.validate.assert_called_once()
175
+ # Check that error message was printed
176
+ print_calls = [str(call) for call in mock_print.call_args_list]
177
+ assert any("✗ Generated code has syntax errors" in str(call) for call in print_calls)
178
+ assert result is not None
179
+
180
+ @patch("machine_dialect.cfg.generate_with_ai.ConfigLoader")
181
+ @patch("machine_dialect.cfg.generate_with_ai.CFGParser")
182
+ @patch("builtins.print")
183
+ def test_generate_code_temperature_and_tokens(
184
+ self, mock_print: Mock, mock_parser_class: Mock, mock_loader_class: Mock
185
+ ) -> None:
186
+ """Test that temperature and max_tokens parameters are used."""
187
+ # Setup mocks
188
+ mock_loader = mock_loader_class.return_value
189
+ mock_config = MagicMock()
190
+ mock_config.key = "test-api-key"
191
+ mock_config.model = "gpt-4"
192
+ mock_loader.load.return_value = mock_config
193
+
194
+ mock_parser = mock_parser_class.return_value
195
+ mock_parser.validate.return_value = True
196
+
197
+ # Call with custom temperature and tokens
198
+ result = generate_code(
199
+ task="complex task",
200
+ temperature=0.2,
201
+ max_tokens=1000,
202
+ validate=True,
203
+ )
204
+
205
+ # Verify parameters were printed
206
+ print_calls = [str(call) for call in mock_print.call_args_list]
207
+ assert any("Temperature: 0.2" in str(call) for call in print_calls)
208
+ assert any("Max tokens: 1000" in str(call) for call in print_calls)
209
+ assert result is not None
210
+
211
+
212
+ class TestMain:
213
+ """Test the main function."""
214
+
215
+ @patch("sys.argv", ["prog", "calculate area"])
216
+ @patch("machine_dialect.cfg.generate_with_ai.generate_code")
217
+ def test_main_basic_task(self, mock_generate: Mock) -> None:
218
+ """Test main with basic task argument."""
219
+ mock_generate.return_value = "Generated code"
220
+
221
+ result = main()
222
+
223
+ assert result == 0
224
+ mock_generate.assert_called_once_with(
225
+ task="calculate area",
226
+ api_key=None,
227
+ model=None,
228
+ temperature=0.7,
229
+ max_tokens=500,
230
+ validate=True,
231
+ )
232
+
233
+ @patch("sys.argv", ["prog", "test task", "--api-key", "my-key", "--model", "gpt-4"])
234
+ @patch("machine_dialect.cfg.generate_with_ai.generate_code")
235
+ def test_main_with_overrides(self, mock_generate: Mock) -> None:
236
+ """Test main with API key and model overrides."""
237
+ mock_generate.return_value = "Generated code"
238
+
239
+ result = main()
240
+
241
+ assert result == 0
242
+ mock_generate.assert_called_once_with(
243
+ task="test task",
244
+ api_key="my-key",
245
+ model="gpt-4",
246
+ temperature=0.7,
247
+ max_tokens=500,
248
+ validate=True,
249
+ )
250
+
251
+ @patch("sys.argv", ["prog", "test task", "--temperature", "0.3", "--max-tokens", "1000"])
252
+ @patch("machine_dialect.cfg.generate_with_ai.generate_code")
253
+ def test_main_with_generation_params(self, mock_generate: Mock) -> None:
254
+ """Test main with temperature and max-tokens parameters."""
255
+ mock_generate.return_value = "Generated code"
256
+
257
+ result = main()
258
+
259
+ assert result == 0
260
+ mock_generate.assert_called_once_with(
261
+ task="test task",
262
+ api_key=None,
263
+ model=None,
264
+ temperature=0.3,
265
+ max_tokens=1000,
266
+ validate=True,
267
+ )
268
+
269
+ @patch("sys.argv", ["prog", "test task", "--no-validate"])
270
+ @patch("machine_dialect.cfg.generate_with_ai.generate_code")
271
+ def test_main_without_validation(self, mock_generate: Mock) -> None:
272
+ """Test main with --no-validate flag."""
273
+ mock_generate.return_value = "Generated code"
274
+
275
+ result = main()
276
+
277
+ assert result == 0
278
+ mock_generate.assert_called_once_with(
279
+ task="test task",
280
+ api_key=None,
281
+ model=None,
282
+ temperature=0.7,
283
+ max_tokens=500,
284
+ validate=False,
285
+ )
286
+
287
+ @patch("sys.argv", ["prog", "test task", "--save", "output.md"])
288
+ @patch("machine_dialect.cfg.generate_with_ai.generate_code")
289
+ @patch("builtins.open", new_callable=mock_open)
290
+ @patch("builtins.print")
291
+ def test_main_with_save_file(self, mock_print: Mock, mock_file: Mock, mock_generate: Mock) -> None:
292
+ """Test main with --save option to write to file."""
293
+ mock_generate.return_value = "Generated code content"
294
+
295
+ result = main()
296
+
297
+ assert result == 0
298
+ mock_generate.assert_called_once()
299
+
300
+ # Verify file was written
301
+ mock_file.assert_called_once_with("output.md", "w")
302
+ mock_file().write.assert_called_once_with("Generated code content")
303
+
304
+ # Verify success message was printed
305
+ print_calls = [str(call) for call in mock_print.call_args_list]
306
+ assert any("Code saved to: output.md" in str(call) for call in print_calls)
307
+
308
+ @patch("sys.argv", ["prog", "test task"])
309
+ @patch("machine_dialect.cfg.generate_with_ai.generate_code")
310
+ @patch("builtins.print")
311
+ def test_main_with_exception(self, mock_print: Mock, mock_generate: Mock) -> None:
312
+ """Test main when generate_code raises an exception."""
313
+ mock_generate.side_effect = ValueError("API key not configured")
314
+
315
+ result = main()
316
+
317
+ assert result == 1
318
+ mock_generate.assert_called_once()
319
+
320
+ # Verify error message was printed
321
+ print_calls = [str(call) for call in mock_print.call_args_list]
322
+ assert any("Error: API key not configured" in str(call) for call in print_calls)
323
+
324
+ @patch("sys.argv", ["prog", "complex task", "--save", "/invalid/path/file.md"])
325
+ @patch("machine_dialect.cfg.generate_with_ai.generate_code")
326
+ @patch("builtins.open", side_effect=OSError("Permission denied"))
327
+ @patch("builtins.print")
328
+ def test_main_with_save_error(self, mock_print: Mock, mock_file: Mock, mock_generate: Mock) -> None:
329
+ """Test main when saving to file fails."""
330
+ mock_generate.return_value = "Generated code"
331
+
332
+ result = main()
333
+
334
+ assert result == 1
335
+ mock_generate.assert_called_once()
336
+
337
+ # Verify error message was printed
338
+ print_calls = [str(call) for call in mock_print.call_args_list]
339
+ assert any("Error: Permission denied" in str(call) for call in print_calls)
340
+
341
+ def test_main_as_script(self) -> None:
342
+ """Test that main can be called as a script."""
343
+ with patch("sys.argv", ["prog", "test"]):
344
+ with patch("machine_dialect.cfg.generate_with_ai.generate_code") as mock_gen:
345
+ mock_gen.return_value = "code"
346
+ # Import and run the module as __main__
347
+ import machine_dialect.cfg.generate_with_ai as module
348
+
349
+ # Simulate running as script
350
+ with patch.object(module, "__name__", "__main__"):
351
+ # This would normally trigger the if __name__ == "__main__" block
352
+ # but we'll call main directly for testing
353
+ exit_code = module.main()
354
+ assert exit_code == 0
@@ -0,0 +1,256 @@
1
+ """Tests for the grammar-based OpenAI generation module."""
2
+
3
+ from unittest.mock import MagicMock
4
+
5
+ import pytest
6
+
7
+ from machine_dialect.cfg.openai_generation import _get_machine_dialect_cfg, generate_with_openai, validate_model_support
8
+
9
+
10
+ class TestGenerateWithOpenAI:
11
+ """Test the grammar-based generate_with_openai function."""
12
+
13
+ def test_gpt5_cfg_generation(self) -> None:
14
+ """Test generation with GPT-5 using context-free grammar."""
15
+ # Mock OpenAI client
16
+ mock_client = MagicMock()
17
+ mock_response = MagicMock()
18
+
19
+ # Set up the response to have output_text directly (primary path)
20
+ mock_response.output_text = "Set x to _10_.\nGive back x."
21
+ # Also set up output as fallback
22
+ mock_output = MagicMock()
23
+ mock_output.input = "Set x to _10_.\nGive back x."
24
+ mock_response.output = [MagicMock(), mock_output] # First is text, second is tool output
25
+
26
+ mock_client.responses.create.return_value = mock_response
27
+
28
+ result = generate_with_openai(
29
+ client=mock_client,
30
+ model="gpt-5",
31
+ task_description="set x to 10 and display it",
32
+ max_tokens=200,
33
+ temperature=0.7,
34
+ )
35
+
36
+ # Result should be a tuple of (code, token_info)
37
+ assert isinstance(result, tuple)
38
+ assert len(result) == 2
39
+ code, token_info = result
40
+ assert code == "Set x to _10_.\nGive back x."
41
+ assert isinstance(token_info, dict)
42
+
43
+ # Verify API call structure
44
+ call_args = mock_client.responses.create.call_args
45
+ assert call_args.kwargs["model"] == "gpt-5"
46
+ # Note: GPT-5 doesn't support max_completion_tokens or temperature
47
+ assert "max_completion_tokens" not in call_args.kwargs
48
+ assert "temperature" not in call_args.kwargs
49
+ assert call_args.kwargs["parallel_tool_calls"] is False
50
+
51
+ # Check that custom tool with CFG was provided
52
+ tools = call_args.kwargs["tools"]
53
+ assert len(tools) == 1
54
+ assert tools[0]["type"] == "custom"
55
+ assert tools[0]["name"] == "machine_dialect_generator"
56
+ assert "format" in tools[0]
57
+
58
+ # Check CFG format - now using Lark syntax
59
+ cfg = tools[0]["format"]
60
+ assert cfg["type"] == "grammar"
61
+ assert cfg["syntax"] == "lark"
62
+ assert "definition" in cfg
63
+ # The definition is now a Lark grammar string
64
+ assert isinstance(cfg["definition"], str)
65
+ assert "start:" in cfg["definition"]
66
+ assert "statement:" in cfg["definition"]
67
+
68
+ def test_non_gpt5_model_raises_error(self) -> None:
69
+ """Test that non-GPT-5 models raise an error."""
70
+ mock_client = MagicMock()
71
+
72
+ with pytest.raises(ValueError, match="does not support context-free grammar"):
73
+ generate_with_openai(client=mock_client, model="gpt-4o", task_description="test task", max_tokens=100)
74
+
75
+ # Should not have made any API calls
76
+ mock_client.responses.create.assert_not_called()
77
+
78
+ def test_gpt5_mini_supported(self) -> None:
79
+ """Test that gpt-5-mini is recognized as supporting CFG."""
80
+ mock_client = MagicMock()
81
+ mock_response = MagicMock()
82
+ # Set up the response to have output_text directly
83
+ mock_response.output_text = 'Give back _"Hello"_.'
84
+ # Also set up output as fallback
85
+ mock_output = MagicMock()
86
+ mock_output.input = 'Give back _"Hello"_.'
87
+ mock_response.output = [MagicMock(), mock_output]
88
+ mock_client.responses.create.return_value = mock_response
89
+
90
+ result = generate_with_openai(
91
+ client=mock_client, model="gpt-5-mini", task_description="say hello", max_tokens=50
92
+ )
93
+
94
+ # Result should be a tuple of (code, token_info)
95
+ assert isinstance(result, tuple)
96
+ assert len(result) == 2
97
+ code, token_info = result
98
+ assert code == 'Give back _"Hello"_.'
99
+ assert isinstance(token_info, dict)
100
+ assert mock_client.responses.create.called
101
+
102
+ def test_empty_response_raises_error(self) -> None:
103
+ """Test that empty response raises ValueError."""
104
+ mock_client = MagicMock()
105
+ mock_response = MagicMock()
106
+ # Make response have no valid attributes
107
+ mock_response.output_text = None
108
+ mock_response.output = [] # Empty output
109
+ mock_client.responses.create.return_value = mock_response
110
+
111
+ with pytest.raises(ValueError, match="Failed to extract valid code"):
112
+ generate_with_openai(client=mock_client, model="gpt-5", task_description="test task", max_tokens=100)
113
+
114
+ def test_empty_code_raises_error(self) -> None:
115
+ """Test that empty generated code raises ValueError."""
116
+ mock_client = MagicMock()
117
+ mock_response = MagicMock()
118
+ # Set up response to have empty code
119
+ mock_response.output_text = "" # Empty code
120
+ del mock_response.output # Remove output attribute
121
+ mock_client.responses.create.return_value = mock_response
122
+
123
+ with pytest.raises(ValueError, match="Failed to extract valid code"):
124
+ generate_with_openai(client=mock_client, model="gpt-5", task_description="test task", max_tokens=100)
125
+
126
+ def test_input_messages_structure(self) -> None:
127
+ """Test that input messages are structured correctly."""
128
+ mock_client = MagicMock()
129
+ mock_response = MagicMock()
130
+ # Set up the response to have output_text directly
131
+ mock_response.output_text = "test"
132
+ # Also set up output as fallback
133
+ mock_output = MagicMock()
134
+ mock_output.input = "test"
135
+ mock_response.output = [MagicMock(), mock_output]
136
+ mock_client.responses.create.return_value = mock_response
137
+
138
+ result = generate_with_openai(
139
+ client=mock_client, model="gpt-5-nano", task_description="test task", max_tokens=50
140
+ )
141
+ assert isinstance(result, tuple) # Verify it returns a tuple
142
+
143
+ # Get the input messages passed to the API
144
+ call_args = mock_client.responses.create.call_args
145
+ messages = call_args.kwargs["input"]
146
+
147
+ assert len(messages) == 2
148
+
149
+ # Developer message
150
+ assert messages[0]["role"] == "developer"
151
+ assert "Machine Dialect™ code generator" in messages[0]["content"]
152
+ assert "context-free grammar" in messages[0]["content"]
153
+
154
+ # User message
155
+ assert messages[1]["role"] == "user"
156
+ assert "test task" in messages[1]["content"]
157
+
158
+
159
+ class TestValidateModelSupport:
160
+ """Test the validate_model_support function."""
161
+
162
+ def test_gpt5_models_supported(self) -> None:
163
+ """Test that GPT-5 models are recognized as supported."""
164
+ assert validate_model_support("gpt-5") is True
165
+ assert validate_model_support("GPT-5") is True
166
+ assert validate_model_support("gpt-5-mini") is True
167
+ assert validate_model_support("GPT-5-MINI") is True
168
+ assert validate_model_support("gpt-5-nano") is True
169
+ assert validate_model_support("gpt-5-Nano") is True
170
+
171
+ def test_non_gpt5_models_not_supported(self) -> None:
172
+ """Test that non-GPT-5 models are not supported."""
173
+ assert validate_model_support("gpt-4") is False
174
+ assert validate_model_support("gpt-4o") is False
175
+ assert validate_model_support("gpt-3.5-turbo") is False
176
+ assert validate_model_support("claude-3") is False
177
+ assert validate_model_support("gemini-pro") is False
178
+
179
+ def test_partial_matches(self) -> None:
180
+ """Test models with GPT-5 in the name are supported."""
181
+ assert validate_model_support("gpt-5-2025-08-07") is True
182
+ assert validate_model_support("gpt-5-mini-latest") is True
183
+ assert validate_model_support("custom-gpt-5-model") is True
184
+
185
+
186
+ class TestMachineDialectCFG:
187
+ """Test the Machine Dialect™ CFG structure."""
188
+
189
+ def test_cfg_structure(self) -> None:
190
+ """Test that the CFG has the correct structure."""
191
+ cfg = _get_machine_dialect_cfg()
192
+
193
+ # Check top-level structure
194
+ assert cfg["type"] == "grammar"
195
+ assert cfg["syntax"] == "lark" # Now using Lark syntax
196
+ assert "definition" in cfg
197
+
198
+ # The definition is now a Lark grammar string
199
+ definition = cfg["definition"]
200
+ assert isinstance(definition, str)
201
+
202
+ # Check that key rules exist in the Lark grammar
203
+ assert "start:" in definition or "program:" in definition
204
+ assert "statement:" in definition
205
+ assert "set_stmt:" in definition
206
+ assert "give_back_stmt:" in definition
207
+ assert "if_stmt:" in definition
208
+ assert "expression:" in definition
209
+
210
+ # Check that terminals are defined (using new literal patterns)
211
+ assert "LITERAL_" in definition or "IDENT" in definition
212
+ assert "IDENTIFIER" in definition
213
+
214
+ def test_lark_grammar_content(self) -> None:
215
+ """Test that the Lark grammar has expected content."""
216
+ cfg = _get_machine_dialect_cfg()
217
+ grammar = cfg["definition"]
218
+
219
+ # Check for statement rules
220
+ assert "program:" in grammar or "start:" in grammar
221
+ assert "statement:" in grammar
222
+ assert "set_stmt" in grammar
223
+ assert "give_back_stmt" in grammar
224
+
225
+ # Check for set and give back statements
226
+ assert 'set_stmt: "Set"i identifier "to"i expression' in grammar
227
+ assert 'give_back_stmt: ("Give"i "back"i | "Gives"i "back"i) expression' in grammar
228
+
229
+ # Check for expression rules
230
+ assert "expression:" in grammar or "expr:" in grammar
231
+ assert "or" in grammar.lower()
232
+ assert "and" in grammar.lower()
233
+
234
+ # Check for comparison operators
235
+ assert '"<"' in grammar
236
+ assert '">"' in grammar
237
+ assert '"equals"i' in grammar or "equals" in grammar.lower()
238
+
239
+ # Check for arithmetic operators
240
+ assert '"+"' in grammar
241
+ assert '"-"' in grammar
242
+ assert '"*"' in grammar
243
+ assert '"/"' in grammar
244
+
245
+ def test_grammar_terminals(self) -> None:
246
+ """Test that terminals are properly defined in Lark grammar."""
247
+ cfg = _get_machine_dialect_cfg()
248
+ grammar = cfg["definition"]
249
+
250
+ # Check terminal definitions (new pattern with literals)
251
+ assert "IDENTIFIER" in grammar or "IDENT" in grammar
252
+ assert "LITERAL_" in grammar # Check for literal patterns
253
+
254
+ # Check whitespace handling
255
+ assert "%import common.WS" in grammar
256
+ assert "%ignore WS" in grammar
@@ -0,0 +1,5 @@
1
+ """Bytecode generation for Machine Dialect™."""
2
+
3
+ from .bytecode_serializer import BytecodeWriter
4
+
5
+ __all__ = ["BytecodeWriter"]