Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 183 additions & 67 deletions LLama.Unittest/GrammarParserTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LLama.Native;
using LLama.Exceptions;
using LLama.Native;
using LLama.Grammars;

namespace LLama.Unittest
Expand All @@ -11,16 +12,48 @@ namespace LLama.Unittest
/// </summary>
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]
public void ParseComplexGrammar()
{
GBNFGrammarParser parsedGrammar = new GBNFGrammarParser();
string grammarBytes = @"root ::= (expr ""="" term ""\n"")+
expr ::= term ([-+*/] term)*
term ::= [0-9]+";

var state = parsedGrammar.Parse(grammarBytes, "root");
Assert.Equal(0ul, state.StartRuleIndex);
var grammarBytes = @"root ::= (expr ""="" term ""\n"")+
expr ::= term ([-\x2b\x2A/] term)*
term ::= [\x30-\x39]+";

var expected = new List<KeyValuePair<string, uint>>
{
Expand All @@ -34,12 +67,6 @@ public void ParseComplexGrammar()
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>
{
new LLamaGrammarElement(LLamaGrammarElementType.RULE_REF, 4),
Expand Down Expand Up @@ -78,35 +105,12 @@ public void ParseComplexGrammar()
new LLamaGrammarElement(LLamaGrammarElementType.END, 0),
};

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);
CheckGrammar(grammarBytes, "root", expected, expectedRules);
}

[Fact]
public void ParseExtraComplexGrammar()
{
GBNFGrammarParser parsedGrammar = new GBNFGrammarParser();
string grammarBytes = @"
root ::= (expr ""="" ws term ""\n"")+
expr ::= term ([-+*/] term)*
Expand All @@ -116,9 +120,6 @@ public void ParseExtraComplexGrammar()
ws ::= [ \t\n]*
";

var state = parsedGrammar.Parse(grammarBytes, "root");
Assert.Equal(0ul, state.StartRuleIndex);

var expected = new List<KeyValuePair<string, uint>>
{
new KeyValuePair<string, uint>("expr", 2),
Expand All @@ -136,12 +137,6 @@ public void ParseExtraComplexGrammar()
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>
{
new LLamaGrammarElement(LLamaGrammarElementType.RULE_REF, 5),
Expand Down Expand Up @@ -213,29 +208,150 @@ public void ParseExtraComplexGrammar()
new LLamaGrammarElement(LLamaGrammarElementType.END, 0)
};

uint index = 0;
foreach (var rule in state.Rules)
CheckGrammar(grammarBytes, "root", expected, expectedRules);
}


[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>(() =>
{
// compare rule to expected rule
for (uint i = 0; i < rule.Elements.Count; i++)
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]
public void InvalidRuleNoElements()
{
Assert.Throws<ArgumentException>(() =>
{
// ReSharper disable once ObjectCreationAsStatement
new GrammarRule("name", Array.Empty<LLamaGrammarElement>());
});
}

[Fact]
public void InvalidRuleNoEndElement()
{
Assert.Throws<ArgumentException>(() =>
{
// ReSharper disable once ObjectCreationAsStatement
new GrammarRule("name", new[]
{
var element = rule.Elements[(int)i];
var expectedElement = expectedRules[(int)index];
new LLamaGrammarElement(LLamaGrammarElementType.ALT, 0)
});
});
}

// 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 InvalidRuleExtraEndElement()
{
Assert.Throws<GrammarUnexpectedEndElement>(() =>
{
// ReSharper disable once ObjectCreationAsStatement
new GrammarRule("name", new[]
{
new LLamaGrammarElement(LLamaGrammarElementType.END, 0),
new LLamaGrammarElement(LLamaGrammarElementType.ALT, 0),
new LLamaGrammarElement(LLamaGrammarElementType.END, 0)
});
});
}

[Fact]
public void InvalidRuleMalformedRange()
{
Assert.Throws<GrammarUnexpectedCharRngElement>(() =>
{
// ReSharper disable once ObjectCreationAsStatement
new GrammarRule("name", new[]
{
new LLamaGrammarElement(LLamaGrammarElementType.ALT, 0),
new LLamaGrammarElement(LLamaGrammarElementType.CHAR_RNG_UPPER, 0),
new LLamaGrammarElement(LLamaGrammarElementType.END, 0)
});
});


}

[Fact]
public void InvalidRuleMalformedCharAlt()
{
Assert.Throws<GrammarUnexpectedCharAltElement>(() =>
{
// ReSharper disable once ObjectCreationAsStatement
new GrammarRule("name", new[]
{
new LLamaGrammarElement(LLamaGrammarElementType.RULE_REF, 0),
new LLamaGrammarElement(LLamaGrammarElementType.CHAR_ALT, 0),
new LLamaGrammarElement(LLamaGrammarElementType.END, 0)
});
});
}

[Fact]
public void InvalidRuleElement()
{
Assert.Throws<ArgumentException>(() =>
{
// ReSharper disable once ObjectCreationAsStatement
new GrammarRule("name", new[]
{
new LLamaGrammarElement((LLamaGrammarElementType)99999, 0),
new LLamaGrammarElement(LLamaGrammarElementType.END, 0)
});
});
}
}
}