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,423 @@
1
+ """Tests for Utility statements (functions) in Machine Dialect™."""
2
+
3
+ from machine_dialect.ast import BlockStatement, SetStatement, UtilityStatement
4
+ from machine_dialect.parser import Parser
5
+
6
+
7
+ class TestUtilityStatements:
8
+ """Test parsing of Utility statements (functions)."""
9
+
10
+ def test_simple_utility_without_parameters(self) -> None:
11
+ """Test parsing a simple utility without parameters."""
12
+ source = """### **Utility**: `calculate pi`
13
+
14
+ <details>
15
+ <summary>Calculates the value of pi.</summary>
16
+
17
+ > Define `result` as Number.
18
+ > Set `result` to _3.14159_.
19
+ > Give back `result`.
20
+
21
+ </details>"""
22
+
23
+ parser = Parser()
24
+ program = parser.parse(source, check_semantics=False)
25
+
26
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
27
+ assert len(program.statements) == 1
28
+
29
+ utility_stmt = program.statements[0]
30
+ assert isinstance(utility_stmt, UtilityStatement)
31
+ assert utility_stmt.name.value == "calculate pi"
32
+ assert len(utility_stmt.inputs) == 0
33
+ assert len(utility_stmt.outputs) == 0
34
+ assert isinstance(utility_stmt.body, BlockStatement)
35
+ assert len(utility_stmt.body.statements) == 3 # Define + Set + Give back
36
+
37
+ from machine_dialect.ast import DefineStatement
38
+
39
+ # Check first statement: Define `result` as Number.
40
+ define_stmt = utility_stmt.body.statements[0]
41
+ assert isinstance(define_stmt, DefineStatement)
42
+ assert define_stmt.name.value == "result"
43
+
44
+ # Check second statement: Set `result` to _3.14159_.
45
+ set_stmt = utility_stmt.body.statements[1]
46
+ assert isinstance(set_stmt, SetStatement)
47
+ assert set_stmt.name and set_stmt.name.value == "result"
48
+
49
+ def test_utility_with_inputs_and_outputs(self) -> None:
50
+ """Test parsing a utility with input and output parameters."""
51
+ source = """### **Utility**: `add two numbers`
52
+
53
+ <details>
54
+ <summary>Adds two numbers and returns the result</summary>
55
+
56
+ > Define `result` as Whole Number.
57
+ > Set `result` to `addend 1` + `addend 2`.
58
+
59
+ </details>
60
+
61
+ #### Inputs:
62
+ - `addend 1` **as** Whole Number (required)
63
+ - `addend 2` **as** Whole Number (required)
64
+
65
+ #### Outputs:
66
+ - `result` **as** Yes/No (default: _Empty_)"""
67
+
68
+ parser = Parser()
69
+ program = parser.parse(source, check_semantics=False)
70
+
71
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
72
+ assert len(program.statements) == 1
73
+
74
+ utility_stmt = program.statements[0]
75
+ assert isinstance(utility_stmt, UtilityStatement)
76
+ assert utility_stmt.name.value == "add two numbers"
77
+ assert len(utility_stmt.inputs) == 2
78
+ assert len(utility_stmt.outputs) == 1
79
+
80
+ # Check inputs
81
+ assert utility_stmt.inputs[0].name.value == "addend 1"
82
+ assert utility_stmt.inputs[0].type_name == "Whole Number"
83
+ assert utility_stmt.inputs[0].is_required is True
84
+
85
+ assert utility_stmt.inputs[1].name.value == "addend 2"
86
+ assert utility_stmt.inputs[1].type_name == "Whole Number"
87
+ assert utility_stmt.inputs[1].is_required is True
88
+
89
+ # Check outputs (now using Output class)
90
+ from machine_dialect.ast import EmptyLiteral, Output
91
+
92
+ assert len(utility_stmt.outputs) == 1
93
+ assert isinstance(utility_stmt.outputs[0], Output)
94
+ assert utility_stmt.outputs[0].name.value == "result"
95
+ assert utility_stmt.outputs[0].type_name == "Yes/No"
96
+ # Check that it has a default value of Empty
97
+ assert utility_stmt.outputs[0].default_value is not None
98
+ assert isinstance(utility_stmt.outputs[0].default_value, EmptyLiteral)
99
+
100
+ def test_utility_with_heading_level(self) -> None:
101
+ """Test that utility heading level (###) is parsed correctly."""
102
+ source = """### **Utility**: `double value`
103
+
104
+ <details>
105
+ <summary>Doubles the input value.</summary>
106
+
107
+ > Define `result` as Number.
108
+ > Set `result` to `value` * _2_.
109
+
110
+ </details>"""
111
+
112
+ parser = Parser()
113
+ program = parser.parse(source, check_semantics=False)
114
+
115
+ assert len(parser.errors) == 0
116
+ assert len(program.statements) == 1
117
+
118
+ utility_stmt = program.statements[0]
119
+ assert isinstance(utility_stmt, UtilityStatement)
120
+ assert utility_stmt.name.value == "double value"
121
+
122
+ def test_utility_with_multi_word_name(self) -> None:
123
+ """Test utility with multi-word name in backticks."""
124
+ source = """### **Utility**: `calculate compound interest`
125
+
126
+ <details>
127
+ <summary>Calculates compound interest.</summary>
128
+
129
+ > Define `amount` as Number.
130
+ > Set `amount` to _1000_.
131
+
132
+ </details>"""
133
+
134
+ parser = Parser()
135
+ program = parser.parse(source, check_semantics=False)
136
+
137
+ assert len(parser.errors) == 0
138
+ assert len(program.statements) == 1
139
+
140
+ utility_stmt = program.statements[0]
141
+ assert isinstance(utility_stmt, UtilityStatement)
142
+ assert utility_stmt.name.value == "calculate compound interest"
143
+
144
+ def test_utility_with_empty_body(self) -> None:
145
+ """Test utility with no statements in body."""
146
+ source = """### **Utility**: `identity function`
147
+
148
+ <details>
149
+ <summary>Returns nothing.</summary>
150
+
151
+ </details>"""
152
+
153
+ parser = Parser()
154
+ program = parser.parse(source, check_semantics=False)
155
+
156
+ assert len(parser.errors) == 0
157
+ assert len(program.statements) == 1
158
+
159
+ utility_stmt = program.statements[0]
160
+ assert isinstance(utility_stmt, UtilityStatement)
161
+ assert utility_stmt.name.value == "identity function"
162
+ assert len(utility_stmt.body.statements) == 0
163
+
164
+ def test_multiple_utilities(self) -> None:
165
+ """Test parsing multiple utilities in one program."""
166
+ source = """### **Utility**: `first utility`
167
+
168
+ <details>
169
+ <summary>First utility.</summary>
170
+
171
+ > Define `x` as Number.
172
+ > Set `x` to _1_.
173
+
174
+ </details>
175
+
176
+ ### **Utility**: `second utility`
177
+
178
+ <details>
179
+ <summary>Second utility.</summary>
180
+
181
+ > Define `second_y` as Number.
182
+ > Set `second_y` to _2_.
183
+
184
+ </details>"""
185
+
186
+ parser = Parser()
187
+ program = parser.parse(source, check_semantics=False)
188
+
189
+ assert len(parser.errors) == 0
190
+ assert len(program.statements) == 2
191
+
192
+ # Check first utility
193
+ first_utility = program.statements[0]
194
+ assert isinstance(first_utility, UtilityStatement)
195
+ assert first_utility.name.value == "first utility"
196
+ assert len(first_utility.body.statements) == 2 # Define + Set
197
+
198
+ # Check second utility
199
+ second_utility = program.statements[1]
200
+ assert isinstance(second_utility, UtilityStatement)
201
+ assert second_utility.name.value == "second utility"
202
+ assert len(second_utility.body.statements) == 2 # Define + Set
203
+
204
+ def test_utility_with_complex_body(self) -> None:
205
+ """Test utility with complex body including conditionals."""
206
+ source = """### **Utility**: `absolute value`
207
+
208
+ <details>
209
+ <summary>Returns the absolute value of a number.</summary>
210
+
211
+ > Define `result` as Number.
212
+ > If `number` < _0_ then:
213
+ > > Set `result` to -`number`.
214
+ > Else:
215
+ > > Set `result` to `number`.
216
+ >
217
+ > Give back `result`.
218
+
219
+ </details>"""
220
+
221
+ parser = Parser()
222
+ program = parser.parse(source, check_semantics=False)
223
+
224
+ assert len(parser.errors) == 0
225
+ assert len(program.statements) == 1
226
+
227
+ utility_stmt = program.statements[0]
228
+ assert isinstance(utility_stmt, UtilityStatement)
229
+ assert utility_stmt.name.value == "absolute value"
230
+ assert utility_stmt.description == "Returns the absolute value of a number."
231
+ assert len(utility_stmt.body.statements) == 3 # Define + If statement + Give back statement
232
+
233
+ def test_mixed_statements_with_utility(self) -> None:
234
+ """Test that utilities can coexist with actions and interactions."""
235
+ source = """### **Action**: `private method`
236
+
237
+ <details>
238
+ <summary>A private action.</summary>
239
+
240
+ > Define `x` as Number.
241
+ > Set `x` to _1_.
242
+
243
+ </details>
244
+
245
+ ### **Utility**: `helper function`
246
+
247
+ <details>
248
+ <summary>A utility function.</summary>
249
+
250
+ > Give back _42_.
251
+
252
+ </details>
253
+
254
+ ### **Interaction**: `public method`
255
+
256
+ <details>
257
+ <summary>A public interaction.</summary>
258
+
259
+ > Define `y` as Number.
260
+ > Set `y` to _2_.
261
+
262
+ </details>"""
263
+
264
+ parser = Parser()
265
+ program = parser.parse(source, check_semantics=False)
266
+
267
+ assert len(parser.errors) == 0
268
+ assert len(program.statements) == 3
269
+
270
+ # Check action
271
+ from machine_dialect.ast import ActionStatement, InteractionStatement
272
+
273
+ action_stmt = program.statements[0]
274
+ assert isinstance(action_stmt, ActionStatement)
275
+ assert action_stmt.name.value == "private method"
276
+
277
+ # Check utility
278
+ utility_stmt = program.statements[1]
279
+ assert isinstance(utility_stmt, UtilityStatement)
280
+ assert utility_stmt.name.value == "helper function"
281
+
282
+ # Check interaction
283
+ interaction_stmt = program.statements[2]
284
+ assert isinstance(interaction_stmt, InteractionStatement)
285
+ assert interaction_stmt.name.value == "public method"
286
+
287
+ def test_utility_with_recursive_call(self) -> None:
288
+ """Test utility with recursive call (Fibonacci example)."""
289
+ source = """### **Utility**: `Fibonacci`
290
+
291
+ <details>
292
+ <summary>Calculate the nth Fibonacci number recursively</summary>
293
+
294
+ > If `n` is less than or equal to _1_:
295
+ > >
296
+ > > Give back `n`.
297
+ > >
298
+ > Else:
299
+ > >
300
+ > > Define `n_minus_1` as Whole Number.
301
+ > > Set `n_minus_1` to `n` - _1_.
302
+ > > Define `n_minus_2` as Whole Number.
303
+ > > Set `n_minus_2` to `n` - _2_.
304
+ > > Define `fib_1` as Whole Number.
305
+ > > Set `fib_1` using `Fibonacci` with `n_minus_1`.
306
+ > > Define `fib_2` as Whole Number.
307
+ > > Set `fib_2` using `Fibonacci` with `n_minus_2`.
308
+ > > Define `result` as Whole Number.
309
+ > > Set `result` to `fib_1` + `fib_2`.
310
+ > > Give back `result`.
311
+
312
+ </details>
313
+
314
+ #### Inputs:
315
+
316
+ - `n` **as** Whole Number (required)
317
+
318
+ #### Outputs:
319
+
320
+ - `result` **as** Whole Number
321
+
322
+ Define `m` as Whole Number.
323
+ Set `m` to _10_.
324
+
325
+ Define `final result` as Whole Number.
326
+ Set `final result` using `Fibonacci` with `m`.
327
+
328
+ Say `final result`."""
329
+
330
+ parser = Parser()
331
+ program = parser.parse(source, check_semantics=False)
332
+
333
+ assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
334
+ assert (
335
+ len(program.statements) == 6
336
+ ) # Utility + Define m + Set m + Define final result + Set final result using + Say
337
+
338
+ # Check the utility statement
339
+ utility_stmt = program.statements[0]
340
+ assert isinstance(utility_stmt, UtilityStatement)
341
+ assert utility_stmt.name.value == "Fibonacci"
342
+ assert utility_stmt.description == "Calculate the nth Fibonacci number recursively"
343
+
344
+ # Check inputs
345
+ assert len(utility_stmt.inputs) == 1
346
+ assert utility_stmt.inputs[0].name.value == "n"
347
+ assert utility_stmt.inputs[0].type_name == "Whole Number"
348
+ assert utility_stmt.inputs[0].is_required is True
349
+
350
+ # Check outputs
351
+ assert len(utility_stmt.outputs) == 1
352
+ assert utility_stmt.outputs[0].name.value == "result"
353
+ assert utility_stmt.outputs[0].type_name == "Whole Number"
354
+
355
+ # Check the body contains an if statement
356
+ from machine_dialect.ast import DefineStatement, IfStatement, ReturnStatement, SayStatement
357
+
358
+ assert len(utility_stmt.body.statements) == 1
359
+ if_stmt = utility_stmt.body.statements[0]
360
+ assert isinstance(if_stmt, IfStatement)
361
+
362
+ # Check the if branch (n <= 1)
363
+ assert isinstance(if_stmt.consequence, BlockStatement)
364
+ assert len(if_stmt.consequence.statements) == 1
365
+ assert isinstance(if_stmt.consequence.statements[0], ReturnStatement)
366
+
367
+ # Check the else branch (recursive case)
368
+ assert if_stmt.alternative is not None
369
+ assert isinstance(if_stmt.alternative, BlockStatement)
370
+ else_statements = if_stmt.alternative.statements
371
+
372
+ # Should have: 2 Define, 2 Set for n_minus_1 and n_minus_2
373
+ # Then 2 Define, 2 Set using for fib_1 and fib_2
374
+ # Then 1 Define, 1 Set for result, and 1 Give back
375
+ assert len(else_statements) == 11
376
+
377
+ # Check recursive calls (Set using statements with CallExpression)
378
+ from machine_dialect.ast import CallExpression
379
+
380
+ recursive_calls = [
381
+ stmt
382
+ for stmt in else_statements
383
+ if isinstance(stmt, SetStatement) and isinstance(stmt.value, CallExpression)
384
+ ]
385
+ assert len(recursive_calls) == 2
386
+
387
+ # Both should call "Fibonacci"
388
+ from machine_dialect.ast import Identifier
389
+
390
+ for call in recursive_calls:
391
+ assert isinstance(call.value, CallExpression)
392
+ assert call.value.function_name is not None
393
+ assert isinstance(call.value.function_name, Identifier)
394
+ assert call.value.function_name.value == "Fibonacci"
395
+
396
+ # Check the main program statements after the utility
397
+ assert isinstance(program.statements[1], DefineStatement)
398
+ assert program.statements[1].name.value == "m"
399
+
400
+ assert isinstance(program.statements[2], SetStatement)
401
+ set_m = program.statements[2]
402
+ assert set_m.name is not None
403
+ assert set_m.name.value == "m"
404
+
405
+ assert isinstance(program.statements[3], DefineStatement)
406
+ assert program.statements[3].name.value == "final result"
407
+
408
+ # Check the utility call in main program
409
+ assert isinstance(program.statements[4], SetStatement)
410
+ set_final = program.statements[4]
411
+ assert set_final.name is not None
412
+ assert set_final.name.value == "final result"
413
+ assert isinstance(set_final.value, CallExpression)
414
+ assert set_final.value.function_name is not None
415
+ assert isinstance(set_final.value.function_name, Identifier)
416
+ assert set_final.value.function_name.value == "Fibonacci"
417
+
418
+ # Check the Say statement
419
+ assert isinstance(program.statements[5], SayStatement)
420
+ say_stmt = program.statements[5]
421
+ # The Say statement should reference the final result identifier
422
+ assert isinstance(say_stmt.expression, Identifier)
423
+ assert say_stmt.expression.value == "final result"
@@ -0,0 +1,159 @@
1
+ """Token buffer for streaming tokens from lexer to parser.
2
+
3
+ This module provides a TokenBuffer class that maintains a small buffer of tokens
4
+ and streams them from the lexer to the parser on demand, instead of generating
5
+ all tokens upfront.
6
+ """
7
+
8
+ from machine_dialect.lexer.tokens import Token, TokenType
9
+
10
+ # Buffer size constant - number of tokens to keep in the buffer
11
+ BUFFER_SIZE = 4
12
+
13
+
14
+ class TokenBuffer:
15
+ """Buffer for streaming tokens from lexer to parser.
16
+
17
+ Maintains a small buffer of tokens and fetches new tokens from the lexer
18
+ as needed. This allows for memory-efficient parsing of large files.
19
+
20
+ Attributes:
21
+ _lexer: The lexer instance to get tokens from.
22
+ _buffer: Internal buffer of tokens.
23
+ _eof_reached: Whether EOF has been reached.
24
+ """
25
+
26
+ def __init__(self, lexer) -> None: # type: ignore
27
+ """Initialize the token buffer with a lexer.
28
+
29
+ Args:
30
+ lexer: The lexer instance to stream tokens from.
31
+ """
32
+ from machine_dialect.lexer import Lexer
33
+
34
+ self._lexer: Lexer = lexer
35
+ self._buffer: list[Token] = []
36
+ self._at_line_start: list[bool] = [] # Track if each token is at line start
37
+ self._eof_reached = False
38
+ self._in_block = False # Track block context
39
+ self._in_list_context = False # Track list definition context
40
+
41
+ # Pre-fill the buffer
42
+ self._fill_buffer()
43
+
44
+ def _fill_buffer(self) -> None:
45
+ """Fill the buffer with tokens from the lexer up to BUFFER_SIZE."""
46
+ while len(self._buffer) < BUFFER_SIZE and not self._eof_reached:
47
+ token = self._get_next_token()
48
+ if token is not None:
49
+ self._buffer.append(token)
50
+ # Determine if token is at line start based on its column position
51
+ # Column 1 means it's at the start of a line
52
+ at_line_start = token.position == 1
53
+ self._at_line_start.append(at_line_start)
54
+ if token.type == TokenType.MISC_EOF:
55
+ self._eof_reached = True
56
+ else:
57
+ # No more tokens available
58
+ self._eof_reached = True
59
+ # Add EOF token if buffer is empty
60
+ if not self._buffer:
61
+ self._buffer.append(
62
+ Token(TokenType.MISC_EOF, "", line=self._lexer.line, position=self._lexer.column)
63
+ )
64
+ self._at_line_start.append(False)
65
+
66
+ def _get_next_token(self) -> Token | None:
67
+ """Get the next token from the lexer.
68
+
69
+ Returns:
70
+ The next token, or None if no more tokens are available.
71
+ """
72
+ # Pass the block and list contexts to the lexer
73
+ return self._lexer.next_token(in_block=self._in_block, in_list_context=self._in_list_context)
74
+
75
+ def current(self) -> Token | None:
76
+ """Get the current token without consuming it.
77
+
78
+ Returns:
79
+ The current token, or None if no tokens are available.
80
+ """
81
+ if self._buffer:
82
+ return self._buffer[0]
83
+ return None
84
+
85
+ def peek(self, offset: int = 1) -> Token | None:
86
+ """Peek at a token at the given offset without consuming tokens.
87
+
88
+ Args:
89
+ offset: How many tokens ahead to look (1 = next token).
90
+
91
+ Returns:
92
+ The token at the given offset, or None if not available.
93
+ """
94
+ # Ensure we have enough tokens in the buffer
95
+ while len(self._buffer) <= offset and not self._eof_reached:
96
+ token = self._get_next_token()
97
+ if token is not None:
98
+ self._buffer.append(token)
99
+ if token.type == TokenType.MISC_EOF:
100
+ self._eof_reached = True
101
+ else:
102
+ self._eof_reached = True
103
+ break
104
+
105
+ if offset < len(self._buffer):
106
+ return self._buffer[offset]
107
+
108
+ # Return EOF token if we're past the buffer
109
+ return Token(TokenType.MISC_EOF, "", line=self._lexer.line, position=self._lexer.column)
110
+
111
+ def advance(self) -> None:
112
+ """Consume the current token and advance to the next one."""
113
+ if self._buffer:
114
+ self._buffer.pop(0)
115
+ if self._at_line_start:
116
+ self._at_line_start.pop(0)
117
+ # Refill the buffer to maintain BUFFER_SIZE tokens
118
+ self._fill_buffer()
119
+
120
+ def has_tokens(self) -> bool:
121
+ """Check if there are more tokens available.
122
+
123
+ Returns:
124
+ True if there are tokens available, False otherwise.
125
+ """
126
+ return bool(self._buffer)
127
+
128
+ def set_block_context(self, in_block: bool) -> None:
129
+ """Set the block parsing context.
130
+
131
+ Args:
132
+ in_block: Whether we're currently parsing inside a block.
133
+ """
134
+ self._in_block = in_block
135
+
136
+ def set_list_context(self, in_list: bool) -> None:
137
+ """Set the list definition parsing context.
138
+
139
+ Args:
140
+ in_list: Whether we're currently parsing a list definition.
141
+ """
142
+ if self._in_list_context != in_list:
143
+ self._in_list_context = in_list
144
+
145
+ # Update token types in the buffer based on new context
146
+ for i, token in enumerate(self._buffer):
147
+ if token.literal == "-" and i < len(self._at_line_start):
148
+ at_line_start = self._at_line_start[i]
149
+
150
+ if in_list and at_line_start:
151
+ # In list context and at line start: should be PUNCT_DASH
152
+ if token.type == TokenType.OP_MINUS:
153
+ self._buffer[i] = Token(TokenType.PUNCT_DASH, token.literal, token.line, token.position)
154
+ elif not in_list or not at_line_start:
155
+ # Not in list context or not at line start: should be OP_MINUS
156
+ if token.type == TokenType.PUNCT_DASH:
157
+ self._buffer[i] = Token(TokenType.OP_MINUS, token.literal, token.line, token.position)
158
+ else:
159
+ self._in_list_context = in_list
@@ -0,0 +1,3 @@
1
+ from machine_dialect.repl.repl import main
2
+
3
+ __all__ = ["main"]