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,680 @@
|
|
1
|
+
"""Tests for parsing infix expressions.
|
2
|
+
|
3
|
+
This module tests the parser's ability to handle infix expressions including:
|
4
|
+
- Arithmetic operators: +, -, *, /
|
5
|
+
- Comparison operators: ==, !=, <, >
|
6
|
+
- Operator precedence and associativity
|
7
|
+
- Complex expressions with mixed operators
|
8
|
+
"""
|
9
|
+
|
10
|
+
import pytest
|
11
|
+
|
12
|
+
from machine_dialect.ast import (
|
13
|
+
ExpressionStatement,
|
14
|
+
InfixExpression,
|
15
|
+
WholeNumberLiteral,
|
16
|
+
YesNoLiteral,
|
17
|
+
)
|
18
|
+
from machine_dialect.parser import Parser
|
19
|
+
from machine_dialect.parser.tests.helper_functions import (
|
20
|
+
assert_infix_expression,
|
21
|
+
assert_program_statements,
|
22
|
+
)
|
23
|
+
|
24
|
+
|
25
|
+
class TestInfixExpressions:
|
26
|
+
"""Test parsing of infix expressions."""
|
27
|
+
|
28
|
+
@pytest.mark.parametrize(
|
29
|
+
"source,left,operator,right",
|
30
|
+
[
|
31
|
+
# Addition
|
32
|
+
("5 + 5", 5, "+", 5),
|
33
|
+
("10 + 20", 10, "+", 20),
|
34
|
+
("42 + 123", 42, "+", 123),
|
35
|
+
("0 + 0", 0, "+", 0),
|
36
|
+
# Addition with underscores
|
37
|
+
("_5_ + _5_", 5, "+", 5),
|
38
|
+
("_10_ + _20_", 10, "+", 20),
|
39
|
+
# Subtraction
|
40
|
+
("5 - 5", 5, "-", 5),
|
41
|
+
("20 - 10", 20, "-", 10),
|
42
|
+
("100 - 50", 100, "-", 50),
|
43
|
+
("0 - 0", 0, "-", 0),
|
44
|
+
# Subtraction with underscores
|
45
|
+
("_5_ - _5_", 5, "-", 5),
|
46
|
+
("_20_ - _10_", 20, "-", 10),
|
47
|
+
# Multiplication
|
48
|
+
("5 * 5", 5, "*", 5),
|
49
|
+
("10 * 20", 10, "*", 20),
|
50
|
+
("7 * 8", 7, "*", 8),
|
51
|
+
("0 * 100", 0, "*", 100),
|
52
|
+
# Multiplication with underscores
|
53
|
+
("_5_ * _5_", 5, "*", 5),
|
54
|
+
("_10_ * _20_", 10, "*", 20),
|
55
|
+
# Division
|
56
|
+
("10 / 5", 10, "/", 5),
|
57
|
+
("20 / 4", 20, "/", 4),
|
58
|
+
("100 / 10", 100, "/", 10),
|
59
|
+
("0 / 1", 0, "/", 1),
|
60
|
+
# Division with underscores
|
61
|
+
("_10_ / _5_", 10, "/", 5),
|
62
|
+
("_20_ / _4_", 20, "/", 4),
|
63
|
+
],
|
64
|
+
)
|
65
|
+
def test_integer_arithmetic_expressions(self, source: str, left: int, operator: str, right: int) -> None:
|
66
|
+
"""Test parsing integer arithmetic infix expressions.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
source: The source code containing an infix expression.
|
70
|
+
left: Expected left operand value.
|
71
|
+
operator: Expected operator string.
|
72
|
+
right: Expected right operand value.
|
73
|
+
"""
|
74
|
+
parser = Parser()
|
75
|
+
|
76
|
+
program = parser.parse(source)
|
77
|
+
|
78
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
79
|
+
assert_program_statements(parser, program)
|
80
|
+
|
81
|
+
statement = program.statements[0]
|
82
|
+
assert isinstance(statement, ExpressionStatement)
|
83
|
+
assert statement.expression is not None
|
84
|
+
|
85
|
+
assert_infix_expression(statement.expression, left, operator, right)
|
86
|
+
|
87
|
+
@pytest.mark.parametrize(
|
88
|
+
"source,left,operator,right",
|
89
|
+
[
|
90
|
+
# Float addition
|
91
|
+
("3.14 + 2.86", 3.14, "+", 2.86),
|
92
|
+
("0.5 + 0.5", 0.5, "+", 0.5),
|
93
|
+
("10.0 + 20.0", 10.0, "+", 20.0),
|
94
|
+
# Float subtraction
|
95
|
+
("5.5 - 2.5", 5.5, "-", 2.5),
|
96
|
+
("10.0 - 5.0", 10.0, "-", 5.0),
|
97
|
+
("3.14 - 1.14", 3.14, "-", 1.14),
|
98
|
+
# Float multiplication
|
99
|
+
("2.5 * 2.0", 2.5, "*", 2.0),
|
100
|
+
("3.14 * 2.0", 3.14, "*", 2.0),
|
101
|
+
("0.5 * 0.5", 0.5, "*", 0.5),
|
102
|
+
# Float division
|
103
|
+
("10.0 / 2.0", 10.0, "/", 2.0),
|
104
|
+
("7.5 / 2.5", 7.5, "/", 2.5),
|
105
|
+
("3.14 / 2.0", 3.14, "/", 2.0),
|
106
|
+
# Mixed integer and float
|
107
|
+
("5 + 2.5", 5, "+", 2.5),
|
108
|
+
("10.0 - 5", 10.0, "-", 5),
|
109
|
+
("3 * 2.5", 3, "*", 2.5),
|
110
|
+
("10.0 / 2", 10.0, "/", 2),
|
111
|
+
],
|
112
|
+
)
|
113
|
+
def test_float_arithmetic_expressions(
|
114
|
+
self, source: str, left: int | float, operator: str, right: int | float
|
115
|
+
) -> None:
|
116
|
+
"""Test parsing float and mixed arithmetic infix expressions.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
source: The source code containing an infix expression.
|
120
|
+
left: Expected left operand value.
|
121
|
+
operator: Expected operator string.
|
122
|
+
right: Expected right operand value.
|
123
|
+
"""
|
124
|
+
parser = Parser()
|
125
|
+
|
126
|
+
program = parser.parse(source)
|
127
|
+
|
128
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
129
|
+
assert_program_statements(parser, program)
|
130
|
+
|
131
|
+
statement = program.statements[0]
|
132
|
+
assert isinstance(statement, ExpressionStatement)
|
133
|
+
assert statement.expression is not None
|
134
|
+
|
135
|
+
assert_infix_expression(statement.expression, left, operator, right)
|
136
|
+
|
137
|
+
@pytest.mark.parametrize(
|
138
|
+
"source,left,operator,right",
|
139
|
+
[
|
140
|
+
# Integer comparisons
|
141
|
+
("5 equals 5", 5, "equals", 5),
|
142
|
+
("10 equals 20", 10, "equals", 20),
|
143
|
+
("5 is not 5", 5, "is not", 5),
|
144
|
+
("10 is not 20", 10, "is not", 20),
|
145
|
+
("5 < 10", 5, "<", 10),
|
146
|
+
("20 < 10", 20, "<", 10),
|
147
|
+
("5 > 10", 5, ">", 10),
|
148
|
+
("20 > 10", 20, ">", 10),
|
149
|
+
("5 <= 10", 5, "<=", 10),
|
150
|
+
("10 <= 10", 10, "<=", 10),
|
151
|
+
("20 <= 10", 20, "<=", 10),
|
152
|
+
("5 >= 10", 5, ">=", 10),
|
153
|
+
("10 >= 10", 10, ">=", 10),
|
154
|
+
("20 >= 10", 20, ">=", 10),
|
155
|
+
# Float comparisons
|
156
|
+
("3.14 equals 3.14", 3.14, "equals", 3.14),
|
157
|
+
("2.5 is not 3.5", 2.5, "is not", 3.5),
|
158
|
+
("1.5 < 2.5", 1.5, "<", 2.5),
|
159
|
+
("3.5 > 2.5", 3.5, ">", 2.5),
|
160
|
+
# Boolean comparisons
|
161
|
+
("Yes equals Yes", True, "equals", True),
|
162
|
+
("Yes equals No", True, "equals", False),
|
163
|
+
("No is not Yes", False, "is not", True),
|
164
|
+
("No is not No", False, "is not", False),
|
165
|
+
# Mixed type comparisons (will be type-checked at runtime)
|
166
|
+
("5 equals 5.0", 5, "equals", 5.0),
|
167
|
+
("10 is not 10.5", 10, "is not", 10.5),
|
168
|
+
("3 < 3.14", 3, "<", 3.14),
|
169
|
+
("5.0 > 4", 5.0, ">", 4),
|
170
|
+
],
|
171
|
+
)
|
172
|
+
def test_comparison_expressions(
|
173
|
+
self, source: str, left: int | float | bool, operator: str, right: int | float | bool
|
174
|
+
) -> None:
|
175
|
+
"""Test parsing comparison infix expressions.
|
176
|
+
|
177
|
+
Args:
|
178
|
+
source: The source code containing a comparison expression.
|
179
|
+
left: Expected left operand value.
|
180
|
+
operator: Expected comparison operator.
|
181
|
+
right: Expected right operand value.
|
182
|
+
"""
|
183
|
+
parser = Parser()
|
184
|
+
|
185
|
+
program = parser.parse(source)
|
186
|
+
|
187
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
188
|
+
assert_program_statements(parser, program)
|
189
|
+
|
190
|
+
statement = program.statements[0]
|
191
|
+
assert isinstance(statement, ExpressionStatement)
|
192
|
+
assert statement.expression is not None
|
193
|
+
|
194
|
+
assert_infix_expression(statement.expression, left, operator, right)
|
195
|
+
|
196
|
+
@pytest.mark.parametrize(
|
197
|
+
"source,left,operator,right",
|
198
|
+
[
|
199
|
+
# Identifier arithmetic
|
200
|
+
("x + z", "x", "+", "z"),
|
201
|
+
("foo - bar", "foo", "-", "bar"),
|
202
|
+
("p * q", "p", "*", "q"),
|
203
|
+
("width / height", "width", "/", "height"),
|
204
|
+
# Identifier comparisons
|
205
|
+
("x equals z", "x", "equals", "z"),
|
206
|
+
("foo is not bar", "foo", "is not", "bar"),
|
207
|
+
("p < q", "p", "<", "q"),
|
208
|
+
("width > height", "width", ">", "height"),
|
209
|
+
# Mixed identifier and literal
|
210
|
+
("x + 5", "x", "+", 5),
|
211
|
+
("10 - z", 10, "-", "z"),
|
212
|
+
("pi * 2", "pi", "*", 2),
|
213
|
+
("total / 100.0", "total", "/", 100.0),
|
214
|
+
# Backtick identifiers
|
215
|
+
("`first name` + `last name`", "first name", "+", "last name"),
|
216
|
+
("`total cost` * `tax rate`", "total cost", "*", "tax rate"),
|
217
|
+
("`is valid` equals Yes", "is valid", "equals", True),
|
218
|
+
],
|
219
|
+
)
|
220
|
+
def test_identifier_expressions(
|
221
|
+
self, source: str, left: str | int | float, operator: str, right: str | int | float | bool
|
222
|
+
) -> None:
|
223
|
+
"""Test parsing infix expressions with identifiers.
|
224
|
+
|
225
|
+
Args:
|
226
|
+
source: The source code containing an infix expression with identifiers.
|
227
|
+
left: Expected left operand value.
|
228
|
+
operator: Expected operator string.
|
229
|
+
right: Expected right operand value.
|
230
|
+
"""
|
231
|
+
parser = Parser()
|
232
|
+
|
233
|
+
program = parser.parse(source)
|
234
|
+
|
235
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
236
|
+
assert_program_statements(parser, program)
|
237
|
+
|
238
|
+
statement = program.statements[0]
|
239
|
+
assert isinstance(statement, ExpressionStatement)
|
240
|
+
assert statement.expression is not None
|
241
|
+
|
242
|
+
assert_infix_expression(statement.expression, left, operator, right)
|
243
|
+
|
244
|
+
@pytest.mark.parametrize(
|
245
|
+
"source,left,operator,right",
|
246
|
+
[
|
247
|
+
# Logical AND
|
248
|
+
("Yes and Yes", True, "and", True),
|
249
|
+
("Yes and No", True, "and", False),
|
250
|
+
("No and Yes", False, "and", True),
|
251
|
+
("No and No", False, "and", False),
|
252
|
+
# Logical OR
|
253
|
+
("Yes or Yes", True, "or", True),
|
254
|
+
("Yes or No", True, "or", False),
|
255
|
+
("No or Yes", False, "or", True),
|
256
|
+
("No or No", False, "or", False),
|
257
|
+
# Case variations
|
258
|
+
("yes AND no", True, "and", False),
|
259
|
+
("YES And NO", True, "and", False),
|
260
|
+
("yes OR no", True, "or", False),
|
261
|
+
("YES Or NO", True, "or", False),
|
262
|
+
# With identifiers
|
263
|
+
("x and z", "x", "and", "z"),
|
264
|
+
("foo or bar", "foo", "or", "bar"),
|
265
|
+
("`is valid` and `has permission`", "is valid", "and", "has permission"),
|
266
|
+
# Mixed with literals
|
267
|
+
("x and Yes", "x", "and", True),
|
268
|
+
("No or z", False, "or", "z"),
|
269
|
+
# With underscores
|
270
|
+
("_Yes_ and _No_", True, "and", False),
|
271
|
+
("_x_ or _y_", "_x_", "or", "_y_"),
|
272
|
+
],
|
273
|
+
)
|
274
|
+
def test_logical_operators(self, source: str, left: bool | str, operator: str, right: bool | str) -> None:
|
275
|
+
"""Test parsing logical operator expressions (and, or).
|
276
|
+
|
277
|
+
Args:
|
278
|
+
source: The source code containing a logical expression.
|
279
|
+
left: Expected left operand value.
|
280
|
+
operator: Expected logical operator ('and' or 'or').
|
281
|
+
right: Expected right operand value.
|
282
|
+
"""
|
283
|
+
parser = Parser()
|
284
|
+
|
285
|
+
program = parser.parse(source)
|
286
|
+
|
287
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
288
|
+
assert_program_statements(parser, program)
|
289
|
+
|
290
|
+
statement = program.statements[0]
|
291
|
+
assert isinstance(statement, ExpressionStatement)
|
292
|
+
assert statement.expression is not None
|
293
|
+
|
294
|
+
assert_infix_expression(statement.expression, left, operator.lower(), right)
|
295
|
+
|
296
|
+
def test_natural_language_comparison_operators(self) -> None:
|
297
|
+
"""Test parsing natural language comparison operators."""
|
298
|
+
test_cases = [
|
299
|
+
# Equality variations
|
300
|
+
("5 is equal to 5", 5, "equals", 5),
|
301
|
+
("x is equal to z", "x", "equals", "z"),
|
302
|
+
("10 is the same as 10", 10, "equals", 10),
|
303
|
+
("foo is the same as bar", "foo", "equals", "bar"),
|
304
|
+
("3.14 equals 3.14", 3.14, "equals", 3.14),
|
305
|
+
("`value` equals 42", "value", "equals", 42),
|
306
|
+
# Inequality variations
|
307
|
+
("5 is not 10", 5, "is not", 10),
|
308
|
+
("x is not z", "x", "is not", "z"),
|
309
|
+
("5 isn't 10", 5, "is not", 10),
|
310
|
+
("`value` isn't 0", "value", "is not", 0),
|
311
|
+
("10 is not equal to 20", 10, "is not", 20),
|
312
|
+
("foo is not equal to bar", "foo", "is not", "bar"),
|
313
|
+
("5 doesn't equal 10", 5, "is not", 10),
|
314
|
+
("result doesn't equal expected", "result", "is not", "expected"),
|
315
|
+
("7 is different from 8", 7, "is not", 8),
|
316
|
+
("actual is different from expected", "actual", "is not", "expected"),
|
317
|
+
# Greater than variations
|
318
|
+
("10 is greater than 5", 10, ">", 5),
|
319
|
+
("x is greater than 0", "x", ">", 0),
|
320
|
+
("20 is more than 10", 20, ">", 10),
|
321
|
+
("total is more than limit", "total", ">", "limit"),
|
322
|
+
# Less than variations
|
323
|
+
("5 is less than 10", 5, "<", 10),
|
324
|
+
("`value` is less than max", "value", "<", "max"),
|
325
|
+
("3 is under 10", 3, "<", 10),
|
326
|
+
("price is under budget", "price", "<", "budget"),
|
327
|
+
("2 is fewer than 5", 2, "<", 5),
|
328
|
+
("errors is fewer than threshold", "errors", "<", "threshold"),
|
329
|
+
# Greater than or equal variations
|
330
|
+
("10 is greater than or equal to 10", 10, ">=", 10),
|
331
|
+
("x is greater than or equal to min", "x", ">=", "min"),
|
332
|
+
("5 is at least 5", 5, ">=", 5),
|
333
|
+
("score is at least passing", "score", ">=", "passing"),
|
334
|
+
("10 is no less than 5", 10, ">=", 5),
|
335
|
+
("`value` is no less than minimum", "value", ">=", "minimum"),
|
336
|
+
# Less than or equal variations
|
337
|
+
("5 is less than or equal to 10", 5, "<=", 10),
|
338
|
+
("x is less than or equal to max", "x", "<=", "max"),
|
339
|
+
("10 is at most 10", 10, "<=", 10),
|
340
|
+
("cost is at most budget", "cost", "<=", "budget"),
|
341
|
+
("5 is no more than 10", 5, "<=", 10),
|
342
|
+
("usage is no more than limit", "usage", "<=", "limit"),
|
343
|
+
# Mixed with identifiers and literals
|
344
|
+
("`total cost` is equal to 100.50", "total cost", "equals", 100.50),
|
345
|
+
("`error count` is less than 5", "error count", "<", 5),
|
346
|
+
("Yes is not No", True, "is not", False),
|
347
|
+
("_42_ equals _42_", 42, "equals", 42),
|
348
|
+
]
|
349
|
+
|
350
|
+
for source, left_value, expected_operator, right_value in test_cases:
|
351
|
+
parser = Parser()
|
352
|
+
|
353
|
+
program = parser.parse(source)
|
354
|
+
|
355
|
+
assert len(parser.errors) == 0, f"Parser errors for '{source}': {parser.errors}"
|
356
|
+
assert_program_statements(parser, program)
|
357
|
+
|
358
|
+
statement = program.statements[0]
|
359
|
+
assert isinstance(statement, ExpressionStatement)
|
360
|
+
assert statement.expression is not None
|
361
|
+
|
362
|
+
assert_infix_expression(statement.expression, left_value, expected_operator, right_value)
|
363
|
+
|
364
|
+
def test_natural_language_operators_in_complex_expressions(self) -> None:
|
365
|
+
"""Test natural language operators in complex expressions with precedence."""
|
366
|
+
test_cases = [
|
367
|
+
# With logical operators
|
368
|
+
("x is equal to 5 and y is greater than 10", "((x equals 5) and (y > 10))"),
|
369
|
+
("foo is not bar or baz is less than qux", "((foo is not bar) or (baz < qux))"),
|
370
|
+
("`value` is at least 0 and `value` is at most 100", "((value >= 0) and (value <= 100))"),
|
371
|
+
# With arithmetic
|
372
|
+
("x + 5 is equal to 10", "((x + 5) equals 10)"),
|
373
|
+
("2 * y is greater than 20", "((2 * y) > 20)"),
|
374
|
+
("total / count is less than average", "((total / count) < average)"),
|
375
|
+
# With parentheses
|
376
|
+
("(x is equal to 5) and (y is not 10)", "((x equals 5) and (y is not 10))"),
|
377
|
+
("not (x is greater than 10)", "(not (x > 10))"),
|
378
|
+
# Nested comparisons
|
379
|
+
("x is greater than y and y is greater than z", "((x > y) and (y > z))"),
|
380
|
+
("score is at least passing or retake is equal to True", "((score >= passing) or (retake equals True))"),
|
381
|
+
]
|
382
|
+
|
383
|
+
for source, _ in test_cases:
|
384
|
+
parser = Parser()
|
385
|
+
|
386
|
+
program = parser.parse(source)
|
387
|
+
|
388
|
+
assert len(parser.errors) == 0, f"Parser errors for '{source}': {parser.errors}"
|
389
|
+
assert len(program.statements) == 1
|
390
|
+
|
391
|
+
statement = program.statements[0]
|
392
|
+
assert isinstance(statement, ExpressionStatement)
|
393
|
+
assert statement.expression is not None
|
394
|
+
|
395
|
+
# For now, just ensure it parses without errors
|
396
|
+
# The exact string representation would depend on how we format natural language operators
|
397
|
+
|
398
|
+
def test_operator_precedence(self) -> None:
|
399
|
+
"""Test that operators follow correct precedence rules."""
|
400
|
+
# Test cases with expected parsing based on precedence
|
401
|
+
test_cases = [
|
402
|
+
# Multiplication before addition
|
403
|
+
("5 + 2 * 3", "(_5_ + (_2_ * _3_))"),
|
404
|
+
("2 * 3 + 5", "((_2_ * _3_) + _5_)"),
|
405
|
+
# Division before subtraction
|
406
|
+
("10 - 6 / 2", "(_10_ - (_6_ / _2_))"),
|
407
|
+
("6 / 2 - 1", "((_6_ / _2_) - _1_)"),
|
408
|
+
# Same precedence operators are left-associative
|
409
|
+
("5 - 3 - 1", "((_5_ - _3_) - _1_)"),
|
410
|
+
("10 / 5 / 2", "((_10_ / _5_) / _2_)"),
|
411
|
+
# Complex expressions
|
412
|
+
("1 + 2 * 3 + 4", "((_1_ + (_2_ * _3_)) + _4_)"),
|
413
|
+
("5 + 6 * 7 - 8 / 2", "((_5_ + (_6_ * _7_)) - (_8_ / _2_))"),
|
414
|
+
# Comparison operators have lower precedence than arithmetic
|
415
|
+
("5 + 3 equals 8", "((_5_ + _3_) equals _8_)"),
|
416
|
+
("2 * 3 < 10", "((_2_ * _3_) < _10_)"),
|
417
|
+
("10 / 2 > 4", "((_10_ / _2_) > _4_)"),
|
418
|
+
("3 + 2 <= 5", "((_3_ + _2_) <= _5_)"),
|
419
|
+
("8 - 3 >= 5", "((_8_ - _3_) >= _5_)"),
|
420
|
+
# Logical operators have lowest precedence
|
421
|
+
("Yes and No or Yes", "((_Yes_ and _No_) or _Yes_)"),
|
422
|
+
("Yes or No and Yes", "(_Yes_ or (_No_ and _Yes_))"),
|
423
|
+
("5 > 3 and 10 < 20", "((_5_ > _3_) and (_10_ < _20_))"),
|
424
|
+
("x equals z or p is not q", "((`x` equals `z`) or (`p` is not `q`))"),
|
425
|
+
# Mixed precedence with logical operators
|
426
|
+
("5 + 3 > 7 and 2 * 3 equals 6", "(((_5_ + _3_) > _7_) and ((_2_ * _3_) equals _6_))"),
|
427
|
+
("not x equals z and w > 0", "(((not `x`) equals `z`) and (`w` > _0_))"),
|
428
|
+
]
|
429
|
+
|
430
|
+
for source, expected in test_cases:
|
431
|
+
parser = Parser()
|
432
|
+
program = parser.parse(source)
|
433
|
+
|
434
|
+
assert len(parser.errors) == 0, f"Parser errors for '{source}': {parser.errors}"
|
435
|
+
assert len(program.statements) == 1
|
436
|
+
|
437
|
+
statement = program.statements[0]
|
438
|
+
assert isinstance(statement, ExpressionStatement)
|
439
|
+
assert statement.expression is not None
|
440
|
+
|
441
|
+
# Check string representation matches expected precedence
|
442
|
+
assert str(statement.expression) == expected, (
|
443
|
+
f"For '{source}': expected {expected}, got {statement.expression!s}"
|
444
|
+
)
|
445
|
+
|
446
|
+
def test_grouped_expressions(self) -> None:
|
447
|
+
"""Test parsing expressions with parentheses for grouping."""
|
448
|
+
test_cases = [
|
449
|
+
# Parentheses override precedence
|
450
|
+
("(5 + 2) * 3", "((_5_ + _2_) * _3_)"),
|
451
|
+
("3 * (5 + 2)", "(_3_ * (_5_ + _2_))"),
|
452
|
+
("(10 - 6) / 2", "((_10_ - _6_) / _2_)"),
|
453
|
+
("2 / (10 - 6)", "(_2_ / (_10_ - _6_))"),
|
454
|
+
# Nested parentheses
|
455
|
+
("((5 + 2) * 3) + 4", "(((_5_ + _2_) * _3_) + _4_)"),
|
456
|
+
("5 + ((2 * 3) + 4)", "(_5_ + ((_2_ * _3_) + _4_))"),
|
457
|
+
# Complex grouped expressions
|
458
|
+
("(2 + 3) * (4 + 5)", "((_2_ + _3_) * (_4_ + _5_))"),
|
459
|
+
("((1 + 2) * 3) / (4 - 2)", "(((_1_ + _2_) * _3_) / (_4_ - _2_))"),
|
460
|
+
# Logical operators with parentheses
|
461
|
+
("(Yes or No) and Yes", "((_Yes_ or _No_) and _Yes_)"),
|
462
|
+
("Yes and (No or Yes)", "(_Yes_ and (_No_ or _Yes_))"),
|
463
|
+
("(No and Yes) or No", "((_No_ and _Yes_) or _No_)"),
|
464
|
+
("No or (Yes and No)", "(_No_ or (_Yes_ and _No_))"),
|
465
|
+
# Complex logical expressions with parentheses
|
466
|
+
("(x or z) and (p or q)", "((`x` or `z`) and (`p` or `q`))"),
|
467
|
+
("(foo and bar) or (baz and qux)", "((`foo` and `bar`) or (`baz` and `qux`))"),
|
468
|
+
("not (x and z)", "(not (`x` and `z`))"),
|
469
|
+
("not (x or z)", "(not (`x` or `z`))"),
|
470
|
+
# Mixed logical and comparison with parentheses
|
471
|
+
("(x > 5) and (y < 10)", "((`x` > _5_) and (`y` < _10_))"),
|
472
|
+
("(foo equals bar) or (baz is not qux)", "((`foo` equals `bar`) or (`baz` is not `qux`))"),
|
473
|
+
("(5 > 3) and (10 < 20 or 15 equals 15)", "((_5_ > _3_) and ((_10_ < _20_) or (_15_ equals _15_)))"),
|
474
|
+
# Deeply nested logical expressions
|
475
|
+
("((x or z) and p) or q", "(((`x` or `z`) and `p`) or `q`)"),
|
476
|
+
("x or (z and (p or q))", "(`x` or (`z` and (`p` or `q`)))"),
|
477
|
+
(
|
478
|
+
"((Yes or No) and (No or Yes)) or No",
|
479
|
+
"(((_Yes_ or _No_) and (_No_ or _Yes_)) or _No_)",
|
480
|
+
),
|
481
|
+
]
|
482
|
+
|
483
|
+
for source, expected in test_cases:
|
484
|
+
parser = Parser()
|
485
|
+
program = parser.parse(source)
|
486
|
+
|
487
|
+
assert len(parser.errors) == 0, f"Parser errors for '{source}': {parser.errors}"
|
488
|
+
assert len(program.statements) == 1
|
489
|
+
|
490
|
+
statement = program.statements[0]
|
491
|
+
assert isinstance(statement, ExpressionStatement)
|
492
|
+
assert statement.expression is not None
|
493
|
+
|
494
|
+
assert str(statement.expression) == expected, (
|
495
|
+
f"For '{source}': expected {expected}, got {statement.expression!s}"
|
496
|
+
)
|
497
|
+
|
498
|
+
def test_complex_logical_with_comparison(self) -> None:
|
499
|
+
"""Test parsing complex expressions with comparison and logical operators."""
|
500
|
+
source = "5 > 3 and Yes"
|
501
|
+
parser = Parser()
|
502
|
+
|
503
|
+
program = parser.parse(source)
|
504
|
+
|
505
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
506
|
+
assert len(program.statements) == 1
|
507
|
+
|
508
|
+
statement = program.statements[0]
|
509
|
+
assert isinstance(statement, ExpressionStatement)
|
510
|
+
assert statement.expression is not None
|
511
|
+
|
512
|
+
# The expression should be an InfixExpression with 'and' operator
|
513
|
+
expr = statement.expression
|
514
|
+
assert isinstance(expr, InfixExpression)
|
515
|
+
assert expr.operator == "and"
|
516
|
+
|
517
|
+
# The left side should be the comparison (5 > 3)
|
518
|
+
assert expr.left is not None
|
519
|
+
assert isinstance(expr.left, InfixExpression)
|
520
|
+
left_expr = expr.left
|
521
|
+
assert left_expr.operator == ">"
|
522
|
+
|
523
|
+
# Check the comparison operands
|
524
|
+
assert left_expr.left is not None
|
525
|
+
assert isinstance(left_expr.left, WholeNumberLiteral)
|
526
|
+
assert left_expr.left.value == 5
|
527
|
+
|
528
|
+
assert left_expr.right is not None
|
529
|
+
assert isinstance(left_expr.right, WholeNumberLiteral)
|
530
|
+
assert left_expr.right.value == 3
|
531
|
+
|
532
|
+
# The right side should be True
|
533
|
+
assert expr.right is not None
|
534
|
+
assert isinstance(expr.right, YesNoLiteral)
|
535
|
+
assert expr.right.value is True
|
536
|
+
|
537
|
+
# Check the string representation
|
538
|
+
assert str(statement.expression) == "((_5_ > _3_) and _Yes_)"
|
539
|
+
|
540
|
+
def test_mixed_prefix_and_infix_expressions(self) -> None:
|
541
|
+
"""Test parsing expressions that combine prefix and infix operators."""
|
542
|
+
test_cases = [
|
543
|
+
# Negative numbers in arithmetic
|
544
|
+
("-5 + 10", "((-_5_) + _10_)"),
|
545
|
+
("10 + -5", "(_10_ + (-_5_))"),
|
546
|
+
("-5 * -5", "((-_5_) * (-_5_))"),
|
547
|
+
("-10 / 2", "((-_10_) / _2_)"),
|
548
|
+
# Boolean negation with comparisons
|
549
|
+
("not x equals z", "((not `x`) equals `z`)"),
|
550
|
+
("not 5 < 10", "((not _5_) < _10_)"),
|
551
|
+
("not Yes equals No", "((not _Yes_) equals _No_)"),
|
552
|
+
# Complex mixed expressions
|
553
|
+
("-x + z * -w", "((-`x`) + (`z` * (-`w`)))"),
|
554
|
+
("not p equals q and r > v", "(((not `p`) equals `q`) and (`r` > `v`))"),
|
555
|
+
]
|
556
|
+
|
557
|
+
for source, expected in test_cases:
|
558
|
+
parser = Parser()
|
559
|
+
program = parser.parse(source)
|
560
|
+
|
561
|
+
assert len(parser.errors) == 0, f"Parser errors for '{source}': {parser.errors}"
|
562
|
+
assert len(program.statements) == 1
|
563
|
+
|
564
|
+
statement = program.statements[0]
|
565
|
+
assert isinstance(statement, ExpressionStatement)
|
566
|
+
assert statement.expression is not None
|
567
|
+
|
568
|
+
assert str(statement.expression) == expected, (
|
569
|
+
f"For '{source}': expected {expected}, got {statement.expression!s}"
|
570
|
+
)
|
571
|
+
|
572
|
+
def test_multiple_infix_expressions(self) -> None:
|
573
|
+
"""Test parsing multiple infix expressions in sequence."""
|
574
|
+
source = "5 + 5. 10 - 2. 3 * 4. 8 / 2."
|
575
|
+
parser = Parser()
|
576
|
+
|
577
|
+
program = parser.parse(source)
|
578
|
+
|
579
|
+
assert len(parser.errors) == 0
|
580
|
+
assert len(program.statements) == 4
|
581
|
+
|
582
|
+
# First: 5 + 5
|
583
|
+
statement = program.statements[0]
|
584
|
+
assert isinstance(statement, ExpressionStatement)
|
585
|
+
assert statement.expression is not None
|
586
|
+
assert_infix_expression(statement.expression, 5, "+", 5)
|
587
|
+
|
588
|
+
# Second: 10 - 2
|
589
|
+
statement = program.statements[1]
|
590
|
+
assert isinstance(statement, ExpressionStatement)
|
591
|
+
assert statement.expression is not None
|
592
|
+
assert_infix_expression(statement.expression, 10, "-", 2)
|
593
|
+
|
594
|
+
# Third: 3 * 4
|
595
|
+
statement = program.statements[2]
|
596
|
+
assert isinstance(statement, ExpressionStatement)
|
597
|
+
assert statement.expression is not None
|
598
|
+
assert_infix_expression(statement.expression, 3, "*", 4)
|
599
|
+
|
600
|
+
# Fourth: 8 / 2
|
601
|
+
statement = program.statements[3]
|
602
|
+
assert isinstance(statement, ExpressionStatement)
|
603
|
+
assert statement.expression is not None
|
604
|
+
assert_infix_expression(statement.expression, 8, "/", 2)
|
605
|
+
|
606
|
+
def test_infix_expression_string_representation(self) -> None:
|
607
|
+
"""Test the string representation of infix expressions."""
|
608
|
+
test_cases = [
|
609
|
+
# Basic arithmetic
|
610
|
+
("5 + 5", "(_5_ + _5_)"),
|
611
|
+
("10 - 5", "(_10_ - _5_)"),
|
612
|
+
("3 * 4", "(_3_ * _4_)"),
|
613
|
+
("10 / 2", "(_10_ / _2_)"),
|
614
|
+
# Comparisons
|
615
|
+
("5 equals 5", "(_5_ equals _5_)"),
|
616
|
+
("10 is not 5", "(_10_ is not _5_)"),
|
617
|
+
("3 < 4", "(_3_ < _4_)"),
|
618
|
+
("10 > 2", "(_10_ > _2_)"),
|
619
|
+
# With identifiers
|
620
|
+
("x + z", "(`x` + `z`)"),
|
621
|
+
("foo equals bar", "(`foo` equals `bar`)"),
|
622
|
+
# Complex expressions
|
623
|
+
("5 + 2 * 3", "(_5_ + (_2_ * _3_))"),
|
624
|
+
("-5 + 10", "((-_5_) + _10_)"),
|
625
|
+
]
|
626
|
+
|
627
|
+
for source, expected in test_cases:
|
628
|
+
parser = Parser()
|
629
|
+
program = parser.parse(source)
|
630
|
+
|
631
|
+
assert len(parser.errors) == 0
|
632
|
+
assert len(program.statements) == 1
|
633
|
+
|
634
|
+
statement = program.statements[0]
|
635
|
+
assert isinstance(statement, ExpressionStatement)
|
636
|
+
assert statement.expression is not None
|
637
|
+
|
638
|
+
assert str(statement.expression) == expected
|
639
|
+
|
640
|
+
@pytest.mark.parametrize(
|
641
|
+
"source,expected_error",
|
642
|
+
[
|
643
|
+
# Missing right operand
|
644
|
+
("5 +", "expected expression, got <end-of-file>"),
|
645
|
+
("10 -", "expected expression, got <end-of-file>"),
|
646
|
+
("x *", "expected expression, got <end-of-file>"),
|
647
|
+
# Missing left operand (these would be parsed as prefix expressions or cause errors)
|
648
|
+
("+ 5", "unexpected token '+' at start of expression"),
|
649
|
+
("* 10", "unexpected token '*' at start of expression"),
|
650
|
+
("/ 2", "unexpected token '/' at start of expression"),
|
651
|
+
# Invalid operator combinations
|
652
|
+
("5 ++ 5", "unexpected token '+' at start of expression"),
|
653
|
+
# Missing operands in complex expressions
|
654
|
+
("5 + * 3", "unexpected token '*' at start of expression"),
|
655
|
+
("(5 + ) * 3", "No suitable parse function was found to handle ')'"),
|
656
|
+
# Natural language operator errors
|
657
|
+
("x is equal to", "expected expression, got <end-of-file>"),
|
658
|
+
("is greater than 5", "unexpected token 'is greater than' at start of expression"),
|
659
|
+
("5 is", "No suitable parse function was found to handle 'is'"),
|
660
|
+
],
|
661
|
+
)
|
662
|
+
def test_invalid_infix_expressions(self, source: str, expected_error: str) -> None:
|
663
|
+
"""Test that invalid infix expressions produce appropriate errors.
|
664
|
+
|
665
|
+
Args:
|
666
|
+
source: The invalid source code.
|
667
|
+
expected_error: Expected error message substring.
|
668
|
+
"""
|
669
|
+
parser = Parser()
|
670
|
+
|
671
|
+
parser.parse(source)
|
672
|
+
|
673
|
+
# Should have at least one error
|
674
|
+
assert len(parser.errors) > 0, f"Expected errors for '{source}', but got none"
|
675
|
+
|
676
|
+
# Check that at least one error contains the expected message
|
677
|
+
error_messages = [str(error) for error in parser.errors]
|
678
|
+
assert any(expected_error in msg for msg in error_messages), (
|
679
|
+
f"Expected error containing '{expected_error}' for '{source}', but got: {error_messages}"
|
680
|
+
)
|