From 65601d2b7c667a2509a9ecb205e6ec540a05de95 Mon Sep 17 00:00:00 2001 From: b3b00 Date: Fri, 15 Nov 2024 15:56:36 +0100 Subject: [PATCH] #507 --- src/samples/ParserExample/Program.cs | 3 +- src/sly/parser/generator/EBNFParserBuilder.cs | 26 +++++++++ .../parser/generator/ParserConfiguration.cs | 9 +++ .../syntax/grammar/NonTerminalClause.cs | 13 ++++- tests/ParserTests/EBNFTests.cs | 56 ++++++++++++++++++- 5 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/samples/ParserExample/Program.cs b/src/samples/ParserExample/Program.cs index a759b064..853ddeaf 100644 --- a/src/samples/ParserExample/Program.cs +++ b/src/samples/ParserExample/Program.cs @@ -137,7 +137,8 @@ public static object Rec(List args) private static void TestIssue507() { var tests = new EBNFTests(); - tests.TestIssue507TransitiveEmptyStarter(); + //tests.TestIssue507TransitiveEmptyStarter(); + tests.TestIssue507MoreTransitiveEmptyStarter(); } private static void BenchSimpleExpression() diff --git a/src/sly/parser/generator/EBNFParserBuilder.cs b/src/sly/parser/generator/EBNFParserBuilder.cs index 5de6e0a5..3fd86336 100644 --- a/src/sly/parser/generator/EBNFParserBuilder.cs +++ b/src/sly/parser/generator/EBNFParserBuilder.cs @@ -59,6 +59,7 @@ public override BuildResult> BuildParser(object parserInstance, configuration.AutoCloseIndentations = autoCloseIndentations; LeftRecursionChecker recursionChecker = new LeftRecursionChecker(); + SetEmptyNonTerminals(configuration); // check left recursion. var (foundRecursion, recursions) = LeftRecursionChecker.CheckLeftRecursion(configuration); if (foundRecursion) @@ -118,6 +119,31 @@ protected override ISyntaxParser BuildSyntaxParser(ParserConfiguration< return parser; } + private void SetEmptyNonTerminals(ParserConfiguration conf) + { + bool stillSetting = true; + while (stillSetting) + { + stillSetting = false; + foreach (var nt in conf.NonTerminals) + { + foreach (var rule in nt.Value.Rules) + { + foreach (var clause in rule.Clauses) + { + if (clause is NonTerminalClause nonTerminalClause) + { + var rules = conf.GetRulesForNonTerminal(nonTerminalClause.NonTerminalName); + stillSetting |= nonTerminalClause.SetMayBeEmpty(rules.Exists(x => x.MayBeEmpty)); + } + } + } + } + } + } + + + #region configuration diff --git a/src/sly/parser/generator/ParserConfiguration.cs b/src/sly/parser/generator/ParserConfiguration.cs index 19b2f126..61839593 100644 --- a/src/sly/parser/generator/ParserConfiguration.cs +++ b/src/sly/parser/generator/ParserConfiguration.cs @@ -61,6 +61,15 @@ public List> GetAllExplicitTokenClauses() return clauses; } + public List> GetRulesForNonTerminal(string nonTerminal) + { + if (NonTerminals.TryGetValue(nonTerminal, out NonTerminal nonTerminalConfig)) + { + return nonTerminalConfig.Rules.ToList(); + } + return new List>(); + } + [ExcludeFromCodeCoverage] public string Dump() { diff --git a/src/sly/parser/syntax/grammar/NonTerminalClause.cs b/src/sly/parser/syntax/grammar/NonTerminalClause.cs index 54073f9c..d074d8a7 100644 --- a/src/sly/parser/syntax/grammar/NonTerminalClause.cs +++ b/src/sly/parser/syntax/grammar/NonTerminalClause.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; namespace sly.parser.syntax.grammar @@ -14,10 +13,18 @@ public NonTerminalClause(string name) public bool IsGroup { get; set; } = false; + private bool _mayBeEmpty = false; + public bool MayBeEmpty() { - // TODO : issue #507 non terminal may be empty if at least 1 rule may be empty - return false; + return _mayBeEmpty; + } + + public bool SetMayBeEmpty(bool mayBeEmpty) + { + bool setted = mayBeEmpty && !_mayBeEmpty; + _mayBeEmpty = mayBeEmpty; + return setted; } diff --git a/tests/ParserTests/EBNFTests.cs b/tests/ParserTests/EBNFTests.cs index 7e219857..21263724 100644 --- a/tests/ParserTests/EBNFTests.cs +++ b/tests/ParserTests/EBNFTests.cs @@ -604,6 +604,38 @@ public string Z(Token a) return a.Value; } } + + public class Issue507MoreTransitiveEmptyStarterParser + { + [Production("x : w")] + public string X(string w) + { + return w; + } + + [Production("w : y")] + public string W(string y) + { + return y; + } + + [Production("y : z*")] + public string Y(List zs) + { + if (zs.Any()) + { + return string.Join(",", zs); + } + + return "empty"; + } + + [Production("z : a")] + public string Z(Token a) + { + return a.Value; + } + } public class Bugfix104Test { @@ -1362,9 +1394,27 @@ public void TestIssue507TransitiveEmptyStarter() Check.That(parserResultNotEmpty).IsOkParsing(); Check.That(parserResultNotEmpty.Result).IsEqualTo("a,a,a"); - var parserResulttEmpty = parser.Parse(""); - Check.That(parserResulttEmpty).IsOkParsing(); - Check.That(parserResulttEmpty.Result).IsEqualTo("empty"); + var parserResultEmpty = parser.Parse(""); + Check.That(parserResultEmpty).IsOkParsing(); + Check.That(parserResultEmpty.Result).IsEqualTo("empty"); + } + + [Fact] + public void TestIssue507MoreTransitiveEmptyStarter() + { + var startingRule = $"x"; + var parserInstance = new Issue507MoreTransitiveEmptyStarterParser(); + var builder = new ParserBuilder(); + var builtParser = builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, startingRule); + Check.That(builtParser).IsOk(); + var parser = builtParser.Result; + var parserResultNotEmpty = parser.Parse("a a a"); + Check.That(parserResultNotEmpty).IsOkParsing(); + Check.That(parserResultNotEmpty.Result).IsEqualTo("a,a,a"); + + var parserResultEmpty = parser.Parse(""); + Check.That(parserResultEmpty).IsOkParsing(); + Check.That(parserResultEmpty.Result).IsEqualTo("empty"); } [Fact]