Something went wrong. Try again.
OR-1 dataflow CPU sketch
Something went wrong. Try again.
12 kB · 362 lines
Python
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363"""Tests for the Resolve pass (name resolution in IRGraph).
Tests verify:- Valid programs with all names resolved (AC4.1, AC4.2)- Undefined name references with "did you mean" suggestions (AC4.3)- Scope violations when cross-referencing function-local labels (AC4.4)- Levenshtein distance computation for suggestions (AC4.5)"""
from tests.pipeline import parse_lower_resolve
from asm.resolve import _levenshteinfrom asm.errors import ErrorCategory
class TestValidResolution: """Tests for successful name resolution (AC4.1, AC4.2)."""
def test_simple_two_node_edge_resolves(self, parser): """Simple program with two nodes and an edge between them resolves with no errors.""" graph = parse_lower_resolve( parser, """\ &a <| pass &b <| add &a |> &b:L """, )
# Should have no resolution errors name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] scope_errors = [e for e in graph.errors if e.category == ErrorCategory.SCOPE] assert len(name_errors) == 0, f"Unexpected NAME errors: {name_errors}" assert len(scope_errors) == 0, f"Unexpected SCOPE errors: {scope_errors}"
def test_cross_function_wiring_via_global_nodes(self, parser): """Cross-function wiring via @nodes resolves correctly.
Test that a global node can be wired to and from function-scoped labels. After lowering, these are stored as region.body edges with simple names, but when resolved, they reference the flattened qualified names. """ graph = parse_lower_resolve( parser, """\ @bridge <| pass
$foo |> { &a <| pass &a |> @bridge:L }
$bar |> { &b <| add @bridge |> &b:L } """, )
# Should have no resolution errors name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 0, f"Unexpected NAME errors: {name_errors}"
def test_function_scoped_labels_within_same_function_resolve(self, parser): """Program with function-scoped labels and edges within same function resolve correctly.""" graph = parse_lower_resolve( parser, """\ $main |> { &input <| pass &process <| add &output <| pass &input |> &process:L &process |> &output:L } """, )
# Should have no resolution errors name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 0, f"Unexpected NAME errors: {name_errors}"
def test_global_and_function_nodes_coexist(self, parser): """Program with both global @nodes and function-scoped &labels resolves correctly.""" graph = parse_lower_resolve( parser, """\ @global <| pass
$worker |> { &local <| add &local |> @global:L } """, )
# Should have no resolution errors name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 0, f"Unexpected NAME errors: {name_errors}"
class TestUndefinedReference: """Tests for undefined name errors with suggestions (AC4.3)."""
def test_undefined_label_produces_name_error(self, parser): """Edge referencing undefined &nonexistent produces error with NAME category.""" graph = parse_lower_resolve( parser, """\ &a <| pass &b <| add &a |> &nonexistent:L """, )
# Should have a NAME error name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 1 error = name_errors[0] assert "undefined" in error.message.lower()
def test_error_includes_source_location(self, parser): """NAME error includes source location (line/column).""" graph = parse_lower_resolve( parser, """\ &a <| pass &b <| add &a |> &nonexistent:L """, )
name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 1 error = name_errors[0] # Error should have a valid location assert error.loc.line > 0 assert error.loc.column >= 0
def test_suggestion_for_similar_name(self, parser): """Reference to &nonexistant suggests &nonexistent if it exists.""" graph = parse_lower_resolve( parser, """\ &nonexistent <| pass &a <| add &a |> &nonexistant:L """, )
name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 1 error = name_errors[0] # Should have suggestions assert len(error.suggestions) > 0
class TestScopeViolation: """Tests for scope violation errors (AC4.4)."""
def test_cross_scope_reference_produces_scope_error(self, parser): """Reference to function-local label from top level produces SCOPE error.""" graph = parse_lower_resolve( parser, """\ $foo |> { &private <| pass }
&top <| add &top |> &private:L """, )
# Should have a SCOPE error, not NAME scope_errors = [e for e in graph.errors if e.category == ErrorCategory.SCOPE] assert len(scope_errors) == 1 error = scope_errors[0] # Error should identify the function containing the label assert "$foo" in error.message or "function" in error.message.lower()
def test_scope_error_mentions_actual_scope(self, parser): """Scope error message mentions the function where label is actually defined.""" graph = parse_lower_resolve( parser, """\ $foo |> { &private <| pass }
$bar |> { &x <| add }
&top <| pass &top |> &private:L """, )
scope_errors = [e for e in graph.errors if e.category == ErrorCategory.SCOPE] assert len(scope_errors) == 1 error = scope_errors[0] assert "$foo" in error.message
def test_reference_from_different_function_is_scope_error(self, parser): """Reference to label in $foo from within $bar produces SCOPE error.""" graph = parse_lower_resolve( parser, """\ $foo |> { &data <| const, 42 }
$bar |> { &use <| add &use |> &data:L } """, )
# Should have a SCOPE error scope_errors = [e for e in graph.errors if e.category == ErrorCategory.SCOPE] assert len(scope_errors) == 1
class TestLevenshteinSuggestions: """Tests for Levenshtein distance suggestions (AC4.5)."""
def test_levenshtein_kitten_sitting(self): """Direct test: _levenshtein("kitten", "sitting") == 3.""" distance = _levenshtein("kitten", "sitting") assert distance == 3
def test_levenshtein_identical_strings(self): """_levenshtein identical strings returns 0.""" assert _levenshtein("test", "test") == 0
def test_levenshtein_empty_string(self): """_levenshtein empty string.""" assert _levenshtein("", "") == 0 assert _levenshtein("abc", "") == 3 assert _levenshtein("", "abc") == 3
def test_levenshtein_single_insertion(self): """_levenshtein single character insertion.""" assert _levenshtein("add", "addd") == 1
def test_levenshtein_single_deletion(self): """_levenshtein single character deletion.""" assert _levenshtein("addd", "add") == 1
def test_levenshtein_single_substitution(self): """_levenshtein single character substitution.""" assert _levenshtein("cat", "bat") == 1
def test_suggestion_for_typo_one_char(self, parser): """Reference to &ad suggests &add (distance 1).""" graph = parse_lower_resolve( parser, """\ &add <| pass &x <| pass &x |> &ad:L """, )
# Should have NAME error with suggestion name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 1 error = name_errors[0] assert len(error.suggestions) > 0 # Suggestion should include &add suggestion_text = " ".join(error.suggestions) assert "add" in suggestion_text
def test_suggestion_for_typo_two_chars(self, parser): """Reference to &addd suggests &add (distance 1).""" graph = parse_lower_resolve( parser, """\ &add <| pass &x <| pass &x |> &addd:L """, )
name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 1 error = name_errors[0] assert len(error.suggestions) > 0 suggestion_text = " ".join(error.suggestions) assert "add" in suggestion_text
def test_no_suggestion_for_completely_wrong_name(self, parser): """Reference to &completely_wrong with no similar names may have no suggestion.""" graph = parse_lower_resolve( parser, """\ &add <| pass &x <| pass &x |> &completely_wrong:L """, )
name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 1 # May or may not have suggestions depending on implementation # (best-effort or distance threshold)
def test_multiple_errors_all_collected(self, parser): """Multiple undefined references all produce errors (error accumulation).""" graph = parse_lower_resolve( parser, """\ &a <| pass &b <| add &a |> &undef1:L &b |> &undef2:R """, )
name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] # Should have 2 NAME errors (both undefined refs) assert len(name_errors) == 2
class TestEdgeCases: """Edge case tests for resolution."""
def test_empty_program_resolves(self, parser): """Empty program resolves with no errors.""" graph = parse_lower_resolve(parser, "") assert len(graph.errors) == 0
def test_program_only_defs_no_edges_resolves(self, parser): """Program with only definitions and no edges resolves with no errors.""" graph = parse_lower_resolve( parser, """\ &a <| pass &b <| add &c <| sub """, )
name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 0
def test_circular_wiring_resolves(self, parser): """Circular wiring (feedback loops) resolves correctly.""" graph = parse_lower_resolve( parser, """\ &a <| pass &b <| add &a |> &b:L &b |> &a:R """, )
name_errors = [e for e in graph.errors if e.category == ErrorCategory.NAME] assert len(name_errors) == 0