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,181 @@
|
|
1
|
+
"""Tests specifically for MD101: Statement Termination rule."""
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from machine_dialect.ast import ExpressionStatement, ReturnStatement, SetStatement
|
6
|
+
from machine_dialect.lexer import Token, TokenType
|
7
|
+
from machine_dialect.linter import Linter
|
8
|
+
from machine_dialect.linter.rules.base import Context
|
9
|
+
from machine_dialect.linter.rules.statement_termination import StatementTerminationRule
|
10
|
+
from machine_dialect.linter.violations import ViolationSeverity
|
11
|
+
|
12
|
+
|
13
|
+
class TestMD101StatementTermination:
|
14
|
+
"""Test MD101: Statement Termination rule."""
|
15
|
+
|
16
|
+
def test_rule_metadata(self) -> None:
|
17
|
+
"""Test that MD101 has correct metadata."""
|
18
|
+
rule = StatementTerminationRule()
|
19
|
+
assert rule.rule_id == "MD101"
|
20
|
+
assert "period" in rule.description.lower()
|
21
|
+
assert "statement" in rule.description.lower()
|
22
|
+
|
23
|
+
@pytest.mark.parametrize(
|
24
|
+
("source", "expected_violations"),
|
25
|
+
[
|
26
|
+
# Valid cases - no violations
|
27
|
+
("42.", 0),
|
28
|
+
("True.", 0),
|
29
|
+
("not False.", 0),
|
30
|
+
("-123.", 0),
|
31
|
+
("Set `x` to 42.", 0),
|
32
|
+
("give back 42.", 0),
|
33
|
+
("gives back True.", 0),
|
34
|
+
# Valid cases - no period needed at EOF
|
35
|
+
("42", 0),
|
36
|
+
("True", 0),
|
37
|
+
("not False", 0),
|
38
|
+
("-123", 0),
|
39
|
+
# Multiple statements
|
40
|
+
("42. True.", 0),
|
41
|
+
# Note: These now generate parse errors, not MD101 violations
|
42
|
+
# because periods are mandatory (except at EOF) at the parser level
|
43
|
+
("42\nTrue.", 0), # Parse error, not MD101
|
44
|
+
("42.\nTrue", 0), # Valid: second line at EOF
|
45
|
+
("42\nTrue", 0), # Parse error, not MD101
|
46
|
+
],
|
47
|
+
)
|
48
|
+
def test_full_integration(self, source: str, expected_violations: int) -> None:
|
49
|
+
"""Test MD101 through the full linter integration."""
|
50
|
+
linter = Linter()
|
51
|
+
violations = linter.lint(source)
|
52
|
+
|
53
|
+
# Filter for MD101 violations only
|
54
|
+
md101_violations = [v for v in violations if v.rule_id == "MD101"]
|
55
|
+
assert len(md101_violations) == expected_violations
|
56
|
+
|
57
|
+
# All MD101 violations should be STYLE severity
|
58
|
+
for violation in md101_violations:
|
59
|
+
assert violation.severity == ViolationSeverity.STYLE
|
60
|
+
assert "period" in violation.message.lower()
|
61
|
+
|
62
|
+
def test_statements_with_complex_content(self) -> None:
|
63
|
+
"""Test MD101 with complex statement content."""
|
64
|
+
rule = StatementTerminationRule()
|
65
|
+
|
66
|
+
# Test with expression that has operators
|
67
|
+
token = Token(TokenType.OP_MINUS, "-", line=1, position=0)
|
68
|
+
node = ExpressionStatement(token=token, expression=None)
|
69
|
+
|
70
|
+
# Source: "-42 + 5" (no period, at EOF - valid)
|
71
|
+
context = Context("test.md", "-42 + 5")
|
72
|
+
violations = rule.check(node, context)
|
73
|
+
assert len(violations) == 0 # No period needed at EOF
|
74
|
+
|
75
|
+
# Source: "-42 + 5." (with period)
|
76
|
+
context = Context("test.md", "-42 + 5.")
|
77
|
+
violations = rule.check(node, context)
|
78
|
+
assert len(violations) == 0
|
79
|
+
|
80
|
+
def test_set_statements(self) -> None:
|
81
|
+
"""Test MD101 specifically with Set statements."""
|
82
|
+
rule = StatementTerminationRule()
|
83
|
+
|
84
|
+
# Create a Set statement node
|
85
|
+
token = Token(TokenType.KW_SET, "Set", line=1, position=0)
|
86
|
+
node = SetStatement(token=token)
|
87
|
+
|
88
|
+
# Test without period (at EOF - valid)
|
89
|
+
context = Context("test.md", "Set `x` to 42")
|
90
|
+
violations = rule.check(node, context)
|
91
|
+
assert len(violations) == 0 # No period needed at EOF
|
92
|
+
|
93
|
+
# Test with period
|
94
|
+
context = Context("test.md", "Set `x` to 42.")
|
95
|
+
violations = rule.check(node, context)
|
96
|
+
assert len(violations) == 0
|
97
|
+
|
98
|
+
def test_return_statements(self) -> None:
|
99
|
+
"""Test MD101 specifically with Return statements."""
|
100
|
+
rule = StatementTerminationRule()
|
101
|
+
|
102
|
+
# Create a Return statement node
|
103
|
+
token = Token(TokenType.KW_RETURN, "give back", line=1, position=0)
|
104
|
+
node = ReturnStatement(token=token)
|
105
|
+
|
106
|
+
# Test without period (at EOF - valid)
|
107
|
+
context = Context("test.md", "give back 42")
|
108
|
+
violations = rule.check(node, context)
|
109
|
+
assert len(violations) == 0 # No period needed at EOF
|
110
|
+
|
111
|
+
# Test with period
|
112
|
+
context = Context("test.md", "give back 42.")
|
113
|
+
violations = rule.check(node, context)
|
114
|
+
assert len(violations) == 0
|
115
|
+
|
116
|
+
def test_multiline_source(self) -> None:
|
117
|
+
"""Test MD101 with multiline source code."""
|
118
|
+
rule = StatementTerminationRule()
|
119
|
+
|
120
|
+
source = """42.
|
121
|
+
True
|
122
|
+
-5.
|
123
|
+
not False"""
|
124
|
+
|
125
|
+
# Test line 2 (True without period)
|
126
|
+
token = Token(TokenType.LIT_YES, "True", line=2, position=0)
|
127
|
+
node = ExpressionStatement(token=token, expression=None)
|
128
|
+
context = Context("test.md", source)
|
129
|
+
violations = rule.check(node, context)
|
130
|
+
assert len(violations) == 1
|
131
|
+
assert violations[0].line == 2
|
132
|
+
|
133
|
+
# Test line 4 (not False without period, at EOF - valid)
|
134
|
+
token = Token(TokenType.KW_NEGATION, "not", line=4, position=0)
|
135
|
+
node = ExpressionStatement(token=token, expression=None)
|
136
|
+
violations = rule.check(node, context)
|
137
|
+
assert len(violations) == 0 # No period needed at EOF
|
138
|
+
|
139
|
+
def test_fix_suggestion(self) -> None:
|
140
|
+
"""Test that MD101 provides fix suggestions."""
|
141
|
+
rule = StatementTerminationRule()
|
142
|
+
|
143
|
+
# Test with a statement not at EOF
|
144
|
+
token = Token(TokenType.LIT_WHOLE_NUMBER, "42", line=1, position=0)
|
145
|
+
node = ExpressionStatement(token=token, expression=None)
|
146
|
+
context = Context("test.md", "42\nTrue.") # Not at EOF
|
147
|
+
|
148
|
+
violations = rule.check(node, context)
|
149
|
+
assert len(violations) == 1
|
150
|
+
assert violations[0].fix_suggestion is not None
|
151
|
+
assert "period" in violations[0].fix_suggestion.lower()
|
152
|
+
|
153
|
+
def test_edge_cases(self) -> None:
|
154
|
+
"""Test MD101 edge cases."""
|
155
|
+
rule = StatementTerminationRule()
|
156
|
+
|
157
|
+
# Empty line
|
158
|
+
token = Token(TokenType.LIT_WHOLE_NUMBER, "42", line=1, position=0)
|
159
|
+
node = ExpressionStatement(token=token, expression=None)
|
160
|
+
context = Context("test.md", "")
|
161
|
+
violations = rule.check(node, context)
|
162
|
+
assert len(violations) == 0 # No crash on empty source
|
163
|
+
|
164
|
+
# Line number out of bounds
|
165
|
+
token = Token(TokenType.LIT_WHOLE_NUMBER, "42", line=10, position=0)
|
166
|
+
node = ExpressionStatement(token=token, expression=None)
|
167
|
+
context = Context("test.md", "42")
|
168
|
+
violations = rule.check(node, context)
|
169
|
+
assert len(violations) == 0 # No crash on invalid line number
|
170
|
+
|
171
|
+
def test_disabled_rule(self) -> None:
|
172
|
+
"""Test that MD101 can be disabled."""
|
173
|
+
# Test with MD101 disabled
|
174
|
+
config = {"rules": {"MD101": False}}
|
175
|
+
linter = Linter(config)
|
176
|
+
|
177
|
+
source = "42" # Missing period
|
178
|
+
violations = linter.lint(source)
|
179
|
+
|
180
|
+
# Should have no violations since MD101 is disabled
|
181
|
+
assert len(violations) == 0
|
@@ -0,0 +1,81 @@
|
|
1
|
+
"""Tests for the main linter functionality."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
from machine_dialect.linter import Linter, Violation, ViolationSeverity
|
6
|
+
|
7
|
+
|
8
|
+
class TestLinter:
|
9
|
+
"""Test the main Linter class."""
|
10
|
+
|
11
|
+
def test_linter_initialization(self) -> None:
|
12
|
+
"""Test that linter initializes with default rules."""
|
13
|
+
linter = Linter()
|
14
|
+
assert len(linter.rules) > 0
|
15
|
+
assert any(rule.rule_id == "MD101" for rule in linter.rules)
|
16
|
+
|
17
|
+
def test_linter_with_config(self) -> None:
|
18
|
+
"""Test linter initialization with configuration."""
|
19
|
+
config = {"rules": {"MD101": False}}
|
20
|
+
linter = Linter(config)
|
21
|
+
|
22
|
+
# MD101 should not be in the rules list
|
23
|
+
assert not any(rule.rule_id == "MD101" for rule in linter.rules)
|
24
|
+
|
25
|
+
def test_lint_parse_errors(self) -> None:
|
26
|
+
"""Test that parse errors are reported as violations."""
|
27
|
+
source = "* 42" # Invalid - no prefix parse function for *
|
28
|
+
|
29
|
+
linter = Linter()
|
30
|
+
violations = linter.lint(source)
|
31
|
+
|
32
|
+
assert len(violations) > 0
|
33
|
+
assert any(v.rule_id == "parse-error" for v in violations)
|
34
|
+
assert any(v.severity == ViolationSeverity.ERROR for v in violations)
|
35
|
+
|
36
|
+
def test_lint_stops_on_parse_errors(self) -> None:
|
37
|
+
"""Test that linter doesn't run rules when there are parse errors."""
|
38
|
+
source = "* 42" # Parse error
|
39
|
+
|
40
|
+
# Even with missing period, we should only get parse errors
|
41
|
+
linter = Linter()
|
42
|
+
violations = linter.lint(source)
|
43
|
+
|
44
|
+
# All violations should be parse errors
|
45
|
+
assert all(v.rule_id == "parse-error" for v in violations)
|
46
|
+
|
47
|
+
def test_lint_file(self, tmp_path: Path) -> None:
|
48
|
+
"""Test linting a file from disk."""
|
49
|
+
# Create a temporary file
|
50
|
+
test_file = tmp_path / "test.md"
|
51
|
+
test_file.write_text("42.")
|
52
|
+
|
53
|
+
linter = Linter()
|
54
|
+
violations = linter.lint_file(str(test_file))
|
55
|
+
|
56
|
+
# Should have no violations
|
57
|
+
assert len(violations) == 0
|
58
|
+
|
59
|
+
def test_add_rule(self) -> None:
|
60
|
+
"""Test adding custom rules to the linter."""
|
61
|
+
from machine_dialect.ast import ASTNode
|
62
|
+
from machine_dialect.linter.rules.base import Context, Rule
|
63
|
+
|
64
|
+
class CustomRule(Rule):
|
65
|
+
@property
|
66
|
+
def rule_id(self) -> str:
|
67
|
+
return "CUSTOM001"
|
68
|
+
|
69
|
+
@property
|
70
|
+
def description(self) -> str:
|
71
|
+
return "Custom test rule"
|
72
|
+
|
73
|
+
def check(self, node: ASTNode, context: Context) -> list[Violation]:
|
74
|
+
return []
|
75
|
+
|
76
|
+
linter = Linter()
|
77
|
+
initial_count = len(linter.rules)
|
78
|
+
|
79
|
+
linter.add_rule(CustomRule())
|
80
|
+
assert len(linter.rules) == initial_count + 1
|
81
|
+
assert any(rule.rule_id == "CUSTOM001" for rule in linter.rules)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
"""Tests for individual linting rules."""
|
2
|
+
|
3
|
+
from machine_dialect.ast import ExpressionStatement, WholeNumberLiteral
|
4
|
+
from machine_dialect.lexer import Token, TokenType
|
5
|
+
from machine_dialect.linter.rules.base import Context
|
6
|
+
from machine_dialect.linter.rules.statement_termination import StatementTerminationRule
|
7
|
+
from machine_dialect.linter.violations import ViolationSeverity
|
8
|
+
|
9
|
+
|
10
|
+
class TestStatementTerminationRule:
|
11
|
+
"""Test the statement termination rule."""
|
12
|
+
|
13
|
+
def test_rule_properties(self) -> None:
|
14
|
+
"""Test rule ID and description."""
|
15
|
+
rule = StatementTerminationRule()
|
16
|
+
assert rule.rule_id == "MD101"
|
17
|
+
assert "period" in rule.description.lower()
|
18
|
+
|
19
|
+
def test_valid_statement_with_period(self) -> None:
|
20
|
+
"""Test that statements ending with periods pass."""
|
21
|
+
rule = StatementTerminationRule()
|
22
|
+
|
23
|
+
# Create a mock expression statement
|
24
|
+
token = Token(TokenType.LIT_WHOLE_NUMBER, "42", line=1, position=1)
|
25
|
+
node = ExpressionStatement(token=token, expression=WholeNumberLiteral(token=token, value=42))
|
26
|
+
|
27
|
+
# Create context with source that has a period
|
28
|
+
context = Context("test.md", "42.")
|
29
|
+
|
30
|
+
violations = rule.check(node, context)
|
31
|
+
assert len(violations) == 0
|
32
|
+
|
33
|
+
def test_missing_period(self) -> None:
|
34
|
+
"""Test that statements without periods at EOF are valid."""
|
35
|
+
rule = StatementTerminationRule()
|
36
|
+
|
37
|
+
# Create a mock expression statement
|
38
|
+
token = Token(TokenType.LIT_WHOLE_NUMBER, "42", line=1, position=1)
|
39
|
+
node = ExpressionStatement(token=token, expression=WholeNumberLiteral(token=token, value=42))
|
40
|
+
|
41
|
+
# Create context with source that lacks a period (at EOF - valid)
|
42
|
+
context = Context("test.md", "42")
|
43
|
+
|
44
|
+
violations = rule.check(node, context)
|
45
|
+
assert len(violations) == 0 # No period needed at EOF
|
46
|
+
|
47
|
+
# Test with statement not at EOF (should have violation)
|
48
|
+
context = Context("test.md", "42\n100.")
|
49
|
+
violations = rule.check(node, context)
|
50
|
+
assert len(violations) == 1
|
51
|
+
assert violations[0].rule_id == "MD101"
|
52
|
+
assert violations[0].severity == ViolationSeverity.STYLE
|
53
|
+
assert violations[0].line == 1
|
54
|
+
|
55
|
+
def test_multiple_statements_on_line(self) -> None:
|
56
|
+
"""Test handling of multiple statements on one line."""
|
57
|
+
rule = StatementTerminationRule()
|
58
|
+
|
59
|
+
# First statement in "42. 100"
|
60
|
+
token = Token(TokenType.LIT_WHOLE_NUMBER, "42", line=1, position=1)
|
61
|
+
node = ExpressionStatement(token=token, expression=WholeNumberLiteral(token=token, value=42))
|
62
|
+
|
63
|
+
context = Context("test.md", "42. 100")
|
64
|
+
|
65
|
+
# First statement has a period, so no violation
|
66
|
+
violations = rule.check(node, context)
|
67
|
+
assert len(violations) == 0
|
68
|
+
|
69
|
+
def test_statement_with_whitespace(self) -> None:
|
70
|
+
"""Test statements with trailing whitespace."""
|
71
|
+
rule = StatementTerminationRule()
|
72
|
+
|
73
|
+
token = Token(TokenType.LIT_WHOLE_NUMBER, "42", line=1, position=1)
|
74
|
+
node = ExpressionStatement(token=token, expression=WholeNumberLiteral(token=token, value=42))
|
75
|
+
|
76
|
+
# Test with trailing whitespace but no period (at EOF - valid)
|
77
|
+
context = Context("test.md", "42 ")
|
78
|
+
|
79
|
+
violations = rule.check(node, context)
|
80
|
+
assert len(violations) == 0 # No period needed at EOF
|
81
|
+
|
82
|
+
def test_non_statement_nodes(self) -> None:
|
83
|
+
"""Test that non-statement nodes are ignored."""
|
84
|
+
rule = StatementTerminationRule()
|
85
|
+
|
86
|
+
# Test with a literal node (not a statement)
|
87
|
+
token = Token(TokenType.LIT_WHOLE_NUMBER, "42", line=1, position=1)
|
88
|
+
node = WholeNumberLiteral(token=token, value=42)
|
89
|
+
|
90
|
+
context = Context("test.md", "42")
|
91
|
+
|
92
|
+
# Should not check non-statement nodes
|
93
|
+
violations = rule.check(node, context)
|
94
|
+
assert len(violations) == 0
|
95
|
+
|
96
|
+
def test_rule_enabled_by_default(self) -> None:
|
97
|
+
"""Test that rules are enabled by default."""
|
98
|
+
rule = StatementTerminationRule()
|
99
|
+
assert rule.is_enabled({})
|
100
|
+
assert rule.is_enabled({"rules": {}})
|
101
|
+
|
102
|
+
def test_rule_can_be_disabled(self) -> None:
|
103
|
+
"""Test that rules can be disabled in config."""
|
104
|
+
rule = StatementTerminationRule()
|
105
|
+
|
106
|
+
config = {"rules": {"MD101": False}}
|
107
|
+
assert not rule.is_enabled(config)
|
108
|
+
|
109
|
+
config = {"rules": {"MD101": True}}
|
110
|
+
assert rule.is_enabled(config)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
"""Tests for the violations module."""
|
2
|
+
|
3
|
+
from machine_dialect.linter.violations import Violation, ViolationSeverity
|
4
|
+
|
5
|
+
|
6
|
+
class TestViolation:
|
7
|
+
"""Test the Violation class."""
|
8
|
+
|
9
|
+
def test_violation_creation(self) -> None:
|
10
|
+
"""Test creating a violation."""
|
11
|
+
violation = Violation(
|
12
|
+
rule_id="TEST001",
|
13
|
+
message="Test violation",
|
14
|
+
severity=ViolationSeverity.WARNING,
|
15
|
+
line=10,
|
16
|
+
column=5,
|
17
|
+
)
|
18
|
+
|
19
|
+
assert violation.rule_id == "TEST001"
|
20
|
+
assert violation.message == "Test violation"
|
21
|
+
assert violation.severity == ViolationSeverity.WARNING
|
22
|
+
assert violation.line == 10
|
23
|
+
assert violation.column == 5
|
24
|
+
assert violation.node is None
|
25
|
+
assert violation.fix_suggestion is None
|
26
|
+
|
27
|
+
def test_violation_with_optional_fields(self) -> None:
|
28
|
+
"""Test violation with optional fields."""
|
29
|
+
violation = Violation(
|
30
|
+
rule_id="TEST002",
|
31
|
+
message="Test with suggestion",
|
32
|
+
severity=ViolationSeverity.STYLE,
|
33
|
+
line=1,
|
34
|
+
column=0,
|
35
|
+
fix_suggestion="Add a period",
|
36
|
+
)
|
37
|
+
|
38
|
+
assert violation.fix_suggestion == "Add a period"
|
39
|
+
|
40
|
+
def test_violation_string_representation(self) -> None:
|
41
|
+
"""Test string representation of violations."""
|
42
|
+
violation = Violation(
|
43
|
+
rule_id="MD101",
|
44
|
+
message="Missing period",
|
45
|
+
severity=ViolationSeverity.STYLE,
|
46
|
+
line=5,
|
47
|
+
column=10,
|
48
|
+
)
|
49
|
+
|
50
|
+
str_repr = str(violation)
|
51
|
+
assert "5:10" in str_repr
|
52
|
+
assert "style" in str_repr
|
53
|
+
assert "MD101" in str_repr
|
54
|
+
assert "Missing period" in str_repr
|
55
|
+
|
56
|
+
|
57
|
+
class TestViolationSeverity:
|
58
|
+
"""Test the ViolationSeverity enum."""
|
59
|
+
|
60
|
+
def test_severity_values(self) -> None:
|
61
|
+
"""Test that severity levels have expected values."""
|
62
|
+
assert ViolationSeverity.ERROR.value == "error"
|
63
|
+
assert ViolationSeverity.WARNING.value == "warning"
|
64
|
+
assert ViolationSeverity.INFO.value == "info"
|
65
|
+
assert ViolationSeverity.STYLE.value == "style"
|
66
|
+
|
67
|
+
def test_all_severities_defined(self) -> None:
|
68
|
+
"""Test that all expected severities are defined."""
|
69
|
+
expected_severities = {"ERROR", "WARNING", "INFO", "STYLE"}
|
70
|
+
actual_severities = {s.name for s in ViolationSeverity}
|
71
|
+
assert expected_severities == actual_severities
|
@@ -0,0 +1,51 @@
|
|
1
|
+
"""Violation representation for the Machine Dialect™ linter.
|
2
|
+
|
3
|
+
This module defines the Violation class used to represent linting issues
|
4
|
+
found in Machine Dialect™ code.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from dataclasses import dataclass
|
8
|
+
from enum import Enum
|
9
|
+
|
10
|
+
from machine_dialect.ast import ASTNode
|
11
|
+
|
12
|
+
|
13
|
+
class ViolationSeverity(Enum):
|
14
|
+
"""Severity levels for linting violations."""
|
15
|
+
|
16
|
+
ERROR = "error"
|
17
|
+
WARNING = "warning"
|
18
|
+
INFO = "info"
|
19
|
+
STYLE = "style"
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass
|
23
|
+
class Violation:
|
24
|
+
"""Represents a linting violation found in Machine Dialect™ code.
|
25
|
+
|
26
|
+
Attributes:
|
27
|
+
rule_id: Unique identifier for the rule that was violated.
|
28
|
+
message: Human-readable description of the violation.
|
29
|
+
severity: The severity level of the violation.
|
30
|
+
line: Line number where the violation occurred.
|
31
|
+
column: Column number where the violation occurred.
|
32
|
+
node: The AST node associated with the violation (optional).
|
33
|
+
fix_suggestion: Suggested fix for the violation (optional).
|
34
|
+
"""
|
35
|
+
|
36
|
+
rule_id: str
|
37
|
+
message: str
|
38
|
+
severity: ViolationSeverity
|
39
|
+
line: int
|
40
|
+
column: int
|
41
|
+
node: ASTNode | None = None
|
42
|
+
fix_suggestion: str | None = None
|
43
|
+
|
44
|
+
def __str__(self) -> str:
|
45
|
+
"""Return a formatted string representation of the violation.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
A string in the format: "line:column severity rule_id: message"
|
49
|
+
"""
|
50
|
+
location = f"{self.line}:{self.column}"
|
51
|
+
return f"{location} {self.severity.value} {self.rule_id}: {self.message}"
|
@@ -0,0 +1,69 @@
|
|
1
|
+
"""Machine Dialect™ MIR (Medium-level Intermediate Representation).
|
2
|
+
|
3
|
+
This module provides a Three-Address Code (TAC) based intermediate representation
|
4
|
+
with Static Single Assignment (SSA) support for Machine Dialect™ compilation.
|
5
|
+
|
6
|
+
The MIR sits between the HIR (desugared AST) and the final code generation
|
7
|
+
targets (bytecode and LLVM IR).
|
8
|
+
"""
|
9
|
+
|
10
|
+
from .basic_block import CFG, BasicBlock
|
11
|
+
from .hir_to_mir import HIRToMIRLowering, lower_to_mir
|
12
|
+
from .mir_function import MIRFunction
|
13
|
+
from .mir_instructions import (
|
14
|
+
BinaryOp,
|
15
|
+
Call,
|
16
|
+
ConditionalJump,
|
17
|
+
Copy,
|
18
|
+
Jump,
|
19
|
+
Label,
|
20
|
+
LoadConst,
|
21
|
+
LoadVar,
|
22
|
+
MIRInstruction,
|
23
|
+
Phi,
|
24
|
+
Return,
|
25
|
+
StoreVar,
|
26
|
+
UnaryOp,
|
27
|
+
)
|
28
|
+
from .mir_module import MIRModule
|
29
|
+
from .mir_types import MIRType
|
30
|
+
from .mir_values import Constant, FunctionRef, MIRValue, Temp, Variable
|
31
|
+
from .optimization_config import OptimizationConfig
|
32
|
+
from .optimization_pipeline import OptimizationLevel, OptimizationPipeline, PipelineBuilder
|
33
|
+
from .optimize_mir import optimize_mir, optimize_mir_simple
|
34
|
+
from .pass_manager import PassManager
|
35
|
+
|
36
|
+
__all__ = [
|
37
|
+
"CFG",
|
38
|
+
"BasicBlock",
|
39
|
+
"BinaryOp",
|
40
|
+
"Call",
|
41
|
+
"ConditionalJump",
|
42
|
+
"Constant",
|
43
|
+
"Copy",
|
44
|
+
"FunctionRef",
|
45
|
+
"HIRToMIRLowering",
|
46
|
+
"Jump",
|
47
|
+
"Label",
|
48
|
+
"LoadConst",
|
49
|
+
"LoadVar",
|
50
|
+
"MIRFunction",
|
51
|
+
"MIRInstruction",
|
52
|
+
"MIRModule",
|
53
|
+
"MIRType",
|
54
|
+
"MIRValue",
|
55
|
+
"OptimizationConfig",
|
56
|
+
"OptimizationLevel",
|
57
|
+
"OptimizationPipeline",
|
58
|
+
"PassManager",
|
59
|
+
"Phi",
|
60
|
+
"PipelineBuilder",
|
61
|
+
"Return",
|
62
|
+
"StoreVar",
|
63
|
+
"Temp",
|
64
|
+
"UnaryOp",
|
65
|
+
"Variable",
|
66
|
+
"lower_to_mir",
|
67
|
+
"optimize_mir",
|
68
|
+
"optimize_mir_simple",
|
69
|
+
]
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"""MIR analysis passes."""
|
2
|
+
|
3
|
+
from machine_dialect.mir.analyses.alias_analysis import AliasAnalysis, AliasInfo
|
4
|
+
from machine_dialect.mir.analyses.dominance_analysis import DominanceAnalysis
|
5
|
+
from machine_dialect.mir.analyses.escape_analysis import EscapeAnalysis, EscapeInfo
|
6
|
+
from machine_dialect.mir.analyses.loop_analysis import Loop, LoopAnalysis, LoopInfo
|
7
|
+
from machine_dialect.mir.analyses.use_def_chains import UseDefChains, UseDefChainsAnalysis
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"AliasAnalysis",
|
11
|
+
"AliasInfo",
|
12
|
+
"DominanceAnalysis",
|
13
|
+
"EscapeAnalysis",
|
14
|
+
"EscapeInfo",
|
15
|
+
"Loop",
|
16
|
+
"LoopAnalysis",
|
17
|
+
"LoopInfo",
|
18
|
+
"UseDefChains",
|
19
|
+
"UseDefChainsAnalysis",
|
20
|
+
]
|