123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- {
- This file is part of the Free Component Library (FCL)
- Copyright (c) 2025 by Michael Van Canneyt ([email protected])
- Test EBNF Parser
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- unit utcparser;
- interface
- uses
- SysUtils, classes, fpcunit, testregistry,
- ebnf.tree,
- ebnf.parser;
- type
- { TTestEBNFParser }
- TTestEBNFParser = class(TTestCase)
- private
- FGrammar: TEBNFGrammar;
- FParser: TEBNFParser;
- protected
- procedure TearDown; override;
- procedure AssertEquals(const Msg : String; aExpected, aActual : TEBNFElementType); overload;
- procedure CheckEquals(aExpected, aActual : TEBNFElementType; const Msg : String = ''); overload;
- Property Parser : TEBNFParser Read FParser Write FParser;
- Property Grammar : TEBNFGrammar Read FGrammar Write FGrammar;
- published
- procedure TestOneRuleOneTermOneFactor;
- procedure TestOneRuleOneTermTwoFactors;
- procedure TestOneRuleTwoTermsOneFactorEach;
- procedure TestOneRuleTwoTermsTwoFactorsEach;
- procedure TestTwoRulesOneTermOneFactorEach;
- procedure TestRuleWithOptionalGroup;
- procedure TestRuleWithRepetitionGroup;
- procedure TestRuleWithParenthesizedGroup;
- procedure TestRuleWithSpecialSequence;
- procedure TestDuplicateRuleError;
- procedure TestMissingEqualsError;
- procedure TestMissingSemicolonError;
- procedure TestUnexpectedTokenInFactorError;
- end;
- implementation
- uses typinfo;
- procedure TTestEBNFParser.AssertEquals(const Msg: String; aExpected, aActual: TEBNFElementType);
- begin
- AssertEquals(Msg,GetEnumName(typeInfo(TEBNFElementType),ord(aExpected)),
- GetEnumName(typeInfo(TEBNFElementType),ord(aActual)));
- end;
- procedure TTestEBNFParser.CheckEquals(aExpected, aActual: TEBNFElementType; const Msg: String);
- begin
- AssertEquals(Msg,aExpected,aActual);
- end;
- procedure TTestEBNFParser.TearDown;
- begin
- FreeAndNil(FParser);
- FreeAndNil(FGrammar);
- inherited TearDown;
- end;
- procedure TTestEBNFParser.TestOneRuleOneTermOneFactor;
- var
- EBNFSource: string;
- Rule: TEBNFRule;
- Expression: TEBNFExpression;
- Term: TEBNFTerm;
- Factor: TEBNFFactor;
- begin
- EBNFSource := 'rule1 = "literal" ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- CheckEquals(1, Grammar.ChildCount, 'Expected 1 rule in grammar');
- Rule:=Grammar.Rules['rule1'];
- CheckNotNull(Rule, 'Rule object should not be nil');
- CheckEquals(etExpression, Rule.Expression.NodeType, 'Rule expression should be of type anExpression');
- Expression := TEBNFExpression(Rule.Expression);
- CheckEquals(1, Expression.ChildCount, 'Expected 1 term in expression');
- CheckEquals(etTerm, Expression.Terms[0].NodeType, 'Expression term should be of type anTerm');
- Term := TEBNFTerm(Expression.Terms[0]);
- CheckEquals(1, Term.ChildCount, 'Expected 1 factor in term');
- CheckEquals(etFactorStringLiteral, Term.Factors[0].NodeType, 'Term factor should be of type anFactorStringLiteral');
- Factor := TEBNFFactor(Term.Factors[0]);
- CheckEquals('literal', Factor.Value, 'Factor value should be "literal"');
- end;
- procedure TTestEBNFParser.TestOneRuleOneTermTwoFactors;
- var
- EBNFSource: string;
- Rule: TEBNFRule;
- Expression: TEBNFExpression;
- Term: TEBNFTerm;
- Factor1, Factor2: TEBNFFactor;
- begin
- EBNFSource := 'rule2 = identifier1 "literal2" ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- CheckEquals(1, Grammar.ChildCount, 'Expected 1 rule in grammar');
- Rule:=Grammar.Rules['rule2'];
- CheckNotNull(Rule, 'Expected rule "rule2" to exist');
- Expression := TEBNFExpression(Rule.Expression);
- CheckEquals(1, Expression.ChildCount, 'Expected 1 term in expression');
- Term := TEBNFTerm(Expression.Terms[0]);
- CheckEquals(2, Term.ChildCount, 'Expected 2 factors in term');
- Factor1 := TEBNFFactor(Term.Factors[0]);
- CheckEquals(etFactorIdentifier, Factor1.NodeType, 'First factor should be identifier');
- CheckEquals('identifier1', Factor1.Value, 'First factor value should be "identifier1"');
- Factor2 := TEBNFFactor(Term.Factors[1]);
- CheckEquals(etFactorStringLiteral, Factor2.NodeType, 'Second factor should be string literal');
- CheckEquals('literal2', Factor2.Value, 'Second factor value should be "literal2"');
- end;
- procedure TTestEBNFParser.TestOneRuleTwoTermsOneFactorEach;
- var
- EBNFSource: string;
- Rule: TEBNFRule;
- Expression: TEBNFExpression;
- Term1, Term2: TEBNFTerm;
- Factor1, Factor2: TEBNFFactor;
- begin
- EBNFSource := 'rule3 = factorA | factorB ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- CheckEquals(1, Grammar.ChildCount, 'Expected 1 rule in grammar');
- Rule:=Grammar.Rules['rule3'];
- ChecknotNull(Rule, 'Expected rule "rule3" to exist');
- Expression := TEBNFExpression(Rule.Expression);
- CheckEquals(2, Expression.ChildCount, 'Expected 2 terms in expression');
- // First term
- Term1 := TEBNFTerm(Expression.Terms[0]);
- CheckEquals(1, Term1.ChildCount, 'Expected 1 factor in first term');
- Factor1 := TEBNFFactor(Term1.Factors[0]);
- CheckEquals(etFactorIdentifier, Factor1.NodeType, 'First term factor should be identifier');
- CheckEquals('factorA', Factor1.Value, 'First term factor value should be "factorA"');
- // Second term
- Term2 := TEBNFTerm(Expression.Terms[1]);
- CheckEquals(1, Term2.ChildCount, 'Expected 1 factor in second term');
- Factor2 := TEBNFFactor(Term2.Factors[0]);
- CheckEquals(etFactorIdentifier, Factor2.NodeType, 'Second term factor should be identifier');
- CheckEquals('factorB', Factor2.Value, 'Second term factor value should be "factorB"');
- end;
- procedure TTestEBNFParser.TestOneRuleTwoTermsTwoFactorsEach;
- var
- EBNFSource: string;
- Rule: TEBNFRule;
- Expression: TEBNFExpression;
- Term1, Term2: TEBNFTerm;
- Factor: TEBNFFactor;
- begin
- EBNFSource := 'rule4 = (id1 "lit1") | (id2 "lit2") ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- CheckEquals(1, Grammar.ChildCount, 'Expected 1 rule in grammar');
- Rule:=Grammar.Rules['rule4'];
- CheckNotNull(Rule, 'Expected rule "rule4" to exist');
- Expression := TEBNFExpression(Rule.Expression);
- CheckEquals(2, Expression.ChildCount, 'Expected 2 terms in expression');
- // First term (group)
- Term1 := TEBNFTerm(Expression.Terms[0]);
- CheckEquals(1, Term1.ChildCount, 'Expected 1 factor (group) in first term');
- Factor := TEBNFFactor(Term1.Factors[0]);
- CheckEquals(etFactorGroup, Factor.NodeType, 'Factor should be a group');
- CheckNotNull(Factor.InnerNode, 'Inner node of group should not be nil');
- // Check inner expression of first group
- CheckEquals(etExpression, Factor.InnerNode.NodeType, 'Inner node should be an expression');
- CheckEquals(1, TEBNFExpression(Factor.InnerNode).ChildCount, 'Inner expression should have 1 term');
- CheckEquals(2, TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).ChildCount, 'Inner term should have 2 factors');
- CheckEquals('id1', TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[0]).Value, 'Inner factor 1 value');
- CheckEquals('lit1', TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[1]).Value, 'Inner factor 2 value');
- // Second term (group)
- Term2 := TEBNFTerm(Expression.Terms[1]);
- CheckEquals(1, Term2.ChildCount, 'Expected 1 factor (group) in second term');
- Factor := TEBNFFactor(Term2.Factors[0]);
- CheckEquals(etFactorGroup, Factor.NodeType, 'Factor should be a group');
- CheckNotNull(Factor.InnerNode, 'Inner node of group should not be nil');
- // Check inner expression of second group
- CheckEquals(etExpression, Factor.InnerNode.NodeType, 'Inner node should be an expression');
- CheckEquals(1, TEBNFExpression(Factor.InnerNode).ChildCount, 'Inner expression should have 1 term');
- CheckEquals(2, TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).ChildCount, 'Inner term should have 2 factors');
- CheckEquals('id2', TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[0]).Value, 'Inner factor 1 value');
- CheckEquals('lit2', TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[1]).Value, 'Inner factor 2 value');
- end;
- procedure TTestEBNFParser.TestTwoRulesOneTermOneFactorEach;
- var
- EBNFSource: string;
- Rule1, Rule2: TEBNFRule;
- begin
- EBNFSource :=
- 'ruleA = "first" ;' + sLineBreak +
- 'ruleB = identifierB ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- CheckEquals(2, Grammar.ChildCount, 'Expected 2 rules in grammar');
- // Check ruleA
- Rule1:=Grammar.Rules['ruleA'];
- CheckNotNull(Rule1, 'Expected rule "ruleA" to exist');
- CheckEquals(etExpression, Rule1.Expression.NodeType, 'RuleA expression type');
- CheckEquals(1, TEBNFExpression(Rule1.Expression).ChildCount, 'RuleA expression terms count');
- CheckEquals(1, TEBNFTerm(TEBNFExpression(Rule1.Expression).Terms[0]).ChildCount, 'RuleA term factors count');
- CheckEquals(etFactorStringLiteral, TEBNFFactor(TEBNFTerm(TEBNFExpression(Rule1.Expression).Terms[0]).Factors[0]).NodeType, 'RuleA factor type');
- CheckEquals('first', TEBNFFactor(TEBNFTerm(TEBNFExpression(Rule1.Expression).Terms[0]).Factors[0]).Value, 'RuleA factor value');
- // Check ruleB
- Rule2:=Grammar.Rules['ruleB'];
- CheckNotNull(Rule2, 'Expected rule "ruleB" to exist');
- CheckEquals(etExpression, Rule2.Expression.NodeType, 'RuleB expression type');
- CheckEquals(1, TEBNFExpression(Rule2.Expression).ChildCount, 'RuleB expression terms count');
- CheckEquals(1, TEBNFTerm(TEBNFExpression(Rule2.Expression).Terms[0]).ChildCount, 'RuleB term factors count');
- CheckEquals(etFactorIdentifier, TEBNFFactor(TEBNFTerm(TEBNFExpression(Rule2.Expression).Terms[0]).Factors[0]).NodeType, 'RuleB factor type');
- CheckEquals('identifierB', TEBNFFactor(TEBNFTerm(TEBNFExpression(Rule2.Expression).Terms[0]).Factors[0]).Value, 'RuleB factor value');
- end;
- procedure TTestEBNFParser.TestRuleWithOptionalGroup;
- var
- EBNFSource: string;
- Rule: TEBNFRule;
- Expression: TEBNFExpression;
- Term: TEBNFTerm;
- Factor: TEBNFFactor;
- begin
- EBNFSource := 'optional_rule = [ "optional_part" ] ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- Rule:=Grammar.Rules['optional_rule'];
- CheckNotNull(Rule, 'Expected rule "optional_rule"');
- Expression := TEBNFExpression(Rule.Expression);
- Term := TEBNFTerm(Expression.Terms[0]);
- Factor := TEBNFFactor(Term.Factors[0]);
- CheckEquals(etFactorOptional, Factor.NodeType, 'Factor should be an optional group');
- CheckNotNull(Factor.InnerNode, 'Optional group should have an inner node');
- CheckEquals(etExpression, Factor.InnerNode.NodeType, 'Inner node should be an expression');
- CheckEquals(1, TEBNFExpression(Factor.InnerNode).ChildCount, 'Inner expression should have 1 term');
- CheckEquals(1, TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).ChildCount, 'Inner term should have 1 factor');
- CheckEquals(etFactorStringLiteral, TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[0]).NodeType, 'Inner factor type');
- CheckEquals('optional_part', TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[0]).Value, 'Inner factor value');
- end;
- procedure TTestEBNFParser.TestRuleWithRepetitionGroup;
- var
- EBNFSource: string;
- Rule: TEBNFRule;
- Expression: TEBNFExpression;
- Term: TEBNFTerm;
- Factor: TEBNFFactor;
- begin
- EBNFSource := 'repeat_rule = { identifier_to_repeat } ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- Rule:=Grammar.Rules['repeat_rule'];
- CheckNotNull(Rule, 'Expected rule "repeat_rule"');
- Expression := TEBNFExpression(Rule.Expression);
- Term := TEBNFTerm(Expression.Terms[0]);
- Factor := TEBNFFactor(Term.Factors[0]);
- CheckEquals(etFactorRepetition, Factor.NodeType, 'Factor should be a repetition group');
- CheckNotNull(Factor.InnerNode, 'Repetition group should have an inner node');
- CheckEquals(etExpression, Factor.InnerNode.NodeType, 'Inner node should be an expression');
- CheckEquals(1, TEBNFExpression(Factor.InnerNode).ChildCount, 'Inner expression should have 1 term');
- CheckEquals(1, TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).ChildCount, 'Inner term should have 1 factor');
- CheckEquals(etFactorIdentifier, TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[0]).NodeType, 'Inner factor type');
- CheckEquals('identifier_to_repeat', TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[0]).Value, 'Inner factor value');
- end;
- procedure TTestEBNFParser.TestRuleWithParenthesizedGroup;
- var
- EBNFSource: string;
- Rule: TEBNFRule;
- Expression: TEBNFExpression;
- Term: TEBNFTerm;
- Factor: TEBNFFactor;
- begin
- EBNFSource := 'group_rule = ( "part_one" | "part_two" ) ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- Rule:=Grammar.Rules['group_rule'];
- CheckNotNull(Rule, 'Expected rule "group_rule"');
- Expression := TEBNFExpression(Rule.Expression);
- Term := TEBNFTerm(Expression.Terms[0]);
- Factor := TEBNFFactor(Term.Factors[0]);
- CheckEquals(etFactorGroup, Factor.NodeType, 'Factor should be a parenthesized group');
- CheckNotNull(Factor.InnerNode, 'Group should have an inner node');
- CheckEquals(etExpression, Factor.InnerNode.NodeType, 'Inner node should be an expression');
- CheckEquals(2, TEBNFExpression(Factor.InnerNode).ChildCount, 'Inner expression should have 2 terms'); // Because of '|'
- CheckEquals(1, TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).ChildCount, 'First inner term should have 1 factor');
- CheckEquals('part_one', TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[0]).Factors[0]).Value, 'First inner factor value');
- CheckEquals(1, TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[1]).ChildCount, 'Second inner term should have 1 factor');
- CheckEquals('part_two', TEBNFFactor(TEBNFTerm(TEBNFExpression(Factor.InnerNode).Terms[1]).Factors[0]).Value, 'Second inner factor value');
- end;
- procedure TTestEBNFParser.TestRuleWithSpecialSequence;
- var
- EBNFSource: string;
- Rule: TEBNFRule;
- Expression: TEBNFExpression;
- Term: TEBNFTerm;
- Factor: TEBNFFactor;
- begin
- EBNFSource := 'special_rule = ? "this is a comment" ? ;';
- Parser := TEBNFParser.Create(EBNFSource);
- Grammar := Parser.Parse;
- Rule := Grammar.Rules['special_rule'];
- CheckNotNull(Rule, 'Expected rule "special_rule"');
- Expression := TEBNFExpression(Rule.Expression);
- Term := TEBNFTerm(Expression.Terms[0]);
- Factor := TEBNFFactor(Term.Factors[0]);
- CheckEquals(etFactorSpecialSequence, Factor.NodeType, 'Factor should be a special sequence');
- CheckEquals('this is a comment', Factor.Value, 'Special sequence value should match');
- end;
- procedure TTestEBNFParser.TestDuplicateRuleError;
- var
- EBNFSource: string;
- begin
- EBNFSource :=
- 'ruleA = "first" ;' + sLineBreak +
- 'ruleA = "second" ;'; // Duplicate rule definition
- Parser := TEBNFParser.Create(EBNFSource);
- try
- Parser.Parse;
- Fail('Expected an exception for duplicate rule');
- except
- on E: Exception do
- Check(Pos('Duplicate rule identifier: ruleA', E.Message) > 0, 'Expected "Duplicate rule identifier" error message');
- end;
- end;
- procedure TTestEBNFParser.TestMissingEqualsError;
- var
- EBNFSource: string;
- begin
- EBNFSource := 'rule_bad "literal" ;'; // Missing '='
- Parser := TEBNFParser.Create(EBNFSource);
- try
- Parser.Parse;
- Fail('Expected an exception for missing equals sign');
- except
- on E: Exception do
- Check(Pos('Expected ttEquals, but found ttStringLiteral', E.Message) > 0, 'Expected "Expected ttEquals" error message');
- end;
- end;
- procedure TTestEBNFParser.TestMissingSemicolonError;
- var
- EBNFSource: string;
- begin
- EBNFSource := 'rule_bad = "literal" '; // Missing ';'
- Parser := TEBNFParser.Create(EBNFSource);
- try
- Parser.Parse;
- Fail('Expected an exception for missing semicolon');
- except
- on E: Exception do
- Check(Pos('Expected ttSemicolon, but found ttEOF', E.Message) > 0, 'Expected "Expected ttSemicolon" error message');
- end;
- end;
- procedure TTestEBNFParser.TestUnexpectedTokenInFactorError;
- var
- EBNFSource: string;
- begin
- EBNFSource := 'rule_bad = = ;'; // '==' is not a valid factor
- Parser := TEBNFParser.Create(EBNFSource);
- try
- Parser.Parse;
- Fail('Expected an exception for unexpected token in factor');
- except
- on E: Exception do
- Check(Pos('Unexpected token for factor: ttEquals', E.Message) > 0, 'Expected "Unexpected token for factor" error message');
- end;
- end;
- initialization
- RegisterTest(TTestEBNFParser);
- end.
|