Further improved grammar parser test coverage (up to 92%)

This commit is contained in:
Martin Evans 2023-10-13 02:08:12 +01:00
parent bff41eef37
commit 9f694c584c
1 changed files with 100 additions and 69 deletions

View File

@ -12,16 +12,48 @@ namespace LLama.Unittest
/// </summary> /// </summary>
public sealed class GrammarParserTest public sealed class GrammarParserTest
{ {
private static void CheckGrammar(string grammar, string rootRule, List<KeyValuePair<string, uint>> expected, List<LLamaGrammarElement> expectedRules)
{
var state = Grammar.Parse(grammar, rootRule);
Assert.Equal(0ul, state.StartRuleIndex);
foreach (var symbol in expected)
{
var rule = state.Rules[(int)symbol.Value];
Assert.Equal(symbol.Key, rule.Name);
}
uint index = 0;
foreach (var rule in state.Rules)
{
// compare rule to expected rule
for (uint i = 0; i < rule.Elements.Count; i++)
{
var element = rule.Elements[(int)i];
var expectedElement = expectedRules[(int)index];
// Pretty print error message before asserting
if (expectedElement.Type != element.Type || expectedElement.Value != element.Value)
{
Console.Error.WriteLine($"index: {index}");
Console.Error.WriteLine($"expected_element: {expectedElement.Type}, {expectedElement.Value}");
Console.Error.WriteLine($"actual_element: {element.Type}, {element.Value}");
Console.Error.WriteLine("expected_element != actual_element");
}
Assert.Equal(expectedElement.Type, element.Type);
Assert.Equal(expectedElement.Value, element.Value);
index++;
}
}
Assert.NotEmpty(state.Rules);
}
[Fact] [Fact]
public void ParseComplexGrammar() public void ParseComplexGrammar()
{ {
GBNFGrammarParser parsedGrammar = new GBNFGrammarParser(); var grammarBytes = @"root ::= (expr ""="" term ""\n"")+
string grammarBytes = @"root ::= (expr ""="" term ""\n"")+ expr ::= term ([-\x2b\x2A/] term)*
expr ::= term ([-+*/] term)* term ::= [\x30-\x39]+";
term ::= [0-9]+";
var state = parsedGrammar.Parse(grammarBytes, "root");
Assert.Equal(0ul, state.StartRuleIndex);
var expected = new List<KeyValuePair<string, uint>> var expected = new List<KeyValuePair<string, uint>>
{ {
@ -35,12 +67,6 @@ namespace LLama.Unittest
new KeyValuePair<string, uint>("term_7", 7), new KeyValuePair<string, uint>("term_7", 7),
}; };
foreach (var symbol in expected)
{
var rule = state.Rules[(int)symbol.Value];
Assert.Equal(symbol.Key, rule.Name);
}
var expectedRules = new List<LLamaGrammarElement> var expectedRules = new List<LLamaGrammarElement>
{ {
new LLamaGrammarElement(LLamaGrammarElementType.RULE_REF, 4), new LLamaGrammarElement(LLamaGrammarElementType.RULE_REF, 4),
@ -79,35 +105,12 @@ namespace LLama.Unittest
new LLamaGrammarElement(LLamaGrammarElementType.END, 0), new LLamaGrammarElement(LLamaGrammarElementType.END, 0),
}; };
uint index = 0; CheckGrammar(grammarBytes, "root", expected, expectedRules);
foreach (var rule in state.Rules)
{
// compare rule to expected rule
for (uint i = 0; i < rule.Elements.Count; i++)
{
var element = rule.Elements[(int)i];
var expectedElement = expectedRules[(int)index];
// Pretty print error message before asserting
if (expectedElement.Type != element.Type || expectedElement.Value != element.Value)
{
Console.Error.WriteLine($"index: {index}");
Console.Error.WriteLine($"expected_element: {expectedElement.Type}, {expectedElement.Value}");
Console.Error.WriteLine($"actual_element: {element.Type}, {element.Value}");
Console.Error.WriteLine("expected_element != actual_element");
}
Assert.Equal(expectedElement.Type, element.Type);
Assert.Equal(expectedElement.Value, element.Value);
index++;
}
}
Assert.NotEmpty(state.Rules);
} }
[Fact] [Fact]
public void ParseExtraComplexGrammar() public void ParseExtraComplexGrammar()
{ {
GBNFGrammarParser parsedGrammar = new GBNFGrammarParser();
string grammarBytes = @" string grammarBytes = @"
root ::= (expr ""="" ws term ""\n"")+ root ::= (expr ""="" ws term ""\n"")+
expr ::= term ([-+*/] term)* expr ::= term ([-+*/] term)*
@ -117,9 +120,6 @@ namespace LLama.Unittest
ws ::= [ \t\n]* ws ::= [ \t\n]*
"; ";
var state = parsedGrammar.Parse(grammarBytes, "root");
Assert.Equal(0ul, state.StartRuleIndex);
var expected = new List<KeyValuePair<string, uint>> var expected = new List<KeyValuePair<string, uint>>
{ {
new KeyValuePair<string, uint>("expr", 2), new KeyValuePair<string, uint>("expr", 2),
@ -137,12 +137,6 @@ namespace LLama.Unittest
new KeyValuePair<string, uint>("ws_12", 12), new KeyValuePair<string, uint>("ws_12", 12),
}; };
foreach (var symbol in expected)
{
var rule = state.Rules[(int)symbol.Value];
Assert.Equal(symbol.Key, rule.Name);
}
var expectedRules = new List<LLamaGrammarElement> var expectedRules = new List<LLamaGrammarElement>
{ {
new LLamaGrammarElement(LLamaGrammarElementType.RULE_REF, 5), new LLamaGrammarElement(LLamaGrammarElementType.RULE_REF, 5),
@ -214,31 +208,68 @@ namespace LLama.Unittest
new LLamaGrammarElement(LLamaGrammarElementType.END, 0) new LLamaGrammarElement(LLamaGrammarElementType.END, 0)
}; };
uint index = 0; CheckGrammar(grammarBytes, "root", expected, expectedRules);
foreach (var rule in state.Rules)
{
// compare rule to expected rule
for (uint i = 0; i < rule.Elements.Count; i++)
{
var element = rule.Elements[(int)i];
var expectedElement = expectedRules[(int)index];
// Pretty print error message before asserting
if (expectedElement.Type != element.Type || expectedElement.Value != element.Value)
{
Console.Error.WriteLine($"index: {index}");
Console.Error.WriteLine($"expected_element: {expectedElement.Type}, {expectedElement.Value}");
Console.Error.WriteLine($"actual_element: {element.Type}, {element.Value}");
Console.Error.WriteLine("expected_element != actual_element");
}
Assert.Equal(expectedElement.Type, element.Type);
Assert.Equal(expectedElement.Value, element.Value);
index++;
}
}
Assert.NotEmpty(state.Rules);
} }
[Fact]
public void InvalidGrammarNoClosingBracket()
{
var parsedGrammar = new GBNFGrammarParser();
var grammarBytes = @"
root ::= (expr ""="" ws term ""\n""+ ## <--- Mismatched brackets on this line
expr ::= term ([-+*/] term)*
term ::= ident | num | ""("" ws expr "")"" ws
ident ::= [a-z] [a-z0-9_]* ws
num ::= [0-9]+ ws
ws ::= [ \t\n]*
";
Assert.Throws<GrammarExpectedNext>(() =>
{
parsedGrammar.Parse(grammarBytes, "root");
});
}
[Fact]
public void InvalidGrammarNoName()
{
var parsedGrammar = new GBNFGrammarParser();
var grammarBytes = @"
root ::= (expr ""="" ws term ""\n"")+
::= term ([-+*/] term)* ## <--- Missing a name for this rule!
term ::= ident | num | ""("" ws expr "")"" ws
ident ::= [a-z] [a-z0-9_]* ws
num ::= [0-9]+ ws
ws ::= [ \t\n]*
";
Assert.Throws<GrammarExpectedName>(() =>
{
parsedGrammar.Parse(grammarBytes, "root");
});
}
[Fact]
public void InvalidGrammarBadHex()
{
var parsedGrammar = new GBNFGrammarParser();
var grammarBytes = @"
root ::= (expr ""="" ws term ""\n"")+
expr ::= term ([-+*/] term)*
term ::= ident | num | ""("" ws expr "")"" ws
ident ::= [a-z] [a-z0-9_]* ws
num ::= [0-\xQQ]+ ws ## <--- `\xQQ` is not valid hex!
ws ::= [ \t\n]*
";
Assert.Throws<GrammarUnexpectedHexCharsCount>(() =>
{
parsedGrammar.Parse(grammarBytes, "root");
});
}
[Fact] [Fact]
public void InvalidRuleNoElements() public void InvalidRuleNoElements()
{ {