diff --git a/ParserTests/Issue251/LexerAndParser.cs b/ParserTests/Issue251/LexerAndParser.cs new file mode 100644 index 00000000..5643efd1 --- /dev/null +++ b/ParserTests/Issue251/LexerAndParser.cs @@ -0,0 +1,140 @@ + + using System; +using System.Data; +using System.Text; +using sly.lexer; +using sly.parser.generator; + namespace ParserTests.Issue251 + { + + public class Issue251Parser + { + public enum Issue251Tokens + { + [Lexeme("[0-9][0-9]*")] DIGITS, + + [Lexeme("\"[^\"\\\\]*(\\\\.[^\"\\\\])*\"")] + LITERAL_STRING, // "[^"\\]*(\\.[^"\\]*)*" , which allowing escaped quotes in string. + [Lexeme("[a-zA-Z_][0-9a-zA-Z_]*")] IDENTIFIER, + [Lexeme("\\+")] OP_PLUS, + [Lexeme("-")] OP_MINUS, + [Lexeme("\\*")] OP_MULTIPLY, + [Lexeme("/")] OP_DIVIDE, + [Lexeme("%")] OP_MODULE, + [Lexeme("(")] PARENTHESIS_L, + [Lexeme(")")] PARENTHESIS_R, + + [Lexeme("[ \\t][ \\t]*", isSkippable: true)] + IGNORED, // simply discarded in lexer + } + + public class ExprClosure + { + public enum Types + { + INT, + STRING + } + + public Types ResultType; + + // C# doesn't have std::any or std::variant. Fuck it. + public int valueInt; + public string valueString; + } + + private void SyntaxCheck(bool what, string msg = "") + { + if (!what) + throw new SyntaxErrorException("Syntax error: " + msg); + } + + /* + * RnLang is a script language, so the input expressions + * are directly evaluated in parser. + * + * int ::= DIGITS + * string ::= LITERAL_STRING + * expr ::= int | string + * expr ::= expr OP_PLUS expr + * expr ::= expr OP_MINUS expr + * expr ::= expr OP_MULTIPLY expr + * expr ::= expr OP_DIVIDE expr + * expr ::= expr OP_MODULE expr + * expr ::= IDENTIFIER PARENTHESIS_L expr PARENTHESIS_R + * expr ::= PARENTHESIS_L expr PARENTHESIS_R + */ + [Production("int: DIGITS")] + public int exprIntL(Token expr) => expr.IntValue; + + [Production("string: LITERAL_STRING")] + public string exprStringL(Token expr) => + expr.StringWithoutQuotes.Replace("\\\"", "\""); // default string delimiter works. + + [Production("expr: int")] + public ExprClosure exprInt(int what) => + new ExprClosure() {ResultType = ExprClosure.Types.INT, valueInt = what}; + + [Production("expr: string")] + public ExprClosure exprString(string what) => + new ExprClosure() {ResultType = ExprClosure.Types.STRING, valueString = what}; + + [Production("expr: expr OP_PLUS expr")] + public ExprClosure exprAdd(ExprClosure l, Token _, ExprClosure r) + { + SyntaxCheck(l.ResultType == r.ResultType, "Type error: expected int + int or string + string"); + switch (l.ResultType) + { + case ExprClosure.Types.INT: + l.valueInt += r.valueInt; + return l; + case ExprClosure.Types.STRING: + l.valueString += r.valueString; + return l; + } + throw new ArgumentException("Internal Error"); + } + + [Production("expr: expr OP_MINUS expr")] + public ExprClosure exprMin(ExprClosure l, Token _, ExprClosure r) + { + SyntaxCheck(l.ResultType == ExprClosure.Types.INT, "Type error: expected int - int"); + SyntaxCheck(r.ResultType == ExprClosure.Types.INT, "Type error: expected int - int"); + l.valueInt -= r.valueInt; + return l; + } + + [Production("expr: expr OP_MULTIPLY expr")] + public ExprClosure exprMul(ExprClosure l, Token _, ExprClosure r) + { + SyntaxCheck(r.ResultType == ExprClosure.Types.INT, "Syntax error: expected ? * int"); + switch (l.ResultType) + { + case ExprClosure.Types.INT: + l.valueInt *= r.valueInt; + return l; + case ExprClosure.Types.STRING: + l.valueString = new StringBuilder().Insert(0, l.valueString, r.valueInt).ToString(); + return l; + } + throw new ArgumentException("Internal Error"); + } + + // [Production("expr: int OP_DIVIDE expr")] + // public ExprClosure exprDiv(int l, Token _, ExprClosure r) + // { + // SyntaxCheck(r.ResultType == ExprClosure.Types.INT, "Syntax error: expected int / int, got int / string"); + // r.valueInt = l / r.valueInt; + // return r; + // } + + // [Production("expr: int OP_MODULE expr")] + // public ExprClosure exprImod(int l, Token _, ExprClosure r) + // { + // SyntaxCheck(r.ResultType == ExprClosure.Types.INT, "Syntax error: expected int % int, got int % string"); + // r.valueInt = l % r.valueInt; + // return r; + // } + } +} + diff --git a/ParserTests/IssuesTests.cs b/ParserTests/IssuesTests.cs index 3028a065..50bcc25f 100644 --- a/ParserTests/IssuesTests.cs +++ b/ParserTests/IssuesTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using ParserTests.Issue251; +using sly.buildresult; using sly.lexer; using sly.parser.generator; using Xunit; @@ -206,5 +208,16 @@ public static void Issue219BNF() var exception = Assert.Throws(() => parser.Parse("a = 1")); Assert.Equal("visitor error",exception.Message); } + + [Fact] + public static void Issue251LeftrecForBNF() { + ParserBuilder builder = new ParserBuilder(); + Issue251Parser instance = new Issue251Parser(); + var bres = builder.BuildParser(instance,ParserType.LL_RECURSIVE_DESCENT, "expr"); + Assert.False(bres.IsOk); + Assert.Equal(1,bres.Errors.Count); + var error = bres.Errors.First(); + Assert.Equal(ErrorCodes.PARSER_LEFT_RECURSIVE, error.Code); + } } } \ No newline at end of file