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,189 @@
|
|
1
|
+
"""Error message generation for semantic analysis.
|
2
|
+
|
3
|
+
This module provides helpful error messages with context and suggestions
|
4
|
+
for semantic analysis errors.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from difflib import SequenceMatcher
|
8
|
+
|
9
|
+
|
10
|
+
class ErrorMessageGenerator:
|
11
|
+
"""Generates helpful error messages with context and suggestions."""
|
12
|
+
|
13
|
+
@staticmethod
|
14
|
+
def undefined_variable(var_name: str, line: int, position: int, similar_vars: list[str] | None = None) -> str:
|
15
|
+
"""Generate error message for undefined variable.
|
16
|
+
|
17
|
+
Args:
|
18
|
+
var_name: Variable name that's undefined
|
19
|
+
line: Line number
|
20
|
+
position: Column position
|
21
|
+
similar_vars: List of similar variable names
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
Formatted error message
|
25
|
+
"""
|
26
|
+
base_msg = f"Variable '{var_name}' is not defined"
|
27
|
+
|
28
|
+
# Add line/position info
|
29
|
+
location = f" at line {line}, column {position}"
|
30
|
+
|
31
|
+
# Add suggestions if similar variables exist
|
32
|
+
suggestion = ""
|
33
|
+
if similar_vars:
|
34
|
+
if len(similar_vars) == 1:
|
35
|
+
suggestion = f"\nDid you mean '{similar_vars[0]}'?"
|
36
|
+
else:
|
37
|
+
vars_list = "', '".join(similar_vars[:3])
|
38
|
+
suggestion = f"\nDid you mean one of: '{vars_list}'?"
|
39
|
+
|
40
|
+
# Add hint for defining variable
|
41
|
+
hint = f"\nHint: Add 'Define `{var_name}` as Type.' before using it"
|
42
|
+
|
43
|
+
return base_msg + location + suggestion + hint
|
44
|
+
|
45
|
+
@staticmethod
|
46
|
+
def type_mismatch(
|
47
|
+
var_name: str,
|
48
|
+
expected_types: list[str],
|
49
|
+
actual_type: str,
|
50
|
+
line: int,
|
51
|
+
position: int,
|
52
|
+
value_repr: str | None = None,
|
53
|
+
) -> str:
|
54
|
+
"""Generate error message for type mismatch.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
var_name: Variable name
|
58
|
+
expected_types: List of expected type names
|
59
|
+
actual_type: Actual type found
|
60
|
+
line: Line number
|
61
|
+
position: Column position
|
62
|
+
value_repr: String representation of the value
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
Formatted error message
|
66
|
+
"""
|
67
|
+
location = f"Error at line {line}, column {position}:"
|
68
|
+
|
69
|
+
if len(expected_types) == 1:
|
70
|
+
expected_str = expected_types[0]
|
71
|
+
main_msg = f"Cannot set {expected_str} variable '{var_name}' to {actual_type} value"
|
72
|
+
else:
|
73
|
+
expected_str = " or ".join(expected_types)
|
74
|
+
main_msg = f"Cannot set '{var_name}' to {actual_type} value"
|
75
|
+
|
76
|
+
if value_repr:
|
77
|
+
main_msg += f" {value_repr}"
|
78
|
+
|
79
|
+
type_info = f"\nExpected: {expected_str}\nActual: {actual_type}"
|
80
|
+
|
81
|
+
# Add conversion hint if applicable
|
82
|
+
hint = ""
|
83
|
+
if actual_type == "Whole Number" and "Float" in expected_types:
|
84
|
+
try:
|
85
|
+
# Try to convert value_repr to int first to ensure proper formatting
|
86
|
+
if value_repr and value_repr.isdigit():
|
87
|
+
hint = f"\nHint: Use _{value_repr}.0_ to make it a Float"
|
88
|
+
else:
|
89
|
+
hint = f"\nHint: Use _{value_repr or 'value'}.0_ to make it a Float"
|
90
|
+
except Exception:
|
91
|
+
hint = "\nHint: Use _value.0_ to make it a Float"
|
92
|
+
elif actual_type == "Float" and "Whole Number" in expected_types:
|
93
|
+
hint = "\nHint: Float values cannot be assigned to Whole Number variables"
|
94
|
+
|
95
|
+
return f"{location}\n{main_msg}{type_info}{hint}"
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def redefinition(
|
99
|
+
var_name: str, new_line: int, new_position: int, original_line: int, original_position: int
|
100
|
+
) -> str:
|
101
|
+
"""Generate error message for variable redefinition.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
var_name: Variable name
|
105
|
+
new_line: Line of redefinition attempt
|
106
|
+
new_position: Column of redefinition attempt
|
107
|
+
original_line: Line of original definition
|
108
|
+
original_position: Column of original definition
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Formatted error message
|
112
|
+
"""
|
113
|
+
location = f"Error at line {new_line}, column {new_position}:"
|
114
|
+
main_msg = f"Variable '{var_name}' is already defined"
|
115
|
+
original = f"\nOriginal definition at line {original_line}, column {original_position}"
|
116
|
+
hint = "\nHint: Variables cannot be redefined. Use 'Set' to change the value"
|
117
|
+
|
118
|
+
return f"{location}\n{main_msg}{original}{hint}"
|
119
|
+
|
120
|
+
@staticmethod
|
121
|
+
def uninitialized_use(var_name: str, line: int, position: int, definition_line: int) -> str:
|
122
|
+
"""Generate error message for using uninitialized variable.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
var_name: Variable name
|
126
|
+
line: Line where used
|
127
|
+
position: Column where used
|
128
|
+
definition_line: Line where defined
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
Formatted error message
|
132
|
+
"""
|
133
|
+
location = f"Error at line {line}, column {position}:"
|
134
|
+
main_msg = f"Variable '{var_name}' is used before being initialized"
|
135
|
+
definition = f"\nVariable was defined at line {definition_line}"
|
136
|
+
hint = f"\nHint: Add 'Set `{var_name}` to value.' before using it"
|
137
|
+
|
138
|
+
return f"{location}\n{main_msg}{definition}{hint}"
|
139
|
+
|
140
|
+
@staticmethod
|
141
|
+
def invalid_type(type_name: str, line: int, position: int, valid_types: list[str]) -> str:
|
142
|
+
"""Generate error message for invalid type name.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
type_name: Invalid type name
|
146
|
+
line: Line number
|
147
|
+
position: Column position
|
148
|
+
valid_types: List of valid type names
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
Formatted error message
|
152
|
+
"""
|
153
|
+
location = f"Error at line {line}, column {position}:"
|
154
|
+
main_msg = f"Unknown type '{type_name}'"
|
155
|
+
|
156
|
+
# Find similar valid types
|
157
|
+
similar = ErrorMessageGenerator._find_similar(type_name, valid_types)
|
158
|
+
suggestion = ""
|
159
|
+
if similar:
|
160
|
+
if len(similar) == 1:
|
161
|
+
suggestion = f"\nDid you mean '{similar[0]}'?"
|
162
|
+
else:
|
163
|
+
suggestion = f"\nDid you mean one of: {', '.join(similar[:3])}"
|
164
|
+
|
165
|
+
valid_list = "\nValid types: " + ", ".join(sorted(valid_types))
|
166
|
+
|
167
|
+
return f"{location}\n{main_msg}{suggestion}{valid_list}"
|
168
|
+
|
169
|
+
@staticmethod
|
170
|
+
def _find_similar(name: str, candidates: list[str], threshold: float = 0.6) -> list[str]:
|
171
|
+
"""Find similar names using edit distance.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
name: Name to match
|
175
|
+
candidates: List of candidate names
|
176
|
+
threshold: Similarity threshold (0-1)
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
List of similar names
|
180
|
+
"""
|
181
|
+
similarities = []
|
182
|
+
for candidate in candidates:
|
183
|
+
ratio = SequenceMatcher(None, name.lower(), candidate.lower()).ratio()
|
184
|
+
if ratio >= threshold:
|
185
|
+
similarities.append((candidate, ratio))
|
186
|
+
|
187
|
+
# Sort by similarity and return names only
|
188
|
+
similarities.sort(key=lambda x: x[1], reverse=True)
|
189
|
+
return [name for name, _ in similarities[:5]]
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Tests for semantic analysis components."""
|
@@ -0,0 +1,364 @@
|
|
1
|
+
"""Tests for semantic analyzer functionality.
|
2
|
+
|
3
|
+
This module tests the semantic analysis capabilities including type checking,
|
4
|
+
variable usage validation, and error detection.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from machine_dialect.errors import MDNameError, MDTypeError
|
8
|
+
from machine_dialect.parser import Parser
|
9
|
+
from machine_dialect.semantic.analyzer import SemanticAnalyzer
|
10
|
+
|
11
|
+
|
12
|
+
class TestSemanticAnalyzer:
|
13
|
+
"""Test semantic analysis functionality."""
|
14
|
+
|
15
|
+
def test_undefined_variable_error(self) -> None:
|
16
|
+
"""Test error for using undefined variable."""
|
17
|
+
source = """
|
18
|
+
Set `x` to _5_.
|
19
|
+
"""
|
20
|
+
parser = Parser()
|
21
|
+
program = parser.parse(source, check_semantics=False)
|
22
|
+
|
23
|
+
analyzer = SemanticAnalyzer()
|
24
|
+
_, errors = analyzer.analyze(program)
|
25
|
+
|
26
|
+
assert len(errors) == 1
|
27
|
+
assert isinstance(errors[0], MDNameError)
|
28
|
+
assert "not defined" in str(errors[0])
|
29
|
+
assert "Define" in str(errors[0]) # Should suggest Define
|
30
|
+
|
31
|
+
def test_type_mismatch_error(self) -> None:
|
32
|
+
"""Test error for type mismatch."""
|
33
|
+
source = """
|
34
|
+
Define `age` as Whole Number.
|
35
|
+
Set `age` to _"twenty"_.
|
36
|
+
"""
|
37
|
+
parser = Parser()
|
38
|
+
program = parser.parse(source, check_semantics=False)
|
39
|
+
|
40
|
+
analyzer = SemanticAnalyzer()
|
41
|
+
_, errors = analyzer.analyze(program)
|
42
|
+
|
43
|
+
assert len(errors) == 1
|
44
|
+
assert isinstance(errors[0], MDTypeError)
|
45
|
+
assert "Whole Number" in str(errors[0])
|
46
|
+
assert "Text" in str(errors[0])
|
47
|
+
|
48
|
+
def test_redefinition_error(self) -> None:
|
49
|
+
"""Test error for variable redefinition."""
|
50
|
+
source = """
|
51
|
+
Define `x` as Whole Number.
|
52
|
+
Define `x` as Text.
|
53
|
+
"""
|
54
|
+
parser = Parser()
|
55
|
+
program = parser.parse(source, check_semantics=False)
|
56
|
+
|
57
|
+
analyzer = SemanticAnalyzer()
|
58
|
+
_, errors = analyzer.analyze(program)
|
59
|
+
|
60
|
+
assert len(errors) == 1
|
61
|
+
assert isinstance(errors[0], MDNameError)
|
62
|
+
assert "already defined" in str(errors[0])
|
63
|
+
|
64
|
+
def test_valid_union_type_assignment(self) -> None:
|
65
|
+
"""Test valid assignment to union type."""
|
66
|
+
source = """
|
67
|
+
Define `value` as Whole Number or Text.
|
68
|
+
Set `value` to _42_.
|
69
|
+
Set `value` to _"hello"_.
|
70
|
+
"""
|
71
|
+
parser = Parser()
|
72
|
+
program = parser.parse(source, check_semantics=False)
|
73
|
+
|
74
|
+
analyzer = SemanticAnalyzer()
|
75
|
+
_, errors = analyzer.analyze(program)
|
76
|
+
|
77
|
+
assert len(errors) == 0
|
78
|
+
|
79
|
+
def test_invalid_union_type_assignment(self) -> None:
|
80
|
+
"""Test invalid assignment to union type."""
|
81
|
+
source = """
|
82
|
+
Define `value` as Whole Number or Text.
|
83
|
+
Set `value` to _yes_.
|
84
|
+
"""
|
85
|
+
parser = Parser()
|
86
|
+
program = parser.parse(source, check_semantics=False)
|
87
|
+
|
88
|
+
analyzer = SemanticAnalyzer()
|
89
|
+
_, errors = analyzer.analyze(program)
|
90
|
+
|
91
|
+
assert len(errors) == 1
|
92
|
+
assert isinstance(errors[0], MDTypeError)
|
93
|
+
assert "Yes/No" in str(errors[0])
|
94
|
+
assert "Whole Number" in str(errors[0]) or "Text" in str(errors[0])
|
95
|
+
|
96
|
+
def test_default_value_type_check(self) -> None:
|
97
|
+
"""Test type checking for default values."""
|
98
|
+
source = """
|
99
|
+
Define `count` as Whole Number (default: _"zero"_).
|
100
|
+
"""
|
101
|
+
parser = Parser()
|
102
|
+
program = parser.parse(source, check_semantics=False)
|
103
|
+
|
104
|
+
analyzer = SemanticAnalyzer()
|
105
|
+
_, errors = analyzer.analyze(program)
|
106
|
+
|
107
|
+
assert len(errors) == 1
|
108
|
+
assert isinstance(errors[0], MDTypeError)
|
109
|
+
assert "Default value" in str(errors[0])
|
110
|
+
|
111
|
+
def test_uninitialized_variable_use(self) -> None:
|
112
|
+
"""Test error for using uninitialized variable."""
|
113
|
+
source = """
|
114
|
+
Define `x` as Whole Number.
|
115
|
+
Define `y` as Whole Number.
|
116
|
+
Set `y` to `x`.
|
117
|
+
"""
|
118
|
+
parser = Parser()
|
119
|
+
program = parser.parse(source, check_semantics=False)
|
120
|
+
|
121
|
+
analyzer = SemanticAnalyzer()
|
122
|
+
_, errors = analyzer.analyze(program)
|
123
|
+
|
124
|
+
assert len(errors) == 1
|
125
|
+
assert "before being initialized" in str(errors[0])
|
126
|
+
|
127
|
+
def test_number_type_accepts_int_and_float(self) -> None:
|
128
|
+
"""Test that Number type accepts both Whole Number and Float."""
|
129
|
+
source = """
|
130
|
+
Define `value` as Number.
|
131
|
+
Set `value` to _42_.
|
132
|
+
Set `value` to _3.14_.
|
133
|
+
"""
|
134
|
+
parser = Parser()
|
135
|
+
program = parser.parse(source, check_semantics=False)
|
136
|
+
|
137
|
+
analyzer = SemanticAnalyzer()
|
138
|
+
_, errors = analyzer.analyze(program)
|
139
|
+
|
140
|
+
assert len(errors) == 0
|
141
|
+
|
142
|
+
def test_valid_default_value(self) -> None:
|
143
|
+
"""Test valid default value with matching type."""
|
144
|
+
source = """
|
145
|
+
Define `count` as Whole Number (default: _0_).
|
146
|
+
Define `name` as Text (default: _"John"_).
|
147
|
+
Define `active` as Yes/No (default: _yes_).
|
148
|
+
"""
|
149
|
+
parser = Parser()
|
150
|
+
program = parser.parse(source, check_semantics=False)
|
151
|
+
|
152
|
+
analyzer = SemanticAnalyzer()
|
153
|
+
_, errors = analyzer.analyze(program)
|
154
|
+
|
155
|
+
assert len(errors) == 0
|
156
|
+
|
157
|
+
def test_empty_type_compatibility(self) -> None:
|
158
|
+
"""Test that Empty type works with nullable types."""
|
159
|
+
source = """
|
160
|
+
Define `optional` as Text or Empty.
|
161
|
+
Set `optional` to _"text"_.
|
162
|
+
Set `optional` to empty.
|
163
|
+
"""
|
164
|
+
parser = Parser()
|
165
|
+
program = parser.parse(source, check_semantics=False)
|
166
|
+
|
167
|
+
analyzer = SemanticAnalyzer()
|
168
|
+
_, errors = analyzer.analyze(program)
|
169
|
+
|
170
|
+
assert len(errors) == 0
|
171
|
+
|
172
|
+
def test_invalid_type_name(self) -> None:
|
173
|
+
"""Test error for invalid type name."""
|
174
|
+
source = """
|
175
|
+
Define `x` as String.
|
176
|
+
"""
|
177
|
+
parser = Parser()
|
178
|
+
parser.parse(source, check_semantics=False)
|
179
|
+
|
180
|
+
# Parser already catches invalid type names and creates an ErrorStatement
|
181
|
+
# So semantic analyzer won't see it
|
182
|
+
assert len(parser.errors) == 1
|
183
|
+
assert "String" in str(parser.errors[0])
|
184
|
+
|
185
|
+
def test_expression_type_inference(self) -> None:
|
186
|
+
"""Test type inference for expressions."""
|
187
|
+
source = """
|
188
|
+
Define `sum` as Whole Number.
|
189
|
+
Define `a` as Whole Number.
|
190
|
+
Define `b` as Whole Number.
|
191
|
+
Set `a` to _5_.
|
192
|
+
Set `b` to _10_.
|
193
|
+
Set `sum` to `a` + `b`.
|
194
|
+
"""
|
195
|
+
parser = Parser()
|
196
|
+
program = parser.parse(source, check_semantics=False)
|
197
|
+
|
198
|
+
analyzer = SemanticAnalyzer()
|
199
|
+
_, errors = analyzer.analyze(program)
|
200
|
+
|
201
|
+
# Should be valid - no errors
|
202
|
+
assert len(errors) == 0
|
203
|
+
|
204
|
+
def test_comparison_returns_boolean(self) -> None:
|
205
|
+
"""Test that comparison operators return Yes/No type."""
|
206
|
+
source = """
|
207
|
+
Define `result` as Yes/No.
|
208
|
+
Define `x` as Whole Number.
|
209
|
+
Set `x` to _5_.
|
210
|
+
Set `result` to `x` > _3_.
|
211
|
+
"""
|
212
|
+
parser = Parser()
|
213
|
+
program = parser.parse(source, check_semantics=False)
|
214
|
+
|
215
|
+
analyzer = SemanticAnalyzer()
|
216
|
+
_, errors = analyzer.analyze(program)
|
217
|
+
|
218
|
+
assert len(errors) == 0
|
219
|
+
|
220
|
+
def test_division_returns_float(self) -> None:
|
221
|
+
"""Test that division always returns Float type."""
|
222
|
+
source = """
|
223
|
+
Define `result` as Float.
|
224
|
+
Define `x` as Whole Number.
|
225
|
+
Define `y` as Whole Number.
|
226
|
+
Set `x` to _10_.
|
227
|
+
Set `y` to _3_.
|
228
|
+
Set `result` to `x` / `y`.
|
229
|
+
"""
|
230
|
+
parser = Parser()
|
231
|
+
program = parser.parse(source, check_semantics=False)
|
232
|
+
|
233
|
+
analyzer = SemanticAnalyzer()
|
234
|
+
_, errors = analyzer.analyze(program)
|
235
|
+
|
236
|
+
assert len(errors) == 0
|
237
|
+
|
238
|
+
def test_bitwise_operators_type_inference(self) -> None:
|
239
|
+
"""Test that bitwise operators return Whole Number type."""
|
240
|
+
source = """
|
241
|
+
Define `a` as Whole Number.
|
242
|
+
Define `b` as Whole Number.
|
243
|
+
Define `result` as Whole Number.
|
244
|
+
Set `a` to _5_.
|
245
|
+
Set `b` to _3_.
|
246
|
+
"""
|
247
|
+
# Note: We can't fully test bitwise operators without parser support
|
248
|
+
# but the type inference is ready when the parser supports them
|
249
|
+
parser = Parser()
|
250
|
+
program = parser.parse(source, check_semantics=False)
|
251
|
+
|
252
|
+
analyzer = SemanticAnalyzer()
|
253
|
+
_, errors = analyzer.analyze(program)
|
254
|
+
|
255
|
+
assert len(errors) == 0
|
256
|
+
|
257
|
+
def test_arguments_expression_type(self) -> None:
|
258
|
+
"""Test that Arguments expression returns None for type."""
|
259
|
+
from machine_dialect.ast.expressions import Arguments
|
260
|
+
from machine_dialect.lexer import Token, TokenType
|
261
|
+
|
262
|
+
analyzer = SemanticAnalyzer()
|
263
|
+
|
264
|
+
# Create an Arguments expression
|
265
|
+
args = Arguments(Token(TokenType.DELIM_LPAREN, "(", 1, 1))
|
266
|
+
|
267
|
+
# Type inference should return None for Arguments
|
268
|
+
type_info = analyzer._infer_expression_type(args)
|
269
|
+
assert type_info is None
|
270
|
+
|
271
|
+
def test_function_return_type_inference(self) -> None:
|
272
|
+
"""Test that function calls can infer return type from symbol table."""
|
273
|
+
# This would require parsing function definitions and calls
|
274
|
+
# For now, we just test that the infrastructure is in place
|
275
|
+
from machine_dialect.ast import Identifier
|
276
|
+
from machine_dialect.ast.call_expression import CallExpression
|
277
|
+
from machine_dialect.lexer import Token, TokenType
|
278
|
+
|
279
|
+
analyzer = SemanticAnalyzer()
|
280
|
+
|
281
|
+
# Manually add a function to the symbol table with return type
|
282
|
+
analyzer.symbol_table.define("my_func", ["Function"], 1, 1)
|
283
|
+
func_info = analyzer.symbol_table.lookup("my_func")
|
284
|
+
if func_info:
|
285
|
+
func_info.return_type = "Text" # Set return type
|
286
|
+
|
287
|
+
# Create a call expression
|
288
|
+
func_name = Identifier(Token(TokenType.MISC_IDENT, "my_func", 1, 1), "my_func")
|
289
|
+
call_expr = CallExpression(Token(TokenType.KW_USE, "use", 1, 1), func_name, None)
|
290
|
+
|
291
|
+
# Type inference should return Text
|
292
|
+
type_info = analyzer._infer_expression_type(call_expr)
|
293
|
+
assert type_info is not None
|
294
|
+
assert type_info.type_name == "Text"
|
295
|
+
|
296
|
+
def test_function_definition_with_return_type(self) -> None:
|
297
|
+
"""Test that function definitions store return type information."""
|
298
|
+
from machine_dialect.ast import Identifier
|
299
|
+
from machine_dialect.ast.program import Program
|
300
|
+
from machine_dialect.ast.statements import (
|
301
|
+
ActionStatement,
|
302
|
+
BlockStatement,
|
303
|
+
)
|
304
|
+
from machine_dialect.lexer import Token, TokenType
|
305
|
+
|
306
|
+
# Create an AST for: Action "add_one" with input `x` as Whole Number, output `result` as Whole Number
|
307
|
+
name = Identifier(Token(TokenType.MISC_IDENT, "add_one", 1, 8), "add_one")
|
308
|
+
action = ActionStatement(Token(TokenType.KW_ACTION, "action", 1, 1), name)
|
309
|
+
|
310
|
+
# Create input parameter
|
311
|
+
from machine_dialect.ast.statements import Output, Parameter
|
312
|
+
|
313
|
+
param_name = Identifier(Token(TokenType.MISC_IDENT, "x", 1, 20), "x")
|
314
|
+
input_param = Parameter(Token(TokenType.MISC_IDENT, "x", 1, 20), param_name, type_name="Whole Number")
|
315
|
+
action.inputs = [input_param]
|
316
|
+
|
317
|
+
# Create output parameter
|
318
|
+
output_name = Identifier(Token(TokenType.MISC_IDENT, "result", 1, 40), "result")
|
319
|
+
output_param = Output(Token(TokenType.MISC_IDENT, "result", 1, 40), output_name, type_name="Whole Number")
|
320
|
+
action.outputs = [output_param]
|
321
|
+
|
322
|
+
# Create body (empty for simplicity)
|
323
|
+
action.body = BlockStatement(Token(TokenType.DELIM_LBRACE, "{", 2, 1))
|
324
|
+
action.body.statements = []
|
325
|
+
|
326
|
+
# Create program
|
327
|
+
program = Program([action])
|
328
|
+
|
329
|
+
# Analyze
|
330
|
+
analyzer = SemanticAnalyzer()
|
331
|
+
_, errors = analyzer.analyze(program)
|
332
|
+
|
333
|
+
# Should have no errors
|
334
|
+
assert len(errors) == 0
|
335
|
+
|
336
|
+
# Function should be defined with return type
|
337
|
+
func_info = analyzer.symbol_table.lookup("add_one")
|
338
|
+
assert func_info is not None
|
339
|
+
assert func_info.type_spec == ["Function"]
|
340
|
+
assert func_info.return_type == "Whole Number"
|
341
|
+
|
342
|
+
def test_scoped_definitions(self) -> None:
|
343
|
+
"""Test variable scoping - variables defined in inner scope not accessible in outer scope."""
|
344
|
+
source = """
|
345
|
+
Define `x` as Whole Number.
|
346
|
+
Set `x` to _5_.
|
347
|
+
|
348
|
+
If _yes_ then:
|
349
|
+
> Define `y` as Text.
|
350
|
+
> Set `y` to _"hello"_.
|
351
|
+
|
352
|
+
Set `y` to _"world"_.
|
353
|
+
"""
|
354
|
+
parser = Parser()
|
355
|
+
program = parser.parse(source, check_semantics=False)
|
356
|
+
|
357
|
+
analyzer = SemanticAnalyzer()
|
358
|
+
_, errors = analyzer.analyze(program)
|
359
|
+
|
360
|
+
# Should have one error for accessing 'y' outside its scope
|
361
|
+
assert len(errors) == 1
|
362
|
+
assert isinstance(errors[0], MDNameError)
|
363
|
+
assert "not defined" in str(errors[0])
|
364
|
+
assert "y" in str(errors[0])
|
@@ -0,0 +1,104 @@
|
|
1
|
+
"""Tests for error message generation.
|
2
|
+
|
3
|
+
This module tests the generation of helpful error messages with suggestions.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from machine_dialect.semantic.error_messages import ErrorMessageGenerator
|
7
|
+
|
8
|
+
|
9
|
+
class TestErrorMessages:
|
10
|
+
"""Test error message generation."""
|
11
|
+
|
12
|
+
def test_undefined_variable_message(self) -> None:
|
13
|
+
"""Test undefined variable error message."""
|
14
|
+
msg = ErrorMessageGenerator.undefined_variable("counter", 5, 10, similar_vars=["count", "counter1"])
|
15
|
+
|
16
|
+
assert "Variable 'counter' is not defined" in msg
|
17
|
+
assert "line 5, column 10" in msg
|
18
|
+
assert "Did you mean" in msg
|
19
|
+
assert "count" in msg
|
20
|
+
assert "Define `counter` as Type" in msg
|
21
|
+
|
22
|
+
def test_type_mismatch_message(self) -> None:
|
23
|
+
"""Test type mismatch error message."""
|
24
|
+
msg = ErrorMessageGenerator.type_mismatch("age", ["Whole Number"], "Text", 8, 5, '"twenty"')
|
25
|
+
|
26
|
+
assert "Cannot set Whole Number variable 'age'" in msg
|
27
|
+
assert "Text value" in msg
|
28
|
+
assert "Expected: Whole Number" in msg
|
29
|
+
assert "Actual: Text" in msg
|
30
|
+
|
31
|
+
def test_type_mismatch_union_message(self) -> None:
|
32
|
+
"""Test type mismatch error message for union types."""
|
33
|
+
msg = ErrorMessageGenerator.type_mismatch("id", ["Whole Number", "Text"], "Yes/No", 10, 3, "yes")
|
34
|
+
|
35
|
+
assert "Cannot set 'id'" in msg
|
36
|
+
assert "Yes/No value" in msg
|
37
|
+
assert "Expected: Whole Number or Text" in msg
|
38
|
+
assert "Actual: Yes/No" in msg
|
39
|
+
|
40
|
+
def test_redefinition_message(self) -> None:
|
41
|
+
"""Test redefinition error message."""
|
42
|
+
msg = ErrorMessageGenerator.redefinition("x", 10, 1, 5, 1)
|
43
|
+
|
44
|
+
assert "Variable 'x' is already defined" in msg
|
45
|
+
assert "line 10, column 1" in msg
|
46
|
+
assert "Original definition at line 5" in msg
|
47
|
+
assert "Use 'Set' to change the value" in msg
|
48
|
+
|
49
|
+
def test_uninitialized_use_message(self) -> None:
|
50
|
+
"""Test uninitialized use error message."""
|
51
|
+
msg = ErrorMessageGenerator.uninitialized_use("data", 15, 8, 3)
|
52
|
+
|
53
|
+
assert "Variable 'data' is used before being initialized" in msg
|
54
|
+
assert "line 15, column 8" in msg
|
55
|
+
assert "defined at line 3" in msg
|
56
|
+
assert "Set `data` to value" in msg
|
57
|
+
|
58
|
+
def test_invalid_type_message(self) -> None:
|
59
|
+
"""Test invalid type error message."""
|
60
|
+
msg = ErrorMessageGenerator.invalid_type("Str", 5, 15, ["Text", "Whole Number", "Float", "Yes/No"])
|
61
|
+
|
62
|
+
assert "Unknown type 'Str'" in msg
|
63
|
+
assert "Valid types:" in msg
|
64
|
+
# The similarity detection may not suggest Text for "Str" with default threshold
|
65
|
+
|
66
|
+
def test_undefined_without_suggestions(self) -> None:
|
67
|
+
"""Test undefined variable message without suggestions."""
|
68
|
+
msg = ErrorMessageGenerator.undefined_variable("xyz", 3, 7, similar_vars=None)
|
69
|
+
|
70
|
+
assert "Variable 'xyz' is not defined" in msg
|
71
|
+
assert "line 3, column 7" in msg
|
72
|
+
assert "Did you mean" not in msg
|
73
|
+
assert "Define `xyz` as Type" in msg
|
74
|
+
|
75
|
+
def test_type_conversion_hints(self) -> None:
|
76
|
+
"""Test type mismatch messages include conversion hints."""
|
77
|
+
# Whole Number to Float hint
|
78
|
+
msg = ErrorMessageGenerator.type_mismatch("price", ["Float"], "Whole Number", 5, 10, "42")
|
79
|
+
assert "Use _42.0_ to make it a Float" in msg
|
80
|
+
|
81
|
+
# Float to Whole Number hint
|
82
|
+
msg = ErrorMessageGenerator.type_mismatch("count", ["Whole Number"], "Float", 6, 10, "3.14")
|
83
|
+
assert "Float values cannot be assigned to Whole Number variables" in msg
|
84
|
+
|
85
|
+
def test_similar_name_detection(self) -> None:
|
86
|
+
"""Test similar name detection algorithm."""
|
87
|
+
similar = ErrorMessageGenerator._find_similar(
|
88
|
+
"count", ["counter", "amount", "count1", "total", "cnt"], threshold=0.6
|
89
|
+
)
|
90
|
+
|
91
|
+
# Should find similar names
|
92
|
+
assert "count1" in similar
|
93
|
+
assert "counter" in similar
|
94
|
+
|
95
|
+
# Should be ordered by similarity
|
96
|
+
assert similar.index("count1") < similar.index("cnt")
|
97
|
+
|
98
|
+
def test_invalid_type_with_multiple_suggestions(self) -> None:
|
99
|
+
"""Test invalid type message with multiple suggestions."""
|
100
|
+
msg = ErrorMessageGenerator.invalid_type("Num", 7, 20, ["Number", "Whole Number", "Float", "Text", "Yes/No"])
|
101
|
+
|
102
|
+
assert "Unknown type 'Num'" in msg
|
103
|
+
assert "Did you mean one of:" in msg or "Did you mean 'Number'?" in msg
|
104
|
+
assert "Valid types:" in msg
|
@@ -0,0 +1,10 @@
|
|
1
|
+
"""Edge case tests for Machine Dialectâ„¢ collection operations.
|
2
|
+
|
3
|
+
This package contains comprehensive edge case testing for:
|
4
|
+
- Empty collection operations
|
5
|
+
- Boundary access conditions
|
6
|
+
- Invalid operations
|
7
|
+
- Type mixing scenarios
|
8
|
+
- Nested structures
|
9
|
+
- Named list (dictionary) edge cases
|
10
|
+
"""
|