2026-07-12 23:46:25 +02:00
|
|
|
"""Tests for AST → GBNF converter."""
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
import pytest
|
|
|
|
|
from bex.gbnf import to_gbnf, to_gbnf_with_rules
|
2026-07-12 23:46:25 +02:00
|
|
|
from bex.grammar import (
|
|
|
|
|
Symbol, Concat, Alt, Plus, Optional, Star, Epsilon, Empty,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_a = Symbol('a')
|
|
|
|
|
_b = Symbol('b')
|
|
|
|
|
_c = Symbol('c')
|
|
|
|
|
_d = Symbol('d')
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestToGBNF:
|
|
|
|
|
def test_literal(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Symbol('mockk')) == 'root ::= "mockk"'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_concat(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Concat([Symbol('raise'), Symbol('ValueError')])) == 'root ::= "raise" "ValueError"'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_plus_group(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Plus(Symbol('append'))) == 'root ::= "append"+'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_plus_concat(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Concat([Symbol('raise'), Plus(Symbol('ValueError'))])) == 'root ::= "raise" "ValueError"+'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_nested_optional_plus(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Symbol('assertEquals'), Plus(Concat([Symbol('of'), Optional(Symbol('assertFailsWith'))]))])
|
|
|
|
|
assert to_gbnf(g) == 'root ::= "assertEquals" ("of" "assertFailsWith"?)+'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_long_concat(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Symbol('filesIn'), Symbol('filter'), Symbol('contains'),
|
|
|
|
|
Symbol('assertTrue'), Plus(Symbol('hasImport'))])
|
|
|
|
|
assert to_gbnf(g) == 'root ::= "filesIn" "filter" "contains" "assertTrue" "hasImport"+'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_simple_concat(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Symbol('trim'), Symbol('lowercase'), Plus(Symbol('warn'))])
|
|
|
|
|
assert to_gbnf(g) == 'root ::= "trim" "lowercase" "warn"+'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_flat_concat(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Symbol('DoclingConfig'), Symbol('assertThatThrownBy'),
|
|
|
|
|
Symbol('validateCriticalSettings'), Symbol('isInstanceOf'),
|
|
|
|
|
Symbol('hasMessageContaining')])
|
|
|
|
|
assert to_gbnf(g) == 'root ::= "DoclingConfig" "assertThatThrownBy" "validateCriticalSettings" "isInstanceOf" "hasMessageContaining"'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_simple_plus(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Plus(Symbol('abort'))) == 'root ::= "abort"+'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_concat_with_plus(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Concat([Symbol('return'), Plus(Symbol('url_for'))])) == 'root ::= "return" "url_for"+'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_star(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Star(Symbol('foo'))) == 'root ::= "foo"*'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
def test_optional(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Optional(Symbol('bar'))) == 'root ::= "bar"?'
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestToGBNFWithRules:
|
|
|
|
|
def test_named_rule(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Symbol('raise'), Plus(Symbol('ValueError'))])
|
|
|
|
|
result = to_gbnf_with_rules(g, name='my-pattern')
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
assert result == 'my-pattern ::= "raise" "ValueError"+'
|
|
|
|
|
|
|
|
|
|
def test_default_name(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
result = to_gbnf_with_rules(Symbol('mockk'))
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
assert result == 'root ::= "mockk"'
|
|
|
|
|
|
|
|
|
|
def test_nested(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Symbol('assertEquals'), Plus(Concat([Symbol('of'), Optional(Symbol('assertFailsWith'))]))])
|
|
|
|
|
result = to_gbnf_with_rules(g)
|
feat: implement SORE → GBNF converter
- Recursive descent parser for SORE syntax (+, ?, *, |, ., parens)
- AST intermediate representation (_Literal, _Concat, _Alt, _Plus, _Optional, _Star)
- to_gbnf(sore) → full GBNF rule string
- to_gbnf_with_rules(sore, name) → named rule for composition
- 15 tests covering all SORE operators and nesting patterns
2026-07-12 00:31:47 +02:00
|
|
|
assert result == 'root ::= "assertEquals" ("of" "assertFailsWith"?)+'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestGBNFDisjunction:
|
2026-07-12 23:46:25 +02:00
|
|
|
"""Test Alt nodes in GBNF output."""
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_simple_disjunction(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Alt([_a, _b])) == 'root ::= "a" | "b"'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_disjunction_with_rep(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Plus(Alt([_a, _b]))) == 'root ::= ("a" | "b")+'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_disjunction_optional(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Optional(Alt([_a, _b]))) == 'root ::= ("a" | "b")?'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_disjunction_star(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
assert to_gbnf(Star(Alt([_a, _b]))) == 'root ::= ("a" | "b")*'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_disjunction_in_concat(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Plus(_a), Plus(Alt([Symbol('BAD_REQUEST'), Symbol('CONFLICT')]))])
|
|
|
|
|
assert to_gbnf(g) == 'root ::= "a"+ ("BAD_REQUEST" | "CONFLICT")+'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_four_way_disjunction(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Plus(Alt([Symbol('assertEquals'), Symbol('authorize'), Symbol('coEvery'), Symbol('coVerify')]))
|
|
|
|
|
assert to_gbnf(g) == 'root ::= ("assertEquals" | "authorize" | "coEvery" | "coVerify")+'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_disjunction_parenthesized_in_concat(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Plus(_a), Alt([_b, _c])])
|
|
|
|
|
assert to_gbnf(g) == 'root ::= "a"+ ("b" | "c")'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_disjunction_rep_then_disjunction(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Plus(Alt([_a, _b])), Alt([_c, _d])])
|
|
|
|
|
assert to_gbnf(g) == 'root ::= ("a" | "b")+ ("c" | "d")'
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestGBNFCompoundRepetition:
|
2026-07-12 23:46:25 +02:00
|
|
|
"""Test compound repetition: nested quantifiers collapse correctly."""
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_plus_question(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Plus(Optional(_a))
|
|
|
|
|
result = to_gbnf(g)
|
|
|
|
|
assert '"a"?+' in result or '"a"*' in result
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_plus_star(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Plus(Star(_a))
|
|
|
|
|
result = to_gbnf(g)
|
|
|
|
|
assert '"a"*+' in result or '"a"*' in result
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_question_plus(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Optional(Plus(_a))
|
|
|
|
|
result = to_gbnf(g)
|
|
|
|
|
assert '"a"?+' in result or '"a"+' in result
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_flask_pattern(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
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
|
2026-07-12 02:20:58 +02:00
|
|
|
|
|
|
|
|
def test_double_plus(self):
|
2026-07-12 23:46:25 +02:00
|
|
|
g = Concat([Plus(_a), _b])
|
|
|
|
|
assert to_gbnf(g) == 'root ::= "a"+ "b"'
|