diff --git a/src/samples/ParserExample/Program.cs b/src/samples/ParserExample/Program.cs index 6448da92..48add5c1 100644 --- a/src/samples/ParserExample/Program.cs +++ b/src/samples/ParserExample/Program.cs @@ -42,33 +42,30 @@ using ExpressionContext = postProcessedLexerParser.expressionModel.ExpressionContext; using ExpressionToken = simpleExpressionParser.ExpressionToken; using IfThenElse = indented.IfThenElse; +using ParserTests.ambiguous; namespace ParserExample { public enum ManySugar { - [Sugar("<")] - OPEN, - [Sugar(" args) return "_"; } - private static void testIssue516() + private static void testIssue516() { ParserBuilder builder = new ParserBuilder(); var xmlparser = new MinimalXmlParser(); @@ -155,7 +153,7 @@ inner inner content parsed.Errors.ForEach(Console.WriteLine); } - + var jBuilder = new ParserBuilder(); var jsonParser = new EbnfJsonGenericParser(); var jr = jBuilder.BuildParser(jsonParser, ParserType.EBNF_LL_RECURSIVE_DESCENT, "root"); @@ -167,19 +165,19 @@ inner inner content } } - + private static void TestIssue507() { var tests = new EBNFTests(); //tests.TestIssue507TransitiveEmptyStarter(); tests.TestIssue507MoreTransitiveEmptyStarter(); } - + private static void BenchSimpleExpression() { GenericSimpleExpressionParser p = new GenericSimpleExpressionParser(); var builder = new ParserBuilder(); - + var Parser = builder.BuildParser(p, ParserType.EBNF_LL_RECURSIVE_DESCENT, "root"); if (Parser.IsOk) { @@ -189,9 +187,9 @@ private static void BenchSimpleExpression() if (r.IsOk) { Console.WriteLine(r.Result); - } + } } - + } } @@ -223,7 +221,7 @@ private static void TestFactorial() var compiler = new WhileCompiler(); var code = compiler.TranspileToCSharp(program); - var f = compiler.CompileToFunction(program,false); + var f = compiler.CompileToFunction(program, false); int r = f(); if (r != 3628800) { @@ -236,7 +234,7 @@ private static void TestFactorial() } } } - + private static void TestIndentedFactorial() { var whileParser = new IndentedWhileParserGeneric(); @@ -252,9 +250,9 @@ private static void TestIndentedFactorial() return; } - - var program = -@"# indented factorial + + var program = + @"# indented factorial r:=1 i:=1 while i < 11 do @@ -284,13 +282,13 @@ private static void TestIndentedFactorial() return; } - + var interpreter = new Interpreter(); var context = interpreter.Interprete(result.Result); var compiler = new IndentedWhileCompiler(); var code = compiler.TranspileToCSharp(program); - var f = compiler.CompileToFunction(program,false); + var f = compiler.CompileToFunction(program, false); Console.WriteLine("***********************************"); Console.WriteLine("***********************************"); Console.WriteLine("***********************************"); @@ -495,12 +493,10 @@ public static void TestContextualParser() var buildResult = buildSimpleExpressionParserWithContext(); if (buildResult.IsError) { - buildResult.Errors.ForEach(e => - { - Console.WriteLine(e.Level + " - " + e.Message); - }); + buildResult.Errors.ForEach(e => { Console.WriteLine(e.Level + " - " + e.Message); }); return; } + var parser = buildResult.Result; var res = parser.ParseWithContext("2 + a", new Dictionary { { "a", 2 } }); Console.WriteLine($"result : ok:>{res.IsOk}< value:>{res.Result}<"); @@ -540,47 +536,50 @@ public static void testJSONEscaped(string content = null) { content = File.ReadAllText("test.json"); } - try { - var instance = new EbnfJsonGenericParser(); - var builder = new ParserBuilder(); - var buildResult = builder.BuildParser(instance, ParserType.EBNF_LL_RECURSIVE_DESCENT, "root"); - if (buildResult.IsOk) + try { - Console.WriteLine("parser built."); - var parser = buildResult.Result; - - Console.WriteLine("test.json read."); - for (int i = 0; i < 10; i++) - { + var instance = new EbnfJsonGenericParser(); + var builder = new ParserBuilder(); + var buildResult = builder.BuildParser(instance, ParserType.EBNF_LL_RECURSIVE_DESCENT, "root"); + if (buildResult.IsOk) + { + Console.WriteLine("parser built."); + var parser = buildResult.Result; - var jsonResult = parser.Parse(content); - Console.WriteLine("json parse done."); - if (jsonResult.IsOk) - { - Console.WriteLine("YES !"); - } - else + Console.WriteLine("test.json read."); + for (int i = 0; i < 10; i++) { - Console.WriteLine("Ooh no !"); + + + var jsonResult = parser.Parse(content); + Console.WriteLine("json parse done."); + if (jsonResult.IsOk) + { + Console.WriteLine("YES !"); + } + else + { + Console.WriteLine("Ooh no !"); + } } - } - Console.WriteLine("Done."); - + Console.WriteLine("Done."); + + } + else + { + buildResult.Errors.ForEach(e => Console.WriteLine(e.Message)); + } } - else + catch (Exception e) { - buildResult.Errors.ForEach(e => Console.WriteLine(e.Message)); - } - } - catch(Exception e) { Console.WriteLine($"ERROR {e.Message} : \n {e.StackTrace}"); } } - + public static void testProfileJSONEscaping(string content = null, bool escape = true) { if (escape) @@ -637,7 +636,7 @@ public static void testProfileJSONEscaping(string content = null, bool escape = { content = File.ReadAllText("test.json"); } - + var instanceNot = new EbnfJsonGenericParserStringNotEscaped(); var builderNot = new ParserBuilder(); var buildResultNot = builderNot.BuildParser(instanceNot, ParserType.EBNF_LL_RECURSIVE_DESCENT, "root"); @@ -645,7 +644,7 @@ public static void testProfileJSONEscaping(string content = null, bool escape = { Console.WriteLine("parser built."); var parser = buildResultNot.Result; - + Console.WriteLine("test.json read."); @@ -661,7 +660,7 @@ public static void testProfileJSONEscaping(string content = null, bool escape = } Console.WriteLine("Done. Unescaped."); - + } else { @@ -670,14 +669,14 @@ public static void testProfileJSONEscaping(string content = null, bool escape = } } - + public static void testJSONNotEscaped(string content = null) { if (content == null) { content = File.ReadAllText("test.json"); } - + var instanceNot = new EbnfJsonGenericParserStringNotEscaped(); var builderNot = new ParserBuilder(); var buildResultNot = builderNot.BuildParser(instanceNot, ParserType.EBNF_LL_RECURSIVE_DESCENT, "root"); @@ -685,7 +684,7 @@ public static void testJSONNotEscaped(string content = null) { Console.WriteLine("parser built."); var parser = buildResultNot.Result; - + Console.WriteLine("test.json read."); @@ -701,18 +700,18 @@ public static void testJSONNotEscaped(string content = null) } Console.WriteLine("Done. Unescaped."); - + } else { buildResultNot.Errors.ForEach(e => Console.WriteLine(e.Message)); } } - + public static void testJSONEscapedVsNotEscaped() { var content = File.ReadAllText("test.json"); - + testJSONEscaped(content); testJSONNotEscaped(content); @@ -745,7 +744,7 @@ private static void benchLexer() BenchedLexer.Tokenize(content); } } - + private static void TestChars() { var res = LexerBuilder.BuildLexer(new BuildResult>()); @@ -766,7 +765,7 @@ private static void TestChars() } else { - var errors = string.Join('\n',res.Errors.Select(e => e.Level + " - " + e.Message).ToList()); + var errors = string.Join('\n', res.Errors.Select(e => e.Level + " - " + e.Message).ToList()); Console.WriteLine("error building lexer : "); Console.WriteLine(errors); } @@ -776,11 +775,11 @@ public static void TestRecursion() { var builder = new ParserBuilder(); Console.WriteLine("starting"); - var parserInstance = new RecursiveGrammar(); + var parserInstance = new RecursiveGrammar(); Console.WriteLine("new instance"); - - - var Parser = builder.BuildParser(parserInstance,ParserType.EBNF_LL_RECURSIVE_DESCENT,"clause"); + + + var Parser = builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, "clause"); Console.WriteLine($"built : {Parser.IsOk}"); if (Parser.IsError) { @@ -793,14 +792,14 @@ public static void TestRecursion() Console.WriteLine("/-----------------------------------"); Console.WriteLine("/---"); Console.WriteLine("/-----------------------------------"); - - builder = new ParserBuilder(); + + builder = new ParserBuilder(); Console.WriteLine("starting"); - var parserInstance2 = new RecursiveGrammar2(); + var parserInstance2 = new RecursiveGrammar2(); Console.WriteLine("new instance"); - Parser = builder.BuildParser(parserInstance2,ParserType.EBNF_LL_RECURSIVE_DESCENT,"clause"); + Parser = builder.BuildParser(parserInstance2, ParserType.EBNF_LL_RECURSIVE_DESCENT, "clause"); Console.WriteLine($"built : {Parser.IsOk}"); if (Parser.IsError) { @@ -822,7 +821,7 @@ public static void TestScript() var parser = parserBuild.Result; string ko1 = "|B|test2(a, b, c=100)|E|"; string ko2 = "|B|plotshape(data, style=shapexcross)|E|"; - + var r = parser.Parse(ko1); var graphviz = new GraphVizEBNFSyntaxTreeVisitor(); var root = graphviz.VisitTree(r.SyntaxTree); @@ -833,7 +832,7 @@ public static void TestScript() { foreach (var e in parserBuild.Errors) { - Console.WriteLine(e.Level+ " - " + e.Message); + Console.WriteLine(e.Level + " - " + e.Message); } } } @@ -846,32 +845,35 @@ public static void TestI18N() Console.WriteLine("***"); var e = I18N.Instance.GetText("en", I18NMessage.UnexpectedEos); Console.WriteLine(e); - var ee = I18N.Instance.GetText("en", I18NMessage.UnexpectedToken,"xxx","SOME_TOKEN"); + var ee = I18N.Instance.GetText("en", I18NMessage.UnexpectedToken, "xxx", "SOME_TOKEN"); Console.WriteLine(ee); - var eee = I18N.Instance.GetText("en", I18NMessage.UnexpectedTokenExpecting,"xxx","SOME_TOKEN","OTHER_TOKEN1, OTHER_TOKEN2, OTHER_TOKEN_3"); + var eee = I18N.Instance.GetText("en", I18NMessage.UnexpectedTokenExpecting, "xxx", "SOME_TOKEN", + "OTHER_TOKEN1, OTHER_TOKEN2, OTHER_TOKEN_3"); Console.WriteLine(eee); Console.WriteLine("****************************************"); Console.WriteLine("***"); Console.WriteLine("*** LOCAL "); Console.WriteLine("***"); - e = I18N.Instance.GetText( CultureInfo.CurrentCulture.TwoLetterISOLanguageName, I18NMessage.UnexpectedEos); + e = I18N.Instance.GetText(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, I18NMessage.UnexpectedEos); Console.WriteLine(e); - ee = I18N.Instance.GetText( CultureInfo.CurrentCulture.TwoLetterISOLanguageName, I18NMessage.UnexpectedToken,"xxx","SOME_TOKEN"); + ee = I18N.Instance.GetText(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, I18NMessage.UnexpectedToken, + "xxx", "SOME_TOKEN"); Console.WriteLine(ee); - eee = I18N.Instance.GetText( CultureInfo.CurrentCulture.TwoLetterISOLanguageName, I18NMessage.UnexpectedTokenExpecting,"xxx","SOME_TOKEN","OTHER_TOKEN1, OTHER_TOKEN2, OTHER_TOKEN_3"); + eee = I18N.Instance.GetText(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, + I18NMessage.UnexpectedTokenExpecting, "xxx", "SOME_TOKEN", "OTHER_TOKEN1, OTHER_TOKEN2, OTHER_TOKEN_3"); Console.WriteLine(eee); ; } private static BuildResult> BuildParserExpression() - { + { var StartingRule = $"{nameof(SimpleExpressionParser)}_expressions"; var parserInstance = new SimpleExpressionParser(); var builder = new ParserBuilder(); return builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, StartingRule); } - + public static void TestAssociativityFactorExpressionParser() { var StartingRule = $"{nameof(SimpleExpressionParser)}_expressions"; @@ -898,48 +900,51 @@ public static void TestManyString() var tok1 = r.Tokens[0]; Assert.Equal(ManyString.STRING, tok1.TokenID); Assert.Equal(expectString1, tok1.Value); - Assert.Equal('"',tok1.StringDelimiter); + Assert.Equal('"', tok1.StringDelimiter); var tok2 = r.Tokens[1]; Assert.Equal(ManyString.STRING, tok2.TokenID); Assert.Equal(expectString2, tok2.Value); - Assert.Equal('\'',tok2.StringDelimiter); + Assert.Equal('\'', tok2.StringDelimiter); } - - private static void AddExponentExtension(DoubleExponent token, LexemeAttribute lexem, GenericLexer lexer) { - if (token == DoubleExponent.DOUBLE_EXP) { - + private static void AddExponentExtension(DoubleExponent token, LexemeAttribute lexem, + GenericLexer lexer) + { + if (token == DoubleExponent.DOUBLE_EXP) + { + + // callback on end_exponent node - NodeCallback callback = (FSMMatch match) => + NodeCallback callback = (FSMMatch match) => { - string[] items = match.Result.Value.Split(new[] {'e', 'E'}); + string[] items = match.Result.Value.Split(new[] { 'e', 'E' }); double radix = 0; - double.TryParse(items[0].Replace(".",","), out radix); + double.TryParse(items[0].Replace(".", ","), out radix); double exponent = 0; - double.TryParse(items[1],out exponent); + double.TryParse(items[1], out exponent); double value = Math.Pow(radix, exponent); match.Result.SpanValue = value.ToString().AsMemory(); - + match.Properties[GenericLexer.DerivedToken] = DoubleExponent.DOUBLE_EXP; return match; }; - + var fsmBuilder = lexer.FSMBuilder; - + fsmBuilder.GoTo(GenericLexer.in_double) // start an in_double node - .Transition(new char[] {'E','e'}) // add a transition on '.' with precondition - .Transition(new char[]{'+','-'}) + .Transition(new char[] { 'E', 'e' }) // add a transition on '.' with precondition + .Transition(new char[] { '+', '-' }) .Mark("start_exponent_val") - .RangeTransitionTo('0','9',"start_exponent_val") // first year digit + .RangeTransitionTo('0', '9', "start_exponent_val") // first year digit .Mark("end_exponent") .End(GenericToken.Extension) // mark as ending node .CallBack(callback); // set the ending callback } } - + private static void TestDoubleExponent() { var lex = LexerBuilder.BuildLexer(AddExponentExtension); @@ -952,11 +957,12 @@ private static void TestDoubleExponent() } } - public static void Test164() { + public static void Test164() + { var Parser = BuildParserExpression(); var result = Parser.Result.Parse("1(1"); if (result.IsError) - { + { foreach (var error in result.Errors) { Console.WriteLine(error.ErrorMessage); @@ -967,7 +973,7 @@ public static void Test164() { public static void Test192() { - var parser = Issue192.CreateBlockParser(); + var parser = Issue192.CreateBlockParser(); var t = parser.Parse("A1 B2 "); if (t.IsOk) { @@ -980,6 +986,7 @@ public static void Test192() t.Errors.ForEach(x => Console.WriteLine(x.ErrorMessage)); ; } + ; } @@ -997,12 +1004,12 @@ public static void TestIndentedLang() quatre = 4 "; -Console.WriteLine("********************"); -Console.WriteLine(source); -Console.WriteLine("********************"); + Console.WriteLine("********************"); + Console.WriteLine(source); + Console.WriteLine("********************"); IndentedTest1(source); - + source = @"if truc == 1 un = 1 deux = 2 @@ -1017,7 +1024,7 @@ public static void TestIndentedLang() Console.WriteLine(source); Console.WriteLine("********************"); - IndentedTest1(source); + IndentedTest1(source); //IndentedTest2(source); } @@ -1074,11 +1081,11 @@ public static void TestChannels() var tokens = lexer.Tokenize(source); var width = tokens.Tokens.GetChannels().Select(x => x.Tokens.Count).Max(); - + List headers = new List(); - - - + + + if (tokens.IsOk) { foreach (var channel in tokens.Tokens.GetChannels()) @@ -1089,14 +1096,15 @@ public static void TestChannels() Console.Write(token == null ? "" : token.ToString()); Console.Write(";"); } + Console.WriteLine(); } } - + ; } - + } private static void IndentedTest2(string source) @@ -1137,10 +1145,10 @@ private static void IndentedTest2(string source) } } - + public static void TestIndentedParserNeverEnding() { - var source =@"if truc == 1 + var source = @"if truc == 1 un = 1 deux = 2 cinq = 5 @@ -1155,15 +1163,15 @@ public static void TestIndentedParserNeverEnding() Assert.True(parserRes.IsOk); var parser = parserRes.Result; Assert.NotNull(parser); - + var parseResult = parser.Parse(source); - + GraphVizEBNFSyntaxTreeVisitor grapher = new GraphVizEBNFSyntaxTreeVisitor(); grapher.VisitTree(parseResult.SyntaxTree); var graph = grapher.Graph.Compile(); //File.WriteAllText(@"c:\tmp\graph.dot", graph); - + Assert.True(parseResult.IsOk); var ast = parseResult.Result; indented.Block root = ast as indented.Block; @@ -1172,9 +1180,9 @@ public static void TestIndentedParserNeverEnding() IfThenElse ifthenelse = root.Statements.First() as indented.IfThenElse; Assert.NotNull(ifthenelse.Cond); Assert.NotNull(ifthenelse.Then); - Assert.Equal(3,ifthenelse.Then.Statements.Count); + Assert.Equal(3, ifthenelse.Then.Statements.Count); Assert.NotNull(ifthenelse.Else); - Assert.Equal(2,ifthenelse.Else.Statements.Count); + Assert.Equal(2, ifthenelse.Else.Statements.Count); } private static void TestTemplate() @@ -1186,10 +1194,10 @@ private static void TestTemplate() var fsmGraph = fsm.ToGraphViz(); Console.WriteLine(fsmGraph); - + var source = @"hello - {= world =} - billy - {% if (a == 1) %} - bob - {%else%} - boubou - {%endif%}"; - + var tokens = genericLexer.Tokenize(source); foreach (var token in tokens.Tokens.MainTokens()) { @@ -1222,7 +1230,7 @@ private static void TestTemplate() } } - + private static void TestTemplateFor() { var lexerResult = LexerBuilder.BuildLexer(); @@ -1232,7 +1240,7 @@ private static void TestTemplateFor() var fsmGraph = fsm.ToGraphViz(); Console.WriteLine(fsmGraph); - + var source = @"hello @@ -1255,9 +1263,9 @@ private static void TestTemplateFor() ... {% end%} "; - + var tokens = genericLexer.Tokenize(source); - + var channels = tokens.Tokens.GetChannels(); StringBuilder b = new StringBuilder(); foreach (var channel in channels) @@ -1270,13 +1278,13 @@ private static void TestTemplateFor() b.AppendLine(); } - + // File.WriteAllText(@"c:\temp\tokens.txt",b.ToString()); - - + + foreach (var token in tokens.Tokens.MainTokens()) { - + Console.WriteLine(token); } @@ -1289,7 +1297,7 @@ private static void TestTemplateFor() { { "world", "monde" }, { "a", 1 }, - { "items", new List(){"one","two","three"}} + { "items", new List() { "one", "two", "three" } } }; var r = build.Result.Parse(source); if (r.IsOk) @@ -1313,7 +1321,9 @@ private static void Issue414() { var parserInstance = new Issue414Parser(); var builder = new ParserBuilder(); - var buildResult = builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, "block");//line-based, 1 statement per line. + var buildResult = + builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, + "block"); //line-based, 1 statement per line. var parser = buildResult.Result; string source = "funcA(funcC(B==2));"; Stopwatch chrono = new Stopwatch(); @@ -1322,12 +1332,14 @@ private static void Issue414() chrono.Stop(); Console.WriteLine($"{result.Result} : {chrono.ElapsedMilliseconds} ms"); } - + private static void Issue414Expr() { var parserInstance = new Issue414ExpressionParser(); var builder = new ParserBuilder(); - var buildResult = builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, "block");//line-based, 1 statement per line. + var buildResult = + builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, + "block"); //line-based, 1 statement per line. var parser = buildResult.Result; string source = "funcA(funcC(B==2));"; Stopwatch chrono = new Stopwatch(); @@ -1340,11 +1352,11 @@ private static void Issue414Expr() private static void BroadWindow() { - + var whileParser = new IndentedWhileParserGeneric(); var builder = new ParserBuilder(); var buildResult = builder.BuildParser(whileParser, ParserType.EBNF_LL_RECURSIVE_DESCENT, "program"); - + var parser = buildResult.Result; string program = @" a:=0 @@ -1355,6 +1367,7 @@ print a var result = parser.Parse(program); Console.WriteLine($"{result.IsOk}"); } + private static void Main(string[] args) { TestIssue596GettingStartedParser(); @@ -1440,6 +1453,7 @@ private static void TestXmlParser() r.Errors.ForEach(x => Console.WriteLine(x.Message)); return; } + Console.WriteLine(r.Result.SyntaxParser.Dump()); var pr = r.Result.Parse(@" @@ -1465,7 +1479,7 @@ inner inner content Console.WriteLine(pr.Result); } } - + private static void TestLexerModes() { // testManySugar(); @@ -1486,6 +1500,7 @@ private static void TestLexerModes() { Console.WriteLine(tokens.Error); } + Console.WriteLine("stop"); } @@ -1522,6 +1537,7 @@ private static void TestShortGeneric() Console.WriteLine(error.Message); } } + ; } @@ -1535,7 +1551,7 @@ private static void TestShortOperations() var parser = buildResult.Result; Assert.NotNull(parser); var result = parser.Parse("-1 +2 * (5 + 6) - 4 "); - Assert.Equal(-1+2*(5+6)-4, result.Result); + Assert.Equal(-1 + 2 * (5 + 6) - 4, result.Result); } private static void TestIssue239() @@ -1548,15 +1564,15 @@ private static void TestIssue332() { ParserBuilder Parser = new ParserBuilder(); Issue332Parser oparser = new Issue332Parser(); - var r = Parser.BuildParser(oparser,ParserType.EBNF_LL_RECURSIVE_DESCENT); + var r = Parser.BuildParser(oparser, ParserType.EBNF_LL_RECURSIVE_DESCENT); Check.That(r).Not.IsOk(); foreach (var error in r.Errors) { Console.WriteLine(error.Message); } - + } - + private static void Issue351() { var parserInstance = new ExpressionParser(); @@ -1574,7 +1590,7 @@ private static void Issue351() ; } - + private static void NodeNames() { var parserInstance = new ExpressionParser(); @@ -1603,7 +1619,7 @@ private static void IndentRefactoring() var l = LexerBuilder.BuildLexer(); if (l.IsOk) { - var source =@"if truc == 1 + var source = @"if truc == 1 un = 1 deux = 2 else @@ -1627,47 +1643,48 @@ private static void IndentRefactoring() } } } - + private static List> postProcess(List> tokens) { var mayLeft = new List() { - ExpressionToken.INT, ExpressionToken.DOUBLE, ExpressionToken.IDENTIFIER + ExpressionToken.INT, ExpressionToken.DOUBLE, ExpressionToken.IDENTIFIER }; - + var mayRight = new List() { ExpressionToken.INT, ExpressionToken.DOUBLE, ExpressionToken.LPAREN, ExpressionToken.IDENTIFIER }; - - Func mayOmmitLeft = (ExpressionToken tokenid) => mayLeft.Contains(tokenid); - - Func mayOmmitRight = (ExpressionToken tokenid) => mayRight.Contains(tokenid); - - + + Func mayOmmitLeft = (ExpressionToken tokenid) => mayLeft.Contains(tokenid); + + Func mayOmmitRight = (ExpressionToken tokenid) => mayRight.Contains(tokenid); + + List> newTokens = new List>(); for (int i = 0; i < tokens.Count; i++) { - if ( i >= 1 && - mayOmmitRight(tokens[i].TokenID) && mayOmmitLeft(tokens[i-1].TokenID)) + if (i >= 1 && + mayOmmitRight(tokens[i].TokenID) && mayOmmitLeft(tokens[i - 1].TokenID)) { newTokens.Add(new Token() { TokenID = ExpressionToken.TIMES }); } + newTokens.Add(tokens[i]); } return newTokens; } - - + + private static void TestLexerPostProcess() { var Parser = postProcessedLexerParser.PostProcessedLexerParserBuilder.buildPostProcessedLexerParser(); - + var r = Parser.Parse("2 * x"); if (r.IsError) { @@ -1678,11 +1695,12 @@ private static void TestLexerPostProcess() return; } + var res = r.Result.Evaluate(new ExpressionContext(new Dictionary() { { "x", 2 } })); - Console.WriteLine("2 * x = "+(res.HasValue ? res.Value.ToString() : "?")); - - + Console.WriteLine("2 * x = " + (res.HasValue ? res.Value.ToString() : "?")); + + r = Parser.Parse("2 x"); if (r.IsError) { @@ -1693,10 +1711,11 @@ private static void TestLexerPostProcess() return; } + res = r.Result.Evaluate(new ExpressionContext(new Dictionary() { { "x", 2 } })); - Console.WriteLine("2 x = "+(res.HasValue ? res.Value.ToString() : "?")); - + Console.WriteLine("2 x = " + (res.HasValue ? res.Value.ToString() : "?")); + r = Parser.Parse("2 ( x ) "); if (r.IsError) { @@ -1707,10 +1726,11 @@ private static void TestLexerPostProcess() return; } + res = r.Result.Evaluate(new ExpressionContext(new Dictionary() { { "x", 2 } })); - Console.WriteLine("2 (x) = "+(res.HasValue ? res.Value.ToString() : "?")); - + Console.WriteLine("2 (x) = " + (res.HasValue ? res.Value.ToString() : "?")); + r = Parser.Parse("x x "); if (r.IsError) { @@ -1721,16 +1741,17 @@ private static void TestLexerPostProcess() return; } + res = r.Result.Evaluate(new ExpressionContext(new Dictionary() { { "x", 2 } })); - Console.WriteLine("x x = "+(res.HasValue ? res.Value.ToString() : "?")); - + Console.WriteLine("x x = " + (res.HasValue ? res.Value.ToString() : "?")); + } - - private static void TestLexerPostProcessEBNF() + + private static void TestLexerPostProcessEBNF() { var Parser = postProcessedLexerParser.PostProcessedLexerParserBuilder.buildPostProcessedLexerParser(); - + var r = Parser.Parse("2 * x"); if (r.IsError) { @@ -1741,11 +1762,12 @@ private static void TestLexerPostProcessEBNF() return; } + var res = r.Result.Evaluate(new ExpressionContext(new Dictionary() { { "x", 2 } })); - Console.WriteLine("2 * x = "+(res.HasValue ? res.Value.ToString() : "?")); - - + Console.WriteLine("2 * x = " + (res.HasValue ? res.Value.ToString() : "?")); + + r = Parser.Parse("2 x"); if (r.IsError) { @@ -1756,10 +1778,11 @@ private static void TestLexerPostProcessEBNF() return; } + res = r.Result.Evaluate(new ExpressionContext(new Dictionary() { { "x", 2 } })); - Console.WriteLine("2 x = "+(res.HasValue ? res.Value.ToString() : "?")); - + Console.WriteLine("2 x = " + (res.HasValue ? res.Value.ToString() : "?")); + r = Parser.Parse("2 ( x ) "); if (r.IsError) { @@ -1770,10 +1793,11 @@ private static void TestLexerPostProcessEBNF() return; } + res = r.Result.Evaluate(new ExpressionContext(new Dictionary() { { "x", 2 } })); - Console.WriteLine("2 (x) = "+(res.HasValue ? res.Value.ToString() : "?")); - + Console.WriteLine("2 (x) = " + (res.HasValue ? res.Value.ToString() : "?")); + r = Parser.Parse("x x "); if (r.IsError) { @@ -1784,74 +1808,76 @@ private static void TestLexerPostProcessEBNF() return; } + res = r.Result.Evaluate(new ExpressionContext(new Dictionary() { { "x", 2 } })); - - Console.WriteLine("x x = "+(res.HasValue ? res.Value.ToString() : "?")); - - } - - private static void TestIssue487() - { - string source = "@g_capcalc >> EtnRef: @appcapcalc.bankcapconfigid"; - var lexBuild = LexerBuilder.BuildLexer(); - - Check.That(lexBuild).IsOk(); - - var lexer = lexBuild.Result; - var lexer487 = lexer as GenericLexer; - var gviz = lexer487.FSMBuilder.Fsm.ToGraphViz(); - var lexed = lexer.Tokenize(source); - if (lexed.IsOk) - { - foreach (var token in lexed.Tokens) - { - Console.WriteLine(token); - } - } - else - { - Console.Write(lexed.Error); - } - } - - private static void TestIssue495() - { - Parser parser; - - ParserBuilder builder = new ParserBuilder("en"); - var build = builder.BuildParser(new Issue495Parser(), ParserType.EBNF_LL_RECURSIVE_DESCENT, "program"); - Check.That(build).IsOk(); - parser = build.Result; - - Check.That(parser).IsNotNull(); - Check.That(parser.Lexer).IsNotNull(); - Check.That(parser.Lexer).IsInstanceOf>(); - var lexer = parser.Lexer as GenericLexer; - string source = "test = \"3 3\";"; - var tokenized = lexer.Tokenize(source); - Check.That(tokenized).IsOkLexing(); - var tokens = tokenized.Tokens.MainTokens(); - Check.That(tokens).CountIs(7); - var stringValue = tokens[3]; - Check.That(stringValue).IsNotNull(); - Check.That(stringValue.TokenID).IsEqualTo(Issue495Token.StringValue); - - - var parsed = parser.Parse(source); - Check.That(parsed).IsOkParsing(); - Check.That(parsed.Result).IsEqualTo("test=3 3"); - - } - - private static void TestFStrings() - { - IndentedWhileTests tests = new IndentedWhileTests(); - tests.TestFString(); - } - - - private static void TestIssue596GettingStartedParser() { + + Console.WriteLine("x x = " + (res.HasValue ? res.Value.ToString() : "?")); + + } + + private static void TestIssue487() + { + string source = "@g_capcalc >> EtnRef: @appcapcalc.bankcapconfigid"; + var lexBuild = LexerBuilder.BuildLexer(); + + Check.That(lexBuild).IsOk(); + + var lexer = lexBuild.Result; + var lexer487 = lexer as GenericLexer; + var gviz = lexer487.FSMBuilder.Fsm.ToGraphViz(); + var lexed = lexer.Tokenize(source); + if (lexed.IsOk) + { + foreach (var token in lexed.Tokens) + { + Console.WriteLine(token); + } + } + else + { + Console.Write(lexed.Error); + } + } + + private static void TestIssue495() + { + Parser parser; + + ParserBuilder builder = new ParserBuilder("en"); + var build = builder.BuildParser(new Issue495Parser(), ParserType.EBNF_LL_RECURSIVE_DESCENT, "program"); + Check.That(build).IsOk(); + parser = build.Result; + + Check.That(parser).IsNotNull(); + Check.That(parser.Lexer).IsNotNull(); + Check.That(parser.Lexer).IsInstanceOf>(); + var lexer = parser.Lexer as GenericLexer; + string source = "test = \"3 3\";"; + var tokenized = lexer.Tokenize(source); + Check.That(tokenized).IsOkLexing(); + var tokens = tokenized.Tokens.MainTokens(); + Check.That(tokens).CountIs(7); + var stringValue = tokens[3]; + Check.That(stringValue).IsNotNull(); + Check.That(stringValue.TokenID).IsEqualTo(Issue495Token.StringValue); + + + var parsed = parser.Parse(source); + Check.That(parsed).IsOkParsing(); + Check.That(parsed.Result).IsEqualTo("test=3 3"); + + } + + private static void TestFStrings() + { + IndentedWhileTests tests = new IndentedWhileTests(); + tests.TestFString(); + } + + + private static void TestIssue596GettingStartedParser() + { var parserInstance = new GettingStartedParser(); var builder = new ParserBuilder(); var buildResult = builder.BuildParser(parserInstance, ParserType.LL_RECURSIVE_DESCENT, "expression"); @@ -1864,72 +1890,91 @@ private static void TestIssue596GettingStartedParser() { result = parser.Parse(""); Assert.True(result.IsError); } - } - public enum TestGrammarToken - { - [Lexeme(GenericToken.SugarToken,",")] - COMMA = 1 - } - - - - public class ErroneousGrammar - { - [Production("clauses : clause (COMMA [D] clause)*")] - - public object test() - { - return null; - } - } - - public class RecursiveGrammar - { - [Production("first : second COMMA[d]")] - public object FirstRecurse(object o) + public static void TestDanglingElse() { - return o; - } - - [Production("second : third COMMA[d]")] - public object SecondRecurse(object o) - { - return o; + var parserInstance = new DanglingElseParserWithEbnfChoices(); + var builder = new ParserBuilder(); + var buildResult = builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, "program"); + buildResult.Result.Configuration.CaptureAmbiguities = true; + buildResult.Result.Configuration.AmbiguityStrategy = AmbiguityResolutionStrategy.All; + Assert.True(buildResult.IsOk); + var parser = buildResult.Result; + string source = @" +if x == 1 then +if y == 2 then +a := 3 +else +a := 4"; + var result = parser.Parse(source); + ; + } - - [Production("third : first COMMA[d]")] - public object ThirdRecurse(object o) + + public enum TestGrammarToken { - return o; + [Lexeme(GenericToken.SugarToken, ",")] COMMA = 1 } - } - - public class RecursiveGrammar2 - { - [Production("first : second* third COMMA[d]")] - public object FirstRecurse(List seconds, object third) - { - return third; - } - - [Production("first : second? third COMMA[d]")] - public object FirstRecurse2(ValueOption optSecond, object third) + + + + public class ErroneousGrammar { - return null; + [Production("clauses : clause (COMMA [D] clause)*")] + + public object test() + { + return null; + } } - - [Production("second : COMMA[d]")] - public object SecondRecurse() + + public class RecursiveGrammar { - return null; + [Production("first : second COMMA[d]")] + public object FirstRecurse(object o) + { + return o; + } + + [Production("second : third COMMA[d]")] + public object SecondRecurse(object o) + { + return o; + } + + [Production("third : first COMMA[d]")] + public object ThirdRecurse(object o) + { + return o; + } } - - [Production("third : first COMMA[d]")] - public object ThirdRecurse(object o) + + public class RecursiveGrammar2 { - return o; + [Production("first : second* third COMMA[d]")] + public object FirstRecurse(List seconds, object third) + { + return third; + } + + [Production("first : second? third COMMA[d]")] + public object FirstRecurse2(ValueOption optSecond, object third) + { + return null; + } + + [Production("second : COMMA[d]")] + public object SecondRecurse() + { + return null; + } + + [Production("third : first COMMA[d]")] + public object ThirdRecurse(object o) + { + return o; + } } + } - } \ No newline at end of file diff --git a/src/sly/parser/parser/llparser/bnf/RecursiveDescentSyntaxParser.NonTerminal.cs b/src/sly/parser/parser/llparser/bnf/RecursiveDescentSyntaxParser.NonTerminal.cs index 58130fb9..a30be977 100644 --- a/src/sly/parser/parser/llparser/bnf/RecursiveDescentSyntaxParser.NonTerminal.cs +++ b/src/sly/parser/parser/llparser/bnf/RecursiveDescentSyntaxParser.NonTerminal.cs @@ -66,7 +66,10 @@ public SyntaxParseResult ParseNonTerminal(Token[] tokens, string no if (Configuration.CaptureAmbiguities) { // filter only ok results - var okResults = rulesResults.Where(r => r.IsOk).ToList(); + var okResults = rulesResults + .SelectMany(r => r.AllResults ?? new List> { r }) + .Where(r => r.IsOk) + .ToList(); if (okResults.Any()) { @@ -130,7 +133,6 @@ public SyntaxParseResult ParseNonTerminal(Token[] tokens, string no foreach(var ko in maxKos) { result.AddErrors(ko.GetErrors().ToArray()); } - result.AllResults.Add(result); } } else diff --git a/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.EBNFRecursiveDescentSyntaxParser.Choice.cs b/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.EBNFRecursiveDescentSyntaxParser.Choice.cs index aeb23861..0991ef19 100644 --- a/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.EBNFRecursiveDescentSyntaxParser.Choice.cs +++ b/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.EBNFRecursiveDescentSyntaxParser.Choice.cs @@ -77,7 +77,8 @@ public SyntaxParseResult ParseChoice(Token[] tokens, ChoiceClause r.AlternativeRoots).ToList(), EndingPosition = maxLength, - IsError = false + IsError = false, + AllResults = okResults }; // save ambiguity @@ -101,6 +102,7 @@ public SyntaxParseResult ParseChoice(Token[] tokens, ChoiceClause leaf) diff --git a/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.Many.cs b/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.Many.cs index 108f6ab0..b7e8e3e3 100644 --- a/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.Many.cs +++ b/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.Many.cs @@ -19,6 +19,143 @@ public SyntaxParseResult ParseZeroOrMore(Token[] tokens, ZeroOrMore return parseResult; } + if (Configuration.CaptureAmbiguities) + { + var innerClauseAmbi = clause.Clause; + var paths = new List<(List> children, int position, bool hasByPassNodes)> + { + (new List>(), position, false) + }; + var innerErrorsAmbi = new List>(); + + bool isManyTokens = false; + bool isManyValues = false; + bool isManyGroups = false; + if (innerClauseAmbi is TerminalClause) + { + isManyTokens = true; + } + else if (innerClauseAmbi is NonTerminalClause nonTermClause) + { + isManyGroups = nonTermClause.IsGroup; + isManyValues = !nonTermClause.IsGroup; + } + else if (innerClauseAmbi is ChoiceClause choiceClause) + { + isManyTokens = choiceClause.IsTerminalChoice; + isManyValues = choiceClause.IsNonTerminalChoice; + } + + while (true) + { + var newPaths = new List<(List> children, int position, bool hasByPassNodes)>(); + foreach (var path in paths) + { + SyntaxParseResult innerResult = null; + switch (innerClauseAmbi) + { + case TerminalClause term: + innerResult = ParseTerminal(tokens, term, path.position, parsingContext); + break; + case NonTerminalClause nonTerm: + innerResult = ParseNonTerminal(tokens, nonTerm, path.position, parsingContext); + break; + case ChoiceClause choice: + innerResult = ParseChoice(tokens, choice, path.position, parsingContext); + break; + default: + throw new InvalidOperationException("unable to apply repeater to " + innerClauseAmbi.GetType().Name); + } + + if (innerResult != null && !innerResult.IsError && innerResult.EndingPosition > path.position) + { + var resultsToProcess = innerResult.AllResults ?? new List> { innerResult }; + foreach (var res in resultsToProcess) + { + var alternatives = res.AlternativeRoots; + if (alternatives == null || alternatives.Count == 0) + { + alternatives = new List> { res.Root }; + } + + foreach (var alt in alternatives) + { + var newChildren = new List>(path.children) { alt }; + var newHasByPassNodes = path.hasByPassNodes || res.HasByPassNodes; + newPaths.Add((newChildren, res.EndingPosition, newHasByPassNodes)); + } + + if (res.GetErrors() != null) + { + innerErrorsAmbi.AddRange(res.GetErrors()); + } + } + } + else if (innerResult != null) + { + innerErrorsAmbi.AddRange(innerResult.GetErrors()); + } + } + + if (newPaths.Count == 0) + { + break; + } + + paths = newPaths; + } + + var ambiguousResult = new SyntaxParseResult(); + var resultsByPosition = paths.GroupBy(p => p.position).ToList(); + var results = new List>(); + + foreach (var group in resultsByPosition) + { + var pos = group.Key; + var res = new SyntaxParseResult + { + EndingPosition = pos, + IsError = false, + AlternativeRoots = new List>(), + HasByPassNodes = group.Any(p => p.hasByPassNodes) + }; + + foreach (var path in group) + { + var manyNodeAlt = new ManySyntaxNode($"{innerClauseAmbi}*") + { + IsManyTokens = isManyTokens, + IsManyValues = isManyValues, + IsManyGroups = isManyGroups + }; + manyNodeAlt.Children.AddRange(path.children); + res.AlternativeRoots.Add(manyNodeAlt); + } + + res.IsEnded = pos >= tokens.Length || (pos < tokens.Length && tokens[pos].IsEOS); + results.Add(res); + } + + if (results.Count > 0) + { + var best = results.OrderByDescending(r => r.EndingPosition).First(); + ambiguousResult.AlternativeRoots = best.AlternativeRoots; + ambiguousResult.EndingPosition = best.EndingPosition; + ambiguousResult.IsEnded = best.IsEnded; + ambiguousResult.HasByPassNodes = best.HasByPassNodes; + ambiguousResult.AllResults = results; + } + else + { + ambiguousResult.IsError = true; + ambiguousResult.EndingPosition = position; + } + + ambiguousResult.AddErrors(innerErrorsAmbi); + parsingContext.Memoize(clause, position, ambiguousResult); + return ambiguousResult; + } + var result = new SyntaxParseResult(); var manyNode = new ManySyntaxNode($"{clause.Clause.ToString()}*"); var currentPosition = position; @@ -198,6 +335,170 @@ public SyntaxParseResult ParseOneOrMore(Token[] tokens, OneOrMoreCl return parseResult; } + if (Configuration.CaptureAmbiguities) + { + var innerClauseAmbi = clause.Clause; + var innerErrorsAmbi = new List>(); + var paths = new List<(List> children, int position, bool hasByPassNodes)>(); + + bool isManyTokens = false; + bool isManyValues = false; + bool isManyGroups = false; + if (innerClauseAmbi is TerminalClause) + { + isManyTokens = true; + } + else if (innerClauseAmbi is NonTerminalClause nonTermClause) + { + isManyGroups = nonTermClause.IsGroup; + isManyValues = !nonTermClause.IsGroup; + } + else if (innerClauseAmbi is ChoiceClause choiceClause) + { + isManyTokens = choiceClause.IsTerminalChoice; + isManyValues = choiceClause.IsNonTerminalChoice; + } + + SyntaxParseResult firstResult = null; + switch (innerClauseAmbi) + { + case TerminalClause terminalClause: + firstResult = ParseTerminal(tokens, terminalClause, position, parsingContext); + break; + case NonTerminalClause nonTerm: + firstResult = ParseNonTerminal(tokens, nonTerm, position, parsingContext); + break; + case ChoiceClause choice: + firstResult = ParseChoice(tokens, choice, position, parsingContext); + break; + default: + throw new InvalidOperationException("unable to apply repeater to " + innerClauseAmbi.GetType().Name); + } + + if (firstResult == null || firstResult.IsError) + { + var errorResult = new SyntaxParseResult + { + IsError = true, + EndingPosition = position + }; + if (firstResult != null) + { + errorResult.AddErrors(firstResult.GetErrors()); + } + parsingContext.Memoize(clause, position, errorResult); + return errorResult; + } + + var firstResults = firstResult.AllResults ?? new List> { firstResult }; + foreach (var res in firstResults) + { + if (res.GetErrors() != null) + { + innerErrorsAmbi.AddRange(res.GetErrors()); + } + + var alternatives = res.AlternativeRoots; + if (alternatives == null || alternatives.Count == 0) + { + alternatives = new List> { res.Root }; + } + + foreach (var alt in alternatives) + { + var baseChildren = new List> { alt }; + var more = new ZeroOrMoreClause(innerClauseAmbi); + var moreResult = ParseZeroOrMore(tokens, more, res.EndingPosition, parsingContext); + var moreResults = moreResult?.AllResults ?? new List> { moreResult }; + + foreach (var moreRes in moreResults) + { + if (moreRes == null || moreRes.IsError) + { + continue; + } + + var manyAlternatives = moreRes.AlternativeRoots; + if (manyAlternatives == null || manyAlternatives.Count == 0) + { + manyAlternatives = new List> { moreRes.Root }; + } + + foreach (var manyAlt in manyAlternatives) + { + var combinedChildren = new List>(baseChildren); + if (manyAlt is ManySyntaxNode manyNodeAlt) + { + combinedChildren.AddRange(manyNodeAlt.Children); + } + else + { + combinedChildren.Add(manyAlt); + } + + var combinedByPass = res.HasByPassNodes || moreRes.HasByPassNodes; + paths.Add((combinedChildren, moreRes.EndingPosition, combinedByPass)); + } + + if (moreRes.GetErrors() != null) + { + innerErrorsAmbi.AddRange(moreRes.GetErrors()); + } + } + } + } + + var ambiguousResult = new SyntaxParseResult(); + var resultsByPosition = paths.GroupBy(p => p.position).ToList(); + var results = new List>(); + + foreach (var group in resultsByPosition) + { + var pos = group.Key; + var res = new SyntaxParseResult + { + EndingPosition = pos, + IsError = false, + AlternativeRoots = new List>(), + HasByPassNodes = group.Any(p => p.hasByPassNodes) + }; + + foreach (var path in group) + { + var manyNodeAlt = new ManySyntaxNode($"{innerClauseAmbi}+") + { + IsManyTokens = isManyTokens, + IsManyValues = isManyValues, + IsManyGroups = isManyGroups + }; + manyNodeAlt.Children.AddRange(path.children); + res.AlternativeRoots.Add(manyNodeAlt); + } + + res.IsEnded = pos >= tokens.Length || (pos < tokens.Length && tokens[pos].IsEOS); + results.Add(res); + } + + if (results.Count > 0) + { + var best = results.OrderByDescending(r => r.EndingPosition).First(); + ambiguousResult.AlternativeRoots = best.AlternativeRoots; + ambiguousResult.EndingPosition = best.EndingPosition; + ambiguousResult.IsEnded = best.IsEnded; + ambiguousResult.HasByPassNodes = best.HasByPassNodes; + ambiguousResult.AllResults = results; + } + else + { + ambiguousResult.IsError = true; + ambiguousResult.EndingPosition = position; + } + + ambiguousResult.AddErrors(innerErrorsAmbi); + parsingContext.Memoize(clause, position, ambiguousResult); + return ambiguousResult; + } + var result = new SyntaxParseResult(); var manyNode = new ManySyntaxNode($"{clause.Clause.ToString()}+"); var currentPosition = position; diff --git a/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.Option.cs b/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.Option.cs index 20c2656b..7b5b4388 100644 --- a/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.Option.cs +++ b/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.Option.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using sly.lexer; using sly.parser.syntax.grammar; using sly.parser.syntax.tree; @@ -18,6 +19,104 @@ public SyntaxParseResult ParseOption(Token[] tokens, OptionClause innerResultAmbi = null; + + switch (innerClauseAmbi) + { + case TerminalClause term: + innerResultAmbi = ParseTerminal(tokens, term, position, parsingContext); + break; + case NonTerminalClause nonTerm: + innerResultAmbi = ParseNonTerminal(tokens, nonTerm, position, parsingContext); + break; + case ChoiceClause choice: + innerResultAmbi = ParseChoice(tokens, choice, position, parsingContext); + break; + default: + throw new InvalidOperationException("unable to apply repeater to " + innerClauseAmbi.GetType().Name); + } + + var resultAmbi = new SyntaxParseResult + { + IsError = false, + EndingPosition = position, + AlternativeRoots = new List>(), + AllResults = new List>() + }; + + var emptyNode = new OptionSyntaxNode(rule.NonTerminalName, new List>(), + rule.GetVisitorMethod()) + { + IsGroupOption = clause.IsGroupOption + }; + + var emptyResult = new SyntaxParseResult + { + IsError = false, + Root = emptyNode, + EndingPosition = position, + IsEnded = position >= tokens.Length || (position < tokens.Length && tokens[position].IsEOS) + }; + resultAmbi.AllResults.Add(emptyResult); + + if (innerResultAmbi != null && !innerResultAmbi.IsError) + { + var innerResults = innerResultAmbi.AllResults ?? new List> { innerResultAmbi }; + foreach (var inner in innerResults) + { + var alternatives = inner.AlternativeRoots; + if (alternatives == null || alternatives.Count == 0) + { + alternatives = new List> { inner.Root }; + } + + foreach (var alt in alternatives) + { + var optionNode = new OptionSyntaxNode(rule.NonTerminalName, + new List> { alt }, rule.GetVisitorMethod()) + { + IsGroupOption = clause.IsGroupOption + }; + + var optionResult = new SyntaxParseResult + { + IsError = false, + Root = optionNode, + EndingPosition = inner.EndingPosition, + IsEnded = inner.IsEnded, + HasByPassNodes = inner.HasByPassNodes + }; + resultAmbi.AllResults.Add(optionResult); + } + } + + if (innerResultAmbi.GetErrors() != null) + { + resultAmbi.AddErrors(innerResultAmbi.GetErrors()); + } + } + else if (innerResultAmbi != null) + { + resultAmbi.AddErrors(innerResultAmbi.GetErrors()); + } + + var best = resultAmbi.AllResults.OrderBy(r => r.EndingPosition).Reverse().FirstOrDefault(); + if (best != null) + { + resultAmbi.Root = best.Root; + resultAmbi.EndingPosition = best.EndingPosition; + resultAmbi.IsEnded = best.IsEnded; + resultAmbi.HasByPassNodes = best.HasByPassNodes; + resultAmbi.AlternativeRoots = best.AlternativeRoots; + } + + parsingContext.Memoize(clause, position, resultAmbi); + return resultAmbi; + } + var result = new SyntaxParseResult(); var currentPosition = position; var innerClause = clause.Clause; diff --git a/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.cs b/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.cs index 823a7c49..f1c1ea0c 100644 --- a/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.cs +++ b/src/sly/parser/parser/llparser/ebnf/EBNFRecursiveDescentSyntaxParser.cs @@ -26,6 +26,11 @@ public override SyntaxParseResult Parse(Token[] tokens, Rule Parse(Token[] tokens, Rule ParseWithAmbiguities(Token[] tokens, Rule rule, int position, + string nonTerminalName, SyntaxParsingContext parsingContext) + { + var furthestPosition = position; + var errors = new List>(); + var isError = false; + var ambiguities = new List>(); + var paths = new List<(List> children, int position, bool hasByPassNodes)> + { + (new List>(), position, false) + }; + + if (rule.Match(tokens, position, Configuration) && rule.Clauses != null && rule.Clauses.Count > 0) + { + foreach (var clause in rule.Clauses) + { + var newPaths = new List<(List> children, int position, bool hasByPassNodes)>(); + foreach (var path in paths) + { + SyntaxParseResult clauseRes = null; + switch (clause) + { + case TerminalClause termClause: + clauseRes = ParseTerminal(tokens, termClause, path.position, parsingContext); + break; + case NonTerminalClause nonTerminalClause: + clauseRes = ParseNonTerminal(tokens, nonTerminalClause, path.position, parsingContext); + break; + case OneOrMoreClause oneOrMore: + clauseRes = ParseOneOrMore(tokens, oneOrMore, path.position, parsingContext); + break; + case ZeroOrMoreClause zeroOrMore: + clauseRes = ParseZeroOrMore(tokens, zeroOrMore, path.position, parsingContext); + break; + case RepeatClause repeat: + clauseRes = ParseRepeat(tokens, repeat, path.position, parsingContext); + break; + case OptionClause option: + clauseRes = ParseOption(tokens, option, rule, path.position, parsingContext); + break; + case ChoiceClause choice: + clauseRes = ParseChoice(tokens, choice, path.position, parsingContext); + break; + } + + if (clauseRes != null && !clauseRes.IsError) + { + if (clauseRes.GetErrors() != null && clauseRes.GetErrors().Count > 0) + { + errors.AddRange(clauseRes.GetErrors()); + } + + var resultsToProcess = new List>(); + if (clauseRes.AllResults != null && clauseRes.AllResults.Count > 0) + { + resultsToProcess.AddRange(clauseRes.AllResults); + } + else + { + resultsToProcess.Add(clauseRes); + } + + foreach (var res in resultsToProcess) + { + var alternatives = res.AlternativeRoots; + if (alternatives == null || alternatives.Count == 0) + { + alternatives = new List> { res.Root }; + } + + foreach (var alt in alternatives) + { + var newChildren = new List>(path.children) { alt }; + var newHasByPassNodes = path.hasByPassNodes || res.HasByPassNodes; + newPaths.Add((newChildren, res.EndingPosition, newHasByPassNodes)); + } + + if (res.Ambiguities != null) + { + ambiguities.AddRange(res.Ambiguities); + } + } + } + else if (clauseRes != null) + { + errors.AddRange(clauseRes.GetErrors()); + } + } + + if (newPaths.Any()) + { + furthestPosition = Math.Max(furthestPosition, newPaths.Max(p => p.position)); + } + + paths = newPaths; + if (paths.Count == 0) + { + isError = true; + break; + } + } + } + else + { + isError = true; + } + + var result = new SyntaxParseResult(); + result.IsError = isError; + result.EndingPosition = furthestPosition; + result.AddErrors(errors); + + if (!isError) + { + var resultsByPosition = paths.GroupBy(p => p.position).ToList(); + var results = new List>(); + + foreach (var group in resultsByPosition) + { + var pos = group.Key; + var res = new SyntaxParseResult + { + EndingPosition = pos, + IsError = false, + Ambiguities = ambiguities, + AlternativeRoots = new List>(), + HasByPassNodes = group.Any(p => p.hasByPassNodes) + }; + + foreach (var path in group) + { + var namedChildren = new List>(path.children); + if (rule.SubNodeNames != null && rule.SubNodeNames.Length > 0) + { + for (int i = 0; i < Math.Min(rule.SubNodeNames.Length, namedChildren.Count); i++) + { + var subNodeName = rule.SubNodeNames[i]; + if (subNodeName != null) + { + namedChildren[i].ForceName(subNodeName); + } + } + } + + SyntaxNode node; + if (rule.IsSubRule) + { + node = new GroupSyntaxNode(nonTerminalName, namedChildren); + node = ManageExpressionRules(rule, node); + } + else + { + node = new SyntaxNode(nonTerminalName, namedChildren); + node.ForcedName = rule.ForcedName; + node.Name = string.IsNullOrEmpty(rule.NodeName) ? nonTerminalName : rule.NodeName; + node.ExpressionAffix = rule.ExpressionAffix; + node = ManageExpressionRules(rule, node); + } + + if (node.IsByPassNode) + { + res.AlternativeRoots.Add(namedChildren[0]); + } + else + { + res.AlternativeRoots.Add(node); + } + + bool isEnded; + if (pos >= tokens.Length) + { + isEnded = true; + } + else if (rule.IsSubRule) + { + isEnded = pos >= tokens.Length - 1 + || (pos == tokens.Length - 2 && tokens[tokens.Length - 1].IsEOS); + } + else + { + var hasNext = pos + 1 < tokens.Length; + isEnded = tokens[pos].IsEOS || (node.IsEpsilon && hasNext && tokens[pos + 1].IsEOS); + } + + res.IsEnded = res.IsEnded || isEnded; + } + + results.Add(res); + } + + if (results.Count > 0) + { + var best = results.OrderByDescending(r => r.EndingPosition).First(); + result.AlternativeRoots = best.AlternativeRoots; + result.EndingPosition = best.EndingPosition; + result.IsEnded = best.IsEnded; + result.HasByPassNodes = best.HasByPassNodes; + result.AllResults = results; + result.Ambiguities = ambiguities; + } + else + { + result.IsError = true; + } + } + + return result; + } + #endregion } } \ No newline at end of file diff --git a/tests/ParserTests/ambiguous/AmbiguousGrammarParser.cs b/tests/ParserTests/ambiguous/AmbiguousGrammarParser.cs new file mode 100644 index 00000000..5bac87e0 --- /dev/null +++ b/tests/ParserTests/ambiguous/AmbiguousGrammarParser.cs @@ -0,0 +1,28 @@ +using sly.lexer; +using sly.parser.generator; + +namespace ParserTests.ambiguous; + +public class AmbiguousGrammarParser +{ + // S ::= 'a' S 'a' + [Production("S : A S A")] + public string Rule1(Token a1, string s, Token a2) + { + return $"aSa({s})"; + } + + // S ::= 'a' 'a' S + [Production("S : A A S")] + public string Rule2(Token a1, Token a2, string s) + { + return $"aaS({s})"; + } + + // S ::= 'a' + [Production("S : A")] + public string Rule3(Token a) + { + return "a"; + } +} \ No newline at end of file diff --git a/tests/ParserTests/AmbiguousGrammarTests.cs b/tests/ParserTests/ambiguous/AmbiguousGrammarTests.cs similarity index 67% rename from tests/ParserTests/AmbiguousGrammarTests.cs rename to tests/ParserTests/ambiguous/AmbiguousGrammarTests.cs index dde5e7b6..70b7d2e6 100644 --- a/tests/ParserTests/AmbiguousGrammarTests.cs +++ b/tests/ParserTests/ambiguous/AmbiguousGrammarTests.cs @@ -1,70 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Linq; using NFluent; using sly.buildresult; -using sly.lexer; using sly.parser; using sly.parser.generator; -using sly.parser.parser; -using sly.parser.syntax.tree; using Xunit; -namespace ParserTests +namespace ParserTests.ambiguous { - // Grammaire ambiguë de référence : S ::= 'a' S 'a' | 'a' 'a' S | 'a' - public enum AmbiguousToken - { - [Lexeme("a")] A, - EOF - } - - public class AmbiguousGrammarParser - { - // S ::= 'a' S 'a' - [Production("S : A S A")] - public string Rule1(Token a1, string s, Token a2) - { - return $"aSa({s})"; - } - - // S ::= 'a' 'a' S - [Production("S : A A S")] - public string Rule2(Token a1, Token a2, string s) - { - return $"aaS({s})"; - } - - // S ::= 'a' - [Production("S : A")] - public string Rule3(Token a) - { - return "a"; - } - } - - public class OptionAmbiguousParser - { - - [Production("S : [a|b]")] - public string RootOption(string option) - { - return $"S({option})"; - } - - - [Production("a : A")] - public string RuleA(Token a) - { - return "a(A)"; - } - - [Production("b : A")] - public string RuleB(Token a) - { - return "b(A)"; - } - } public class AmbiguousGrammarTests { @@ -74,13 +16,13 @@ private BuildResult> BuildParser(bool captureAmbi var parserInstance = new AmbiguousGrammarParser(); var builder = new ParserBuilder(); var buildResult = builder.BuildParser(parserInstance, ParserType.LL_RECURSIVE_DESCENT, "S"); - + if (buildResult.IsOk && captureAmbiguities) { buildResult.Result.Configuration.CaptureAmbiguities = true; buildResult.Result.Configuration.AmbiguityStrategy = strategy; } - + return buildResult; } @@ -97,10 +39,10 @@ public void TestNonAmbiguousInput_SingleA() { var buildResult = BuildParser(); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; var parseResult = parser.Parse("a"); - + Check.That(parseResult).IsOkParsing(); Check.That(parseResult.Result).IsEqualTo("a"); Check.That(parseResult.IsAmbiguous).IsFalse(); @@ -112,10 +54,10 @@ public void TestAmbiguousInput_AAA_WithoutCapture() // Sans capture d'ambiguïté, le parseur retourne la première dérivation var buildResult = BuildParser(captureAmbiguities: false); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; var parseResult = parser.Parse("aaa"); - + Check.That(parseResult.IsOk).IsTrue(); Check.That(parseResult.IsAmbiguous).IsFalse(); // Première dérivation : aSa(a) ou aaS(a) selon l'ordre des règles @@ -129,10 +71,10 @@ public void TestAmbiguousInput_AAA_WithCapture() // 2. S -> a a S -> a a (a) [Rule2 avec S=a] var buildResult = BuildParser(captureAmbiguities: true); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; var parseResult = parser.Parse("aaa"); - + Check.That(parseResult.IsOk).IsTrue(); Check.That(parseResult.IsAmbiguous).IsTrue(); Check.That(parseResult.Forest).IsNotNull(); @@ -147,10 +89,10 @@ public void TestAmbiguousInput_AAAAA_WithCapture() // "aaaaa" est encore plus ambigu - plusieurs dérivations possibles var buildResult = BuildParser(captureAmbiguities: true); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; var parseResult = parser.Parse("aaaaa"); - + Check.That(parseResult.IsOk).IsTrue(); Check.That(parseResult.IsAmbiguous).IsTrue(); Check.That(parseResult.Forest.Count).IsEqualTo(4); @@ -162,12 +104,12 @@ public void TestAmbiguousInput_AAAAA_WithCapture() public void TestAmbiguityException_Strategy() { // Avec la stratégie ThrowException, une exception doit être levée - var buildResult = BuildParser(captureAmbiguities: true, + var buildResult = BuildParser(captureAmbiguities: true, strategy: AmbiguityResolutionStrategy.ThrowException); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; - + Check.ThatCode(() => parser.Parse("aaa")) .Throws>(); } @@ -175,12 +117,12 @@ public void TestAmbiguityException_Strategy() [Fact] public void TestAmbiguityException_ContainsForest() { - var buildResult = BuildParser(captureAmbiguities: true, + var buildResult = BuildParser(captureAmbiguities: true, strategy: AmbiguityResolutionStrategy.ThrowException); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; - + try { var parseResult = parser.Parse("aaa"); @@ -197,19 +139,19 @@ public void TestAmbiguityException_ContainsForest() [Fact] public void TestVisitAllTrees() { - var buildResult = BuildParser(captureAmbiguities: true, + var buildResult = BuildParser(captureAmbiguities: true, strategy: AmbiguityResolutionStrategy.All); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; var parseResult = parser.Parse("aaa"); - + Check.That(parseResult.IsOk).IsTrue(); Check.That(parseResult.IsAmbiguous).IsTrue(); - + // Visiter tous les arbres var allResults = parseResult.VisitAllTrees(parser.Visitor); - + Check.That(allResults).IsNotNull(); Check.That(allResults).CountIs(2); Check.That(allResults).ContainsExactly("aSa(a)", "aaS(a)"); @@ -218,24 +160,24 @@ public void TestVisitAllTrees() [Fact] public void TestSelectSpecificTree() { - var buildResult = BuildParser(captureAmbiguities: true, + var buildResult = BuildParser(captureAmbiguities: true, strategy: AmbiguityResolutionStrategy.All); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; var parseResult = parser.Parse("aaa"); - + Check.That(parseResult.IsOk).IsTrue(); Check.That(parseResult.IsAmbiguous).IsTrue(); - + // Sélectionner le premier arbre var result0 = parseResult.SelectTree(0, parser.Visitor); Check.That(result0).IsNotNull(); - + // Sélectionner le deuxième arbre var result1 = parseResult.SelectTree(1, parser.Visitor); Check.That(result1).IsNotNull(); - + // Les deux résultats doivent être différents Check.That(result0).Not.IsEqualTo(result1); } @@ -243,22 +185,22 @@ public void TestSelectSpecificTree() [Fact] public void TestResolveAmbiguity_CustomSelector() { - var buildResult = BuildParser(captureAmbiguities: true, + var buildResult = BuildParser(captureAmbiguities: true, strategy: AmbiguityResolutionStrategy.All); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; var parseResult = parser.Parse("aaa"); - + Check.That(parseResult.IsOk).IsTrue(); Check.That(parseResult.IsAmbiguous).IsTrue(); - + // Résoudre en choisissant le dernier arbre var result = parseResult.ResolveAmbiguity( trees => trees.Last(), parser.Visitor ); - + Check.That(result).IsNotNull(); Check.That(result).IsEqualTo("aaS(a)"); } @@ -268,14 +210,14 @@ public void TestAmbiguityInfo() { var buildResult = BuildParser(captureAmbiguities: true); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; var parseResult = parser.Parse("aaa"); - + Check.That(parseResult.IsOk).IsTrue(); Check.That(parseResult.Forest.Ambiguities).IsNotNull(); Check.That(parseResult.Forest.Ambiguities).Not.IsEmpty(); - + var ambiguity = parseResult.Forest.Ambiguities.First(); Check.That(ambiguity.NonTerminalName).IsEqualTo("S"); Check.That(ambiguity.AlternativeCount).IsEqualTo(2); @@ -288,14 +230,14 @@ public void TestBackwardCompatibility_DefaultBehavior() // Sans activer CaptureAmbiguities, le comportement doit être identique à avant var buildResult = BuildParser(captureAmbiguities: false); Check.That(buildResult).IsOk(); - + var parser = buildResult.Result; - + // Test avec entrée non ambiguë var result1 = parser.Parse("a"); Check.That(result1.IsOk).IsTrue(); Check.That(result1.IsAmbiguous).IsFalse(); - + // Test avec entrée ambiguë (mais pas de capture) var result2 = parser.Parse("aaa"); Check.That(result2.IsOk).IsTrue(); @@ -326,5 +268,103 @@ public void TestEbnfChoiceAmbiguity_WithCapture() Check.That(allResults).CountIs(2); Check.That(allResults).Contains("S(a(A))", "S(b(A))"); } + + [Fact] + public void TestDanglingElseAmbiguityWithEbnfChoices() + { + var parserInstance = new DanglingElseParserWithEbnfChoices(); + var builder = new ParserBuilder(); + var buildResult = builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, "programZero"); + Check.That(buildResult).IsOk(); + + string source = @" +if x == 1 then +if y == 2 then +a := 3 +else +a := 4"; + buildResult.Result.Configuration.CaptureAmbiguities = true; + buildResult.Result.Configuration.AmbiguityStrategy = AmbiguityResolutionStrategy.All; + Check.That(buildResult.Result).IsNotNull(); + var parser = buildResult.Result; + + var parseResult = parser.Parse(source, "programZero"); + Check.That(parseResult).IsOkParsing(checkIfResultisNull:false); + Check.That(parseResult.IsAmbiguous).IsTrue(); + Check.That(parseResult.Forest).IsNotNull(); + Check.That(parseResult.Forest.Count).IsEqualTo(2); + var allResults = parseResult.VisitAllTrees(parser.Visitor); + Check.That(allResults).CountIs(2); + Check.That(allResults).Contains( "if(x==1,if(y==2,a:=3),a:=4)", "if(x==1,if(y==2,a:=3,a:=4))" ); + + parseResult = parser.Parse(source,"programOne"); + Check.That(parseResult).IsOkParsing(checkIfResultisNull:false); + Check.That(parseResult.IsAmbiguous).IsTrue(); + Check.That(parseResult.Forest).IsNotNull(); + Check.That(parseResult.Forest.Count).IsEqualTo(2); + allResults = parseResult.VisitAllTrees(parser.Visitor); + Check.That(allResults).CountIs(2); + Check.That(allResults).Contains( "if(x==1,if(y==2,a:=3),a:=4)", "if(x==1,if(y==2,a:=3,a:=4))" ); + + parseResult = parser.Parse(source,"programOneOrTwo"); + Check.That(parseResult).IsOkParsing(checkIfResultisNull:false); + Check.That(parseResult.IsAmbiguous).IsTrue(); + Check.That(parseResult.Forest).IsNotNull(); + Check.That(parseResult.Forest.Count).IsEqualTo(2); + allResults = parseResult.VisitAllTrees(parser.Visitor); + Check.That(allResults).CountIs(2); + Check.That(allResults).Contains( "if(x==1,if(y==2,a:=3),a:=4)", "if(x==1,if(y==2,a:=3,a:=4))" ); + + + } + + [Fact] + public void TestDanglingElseAmbiguityWithRules() + { + var parserInstance = new DanglingElseParserWithRules(); + var builder = new ParserBuilder(); + var buildResult = builder.BuildParser(parserInstance, ParserType.EBNF_LL_RECURSIVE_DESCENT, "programZero"); + Check.That(buildResult).IsOk(); + + string source = @" +if x == 1 then +if y == 2 then +a := 3 +else +a := 4"; + buildResult.Result.Configuration.CaptureAmbiguities = true; + buildResult.Result.Configuration.AmbiguityStrategy = AmbiguityResolutionStrategy.All; + Check.That(buildResult.Result).IsNotNull(); + var parser = buildResult.Result; + + var parseResult = parser.Parse(source, "programZero"); + Check.That(parseResult).IsOkParsing(checkIfResultisNull:false); + Check.That(parseResult.IsAmbiguous).IsTrue(); + Check.That(parseResult.Forest).IsNotNull(); + Check.That(parseResult.Forest.Count).IsEqualTo(2); + var allResults = parseResult.VisitAllTrees(parser.Visitor); + Check.That(allResults).CountIs(2); + Check.That(allResults).Contains( "if(x==1,if(y==2,a:=3),a:=4)", "if(x==1,if(y==2,a:=3,a:=4))" ); + + parseResult = parser.Parse(source,"programOne"); + Check.That(parseResult).IsOkParsing(checkIfResultisNull:false); + Check.That(parseResult.IsAmbiguous).IsTrue(); + Check.That(parseResult.Forest).IsNotNull(); + Check.That(parseResult.Forest.Count).IsEqualTo(2); + allResults = parseResult.VisitAllTrees(parser.Visitor); + Check.That(allResults).CountIs(2); + Check.That(allResults).Contains( "if(x==1,if(y==2,a:=3),a:=4)", "if(x==1,if(y==2,a:=3,a:=4))" ); + + parseResult = parser.Parse(source,"programOneOrTwo"); + Check.That(parseResult).IsOkParsing(checkIfResultisNull:false); + Check.That(parseResult.IsAmbiguous).IsTrue(); + Check.That(parseResult.Forest).IsNotNull(); + Check.That(parseResult.Forest.Count).IsEqualTo(2); + allResults = parseResult.VisitAllTrees(parser.Visitor); + Check.That(allResults).CountIs(2); + Check.That(allResults).Contains( "if(x==1,if(y==2,a:=3),a:=4)", "if(x==1,if(y==2,a:=3,a:=4))" ); + + + } } } diff --git a/tests/ParserTests/ambiguous/AmbiguousToken.cs b/tests/ParserTests/ambiguous/AmbiguousToken.cs new file mode 100644 index 00000000..ce99b43d --- /dev/null +++ b/tests/ParserTests/ambiguous/AmbiguousToken.cs @@ -0,0 +1,9 @@ +using sly.lexer; + +namespace ParserTests.ambiguous; + +public enum AmbiguousToken +{ + [Lexeme("a")] A, + EOF +} \ No newline at end of file diff --git a/tests/ParserTests/ambiguous/DanglingElseParserWithEbnfChoices.cs b/tests/ParserTests/ambiguous/DanglingElseParserWithEbnfChoices.cs new file mode 100644 index 00000000..a2d312bc --- /dev/null +++ b/tests/ParserTests/ambiguous/DanglingElseParserWithEbnfChoices.cs @@ -0,0 +1,58 @@ +using sly.lexer; +using sly.parser.generator; +using System.Collections.Generic; +using sly.parser.parser; + +namespace ParserTests.ambiguous +{ + [ParserRoot("programZero")] + public class DanglingElseParserWithEbnfChoices + { + [Production("programOne : statement +")] + public string program_statement_1(List statements) + { + return string.Join(";", statements); + } + + [Production("programZero : statement +")] + public string program_statement_0(List statements) + { + return string.Join(";", statements); + } + + [Production("programOneOrTwo : statement statement?")] + public string program_statement_1_2(string statement, ValueOption statement2) + { + return statement2.Match( + (x) => $"{statement},{x}", + () => statement); + } + + [Production("statement : [ if_then | if_then_else | assign ]")] + public string statement_if_assign_(string statement) => statement; + + [Production("if_then : IF[d] cond THEN[d] statement")] + public string if_then(string cond, string thenStatement) + { + return $"if({cond},{thenStatement})"; + } + + [Production("if_then_else : IF[d] cond THEN[d] statement ELSE[d] statement")] + public string if_then_else(string cond, string thenStatement, string elseStatement) + { + return $"if({cond},{thenStatement},{elseStatement})"; + } + + [Production("assign : ID ASSIGN[d] INT")] + public string assign_ID_ASSIGN_INT(Token id, Token value) + { + return $"{id.Value}:={value.Value}"; + } + + [Production("cond : ID EQUALS[d] INT")] + public string cond_ID_EQUALS_INT(Token id, Token value) + { + return $"{id.Value}=={value.Value}"; + } + } +} \ No newline at end of file diff --git a/tests/ParserTests/ambiguous/DanglingElseParserWithRules.cs b/tests/ParserTests/ambiguous/DanglingElseParserWithRules.cs new file mode 100644 index 00000000..c422c43d --- /dev/null +++ b/tests/ParserTests/ambiguous/DanglingElseParserWithRules.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using sly.lexer; +using sly.parser.generator; +using sly.parser.parser; + +namespace ParserTests.ambiguous; + +[ParserRoot("programZero")] +public class DanglingElseParserWithRules +{ + [Production("programOne : statement +")] + public string program_statement_1(List statements) + { + return string.Join(";", statements); + } + + [Production("programZero : statement +")] + public string program_statement_0(List statements) + { + return string.Join(";", statements); + } + + [Production("programOneOrTwo : statement statement?")] + public string program_statement_1_2(string statement, ValueOption statement2) + { + return statement2.Match( + (x) => $"{statement},{x}", + () => statement); + } + + [Production("statement : if_then")] + [Production("statement : if_then_else")] + [Production("statement : assign")] + public string statement_if_assign_(string statement) => statement; + + [Production("if_then : IF[d] cond THEN[d] statement")] + public string if_then(string cond, string thenStatement) + { + return $"if({cond},{thenStatement})"; + } + + [Production("if_then_else : IF[d] cond THEN[d] statement ELSE[d] statement")] + public string if_then_else(string cond, string thenStatement, string elseStatement) + { + return $"if({cond},{thenStatement},{elseStatement})"; + } + + [Production("assign : ID ASSIGN[d] INT")] + public string assign_ID_ASSIGN_INT(Token id, Token value) + { + return $"{id.Value}:={value.Value}"; + } + + [Production("cond : ID EQUALS[d] INT")] + public string cond_ID_EQUALS_INT(Token id, Token value) + { + return $"{id.Value}=={value.Value}"; + } +} \ No newline at end of file diff --git a/tests/ParserTests/ambiguous/DanglingElseToken.cs b/tests/ParserTests/ambiguous/DanglingElseToken.cs new file mode 100644 index 00000000..332c715b --- /dev/null +++ b/tests/ParserTests/ambiguous/DanglingElseToken.cs @@ -0,0 +1,24 @@ +using sly.lexer; +using sly.lexer.fsm; +using sly.i18n; + +namespace ParserTests.ambiguous +{ + public enum DanglingElseToken + { + [AlphaId] + ID, + [Int] + INT, + [Keyword("if")] + IF, + [Keyword("else")] + ELSE, + [Keyword("then")] + THEN, + [Sugar("==")] + EQUALS, + [Sugar(":=")] + ASSIGN, + } +} \ No newline at end of file diff --git a/tests/ParserTests/ambiguous/OptionAmbiguousParser.cs b/tests/ParserTests/ambiguous/OptionAmbiguousParser.cs new file mode 100644 index 00000000..00c5fa4a --- /dev/null +++ b/tests/ParserTests/ambiguous/OptionAmbiguousParser.cs @@ -0,0 +1,27 @@ +using sly.lexer; +using sly.parser.generator; + +namespace ParserTests.ambiguous; + +public class OptionAmbiguousParser +{ + + [Production("S : [a|b]")] + public string RootOption(string option) + { + return $"S({option})"; + } + + + [Production("a : A")] + public string RuleA(Token a) + { + return "a(A)"; + } + + [Production("b : A")] + public string RuleB(Token a) + { + return "b(A)"; + } +} \ No newline at end of file