"""Tests for AST → GBNF converter.""" import pytest from bex.gbnf import to_gbnf, to_gbnf_with_rules from bex.grammar import ( Symbol, Concat, Alt, Plus, Optional, Star, Epsilon, Empty, ) _a = Symbol('a') _b = Symbol('b') _c = Symbol('c') _d = Symbol('d') class TestToGBNF: def test_literal(self): assert to_gbnf(Symbol('mockk')) == 'root ::= "mockk"' def test_concat(self): assert to_gbnf(Concat([Symbol('raise'), Symbol('ValueError')])) == 'root ::= "raise" "ValueError"' def test_plus_group(self): assert to_gbnf(Plus(Symbol('append'))) == 'root ::= "append"+' def test_plus_concat(self): assert to_gbnf(Concat([Symbol('raise'), Plus(Symbol('ValueError'))])) == 'root ::= "raise" "ValueError"+' def test_nested_optional_plus(self): g = Concat([Symbol('assertEquals'), Plus(Concat([Symbol('of'), Optional(Symbol('assertFailsWith'))]))]) assert to_gbnf(g) == 'root ::= "assertEquals" ("of" "assertFailsWith"?)+' def test_long_concat(self): g = Concat([Symbol('filesIn'), Symbol('filter'), Symbol('contains'), Symbol('assertTrue'), Plus(Symbol('hasImport'))]) assert to_gbnf(g) == 'root ::= "filesIn" "filter" "contains" "assertTrue" "hasImport"+' def test_simple_concat(self): g = Concat([Symbol('trim'), Symbol('lowercase'), Plus(Symbol('warn'))]) assert to_gbnf(g) == 'root ::= "trim" "lowercase" "warn"+' def test_flat_concat(self): g = Concat([Symbol('DoclingConfig'), Symbol('assertThatThrownBy'), Symbol('validateCriticalSettings'), Symbol('isInstanceOf'), Symbol('hasMessageContaining')]) assert to_gbnf(g) == 'root ::= "DoclingConfig" "assertThatThrownBy" "validateCriticalSettings" "isInstanceOf" "hasMessageContaining"' def test_simple_plus(self): assert to_gbnf(Plus(Symbol('abort'))) == 'root ::= "abort"+' def test_concat_with_plus(self): assert to_gbnf(Concat([Symbol('return'), Plus(Symbol('url_for'))])) == 'root ::= "return" "url_for"+' def test_star(self): assert to_gbnf(Star(Symbol('foo'))) == 'root ::= "foo"*' def test_optional(self): assert to_gbnf(Optional(Symbol('bar'))) == 'root ::= "bar"?' class TestToGBNFWithRules: def test_named_rule(self): g = Concat([Symbol('raise'), Plus(Symbol('ValueError'))]) result = to_gbnf_with_rules(g, name='my-pattern') assert result == 'my-pattern ::= "raise" "ValueError"+' def test_default_name(self): result = to_gbnf_with_rules(Symbol('mockk')) assert result == 'root ::= "mockk"' def test_nested(self): g = Concat([Symbol('assertEquals'), Plus(Concat([Symbol('of'), Optional(Symbol('assertFailsWith'))]))]) result = to_gbnf_with_rules(g) assert result == 'root ::= "assertEquals" ("of" "assertFailsWith"?)+' class TestGBNFDisjunction: """Test Alt nodes in GBNF output.""" def test_simple_disjunction(self): assert to_gbnf(Alt([_a, _b])) == 'root ::= "a" | "b"' def test_disjunction_with_rep(self): assert to_gbnf(Plus(Alt([_a, _b]))) == 'root ::= ("a" | "b")+' def test_disjunction_optional(self): assert to_gbnf(Optional(Alt([_a, _b]))) == 'root ::= ("a" | "b")?' def test_disjunction_star(self): assert to_gbnf(Star(Alt([_a, _b]))) == 'root ::= ("a" | "b")*' def test_disjunction_in_concat(self): g = Concat([Plus(_a), Plus(Alt([Symbol('BAD_REQUEST'), Symbol('CONFLICT')]))]) assert to_gbnf(g) == 'root ::= "a"+ ("BAD_REQUEST" | "CONFLICT")+' def test_four_way_disjunction(self): g = Plus(Alt([Symbol('assertEquals'), Symbol('authorize'), Symbol('coEvery'), Symbol('coVerify')])) assert to_gbnf(g) == 'root ::= ("assertEquals" | "authorize" | "coEvery" | "coVerify")+' def test_disjunction_parenthesized_in_concat(self): g = Concat([Plus(_a), Alt([_b, _c])]) assert to_gbnf(g) == 'root ::= "a"+ ("b" | "c")' def test_disjunction_rep_then_disjunction(self): g = Concat([Plus(Alt([_a, _b])), Alt([_c, _d])]) assert to_gbnf(g) == 'root ::= ("a" | "b")+ ("c" | "d")' class TestGBNFCompoundRepetition: """Test compound repetition: nested quantifiers collapse correctly.""" def test_plus_question(self): g = Plus(Optional(_a)) result = to_gbnf(g) assert '"a"?+' in result or '"a"*' in result def test_plus_star(self): g = Plus(Star(_a)) result = to_gbnf(g) assert '"a"*+' in result or '"a"*' in result def test_question_plus(self): g = Optional(Plus(_a)) result = to_gbnf(g) assert '"a"?+' in result or '"a"+' in result def test_flask_pattern(self): g = Concat([ Symbol('return'), Plus(Alt([Symbol('key'), Symbol('self')])), Plus(Alt([Symbol('Markup'), Symbol('UUID')])), Plus(Symbol('to_json')), ]) result = to_gbnf(g) assert '"return"' in result assert '"key"' in result assert '"self"' in result assert '"Markup"' in result assert '"UUID"' in result assert '"to_json"' in result def test_double_plus(self): g = Concat([Plus(_a), _b]) assert to_gbnf(g) == 'root ::= "a"+ "b"'