Skip to content

Commit d44d946

Browse files
Copilothzhangxyzgithub-code-quality[bot]
authored
Add pytest infrastructure and tests for bnf package (#76)
* Initial plan * Add pytest tests and dev dependencies for bnf package Co-authored-by: hzhangxyz <[email protected]> * Potential fix for pull request finding 'Unused import' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Update uv.lock. * Update pyproject.toml. * Update root pyproject.toml. * Fix UnparseVisitor.visitUnary bug and add tests to cover both visitUnary methods Co-authored-by: hzhangxyz <[email protected]> * Add type annotations and use strict equality assertions in tests Co-authored-by: hzhangxyz <[email protected]> * Fix test. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: hzhangxyz <[email protected]> Co-authored-by: Hao Zhang(张浩) <[email protected]> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
1 parent 2baa0bb commit d44d946

File tree

5 files changed

+461
-4
lines changed

5 files changed

+461
-4
lines changed

bnf/apyds_bnf/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ def visitFunction(self, ctx):
6262
return f"{self.visit(ctx.term(0))}({', '.join(self.visit(t) for t in ctx.term()[1:])})"
6363

6464
def visitUnary(self, ctx):
65-
return f"({ctx.getChild(0).getText()} {self.visit(ctx.term())})"
65+
return f"({ctx.getChild(1).getText()} {self.visit(ctx.term())})"
6666

6767
def visitBinary(self, ctx):
6868
return f"({self.visit(ctx.term(0))} {ctx.getChild(1).getText()} {self.visit(ctx.term(1))})"
6969

7070

71-
def parse(input):
71+
def parse(input: str) -> str:
7272
chars = InputStream(input)
7373
lexer = DspLexer(chars)
7474
tokens = CommonTokenStream(lexer)
@@ -78,7 +78,7 @@ def parse(input):
7878
return visitor.visit(tree)
7979

8080

81-
def unparse(input):
81+
def unparse(input: str) -> str:
8282
chars = InputStream(input)
8383
lexer = DsLexer(chars)
8484
tokens = CommonTokenStream(lexer)

bnf/pyproject.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,24 @@ root = ".."
2828

2929
[tool.setuptools.packages.find]
3030
include = ["apyds_bnf"]
31+
32+
[project.optional-dependencies]
33+
dev = [
34+
"ruff~=0.14.6",
35+
"pytest~=9.0.1",
36+
"pytest-cov~=7.0.0",
37+
]
38+
39+
[tool.ruff]
40+
line-length = 120
41+
42+
[tool.ruff.lint]
43+
select = ["E4", "E7", "E9", "F"]
44+
45+
[tool.ruff.lint.per-file-ignores]
46+
"tests/*" = ["E741", "E743", "F841"]
47+
48+
[tool.ruff.format]
49+
50+
[tool.pytest.ini_options]
51+
addopts = "--cov=apyds_bnf"

bnf/tests/test_parse_unparse.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
from apyds_bnf import parse, unparse
2+
3+
4+
def test_parse_simple_rule() -> None:
5+
"""Test parsing a simple rule with premises and conclusion"""
6+
dsp_input = "a, b -> c"
7+
ds_output = parse(dsp_input)
8+
expected = "a\nb\n----\nc"
9+
assert ds_output == expected
10+
11+
12+
def test_parse_no_premises() -> None:
13+
"""Test parsing a rule with no premises (axiom)"""
14+
dsp_input = "a"
15+
ds_output = parse(dsp_input)
16+
expected = "----\na"
17+
assert ds_output == expected
18+
19+
20+
def test_parse_function() -> None:
21+
"""Test parsing a function call"""
22+
dsp_input = "f(a, b) -> c"
23+
ds_output = parse(dsp_input)
24+
expected = "(function f a b)\n----------------\nc"
25+
assert ds_output == expected
26+
27+
28+
def test_parse_subscript() -> None:
29+
"""Test parsing subscript notation"""
30+
dsp_input = "a[i, j] -> b"
31+
ds_output = parse(dsp_input)
32+
expected = "(subscript a i j)\n-----------------\nb"
33+
assert ds_output == expected
34+
35+
36+
def test_parse_binary_operator() -> None:
37+
"""Test parsing binary operators"""
38+
dsp_input = "(a + b) -> c"
39+
ds_output = parse(dsp_input)
40+
expected = "(binary + a b)\n--------------\nc"
41+
assert ds_output == expected
42+
43+
44+
def test_parse_unary_operator() -> None:
45+
"""Test parsing unary operators"""
46+
dsp_input = "~ a -> b"
47+
ds_output = parse(dsp_input)
48+
expected = "(unary ~ a)\n-----------\nb"
49+
assert ds_output == expected
50+
51+
52+
def test_parse_multiple_rules() -> None:
53+
"""Test parsing multiple rules"""
54+
dsp_input = "a -> b\nc -> d"
55+
ds_output = parse(dsp_input)
56+
expected = "a\n----\nb\n\nc\n----\nd"
57+
assert ds_output == expected
58+
59+
60+
def test_parse_complex_expression() -> None:
61+
"""Test parsing complex nested expressions"""
62+
dsp_input = "(a + b) * c, d[i] -> f(g, h)"
63+
ds_output = parse(dsp_input)
64+
expected = "(binary * (binary + a b) c)\n(subscript d i)\n---------------------------\n(function f g h)"
65+
assert ds_output == expected
66+
67+
68+
def test_unparse_simple_rule() -> None:
69+
"""Test unparsing a simple rule"""
70+
ds_input = "a\nb\n----\nc"
71+
dsp_output = unparse(ds_input)
72+
expected = "a, b -> c"
73+
assert dsp_output == expected
74+
75+
76+
def test_unparse_no_premises() -> None:
77+
"""Test unparsing a rule with no premises"""
78+
ds_input = "----\na"
79+
dsp_output = unparse(ds_input)
80+
expected = " -> a"
81+
assert dsp_output == expected
82+
83+
84+
def test_unparse_function() -> None:
85+
"""Test unparsing a function"""
86+
ds_input = "(function f a b)\n----\nc"
87+
dsp_output = unparse(ds_input)
88+
expected = "f(a, b) -> c"
89+
assert dsp_output == expected
90+
91+
92+
def test_unparse_subscript() -> None:
93+
"""Test unparsing subscript notation"""
94+
ds_input = "(subscript a i j)\n----\nb"
95+
dsp_output = unparse(ds_input)
96+
expected = "a[i, j] -> b"
97+
assert dsp_output == expected
98+
99+
100+
def test_unparse_binary_operator() -> None:
101+
"""Test unparsing binary operators"""
102+
ds_input = "(binary + a b)\n----\nc"
103+
dsp_output = unparse(ds_input)
104+
expected = "(a + b) -> c"
105+
assert dsp_output == expected
106+
107+
108+
def test_unparse_unary_operator() -> None:
109+
"""Test unparsing unary operators"""
110+
ds_input = "(unary ~ a)\n----\nb"
111+
dsp_output = unparse(ds_input)
112+
expected = "(~ a) -> b"
113+
assert dsp_output == expected
114+
115+
116+
def test_unparse_multiple_rules() -> None:
117+
"""Test unparsing multiple rules"""
118+
ds_input = "a\n----\nb\n\nc\n----\nd"
119+
dsp_output = unparse(ds_input)
120+
expected = "a -> b\nc -> d"
121+
assert dsp_output == expected
122+
123+
124+
def test_unparse_complex_expression() -> None:
125+
"""Test unparsing complex nested expressions"""
126+
ds_input = "(binary * (binary + a b) c)\n(subscript d i)\n----\n(function f g h)"
127+
dsp_output = unparse(ds_input)
128+
expected = "((a + b) * c), d[i] -> f(g, h)"
129+
assert dsp_output == expected
130+
131+
132+
def test_roundtrip_parse_unparse() -> None:
133+
"""Test that parse followed by unparse produces consistent results"""
134+
dsp_original = "a, b -> c"
135+
ds_intermediate = parse(dsp_original)
136+
dsp_result = unparse(ds_intermediate)
137+
assert dsp_result == dsp_original
138+
139+
140+
def test_roundtrip_unparse_parse() -> None:
141+
"""Test that unparse followed by parse produces consistent results"""
142+
ds_original = "a\nb\n----\nc"
143+
dsp_intermediate = unparse(ds_original)
144+
ds_result = parse(dsp_intermediate)
145+
assert ds_result == ds_original

0 commit comments

Comments
 (0)