Assertions now use isinstance() on AST nodes (Concat, Alt, Plus, Optional, Star, Symbol, Empty) instead of comparing SORE strings.
143 lines
5.2 KiB
Python
143 lines
5.2 KiB
Python
"""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"'
|