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.
- machine_dialect/__main__.py +667 -0
- machine_dialect/agent/__init__.py +5 -0
- machine_dialect/agent/agent.py +360 -0
- machine_dialect/ast/__init__.py +95 -0
- machine_dialect/ast/ast_node.py +35 -0
- machine_dialect/ast/call_expression.py +82 -0
- machine_dialect/ast/dict_extraction.py +60 -0
- machine_dialect/ast/expressions.py +439 -0
- machine_dialect/ast/literals.py +309 -0
- machine_dialect/ast/program.py +35 -0
- machine_dialect/ast/statements.py +1433 -0
- machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
- machine_dialect/ast/tests/test_boolean_literal.py +29 -0
- machine_dialect/ast/tests/test_collection_hir.py +138 -0
- machine_dialect/ast/tests/test_define_statement.py +142 -0
- machine_dialect/ast/tests/test_desugar.py +541 -0
- machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
- machine_dialect/cfg/__init__.py +6 -0
- machine_dialect/cfg/config.py +156 -0
- machine_dialect/cfg/examples.py +221 -0
- machine_dialect/cfg/generate_with_ai.py +187 -0
- machine_dialect/cfg/openai_generation.py +200 -0
- machine_dialect/cfg/parser.py +94 -0
- machine_dialect/cfg/tests/__init__.py +1 -0
- machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
- machine_dialect/cfg/tests/test_config.py +188 -0
- machine_dialect/cfg/tests/test_examples.py +391 -0
- machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
- machine_dialect/cfg/tests/test_openai_generation.py +256 -0
- machine_dialect/codegen/__init__.py +5 -0
- machine_dialect/codegen/bytecode_module.py +89 -0
- machine_dialect/codegen/bytecode_serializer.py +300 -0
- machine_dialect/codegen/opcodes.py +101 -0
- machine_dialect/codegen/register_codegen.py +1996 -0
- machine_dialect/codegen/symtab.py +208 -0
- machine_dialect/codegen/tests/__init__.py +1 -0
- machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
- machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
- machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
- machine_dialect/codegen/tests/test_symtab.py +418 -0
- machine_dialect/codegen/vm_serializer.py +621 -0
- machine_dialect/compiler/__init__.py +18 -0
- machine_dialect/compiler/compiler.py +197 -0
- machine_dialect/compiler/config.py +149 -0
- machine_dialect/compiler/context.py +149 -0
- machine_dialect/compiler/phases/__init__.py +19 -0
- machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
- machine_dialect/compiler/phases/codegen.py +40 -0
- machine_dialect/compiler/phases/hir_generation.py +39 -0
- machine_dialect/compiler/phases/mir_generation.py +86 -0
- machine_dialect/compiler/phases/optimization.py +110 -0
- machine_dialect/compiler/phases/parsing.py +39 -0
- machine_dialect/compiler/pipeline.py +143 -0
- machine_dialect/compiler/tests/__init__.py +1 -0
- machine_dialect/compiler/tests/test_compiler.py +568 -0
- machine_dialect/compiler/vm_runner.py +173 -0
- machine_dialect/errors/__init__.py +32 -0
- machine_dialect/errors/exceptions.py +369 -0
- machine_dialect/errors/messages.py +82 -0
- machine_dialect/errors/tests/__init__.py +0 -0
- machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
- machine_dialect/errors/tests/test_name_errors.py +118 -0
- machine_dialect/helpers/__init__.py +0 -0
- machine_dialect/helpers/stopwords.py +225 -0
- machine_dialect/helpers/validators.py +30 -0
- machine_dialect/lexer/__init__.py +9 -0
- machine_dialect/lexer/constants.py +23 -0
- machine_dialect/lexer/lexer.py +907 -0
- machine_dialect/lexer/tests/__init__.py +0 -0
- machine_dialect/lexer/tests/helpers.py +86 -0
- machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
- machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
- machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
- machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
- machine_dialect/lexer/tests/test_comments.py +200 -0
- machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
- machine_dialect/lexer/tests/test_lexer_position.py +113 -0
- machine_dialect/lexer/tests/test_list_tokens.py +282 -0
- machine_dialect/lexer/tests/test_stopwords.py +80 -0
- machine_dialect/lexer/tests/test_strict_equality.py +129 -0
- machine_dialect/lexer/tests/test_token.py +41 -0
- machine_dialect/lexer/tests/test_tokenization.py +294 -0
- machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
- machine_dialect/lexer/tests/test_url_literals.py +169 -0
- machine_dialect/lexer/tokens.py +487 -0
- machine_dialect/linter/__init__.py +10 -0
- machine_dialect/linter/__main__.py +144 -0
- machine_dialect/linter/linter.py +154 -0
- machine_dialect/linter/rules/__init__.py +8 -0
- machine_dialect/linter/rules/base.py +112 -0
- machine_dialect/linter/rules/statement_termination.py +99 -0
- machine_dialect/linter/tests/__init__.py +1 -0
- machine_dialect/linter/tests/mdrules/__init__.py +0 -0
- machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
- machine_dialect/linter/tests/test_linter.py +81 -0
- machine_dialect/linter/tests/test_rules.py +110 -0
- machine_dialect/linter/tests/test_violations.py +71 -0
- machine_dialect/linter/violations.py +51 -0
- machine_dialect/mir/__init__.py +69 -0
- machine_dialect/mir/analyses/__init__.py +20 -0
- machine_dialect/mir/analyses/alias_analysis.py +315 -0
- machine_dialect/mir/analyses/dominance_analysis.py +49 -0
- machine_dialect/mir/analyses/escape_analysis.py +286 -0
- machine_dialect/mir/analyses/loop_analysis.py +272 -0
- machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
- machine_dialect/mir/analyses/type_analysis.py +448 -0
- machine_dialect/mir/analyses/use_def_chains.py +232 -0
- machine_dialect/mir/basic_block.py +385 -0
- machine_dialect/mir/dataflow.py +445 -0
- machine_dialect/mir/debug_info.py +208 -0
- machine_dialect/mir/hir_to_mir.py +1738 -0
- machine_dialect/mir/mir_dumper.py +366 -0
- machine_dialect/mir/mir_function.py +167 -0
- machine_dialect/mir/mir_instructions.py +1877 -0
- machine_dialect/mir/mir_interpreter.py +556 -0
- machine_dialect/mir/mir_module.py +225 -0
- machine_dialect/mir/mir_printer.py +480 -0
- machine_dialect/mir/mir_transformer.py +410 -0
- machine_dialect/mir/mir_types.py +367 -0
- machine_dialect/mir/mir_validation.py +455 -0
- machine_dialect/mir/mir_values.py +268 -0
- machine_dialect/mir/optimization_config.py +233 -0
- machine_dialect/mir/optimization_pass.py +251 -0
- machine_dialect/mir/optimization_pipeline.py +355 -0
- machine_dialect/mir/optimizations/__init__.py +84 -0
- machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
- machine_dialect/mir/optimizations/branch_prediction.py +372 -0
- machine_dialect/mir/optimizations/constant_propagation.py +634 -0
- machine_dialect/mir/optimizations/cse.py +398 -0
- machine_dialect/mir/optimizations/dce.py +288 -0
- machine_dialect/mir/optimizations/inlining.py +551 -0
- machine_dialect/mir/optimizations/jump_threading.py +487 -0
- machine_dialect/mir/optimizations/licm.py +405 -0
- machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
- machine_dialect/mir/optimizations/strength_reduction.py +422 -0
- machine_dialect/mir/optimizations/tail_call.py +207 -0
- machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
- machine_dialect/mir/optimizations/type_narrowing.py +397 -0
- machine_dialect/mir/optimizations/type_specialization.py +447 -0
- machine_dialect/mir/optimizations/type_specific.py +906 -0
- machine_dialect/mir/optimize_mir.py +89 -0
- machine_dialect/mir/pass_manager.py +391 -0
- machine_dialect/mir/profiling/__init__.py +26 -0
- machine_dialect/mir/profiling/profile_collector.py +318 -0
- machine_dialect/mir/profiling/profile_data.py +372 -0
- machine_dialect/mir/profiling/profile_reader.py +272 -0
- machine_dialect/mir/profiling/profile_writer.py +226 -0
- machine_dialect/mir/register_allocation.py +302 -0
- machine_dialect/mir/reporting/__init__.py +17 -0
- machine_dialect/mir/reporting/optimization_reporter.py +314 -0
- machine_dialect/mir/reporting/report_formatter.py +289 -0
- machine_dialect/mir/ssa_construction.py +342 -0
- machine_dialect/mir/tests/__init__.py +1 -0
- machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
- machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
- machine_dialect/mir/tests/test_algebraic_division.py +126 -0
- machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
- machine_dialect/mir/tests/test_basic_block.py +425 -0
- machine_dialect/mir/tests/test_branch_prediction.py +459 -0
- machine_dialect/mir/tests/test_call_lowering.py +168 -0
- machine_dialect/mir/tests/test_collection_lowering.py +604 -0
- machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
- machine_dialect/mir/tests/test_custom_passes.py +166 -0
- machine_dialect/mir/tests/test_debug_info.py +285 -0
- machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
- machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
- machine_dialect/mir/tests/test_double_negation.py +231 -0
- machine_dialect/mir/tests/test_escape_analysis.py +233 -0
- machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
- machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
- machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
- machine_dialect/mir/tests/test_inlining.py +435 -0
- machine_dialect/mir/tests/test_licm.py +472 -0
- machine_dialect/mir/tests/test_mir_dumper.py +313 -0
- machine_dialect/mir/tests/test_mir_instructions.py +445 -0
- machine_dialect/mir/tests/test_mir_module.py +860 -0
- machine_dialect/mir/tests/test_mir_printer.py +387 -0
- machine_dialect/mir/tests/test_mir_types.py +123 -0
- machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
- machine_dialect/mir/tests/test_mir_validation.py +378 -0
- machine_dialect/mir/tests/test_mir_values.py +168 -0
- machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
- machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
- machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
- machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
- machine_dialect/mir/tests/test_pass_manager.py +294 -0
- machine_dialect/mir/tests/test_pass_registration.py +64 -0
- machine_dialect/mir/tests/test_profiling.py +356 -0
- machine_dialect/mir/tests/test_register_allocation.py +307 -0
- machine_dialect/mir/tests/test_report_formatters.py +372 -0
- machine_dialect/mir/tests/test_ssa_construction.py +433 -0
- machine_dialect/mir/tests/test_tail_call.py +236 -0
- machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
- machine_dialect/mir/tests/test_type_narrowing.py +277 -0
- machine_dialect/mir/tests/test_type_specialization.py +421 -0
- machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
- machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
- machine_dialect/mir/type_inference.py +368 -0
- machine_dialect/parser/__init__.py +12 -0
- machine_dialect/parser/enums.py +45 -0
- machine_dialect/parser/parser.py +3655 -0
- machine_dialect/parser/protocols.py +11 -0
- machine_dialect/parser/symbol_table.py +169 -0
- machine_dialect/parser/tests/__init__.py +0 -0
- machine_dialect/parser/tests/helper_functions.py +193 -0
- machine_dialect/parser/tests/test_action_statements.py +334 -0
- machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
- machine_dialect/parser/tests/test_call_statements.py +154 -0
- machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
- machine_dialect/parser/tests/test_collection_mutations.py +264 -0
- machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
- machine_dialect/parser/tests/test_define_integration.py +468 -0
- machine_dialect/parser/tests/test_define_statements.py +311 -0
- machine_dialect/parser/tests/test_dict_extraction.py +115 -0
- machine_dialect/parser/tests/test_empty_literal.py +155 -0
- machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
- machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
- machine_dialect/parser/tests/test_if_empty_block.py +61 -0
- machine_dialect/parser/tests/test_if_statements.py +299 -0
- machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
- machine_dialect/parser/tests/test_infix_expressions.py +680 -0
- machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
- machine_dialect/parser/tests/test_interaction_statements.py +269 -0
- machine_dialect/parser/tests/test_list_literals.py +277 -0
- machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
- machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
- machine_dialect/parser/tests/test_parse_errors.py +114 -0
- machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
- machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
- machine_dialect/parser/tests/test_program.py +13 -0
- machine_dialect/parser/tests/test_return_statements.py +89 -0
- machine_dialect/parser/tests/test_set_statements.py +152 -0
- machine_dialect/parser/tests/test_strict_equality.py +258 -0
- machine_dialect/parser/tests/test_symbol_table.py +217 -0
- machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
- machine_dialect/parser/tests/test_utility_statements.py +423 -0
- machine_dialect/parser/token_buffer.py +159 -0
- machine_dialect/repl/__init__.py +3 -0
- machine_dialect/repl/repl.py +426 -0
- machine_dialect/repl/tests/__init__.py +0 -0
- machine_dialect/repl/tests/test_repl.py +606 -0
- machine_dialect/semantic/__init__.py +12 -0
- machine_dialect/semantic/analyzer.py +906 -0
- machine_dialect/semantic/error_messages.py +189 -0
- machine_dialect/semantic/tests/__init__.py +1 -0
- machine_dialect/semantic/tests/test_analyzer.py +364 -0
- machine_dialect/semantic/tests/test_error_messages.py +104 -0
- machine_dialect/tests/edge_cases/__init__.py +10 -0
- machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
- machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
- machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
- machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
- machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
- machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
- machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
- machine_dialect/tests/integration/test_list_compilation.py +395 -0
- machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
- machine_dialect/type_checking/__init__.py +21 -0
- machine_dialect/type_checking/tests/__init__.py +1 -0
- machine_dialect/type_checking/tests/test_type_system.py +230 -0
- machine_dialect/type_checking/type_system.py +270 -0
- machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
- machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
- machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
- machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
- machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
- machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
- 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
|