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,334 @@
|
|
1
|
+
"""Tests for Action statements (private methods) in Machine Dialect™."""
|
2
|
+
|
3
|
+
from machine_dialect.ast import ActionStatement, BlockStatement, EmptyLiteral, Output, SetStatement
|
4
|
+
from machine_dialect.parser import Parser
|
5
|
+
|
6
|
+
|
7
|
+
class TestActionStatements:
|
8
|
+
"""Test parsing of Action statements (private methods)."""
|
9
|
+
|
10
|
+
def test_simple_action_without_parameters(self) -> None:
|
11
|
+
"""Test parsing a simple action without parameters."""
|
12
|
+
source = """### **Action**: `make noise`
|
13
|
+
|
14
|
+
<details>
|
15
|
+
<summary>Emits the sound of the alarm.</summary>
|
16
|
+
|
17
|
+
> Define `noise` as Text.
|
18
|
+
> Set `noise` to _"WEE-OO WEE-OO WEE-OO"_.
|
19
|
+
> Say `noise`.
|
20
|
+
|
21
|
+
</details>"""
|
22
|
+
|
23
|
+
parser = Parser()
|
24
|
+
program = parser.parse(source)
|
25
|
+
|
26
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
27
|
+
assert len(program.statements) == 1
|
28
|
+
|
29
|
+
action_stmt = program.statements[0]
|
30
|
+
assert isinstance(action_stmt, ActionStatement)
|
31
|
+
assert action_stmt.name.value == "make noise"
|
32
|
+
assert len(action_stmt.inputs) == 0
|
33
|
+
assert len(action_stmt.outputs) == 0
|
34
|
+
assert isinstance(action_stmt.body, BlockStatement)
|
35
|
+
assert len(action_stmt.body.statements) == 3
|
36
|
+
|
37
|
+
# Check first statement: Define `noise` as Text.
|
38
|
+
from machine_dialect.ast import DefineStatement
|
39
|
+
|
40
|
+
define_stmt = action_stmt.body.statements[0]
|
41
|
+
assert isinstance(define_stmt, DefineStatement)
|
42
|
+
assert define_stmt.name.value == "noise"
|
43
|
+
|
44
|
+
# Check second statement: Set `noise` to _"WEE-OO WEE-OO WEE-OO"_.
|
45
|
+
set_stmt = action_stmt.body.statements[1]
|
46
|
+
assert isinstance(set_stmt, SetStatement)
|
47
|
+
assert set_stmt.name and set_stmt.name.value == "noise"
|
48
|
+
|
49
|
+
# Check third statement: Say `noise`.
|
50
|
+
say_stmt = action_stmt.body.statements[2]
|
51
|
+
from machine_dialect.ast import SayStatement
|
52
|
+
|
53
|
+
assert isinstance(say_stmt, SayStatement)
|
54
|
+
|
55
|
+
def test_action_with_heading_level(self) -> None:
|
56
|
+
"""Test that action heading level (###) is parsed correctly."""
|
57
|
+
source = """### **Action**: `calculate`
|
58
|
+
|
59
|
+
<details>
|
60
|
+
<summary>Performs a calculation.</summary>
|
61
|
+
|
62
|
+
> Define `result` as Whole Number.
|
63
|
+
> Set `result` to _42_.
|
64
|
+
|
65
|
+
</details>"""
|
66
|
+
|
67
|
+
parser = Parser()
|
68
|
+
program = parser.parse(source)
|
69
|
+
|
70
|
+
assert len(parser.errors) == 0
|
71
|
+
assert len(program.statements) == 1
|
72
|
+
|
73
|
+
action_stmt = program.statements[0]
|
74
|
+
assert isinstance(action_stmt, ActionStatement)
|
75
|
+
assert action_stmt.name.value == "calculate"
|
76
|
+
|
77
|
+
def test_action_with_multi_word_name(self) -> None:
|
78
|
+
"""Test action with multi-word name in backticks."""
|
79
|
+
source = """### **Action**: `send alert message`
|
80
|
+
|
81
|
+
<details>
|
82
|
+
<summary>Sends an alert.</summary>
|
83
|
+
|
84
|
+
> Say _"Alert!"_.
|
85
|
+
|
86
|
+
</details>"""
|
87
|
+
|
88
|
+
parser = Parser()
|
89
|
+
program = parser.parse(source)
|
90
|
+
|
91
|
+
assert len(parser.errors) == 0
|
92
|
+
assert len(program.statements) == 1
|
93
|
+
|
94
|
+
action_stmt = program.statements[0]
|
95
|
+
assert isinstance(action_stmt, ActionStatement)
|
96
|
+
assert action_stmt.name.value == "send alert message"
|
97
|
+
|
98
|
+
def test_action_plural_form(self) -> None:
|
99
|
+
"""Test that 'Actions' keyword also works."""
|
100
|
+
source = """### **Actions**: `make noise`
|
101
|
+
|
102
|
+
<details>
|
103
|
+
<summary>Emits sound.</summary>
|
104
|
+
|
105
|
+
> Say _"Noise"_.
|
106
|
+
|
107
|
+
</details>"""
|
108
|
+
|
109
|
+
parser = Parser()
|
110
|
+
program = parser.parse(source)
|
111
|
+
|
112
|
+
assert len(parser.errors) == 0
|
113
|
+
assert len(program.statements) == 1
|
114
|
+
|
115
|
+
action_stmt = program.statements[0]
|
116
|
+
assert isinstance(action_stmt, ActionStatement)
|
117
|
+
assert action_stmt.name.value == "make noise"
|
118
|
+
|
119
|
+
def test_action_with_empty_body(self) -> None:
|
120
|
+
"""Test action with no statements in body."""
|
121
|
+
source = """### **Action**: `do nothing`
|
122
|
+
|
123
|
+
<details>
|
124
|
+
<summary>Does nothing.</summary>
|
125
|
+
|
126
|
+
</details>"""
|
127
|
+
|
128
|
+
parser = Parser()
|
129
|
+
program = parser.parse(source)
|
130
|
+
|
131
|
+
assert len(parser.errors) == 0
|
132
|
+
assert len(program.statements) == 1
|
133
|
+
|
134
|
+
action_stmt = program.statements[0]
|
135
|
+
assert isinstance(action_stmt, ActionStatement)
|
136
|
+
assert action_stmt.name.value == "do nothing"
|
137
|
+
assert len(action_stmt.body.statements) == 0
|
138
|
+
|
139
|
+
def test_multiple_actions(self) -> None:
|
140
|
+
"""Test parsing multiple actions in one program."""
|
141
|
+
source = """### **Action**: `first action`
|
142
|
+
|
143
|
+
<details>
|
144
|
+
<summary>First action.</summary>
|
145
|
+
|
146
|
+
> Define `x` as Whole Number.
|
147
|
+
> Set `x` to _1_.
|
148
|
+
|
149
|
+
</details>
|
150
|
+
|
151
|
+
### **Action**: `second action`
|
152
|
+
|
153
|
+
<details>
|
154
|
+
<summary>Second action.</summary>
|
155
|
+
|
156
|
+
> Define `y` as Whole Number.
|
157
|
+
> Set `y` to _2_.
|
158
|
+
|
159
|
+
</details>"""
|
160
|
+
|
161
|
+
parser = Parser()
|
162
|
+
program = parser.parse(source)
|
163
|
+
|
164
|
+
assert len(parser.errors) == 0
|
165
|
+
assert len(program.statements) == 2
|
166
|
+
|
167
|
+
first_action = program.statements[0]
|
168
|
+
assert isinstance(first_action, ActionStatement)
|
169
|
+
assert first_action.name.value == "first action"
|
170
|
+
|
171
|
+
second_action = program.statements[1]
|
172
|
+
assert isinstance(second_action, ActionStatement)
|
173
|
+
assert second_action.name.value == "second action"
|
174
|
+
|
175
|
+
def test_action_with_input_parameters(self) -> None:
|
176
|
+
"""Test parsing an action with input parameters."""
|
177
|
+
source = """### **Action**: `make noise`
|
178
|
+
|
179
|
+
<details>
|
180
|
+
<summary>Emits the sound of the alarm.</summary>
|
181
|
+
|
182
|
+
> Define `noise` as Text.
|
183
|
+
> Set `noise` to `sound`.
|
184
|
+
> Say `noise`.
|
185
|
+
|
186
|
+
</details>
|
187
|
+
|
188
|
+
#### Inputs:
|
189
|
+
- `sound` **as** Text (required)
|
190
|
+
- `volume` **as** Whole Number (optional, default: _60_)"""
|
191
|
+
|
192
|
+
parser = Parser()
|
193
|
+
program = parser.parse(source)
|
194
|
+
|
195
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
196
|
+
assert len(program.statements) == 1
|
197
|
+
|
198
|
+
action_stmt = program.statements[0]
|
199
|
+
assert isinstance(action_stmt, ActionStatement)
|
200
|
+
assert action_stmt.name.value == "make noise"
|
201
|
+
|
202
|
+
# Check inputs
|
203
|
+
assert len(action_stmt.inputs) == 2
|
204
|
+
|
205
|
+
# First input: sound (required)
|
206
|
+
sound_param = action_stmt.inputs[0]
|
207
|
+
assert sound_param.name.value == "sound"
|
208
|
+
assert sound_param.type_name == "Text"
|
209
|
+
assert sound_param.is_required is True
|
210
|
+
assert sound_param.default_value is None
|
211
|
+
|
212
|
+
# Second input: volume (optional with default)
|
213
|
+
volume_param = action_stmt.inputs[1]
|
214
|
+
assert volume_param.name.value == "volume"
|
215
|
+
assert volume_param.type_name == "Whole Number"
|
216
|
+
assert volume_param.is_required is False
|
217
|
+
assert volume_param.default_value is not None
|
218
|
+
from machine_dialect.ast import WholeNumberLiteral
|
219
|
+
|
220
|
+
assert isinstance(volume_param.default_value, WholeNumberLiteral)
|
221
|
+
assert volume_param.default_value.value == 60
|
222
|
+
|
223
|
+
# No outputs
|
224
|
+
assert len(action_stmt.outputs) == 0
|
225
|
+
|
226
|
+
def test_action_with_output_parameters(self) -> None:
|
227
|
+
"""Test parsing an action with output parameters."""
|
228
|
+
source = """### **Action**: `calculate`
|
229
|
+
|
230
|
+
<details>
|
231
|
+
<summary>Performs a calculation.</summary>
|
232
|
+
|
233
|
+
> Define `result` as Number.
|
234
|
+
> Set `result` to _42_.
|
235
|
+
> Give back `result`.
|
236
|
+
|
237
|
+
</details>
|
238
|
+
|
239
|
+
#### Outputs:
|
240
|
+
- `result` **as** Number
|
241
|
+
- `success` **as** Yes/No"""
|
242
|
+
|
243
|
+
parser = Parser()
|
244
|
+
program = parser.parse(source)
|
245
|
+
|
246
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
247
|
+
assert len(program.statements) == 1
|
248
|
+
|
249
|
+
action_stmt = program.statements[0]
|
250
|
+
assert isinstance(action_stmt, ActionStatement)
|
251
|
+
assert action_stmt.name.value == "calculate"
|
252
|
+
|
253
|
+
# No inputs
|
254
|
+
assert len(action_stmt.inputs) == 0
|
255
|
+
|
256
|
+
# Check outputs
|
257
|
+
assert len(action_stmt.outputs) == 2
|
258
|
+
|
259
|
+
# First output: result
|
260
|
+
result_param = action_stmt.outputs[0]
|
261
|
+
assert isinstance(result_param, Output)
|
262
|
+
assert result_param.name.value == "result"
|
263
|
+
assert result_param.type_name == "Number"
|
264
|
+
|
265
|
+
# Second output: success
|
266
|
+
success_param = action_stmt.outputs[1]
|
267
|
+
assert isinstance(success_param, Output)
|
268
|
+
assert success_param.name.value == "success"
|
269
|
+
assert success_param.type_name == "Yes/No"
|
270
|
+
|
271
|
+
def test_action_with_both_inputs_and_outputs(self) -> None:
|
272
|
+
"""Test parsing an action with both input and output parameters."""
|
273
|
+
source = """### **Action**: `process data`
|
274
|
+
|
275
|
+
<details>
|
276
|
+
<summary>Processes input data.</summary>
|
277
|
+
|
278
|
+
> Define `result` as Text.
|
279
|
+
> Set `result` to `input`.
|
280
|
+
> Give back `result`.
|
281
|
+
|
282
|
+
</details>
|
283
|
+
|
284
|
+
#### Inputs:
|
285
|
+
- `input` **as** Text (required)
|
286
|
+
- `format` **as** Text (optional, default: _"json"_)
|
287
|
+
|
288
|
+
#### Outputs:
|
289
|
+
- `result` **as** Text
|
290
|
+
- `error` **as** Text"""
|
291
|
+
|
292
|
+
parser = Parser()
|
293
|
+
program = parser.parse(source)
|
294
|
+
|
295
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
296
|
+
assert len(program.statements) == 1
|
297
|
+
|
298
|
+
action_stmt = program.statements[0]
|
299
|
+
assert isinstance(action_stmt, ActionStatement)
|
300
|
+
assert action_stmt.name.value == "process data"
|
301
|
+
|
302
|
+
# Check inputs
|
303
|
+
assert len(action_stmt.inputs) == 2
|
304
|
+
|
305
|
+
input_param = action_stmt.inputs[0]
|
306
|
+
assert input_param.name.value == "input"
|
307
|
+
assert input_param.type_name == "Text"
|
308
|
+
assert input_param.is_required is True
|
309
|
+
|
310
|
+
format_param = action_stmt.inputs[1]
|
311
|
+
assert format_param.name.value == "format"
|
312
|
+
assert format_param.type_name == "Text"
|
313
|
+
assert format_param.is_required is False
|
314
|
+
assert format_param.default_value is not None
|
315
|
+
from machine_dialect.ast import StringLiteral
|
316
|
+
|
317
|
+
assert isinstance(format_param.default_value, StringLiteral)
|
318
|
+
assert format_param.default_value.value == "json"
|
319
|
+
|
320
|
+
# Check outputs
|
321
|
+
assert len(action_stmt.outputs) == 2
|
322
|
+
|
323
|
+
result_param = action_stmt.outputs[0]
|
324
|
+
assert isinstance(result_param, Output)
|
325
|
+
assert result_param.name.value == "result"
|
326
|
+
assert result_param.type_name == "Text"
|
327
|
+
|
328
|
+
error_param = action_stmt.outputs[1]
|
329
|
+
assert isinstance(error_param, Output)
|
330
|
+
assert error_param.name.value == "error"
|
331
|
+
assert error_param.type_name == "Text"
|
332
|
+
# Outputs without explicit defaults should have Empty as default
|
333
|
+
assert error_param.default_value is not None
|
334
|
+
assert isinstance(error_param.default_value, EmptyLiteral)
|
@@ -0,0 +1,152 @@
|
|
1
|
+
"""Tests for parsing boolean literal expressions.
|
2
|
+
|
3
|
+
This module tests the parser's ability to handle boolean literal expressions
|
4
|
+
including Yes and No in both regular and underscore-wrapped forms.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import pytest
|
8
|
+
|
9
|
+
from machine_dialect.ast import ExpressionStatement
|
10
|
+
from machine_dialect.parser import Parser
|
11
|
+
from machine_dialect.parser.tests.helper_functions import (
|
12
|
+
assert_literal_expression,
|
13
|
+
assert_program_statements,
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
class TestBooleanLiteralExpressions:
|
18
|
+
"""Test parsing of boolean literal expressions."""
|
19
|
+
|
20
|
+
@pytest.mark.parametrize(
|
21
|
+
"source,expected_value",
|
22
|
+
[
|
23
|
+
# Simple boolean literals
|
24
|
+
("Yes", True),
|
25
|
+
("No", False),
|
26
|
+
# Underscore-wrapped boolean literals
|
27
|
+
("_Yes_", True),
|
28
|
+
("_No_", False),
|
29
|
+
],
|
30
|
+
)
|
31
|
+
def test_boolean_literal_expression(self, source: str, expected_value: bool) -> None:
|
32
|
+
"""Test parsing various boolean literal expressions.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
source: The source code containing a boolean literal.
|
36
|
+
expected_value: The expected boolean value.
|
37
|
+
"""
|
38
|
+
parser = Parser()
|
39
|
+
|
40
|
+
program = parser.parse(source)
|
41
|
+
|
42
|
+
assert len(parser.errors) == 0
|
43
|
+
assert_program_statements(parser, program)
|
44
|
+
|
45
|
+
statement = program.statements[0]
|
46
|
+
assert isinstance(statement, ExpressionStatement)
|
47
|
+
assert statement.expression is not None
|
48
|
+
|
49
|
+
assert_literal_expression(statement.expression, expected_value)
|
50
|
+
|
51
|
+
def test_boolean_with_period(self) -> None:
|
52
|
+
"""Test parsing boolean literal with explicit statement terminator."""
|
53
|
+
source = "Yes."
|
54
|
+
parser = Parser()
|
55
|
+
|
56
|
+
program = parser.parse(source)
|
57
|
+
|
58
|
+
assert len(parser.errors) == 0
|
59
|
+
assert_program_statements(parser, program)
|
60
|
+
|
61
|
+
statement = program.statements[0]
|
62
|
+
assert isinstance(statement, ExpressionStatement)
|
63
|
+
assert statement.expression is not None
|
64
|
+
|
65
|
+
assert_literal_expression(statement.expression, True)
|
66
|
+
|
67
|
+
def test_multiple_boolean_statements(self) -> None:
|
68
|
+
"""Test parsing multiple boolean literal statements."""
|
69
|
+
source = "Yes. No. Yes. No."
|
70
|
+
parser = Parser()
|
71
|
+
|
72
|
+
program = parser.parse(source)
|
73
|
+
|
74
|
+
assert len(parser.errors) == 0
|
75
|
+
assert len(program.statements) == 4
|
76
|
+
|
77
|
+
# Check each statement
|
78
|
+
expected_values = [True, False, True, False]
|
79
|
+
for i, expected_value in enumerate(expected_values):
|
80
|
+
statement = program.statements[i]
|
81
|
+
assert isinstance(statement, ExpressionStatement)
|
82
|
+
assert statement.expression is not None
|
83
|
+
assert_literal_expression(statement.expression, expected_value)
|
84
|
+
|
85
|
+
def test_mixed_literals_with_booleans(self) -> None:
|
86
|
+
"""Test parsing mixed literal types including booleans."""
|
87
|
+
source = "Yes. 42. No. 3.14. _Yes_."
|
88
|
+
parser = Parser()
|
89
|
+
|
90
|
+
program = parser.parse(source)
|
91
|
+
|
92
|
+
assert len(parser.errors) == 0
|
93
|
+
assert len(program.statements) == 5
|
94
|
+
|
95
|
+
# Check boolean statements
|
96
|
+
statement = program.statements[0]
|
97
|
+
assert isinstance(statement, ExpressionStatement)
|
98
|
+
assert statement.expression is not None
|
99
|
+
assert_literal_expression(statement.expression, True)
|
100
|
+
|
101
|
+
statement = program.statements[2]
|
102
|
+
assert isinstance(statement, ExpressionStatement)
|
103
|
+
assert statement.expression is not None
|
104
|
+
assert_literal_expression(statement.expression, False)
|
105
|
+
|
106
|
+
statement = program.statements[4]
|
107
|
+
assert isinstance(statement, ExpressionStatement)
|
108
|
+
assert statement.expression is not None
|
109
|
+
assert_literal_expression(statement.expression, True)
|
110
|
+
|
111
|
+
@pytest.mark.parametrize(
|
112
|
+
"source,expected_value",
|
113
|
+
[
|
114
|
+
# Case insensitive variations should all parse as True
|
115
|
+
("yes", True),
|
116
|
+
("YES", True),
|
117
|
+
("YeS", True),
|
118
|
+
("yEs", True),
|
119
|
+
# Case insensitive variations should all parse as False
|
120
|
+
("no", False),
|
121
|
+
("NO", False),
|
122
|
+
("No", False),
|
123
|
+
("nO", False),
|
124
|
+
# Underscore-wrapped case variations
|
125
|
+
("_yes_", True),
|
126
|
+
("_YES_", True),
|
127
|
+
("_no_", False),
|
128
|
+
("_NO_", False),
|
129
|
+
],
|
130
|
+
)
|
131
|
+
def test_case_insensitive_boolean_literals(self, source: str, expected_value: bool) -> None:
|
132
|
+
"""Test that boolean literals are case-insensitive.
|
133
|
+
|
134
|
+
Boolean literals should be recognized regardless of case and stored
|
135
|
+
in their canonical form as defined in tokens.py.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
source: The source code with various case boolean literals.
|
139
|
+
expected_value: The expected boolean value.
|
140
|
+
"""
|
141
|
+
parser = Parser()
|
142
|
+
|
143
|
+
program = parser.parse(source)
|
144
|
+
|
145
|
+
assert len(parser.errors) == 0
|
146
|
+
assert_program_statements(parser, program)
|
147
|
+
|
148
|
+
statement = program.statements[0]
|
149
|
+
assert isinstance(statement, ExpressionStatement)
|
150
|
+
assert statement.expression is not None
|
151
|
+
|
152
|
+
assert_literal_expression(statement.expression, expected_value)
|
@@ -0,0 +1,154 @@
|
|
1
|
+
"""Tests for use statements in Machine Dialect™."""
|
2
|
+
|
3
|
+
from machine_dialect.ast import (
|
4
|
+
Arguments,
|
5
|
+
CallStatement,
|
6
|
+
Identifier,
|
7
|
+
StringLiteral,
|
8
|
+
WholeNumberLiteral,
|
9
|
+
)
|
10
|
+
from machine_dialect.parser import Parser
|
11
|
+
|
12
|
+
|
13
|
+
class TestCallStatements:
|
14
|
+
"""Test parsing of use statements."""
|
15
|
+
|
16
|
+
def test_call_without_parameters(self) -> None:
|
17
|
+
"""Test parsing a use statement without parameters."""
|
18
|
+
source = "use `turn alarm off`."
|
19
|
+
|
20
|
+
parser = Parser()
|
21
|
+
program = parser.parse(source, check_semantics=False)
|
22
|
+
|
23
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
24
|
+
assert len(program.statements) == 1
|
25
|
+
|
26
|
+
call_stmt = program.statements[0]
|
27
|
+
assert isinstance(call_stmt, CallStatement)
|
28
|
+
assert isinstance(call_stmt.function_name, Identifier)
|
29
|
+
assert call_stmt.function_name.value == "turn alarm off"
|
30
|
+
assert call_stmt.arguments is None
|
31
|
+
|
32
|
+
def test_call_with_positional_arguments(self) -> None:
|
33
|
+
"""Test parsing a use statement with positional arguments."""
|
34
|
+
source = "use `add numbers` with _5_, _10_."
|
35
|
+
|
36
|
+
parser = Parser()
|
37
|
+
program = parser.parse(source, check_semantics=False)
|
38
|
+
|
39
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
40
|
+
assert len(program.statements) == 1
|
41
|
+
|
42
|
+
call_stmt = program.statements[0]
|
43
|
+
assert isinstance(call_stmt, CallStatement)
|
44
|
+
assert isinstance(call_stmt.function_name, Identifier)
|
45
|
+
assert call_stmt.function_name.value == "add numbers"
|
46
|
+
|
47
|
+
assert call_stmt.arguments is not None
|
48
|
+
assert isinstance(call_stmt.arguments, Arguments)
|
49
|
+
# Should have 2 positional arguments
|
50
|
+
assert len(call_stmt.arguments.positional) == 2
|
51
|
+
assert len(call_stmt.arguments.named) == 0
|
52
|
+
|
53
|
+
# First argument
|
54
|
+
assert isinstance(call_stmt.arguments.positional[0], WholeNumberLiteral)
|
55
|
+
assert call_stmt.arguments.positional[0].value == 5
|
56
|
+
|
57
|
+
# Second argument
|
58
|
+
assert isinstance(call_stmt.arguments.positional[1], WholeNumberLiteral)
|
59
|
+
assert call_stmt.arguments.positional[1].value == 10
|
60
|
+
|
61
|
+
def test_call_with_named_arguments(self) -> None:
|
62
|
+
"""Test parsing a use statement with named arguments."""
|
63
|
+
source = 'use `make noise` where `sound` is _"WEE-OO WEE-OO WEE-OO"_, `volume` is _80_.'
|
64
|
+
|
65
|
+
parser = Parser()
|
66
|
+
program = parser.parse(source, check_semantics=False)
|
67
|
+
|
68
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
69
|
+
assert len(program.statements) == 1
|
70
|
+
|
71
|
+
call_stmt = program.statements[0]
|
72
|
+
assert isinstance(call_stmt, CallStatement)
|
73
|
+
assert isinstance(call_stmt.function_name, Identifier)
|
74
|
+
assert call_stmt.function_name.value == "make noise"
|
75
|
+
|
76
|
+
assert call_stmt.arguments is not None
|
77
|
+
assert isinstance(call_stmt.arguments, Arguments)
|
78
|
+
# Should have 2 named arguments
|
79
|
+
assert len(call_stmt.arguments.positional) == 0
|
80
|
+
assert len(call_stmt.arguments.named) == 2
|
81
|
+
|
82
|
+
# First named argument: sound
|
83
|
+
name0, val0 = call_stmt.arguments.named[0]
|
84
|
+
assert isinstance(name0, Identifier)
|
85
|
+
assert name0.value == "sound"
|
86
|
+
assert isinstance(val0, StringLiteral)
|
87
|
+
assert val0.value == "WEE-OO WEE-OO WEE-OO"
|
88
|
+
|
89
|
+
# Second named argument: volume
|
90
|
+
name1, val1 = call_stmt.arguments.named[1]
|
91
|
+
assert isinstance(name1, Identifier)
|
92
|
+
assert name1.value == "volume"
|
93
|
+
assert isinstance(val1, WholeNumberLiteral)
|
94
|
+
assert val1.value == 80
|
95
|
+
|
96
|
+
def test_call_with_identifier_as_function_name(self) -> None:
|
97
|
+
"""Test parsing a use statement with an identifier as the function name."""
|
98
|
+
source = 'use `my_function` with _"test"_.'
|
99
|
+
|
100
|
+
parser = Parser()
|
101
|
+
program = parser.parse(source, check_semantics=False)
|
102
|
+
|
103
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
104
|
+
assert len(program.statements) == 1
|
105
|
+
|
106
|
+
call_stmt = program.statements[0]
|
107
|
+
assert isinstance(call_stmt, CallStatement)
|
108
|
+
assert isinstance(call_stmt.function_name, Identifier)
|
109
|
+
assert call_stmt.function_name.value == "my_function"
|
110
|
+
|
111
|
+
def test_call_without_period(self) -> None:
|
112
|
+
"""Test that use statement without period fails."""
|
113
|
+
source = "use `my_function`"
|
114
|
+
|
115
|
+
parser = Parser()
|
116
|
+
parser.parse(source)
|
117
|
+
|
118
|
+
# Should have an error about missing period
|
119
|
+
assert len(parser.errors) > 0
|
120
|
+
assert any("period" in str(err).lower() for err in parser.errors)
|
121
|
+
|
122
|
+
def test_multiple_call_statements(self) -> None:
|
123
|
+
"""Test parsing multiple use statements."""
|
124
|
+
source = """use `start process`.
|
125
|
+
use `log message` with _"Process started"_.
|
126
|
+
use `stop process`.
|
127
|
+
"""
|
128
|
+
|
129
|
+
parser = Parser()
|
130
|
+
program = parser.parse(source, check_semantics=False)
|
131
|
+
|
132
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
133
|
+
assert len(program.statements) == 3
|
134
|
+
|
135
|
+
# First call
|
136
|
+
call1 = program.statements[0]
|
137
|
+
assert isinstance(call1, CallStatement)
|
138
|
+
assert isinstance(call1.function_name, Identifier)
|
139
|
+
assert call1.function_name.value == "start process"
|
140
|
+
assert call1.arguments is None
|
141
|
+
|
142
|
+
# Second call
|
143
|
+
call2 = program.statements[1]
|
144
|
+
assert isinstance(call2, CallStatement)
|
145
|
+
assert isinstance(call2.function_name, Identifier)
|
146
|
+
assert call2.function_name.value == "log message"
|
147
|
+
assert call2.arguments is not None
|
148
|
+
|
149
|
+
# Third call
|
150
|
+
call3 = program.statements[2]
|
151
|
+
assert isinstance(call3, CallStatement)
|
152
|
+
assert isinstance(call3.function_name, Identifier)
|
153
|
+
assert call3.function_name.value == "stop process"
|
154
|
+
assert call3.arguments is None
|