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,468 @@
|
|
1
|
+
"""Integration tests for Define statement with symbol table tracking.
|
2
|
+
|
3
|
+
These tests verify that the parser properly integrates with the symbol table
|
4
|
+
to track variable definitions and validate variable usage.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from machine_dialect.ast import DefineStatement, SetStatement
|
8
|
+
from machine_dialect.errors.exceptions import MDNameError, MDTypeError
|
9
|
+
from machine_dialect.parser import Parser
|
10
|
+
|
11
|
+
|
12
|
+
class TestDefineIntegration:
|
13
|
+
"""Test integration between Define statements and symbol table."""
|
14
|
+
|
15
|
+
def test_define_then_set_valid(self) -> None:
|
16
|
+
"""Test that defining a variable allows it to be set."""
|
17
|
+
source = """
|
18
|
+
Define `count` as Whole Number.
|
19
|
+
Set `count` to _42_.
|
20
|
+
"""
|
21
|
+
parser = Parser()
|
22
|
+
program = parser.parse(source)
|
23
|
+
|
24
|
+
# Should parse successfully
|
25
|
+
assert len(parser.errors) == 0
|
26
|
+
assert len(program.statements) == 2
|
27
|
+
assert isinstance(program.statements[0], DefineStatement)
|
28
|
+
assert isinstance(program.statements[1], SetStatement)
|
29
|
+
|
30
|
+
def test_set_undefined_variable_error(self) -> None:
|
31
|
+
"""Test that using undefined variable generates error."""
|
32
|
+
source = """
|
33
|
+
Set `undefined_var` to _10_.
|
34
|
+
"""
|
35
|
+
parser = Parser()
|
36
|
+
parser.parse(source)
|
37
|
+
|
38
|
+
# Should have exactly one error
|
39
|
+
assert len(parser.errors) == 1
|
40
|
+
error = parser.errors[0]
|
41
|
+
assert isinstance(error, MDNameError)
|
42
|
+
assert "undefined_var" in str(error)
|
43
|
+
assert "not defined" in str(error)
|
44
|
+
|
45
|
+
def test_define_with_default_then_set(self) -> None:
|
46
|
+
"""Test defining variable with default value then setting it."""
|
47
|
+
source = """
|
48
|
+
Define `message` as Text (default: _"Hello"_).
|
49
|
+
Set `message` to _"World"_.
|
50
|
+
"""
|
51
|
+
parser = Parser()
|
52
|
+
program = parser.parse(source)
|
53
|
+
|
54
|
+
# Should parse successfully
|
55
|
+
assert len(parser.errors) == 0
|
56
|
+
assert len(program.statements) == 2
|
57
|
+
|
58
|
+
def test_multiple_defines_then_sets(self) -> None:
|
59
|
+
"""Test multiple variable definitions and uses."""
|
60
|
+
source = """
|
61
|
+
Define `x` as Whole Number.
|
62
|
+
Define `y` as Float.
|
63
|
+
Define `name` as Text.
|
64
|
+
Set `x` to _10_.
|
65
|
+
Set `y` to _3.14_.
|
66
|
+
Set `name` to _"Alice"_.
|
67
|
+
"""
|
68
|
+
parser = Parser()
|
69
|
+
program = parser.parse(source)
|
70
|
+
|
71
|
+
# Should parse successfully
|
72
|
+
assert len(parser.errors) == 0
|
73
|
+
assert len(program.statements) == 6
|
74
|
+
|
75
|
+
def test_redefinition_error(self) -> None:
|
76
|
+
"""Test that redefining a variable generates error."""
|
77
|
+
source = """
|
78
|
+
Define `x` as Whole Number.
|
79
|
+
Define `x` as Text.
|
80
|
+
"""
|
81
|
+
parser = Parser()
|
82
|
+
parser.parse(source)
|
83
|
+
|
84
|
+
# Should have exactly one error for redefinition
|
85
|
+
assert len(parser.errors) == 1
|
86
|
+
error = parser.errors[0]
|
87
|
+
assert isinstance(error, MDNameError)
|
88
|
+
assert "already defined" in str(error)
|
89
|
+
|
90
|
+
def test_define_in_different_scopes(self) -> None:
|
91
|
+
"""Test that variables can be defined in different scopes (future feature)."""
|
92
|
+
# This test is a placeholder for when we implement scope handling
|
93
|
+
# Currently all variables are in global scope
|
94
|
+
source = """
|
95
|
+
Define `global_var` as Whole Number.
|
96
|
+
Set `global_var` to _1_.
|
97
|
+
"""
|
98
|
+
parser = Parser()
|
99
|
+
parser.parse(source)
|
100
|
+
|
101
|
+
assert len(parser.errors) == 0
|
102
|
+
|
103
|
+
def test_use_before_define_error(self) -> None:
|
104
|
+
"""Test that using variable before definition generates error."""
|
105
|
+
source = """
|
106
|
+
Set `x` to _5_.
|
107
|
+
Define `x` as Whole Number.
|
108
|
+
"""
|
109
|
+
parser = Parser()
|
110
|
+
parser.parse(source)
|
111
|
+
|
112
|
+
# Should have error for using undefined variable
|
113
|
+
assert len(parser.errors) == 1
|
114
|
+
error = parser.errors[0]
|
115
|
+
assert isinstance(error, MDNameError)
|
116
|
+
assert "not defined" in str(error)
|
117
|
+
|
118
|
+
def test_complex_program_with_defines_and_sets(self) -> None:
|
119
|
+
"""Test a more complex program with multiple defines and sets."""
|
120
|
+
source = """
|
121
|
+
Define `user_name` as Text.
|
122
|
+
Define `user_age` as Whole Number.
|
123
|
+
Define `is_admin` as Yes/No (default: _no_).
|
124
|
+
|
125
|
+
Set `user_name` to _"John Doe"_.
|
126
|
+
Set `user_age` to _25_.
|
127
|
+
Set `is_admin` to _yes_.
|
128
|
+
|
129
|
+
Define `score` as Float.
|
130
|
+
Set `score` to _98.5_.
|
131
|
+
"""
|
132
|
+
parser = Parser()
|
133
|
+
program = parser.parse(source)
|
134
|
+
|
135
|
+
# Should parse successfully
|
136
|
+
assert len(parser.errors) == 0
|
137
|
+
assert len(program.statements) == 8
|
138
|
+
|
139
|
+
def test_undefined_variable_in_expression(self) -> None:
|
140
|
+
"""Test that undefined variables in expressions generate errors."""
|
141
|
+
source = """
|
142
|
+
Define `x` as Whole Number.
|
143
|
+
Set `x` to _10_.
|
144
|
+
Set `y` to `x` + _5_.
|
145
|
+
"""
|
146
|
+
parser = Parser()
|
147
|
+
parser.parse(source)
|
148
|
+
|
149
|
+
# Should have error for undefined variable y
|
150
|
+
assert len(parser.errors) == 1
|
151
|
+
error = parser.errors[0]
|
152
|
+
assert isinstance(error, MDNameError)
|
153
|
+
assert "y" in str(error)
|
154
|
+
|
155
|
+
def test_define_with_union_types(self) -> None:
|
156
|
+
"""Test defining variable with union types."""
|
157
|
+
source = """
|
158
|
+
Define `flexible` as Whole Number or Text.
|
159
|
+
Set `flexible` to _42_.
|
160
|
+
Define `flexible` as Float.
|
161
|
+
"""
|
162
|
+
parser = Parser()
|
163
|
+
parser.parse(source)
|
164
|
+
|
165
|
+
# Should have error for redefinition
|
166
|
+
assert len(parser.errors) == 1
|
167
|
+
error = parser.errors[0]
|
168
|
+
assert isinstance(error, MDNameError)
|
169
|
+
assert "already defined" in str(error)
|
170
|
+
|
171
|
+
def test_error_recovery_continues_parsing(self) -> None:
|
172
|
+
"""Test that parser continues after encountering errors."""
|
173
|
+
source = """
|
174
|
+
Set `undefined1` to _1_.
|
175
|
+
Define `valid` as Whole Number.
|
176
|
+
Set `undefined2` to _2_.
|
177
|
+
Set `valid` to _100_.
|
178
|
+
Set `undefined3` to _3_.
|
179
|
+
"""
|
180
|
+
parser = Parser()
|
181
|
+
program = parser.parse(source)
|
182
|
+
|
183
|
+
# Should have 3 errors for undefined variables
|
184
|
+
assert len(parser.errors) == 3
|
185
|
+
# But should still parse all 5 statements
|
186
|
+
assert len(program.statements) == 5
|
187
|
+
|
188
|
+
def test_define_without_type_error(self) -> None:
|
189
|
+
"""Test that Define without type generates appropriate error."""
|
190
|
+
source = """
|
191
|
+
Define `x` as.
|
192
|
+
"""
|
193
|
+
parser = Parser()
|
194
|
+
parser.parse(source)
|
195
|
+
|
196
|
+
# Should have syntax error
|
197
|
+
assert len(parser.errors) > 0
|
198
|
+
|
199
|
+
def test_define_with_invalid_syntax_error(self) -> None:
|
200
|
+
"""Test that invalid Define syntax generates appropriate error."""
|
201
|
+
source = """
|
202
|
+
Define as Whole Number.
|
203
|
+
"""
|
204
|
+
parser = Parser()
|
205
|
+
parser.parse(source)
|
206
|
+
|
207
|
+
# Should have syntax error
|
208
|
+
assert len(parser.errors) > 0
|
209
|
+
|
210
|
+
def test_multiple_errors_collected(self) -> None:
|
211
|
+
"""Test that multiple errors are collected in one pass."""
|
212
|
+
source = """
|
213
|
+
Set `a` to _1_.
|
214
|
+
Set `b` to _2_.
|
215
|
+
Define `a` as Whole Number.
|
216
|
+
Define `a` as Text.
|
217
|
+
Set `c` to _3_.
|
218
|
+
"""
|
219
|
+
parser = Parser()
|
220
|
+
parser.parse(source)
|
221
|
+
|
222
|
+
# Should have errors for:
|
223
|
+
# 1. 'a' not defined (first Set)
|
224
|
+
# 2. 'b' not defined (second Set)
|
225
|
+
# 3. 'a' already defined (second Define)
|
226
|
+
# 4. 'c' not defined (last Set)
|
227
|
+
assert len(parser.errors) == 4
|
228
|
+
|
229
|
+
|
230
|
+
class TestDefineTypeChecking:
|
231
|
+
"""Test type checking integration with Define and Set statements."""
|
232
|
+
|
233
|
+
def test_valid_type_assignments(self) -> None:
|
234
|
+
"""Test that valid type assignments work correctly."""
|
235
|
+
source = """
|
236
|
+
Define `count` as Whole Number.
|
237
|
+
Set `count` to _42_.
|
238
|
+
|
239
|
+
Define `price` as Float.
|
240
|
+
Set `price` to _19.99_.
|
241
|
+
|
242
|
+
Define `name` as Text.
|
243
|
+
Set `name` to _"Alice"_.
|
244
|
+
|
245
|
+
Define `active` as Yes/No.
|
246
|
+
Set `active` to _yes_.
|
247
|
+
"""
|
248
|
+
parser = Parser()
|
249
|
+
program = parser.parse(source)
|
250
|
+
|
251
|
+
# Should parse without type errors
|
252
|
+
assert len(parser.errors) == 0
|
253
|
+
assert len(program.statements) == 8
|
254
|
+
|
255
|
+
def test_type_mismatch_whole_number_to_text(self) -> None:
|
256
|
+
"""Test type mismatch when assigning text to whole number."""
|
257
|
+
source = """
|
258
|
+
Define `count` as Whole Number.
|
259
|
+
Set `count` to _"not a number"_.
|
260
|
+
"""
|
261
|
+
parser = Parser()
|
262
|
+
parser.parse(source)
|
263
|
+
|
264
|
+
# Should have a type error
|
265
|
+
assert len(parser.errors) > 0
|
266
|
+
# Find type-related errors
|
267
|
+
from machine_dialect.errors import MDTypeError
|
268
|
+
|
269
|
+
type_errors = [e for e in parser.errors if isinstance(e, MDTypeError)]
|
270
|
+
assert len(type_errors) > 0
|
271
|
+
|
272
|
+
def test_type_mismatch_text_to_whole_number(self) -> None:
|
273
|
+
"""Test type mismatch when assigning number to text."""
|
274
|
+
source = """
|
275
|
+
Define `message` as Text.
|
276
|
+
Set `message` to _42_.
|
277
|
+
"""
|
278
|
+
parser = Parser()
|
279
|
+
parser.parse(source)
|
280
|
+
|
281
|
+
# Should have a type error
|
282
|
+
assert len(parser.errors) > 0
|
283
|
+
|
284
|
+
def test_union_type_valid_assignments(self) -> None:
|
285
|
+
"""Test that union types accept values of any specified type."""
|
286
|
+
source = """
|
287
|
+
Define `flexible` as Whole Number or Text.
|
288
|
+
Set `flexible` to _42_.
|
289
|
+
Set `flexible` to _"hello"_.
|
290
|
+
"""
|
291
|
+
parser = Parser()
|
292
|
+
parser.parse(source)
|
293
|
+
|
294
|
+
# Both assignments should be valid
|
295
|
+
assert len(parser.errors) == 0
|
296
|
+
|
297
|
+
def test_union_type_invalid_assignment(self) -> None:
|
298
|
+
"""Test that union types reject values not in the union."""
|
299
|
+
source = """
|
300
|
+
Define `flexible` as Whole Number or Text.
|
301
|
+
Set `flexible` to _yes_.
|
302
|
+
"""
|
303
|
+
parser = Parser()
|
304
|
+
parser.parse(source)
|
305
|
+
|
306
|
+
# Should have type error (Yes/No not in union)
|
307
|
+
assert len(parser.errors) > 0
|
308
|
+
|
309
|
+
def test_number_type_accepts_whole_and_float(self) -> None:
|
310
|
+
"""Test that Number type accepts both Whole Number and Float."""
|
311
|
+
source = """
|
312
|
+
Define `num` as Number.
|
313
|
+
Set `num` to _42_.
|
314
|
+
Set `num` to _3.14_.
|
315
|
+
"""
|
316
|
+
parser = Parser()
|
317
|
+
parser.parse(source)
|
318
|
+
|
319
|
+
# Both assignments should be valid
|
320
|
+
assert len(parser.errors) == 0
|
321
|
+
|
322
|
+
def test_number_type_rejects_non_numeric(self) -> None:
|
323
|
+
"""Test that Number type rejects non-numeric values."""
|
324
|
+
source = """
|
325
|
+
Define `num` as Number.
|
326
|
+
Set `num` to _"text"_.
|
327
|
+
"""
|
328
|
+
parser = Parser()
|
329
|
+
parser.parse(source)
|
330
|
+
|
331
|
+
# Should have type error
|
332
|
+
assert len(parser.errors) > 0
|
333
|
+
|
334
|
+
def test_empty_assignable_to_any_type(self) -> None:
|
335
|
+
"""Test that empty requires explicit type declaration for strict type checking."""
|
336
|
+
# In strict mode, empty is NOT assignable to non-nullable types
|
337
|
+
source = """
|
338
|
+
Define `maybe_text` as Text.
|
339
|
+
Set `maybe_text` to empty.
|
340
|
+
|
341
|
+
Define `maybe_num` as Whole Number.
|
342
|
+
Set `maybe_num` to empty.
|
343
|
+
|
344
|
+
Define `maybe_bool` as Yes/No.
|
345
|
+
Set `maybe_bool` to empty.
|
346
|
+
"""
|
347
|
+
parser = Parser()
|
348
|
+
parser.parse(source)
|
349
|
+
|
350
|
+
# Should have type errors for all empty assignments to non-nullable types
|
351
|
+
assert len(parser.errors) == 3
|
352
|
+
for error in parser.errors:
|
353
|
+
assert isinstance(error, MDTypeError)
|
354
|
+
assert "Empty" in str(error)
|
355
|
+
|
356
|
+
# Test that explicit nullable types work
|
357
|
+
source_nullable = """
|
358
|
+
Define `maybe_text` as Text or Empty.
|
359
|
+
Set `maybe_text` to empty.
|
360
|
+
|
361
|
+
Define `maybe_num` as Whole Number or Empty.
|
362
|
+
Set `maybe_num` to empty.
|
363
|
+
|
364
|
+
Define `maybe_bool` as Yes/No or Empty.
|
365
|
+
Set `maybe_bool` to empty.
|
366
|
+
"""
|
367
|
+
parser2 = Parser()
|
368
|
+
parser2.parse(source_nullable)
|
369
|
+
|
370
|
+
# All empty assignments to nullable types should be valid
|
371
|
+
assert len(parser2.errors) == 0
|
372
|
+
|
373
|
+
def test_url_type_assignment(self) -> None:
|
374
|
+
"""Test URL type assignments."""
|
375
|
+
source = """
|
376
|
+
Define `website` as URL.
|
377
|
+
Set `website` to _"https://example.com"_.
|
378
|
+
"""
|
379
|
+
parser = Parser()
|
380
|
+
parser.parse(source)
|
381
|
+
|
382
|
+
# URL assignment should be valid
|
383
|
+
assert len(parser.errors) == 0
|
384
|
+
|
385
|
+
def test_multiple_type_errors_collected(self) -> None:
|
386
|
+
"""Test that multiple type errors are collected."""
|
387
|
+
source = """
|
388
|
+
Define `a` as Whole Number.
|
389
|
+
Define `b` as Text.
|
390
|
+
Define `c` as Yes/No.
|
391
|
+
|
392
|
+
Set `a` to _"wrong"_.
|
393
|
+
Set `b` to _42_.
|
394
|
+
Set `c` to _"also wrong"_.
|
395
|
+
"""
|
396
|
+
parser = Parser()
|
397
|
+
parser.parse(source)
|
398
|
+
|
399
|
+
# Should have 3 type errors
|
400
|
+
assert len(parser.errors) == 3
|
401
|
+
|
402
|
+
def test_type_checking_with_expressions(self) -> None:
|
403
|
+
"""Test type checking with complex expressions."""
|
404
|
+
source = """
|
405
|
+
Define `x` as Whole Number.
|
406
|
+
Define `y` as Whole Number.
|
407
|
+
Set `x` to _10_.
|
408
|
+
Set `y` to _20_.
|
409
|
+
"""
|
410
|
+
parser = Parser()
|
411
|
+
parser.parse(source)
|
412
|
+
|
413
|
+
# Should parse without errors
|
414
|
+
assert len(parser.errors) == 0
|
415
|
+
|
416
|
+
def test_float_not_assignable_to_whole_number(self) -> None:
|
417
|
+
"""Test that Float cannot be assigned to Whole Number (no implicit conversion)."""
|
418
|
+
source = """
|
419
|
+
Define `int_only` as Whole Number.
|
420
|
+
Set `int_only` to _3.14_.
|
421
|
+
"""
|
422
|
+
parser = Parser()
|
423
|
+
parser.parse(source)
|
424
|
+
|
425
|
+
# Should have type error (no implicit conversions per spec)
|
426
|
+
assert len(parser.errors) > 0
|
427
|
+
|
428
|
+
def test_whole_number_assignable_to_number(self) -> None:
|
429
|
+
"""Test that Whole Number can be assigned to Number type."""
|
430
|
+
source = """
|
431
|
+
Define `num` as Number.
|
432
|
+
Set `num` to _42_.
|
433
|
+
"""
|
434
|
+
parser = Parser()
|
435
|
+
parser.parse(source)
|
436
|
+
|
437
|
+
# Should be valid (Number accepts Whole Number)
|
438
|
+
assert len(parser.errors) == 0
|
439
|
+
|
440
|
+
def test_complex_union_types(self) -> None:
|
441
|
+
"""Test complex union types with multiple alternatives."""
|
442
|
+
source = """
|
443
|
+
Define `complex` as Whole Number or Text or Yes/No or Empty.
|
444
|
+
Set `complex` to _42_.
|
445
|
+
Set `complex` to _"text"_.
|
446
|
+
Set `complex` to _yes_.
|
447
|
+
Set `complex` to empty.
|
448
|
+
"""
|
449
|
+
parser = Parser()
|
450
|
+
parser.parse(source)
|
451
|
+
|
452
|
+
# All assignments should be valid
|
453
|
+
assert len(parser.errors) == 0
|
454
|
+
|
455
|
+
def test_type_error_message_content(self) -> None:
|
456
|
+
"""Test that type error messages are informative."""
|
457
|
+
source = """
|
458
|
+
Define `num` as Whole Number.
|
459
|
+
Set `num` to _"text"_.
|
460
|
+
"""
|
461
|
+
parser = Parser()
|
462
|
+
parser.parse(source)
|
463
|
+
|
464
|
+
# Should have meaningful error message
|
465
|
+
assert len(parser.errors) > 0
|
466
|
+
error_str = str(parser.errors[0])
|
467
|
+
# Error should mention the variable name and types involved
|
468
|
+
assert "num" in error_str or "Whole Number" in error_str.lower() or "text" in error_str.lower()
|