diff --git a/ParserTests/ExpressionGeneratorTests.cs b/ParserTests/ExpressionGeneratorTests.cs index 0bbccfd9..8d5a86f8 100644 --- a/ParserTests/ExpressionGeneratorTests.cs +++ b/ParserTests/ExpressionGeneratorTests.cs @@ -57,7 +57,7 @@ public void TestBuild() Assert.Contains("TIMES", nt.Name); Assert.Contains("DIVIDE", nt.Name); nt = nonterminals[4]; - Assert.Equal(2, nt.Rules.Count); + Assert.Equal(3, nt.Rules.Count); Assert.Contains("100", nt.Name); Assert.Contains("MINUS", nt.Name); nt = nonterminals[5]; @@ -131,6 +131,16 @@ public void TestUnaryPrecedence() Assert.False(r.IsError); Assert.Equal(-2, r.Result); } + + [Fact] + public void TestPostFix() + { + BuildParser(); + ParseResult r = Parser.Result.Parse("10!", StartingRule); + Assert.False(r.IsError); + Assert.Equal(3628800, r.Result); + } + [Fact] diff --git a/README.md b/README.md index 1a18fc53..8dc4da4f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ :warning: This readme is a bit out of date. Go to to the [wiki](https://github.com/b3b00/csly/wiki) for a more up to date documentation. - #LY is a parser generator halfway between parser combinators and parser generator like + CSLY is a parser generator halfway between parser combinators and parser generator like Yacc or ANTLR ## Why? ## diff --git a/samples/SimpleExpressionParser/SimpleExpressionParser.cs b/samples/SimpleExpressionParser/SimpleExpressionParser.cs index 173f51f0..914e2e11 100644 --- a/samples/SimpleExpressionParser/SimpleExpressionParser.cs +++ b/samples/SimpleExpressionParser/SimpleExpressionParser.cs @@ -11,8 +11,8 @@ public class SimpleExpressionParser { - [Operation((int)ExpressionToken.PLUS, 2, Associativity.Right, 10)] - [Operation((int)ExpressionToken.MINUS, 2, Associativity.Left, 10)] + [Operation((int)ExpressionToken.PLUS, Affix.InFix, Associativity.Right, 10)] + [Operation((int)ExpressionToken.MINUS, Affix.InFix, Associativity.Left, 10)] public int binaryTermExpression(int left, Token operation, int right) { int result = 0; @@ -33,8 +33,8 @@ public int binaryTermExpression(int left, Token operation, int } - [Operation((int)ExpressionToken.TIMES, 2, Associativity.Right, 50)] - [Operation((int)ExpressionToken.DIVIDE, 2, Associativity.Left, 50)] + [Operation((int)ExpressionToken.TIMES, Affix.InFix, Associativity.Right, 50)] + [Operation((int)ExpressionToken.DIVIDE, Affix.InFix, Associativity.Left, 50)] public int binaryFactorExpression(int left, Token operation, int right) { int result = 0; @@ -55,11 +55,22 @@ public int binaryFactorExpression(int left, Token operation, in } - [Operation((int)ExpressionToken.MINUS, 1, Associativity.Right, 100)] - public int unaryExpression(Token operation, int value) + [Operation((int)ExpressionToken.MINUS, Affix.PreFix, Associativity.Right, 100)] + public int preFixExpression(Token operation, int value) { return -value; } + + [Operation((int)ExpressionToken.FACTORIAL, Affix.PostFix, Associativity.Right, 100)] + public int postFixExpression( int value, Token operation) + { + int factorial = 1; + for (int i = 1; i <= value; i++) + { + factorial = factorial * i; + } + return factorial; + } [Operand] [Production("operand : primary_value")] diff --git a/samples/SimpleExpressionParser/SimpleExpressionToken.cs b/samples/SimpleExpressionParser/SimpleExpressionToken.cs index 7b8fcac5..89e19905 100644 --- a/samples/SimpleExpressionParser/SimpleExpressionToken.cs +++ b/samples/SimpleExpressionParser/SimpleExpressionToken.cs @@ -21,26 +21,30 @@ public enum SimpleExpressionToken // the + operator [Lexeme(GenericToken.SugarToken,"+")] PLUS = 5, + + // the ++ operator + [Lexeme(GenericToken.SugarToken,"++")] + INCREMENT = 6, // the - operator [Lexeme(GenericToken.SugarToken,"-")] - MINUS = 6, + MINUS = 7, // the * operator [Lexeme(GenericToken.SugarToken,"*")] - TIMES = 7, + TIMES = 8, // the / operator [Lexeme(GenericToken.SugarToken,"/")] - DIVIDE = 8, + DIVIDE = 9, // a left paranthesis ( [Lexeme(GenericToken.SugarToken,"(")] - LPAREN = 9, + LPAREN = 10, // a right paranthesis ) [Lexeme(GenericToken.SugarToken,")")] - RPAREN = 10, + RPAREN = 11, } } diff --git a/samples/expressionParser/ExpressionToken.cs b/samples/expressionParser/ExpressionToken.cs index 023cdfb5..fcd90118 100644 --- a/samples/expressionParser/ExpressionToken.cs +++ b/samples/expressionParser/ExpressionToken.cs @@ -41,6 +41,10 @@ public enum ExpressionToken // a right paranthesis ) [Lexeme("\\)")] RPAREN = 10, + + [Lexeme("!")] + FACTORIAL = 13, + // a whitespace [Lexeme("[ \\t]+",true)] diff --git a/samples/while/parser/WhileParser.cs b/samples/while/parser/WhileParser.cs index 85488993..c7826d17 100644 --- a/samples/while/parser/WhileParser.cs +++ b/samples/while/parser/WhileParser.cs @@ -121,8 +121,8 @@ public WhileAST Operand(WhileAST prim) #endregion #region NUMERIC OPERATIONS - [Operation((int)WhileToken.PLUS, 2, Associativity.Right, 10)] - [Operation((int)WhileToken.MINUS, 2, Associativity.Right, 10)] + [Operation((int)WhileToken.PLUS, Affix.InFix, Associativity.Right, 10)] + [Operation((int)WhileToken.MINUS, Affix.InFix, Associativity.Right, 10)] public WhileAST binaryTermNumericExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.ADD; @@ -148,8 +148,8 @@ public WhileAST binaryTermNumericExpression(WhileAST left, Token ope return operation; } - [Operation((int)WhileToken.TIMES, 2, Associativity.Right, 50)] - [Operation((int)WhileToken.DIVIDE, 2, Associativity.Right, 50)] + [Operation((int)WhileToken.TIMES, Affix.InFix, Associativity.Right, 50)] + [Operation((int)WhileToken.DIVIDE, Affix.InFix, Associativity.Right, 50)] public WhileAST binaryFactorNumericExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.MULTIPLY; @@ -175,7 +175,7 @@ public WhileAST binaryFactorNumericExpression(WhileAST left, Token o return operation; } - [Operation((int)WhileToken.MINUS, 1, Associativity.Right, 100)] + [Operation((int)WhileToken.MINUS, Affix.PreFix, Associativity.Right, 100)] public WhileAST unaryNumericExpression(Token operation, WhileAST value) { return new Neg(value as Expression); @@ -188,7 +188,7 @@ public WhileAST unaryNumericExpression(Token operation, WhileAST val #region BOOLEAN OPERATIONS - [Operation((int)WhileToken.OR, 2, Associativity.Right, 10)] + [Operation((int)WhileToken.OR, Affix.InFix, Associativity.Right, 10)] public WhileAST binaryOrExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.OR; @@ -198,7 +198,7 @@ public WhileAST binaryOrExpression(WhileAST left, Token operatorToke return operation; } - [Operation((int)WhileToken.AND, 2, Associativity.Right, 50)] + [Operation((int)WhileToken.AND, Affix.InFix, Associativity.Right, 50)] public WhileAST binaryAndExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.AND; @@ -208,7 +208,7 @@ public WhileAST binaryAndExpression(WhileAST left, Token operatorTok return operation; } - [Operation((int)WhileToken.NOT, 1, Associativity.Right, 100)] + [Operation((int)WhileToken.NOT, Affix.PreFix, Associativity.Right, 100)] public WhileAST binaryOrExpression(Token operatorToken, WhileAST value) { @@ -218,10 +218,10 @@ public WhileAST binaryOrExpression(Token operatorToken, WhileAST val #region COMPARISON OPERATIONS - [Operation((int)WhileToken.LESSER, 2, Associativity.Right, 50)] - [Operation((int)WhileToken.GREATER, 2, Associativity.Right, 50)] - [Operation((int)WhileToken.EQUALS, 2, Associativity.Right, 50)] - [Operation((int)WhileToken.DIFFERENT, 2, Associativity.Right, 50)] + [Operation((int)WhileToken.LESSER, Affix.InFix, Associativity.Right, 50)] + [Operation((int)WhileToken.GREATER, Affix.InFix, Associativity.Right, 50)] + [Operation((int)WhileToken.EQUALS, Affix.InFix, Associativity.Right, 50)] + [Operation((int)WhileToken.DIFFERENT, Affix.InFix, Associativity.Right, 50)] public WhileAST binaryComparisonExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.ADD; @@ -262,7 +262,7 @@ public WhileAST binaryComparisonExpression(WhileAST left, Token oper #region STRING OPERATIONS - [Operation((int)WhileToken.CONCAT, 2, Associativity.Right, 10)] + [Operation((int)WhileToken.CONCAT, Affix.InFix, Associativity.Right, 10)] public WhileAST binaryStringExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.CONCAT; diff --git a/samples/while/parser/WhileParserGeneric.cs b/samples/while/parser/WhileParserGeneric.cs index 80d045b8..53bcf2a1 100644 --- a/samples/while/parser/WhileParserGeneric.cs +++ b/samples/while/parser/WhileParserGeneric.cs @@ -114,8 +114,8 @@ public WhileAST Operand(WhileAST prim) #endregion #region NUMERIC OPERATIONS - [Operation((int)WhileTokenGeneric.PLUS, 2, Associativity.Right, 10)] - [Operation((int)WhileTokenGeneric.MINUS, 2, Associativity.Right, 10)] + [Operation((int)WhileTokenGeneric.PLUS, Affix.InFix, Associativity.Right, 10)] + [Operation((int)WhileTokenGeneric.MINUS, Affix.InFix, Associativity.Right, 10)] public WhileAST binaryTermNumericExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.ADD; @@ -141,8 +141,8 @@ public WhileAST binaryTermNumericExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.MULTIPLY; @@ -168,7 +168,7 @@ public WhileAST binaryFactorNumericExpression(WhileAST left, Token operation, WhileAST value) { return new Neg(value as Expression); @@ -181,7 +181,7 @@ public WhileAST unaryNumericExpression(Token operation, While #region BOOLEAN OPERATIONS - [Operation((int)WhileTokenGeneric.OR, 2, Associativity.Right, 10)] + [Operation((int)WhileTokenGeneric.OR, Affix.InFix, Associativity.Right, 10)] public WhileAST binaryOrExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.OR; @@ -191,7 +191,7 @@ public WhileAST binaryOrExpression(WhileAST left, Token opera return operation; } - [Operation((int)WhileTokenGeneric.AND, 2, Associativity.Right, 50)] + [Operation((int)WhileTokenGeneric.AND, Affix.InFix, Associativity.Right, 50)] public WhileAST binaryAndExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.AND; @@ -201,7 +201,7 @@ public WhileAST binaryAndExpression(WhileAST left, Token oper return operation; } - [Operation((int)WhileTokenGeneric.NOT, 1, Associativity.Right, 100)] + [Operation((int)WhileTokenGeneric.NOT, Affix.PreFix, Associativity.Right, 100)] public WhileAST binaryOrExpression(Token operatorToken, WhileAST value) { @@ -211,10 +211,10 @@ public WhileAST binaryOrExpression(Token operatorToken, While #region COMPARISON OPERATIONS - [Operation((int)WhileTokenGeneric.LESSER, 2, Associativity.Right, 50)] - [Operation((int)WhileTokenGeneric.GREATER, 2, Associativity.Right, 50)] - [Operation((int)WhileTokenGeneric.EQUALS, 2, Associativity.Right, 50)] - [Operation((int)WhileTokenGeneric.DIFFERENT, 2, Associativity.Right, 50)] + [Operation((int)WhileTokenGeneric.LESSER, Affix.InFix, Associativity.Right, 50)] + [Operation((int)WhileTokenGeneric.GREATER, Affix.InFix, Associativity.Right, 50)] + [Operation((int)WhileTokenGeneric.EQUALS, Affix.InFix, Associativity.Right, 50)] + [Operation((int)WhileTokenGeneric.DIFFERENT, Affix.InFix, Associativity.Right, 50)] public WhileAST binaryComparisonExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.ADD; @@ -255,7 +255,7 @@ public WhileAST binaryComparisonExpression(WhileAST left, Token operatorToken, WhileAST right) { BinaryOperator oper = BinaryOperator.CONCAT; diff --git a/sly/parser/generator/ExpressionRulesGenerator.cs b/sly/parser/generator/ExpressionRulesGenerator.cs index 4152b1ff..8d32e80f 100644 --- a/sly/parser/generator/ExpressionRulesGenerator.cs +++ b/sly/parser/generator/ExpressionRulesGenerator.cs @@ -19,25 +19,25 @@ public class OperationMetaData where T : struct public T OperatorToken { get; set; } - public int Arity { get; set; } + public Affix Affix { get; set; } - public bool IsBinary => Arity == 2; + public bool IsBinary => Affix == Affix.InFix; - public bool IsUnary => Arity == 1; + public bool IsUnary => Affix != Affix.InFix; - public OperationMetaData(int precedence, Associativity assoc, MethodInfo method, int arity, T oper) + public OperationMetaData(int precedence, Associativity assoc, MethodInfo method, Affix affix, T oper) { Precedence = precedence; Associativity = assoc; VisitorMethod = method; OperatorToken = oper; - Arity = arity; + Affix = affix; } public override string ToString() { - return $"{OperatorToken} / {Arity} : {Precedence} / {Associativity}"; + return $"{OperatorToken} / {Affix} : {Precedence} / {Associativity}"; } } @@ -64,7 +64,7 @@ public static BuildResult> BuildExpressionRules operation = new OperationMetaData(attr.Precedence, attr.Assoc, m, attr.Arity, ConvertIntToEnum(attr.Token)); + OperationMetaData operation = new OperationMetaData(attr.Precedence, attr.Assoc, m, attr.Affix, ConvertIntToEnum(attr.Token)); var operations = new List>(); if (operationsByPrecedence.ContainsKey(operation.Precedence)) { @@ -135,7 +135,7 @@ private static void GenerateExpressionParser(ParserConfiguration nonTerminal = BuilNonTerminal(i == precedences.Count - 1, name, nextName, operations, operationsByPrecedence); + NonTerminal nonTerminal = BuildNonTerminal(i == precedences.Count - 1, name, nextName, operations, operationsByPrecedence); configuration.NonTerminals[nonTerminal.Name] = nonTerminal; } @@ -147,33 +147,46 @@ private static void GenerateExpressionParser(ParserConfiguration rule = new Rule(); rule.Clauses.Add(new NonTerminalClause(lowestname)); rule.IsByPassRule = true; - rule.IsExpressionRule = true; + rule.IsExpressionRule = true; + rule.ExpressionAffix = Affix.NotOperator; configuration.NonTerminals[entrypoint.Name] = entrypoint; entrypoint.Rules.Add(rule); } - private static NonTerminal BuilNonTerminal(bool last, string name, string nextName, List> operations, Dictionary>> operationsByPrecedence) where IN : struct + private static NonTerminal BuildNonTerminal(bool last, string name, string nextName, List> operations, Dictionary>> operationsByPrecedence) where IN : struct { NonTerminal nonTerminal = new NonTerminal(name, new List>()); foreach (OperationMetaData operation in operations) { - if (operation.IsBinary) + if (operation.Affix == Affix.InFix) { Rule rule = new Rule(); rule.Clauses.Add(new NonTerminalClause(nextName)); rule.Clauses.Add(new TerminalClause(operation.OperatorToken)); rule.Clauses.Add(new NonTerminalClause(name)); rule.IsExpressionRule = true; + rule.ExpressionAffix = operation.Affix; rule.SetVisitor(operation); nonTerminal.Rules.Add(rule); } - else if (operation.IsUnary) + else if (operation.Affix == Affix.PreFix) { Rule rule = new Rule(); rule.Clauses.Add(new TerminalClause(operation.OperatorToken)); rule.Clauses.Add(new NonTerminalClause(nextName)); rule.IsExpressionRule = true; + rule.ExpressionAffix = operation.Affix; + rule.SetVisitor(operation); + nonTerminal.Rules.Add(rule); + } + else if (operation.Affix == Affix.PostFix) + { + Rule rule = new Rule(); + rule.Clauses.Add(new NonTerminalClause(nextName)); + rule.Clauses.Add(new TerminalClause(operation.OperatorToken)); + rule.IsExpressionRule = true; + rule.ExpressionAffix = operation.Affix; rule.SetVisitor(operation); nonTerminal.Rules.Add(rule); } @@ -182,6 +195,7 @@ private static NonTerminal BuilNonTerminal(bool last, string name, strin Rule rule0 = new Rule(); rule0.Clauses.Add(new NonTerminalClause(nextName)); rule0.IsExpressionRule = true; + rule0.ExpressionAffix = Affix.NotOperator; rule0.IsByPassRule = true; nonTerminal.Rules.Add(rule0); diff --git a/sly/parser/generator/OperationAttribute.cs b/sly/parser/generator/OperationAttribute.cs index 5773aa9f..6b638657 100644 --- a/sly/parser/generator/OperationAttribute.cs +++ b/sly/parser/generator/OperationAttribute.cs @@ -12,13 +12,23 @@ public enum Associativity Right = 2 } + + public enum Affix + { + NotOperator = 0, + PreFix = 1, + InFix = 2, + PostFix = 3 + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class OperationAttribute : Attribute //where IN : struct { public int Token { get; set; } - public int Arity { get; set; } + public Affix Affix { get; set; } public Associativity Assoc { get; set; } @@ -31,9 +41,9 @@ public class OperationAttribute : Attribute //where IN : struct /// operator arity /// operator aosociativity () /// precedence level: the greater, the higher - public OperationAttribute(int token, int arity, Associativity assoc, int precedence) { + public OperationAttribute(int token, Affix affix, Associativity assoc, int precedence) { Token = token; - Arity = arity; + Affix = affix; Assoc = assoc; Precedence = precedence; } diff --git a/sly/parser/parser/llparser/EBNFRecursiveDescentSyntaxParser.cs b/sly/parser/parser/llparser/EBNFRecursiveDescentSyntaxParser.cs index cc987759..4365ef2b 100644 --- a/sly/parser/parser/llparser/EBNFRecursiveDescentSyntaxParser.cs +++ b/sly/parser/parser/llparser/EBNFRecursiveDescentSyntaxParser.cs @@ -216,6 +216,7 @@ public override SyntaxParseResult Parse(IList> tokens, Rule ru else { node = new SyntaxNode(nonTerminalName + "__" + rule.Key, children); + node.ExpressionAffix = rule.ExpressionAffix; node = ManageExpressionRules(rule, node); result.Root = node; result.IsEnded = currentPosition >= tokens.Count - 1 diff --git a/sly/parser/parser/llparser/RecursiveDescentSyntaxParser.cs b/sly/parser/parser/llparser/RecursiveDescentSyntaxParser.cs index 7c6091fe..137fcb65 100644 --- a/sly/parser/parser/llparser/RecursiveDescentSyntaxParser.cs +++ b/sly/parser/parser/llparser/RecursiveDescentSyntaxParser.cs @@ -334,14 +334,23 @@ protected SyntaxNode ManageExpressionRules(Rule rule, SyntaxNode nod } else if (rule.IsExpressionRule && !rule.IsByPassRule) { + node.ExpressionAffix = rule.ExpressionAffix; if (node.Children.Count == 3) { operatorIndex = 1; } else if (node.Children.Count == 2) { - operatorIndex = 0; + if (node.ExpressionAffix == Affix.PreFix) + { + operatorIndex = 0; + } + else if (node.ExpressionAffix == Affix.PostFix) + { + operatorIndex = 1; + } } + if (operatorIndex >= 0) { if (node.Children[operatorIndex] is SyntaxLeaf operatorNode) diff --git a/sly/parser/syntax/Rule.cs b/sly/parser/syntax/Rule.cs index d4ee5bb4..2c354234 100644 --- a/sly/parser/syntax/Rule.cs +++ b/sly/parser/syntax/Rule.cs @@ -5,6 +5,7 @@ using System.Text; using sly.parser.syntax; using System.Reflection; +using System.Runtime.InteropServices; using sly.parser.generator; namespace sly.parser.syntax @@ -22,6 +23,8 @@ public class Rule : GrammarNode where IN : struct private MethodInfo Visitor { get; set; } public bool IsExpressionRule { get; set; } + + public Affix ExpressionAffix { get; set; } public string RuleString { get; } diff --git a/sly/parser/syntax/SyntaxNode.cs b/sly/parser/syntax/SyntaxNode.cs index 08fd1b2b..b14e4804 100644 --- a/sly/parser/syntax/SyntaxNode.cs +++ b/sly/parser/syntax/SyntaxNode.cs @@ -23,6 +23,8 @@ public class SyntaxNode : ISyntaxNode where IN : struct public bool IsByPassNode { get; set; } = false; public bool IsEmpty => Children == null || !Children.Any(); + + public Affix ExpressionAffix { get; set; } #region expression syntax nodes @@ -30,8 +32,8 @@ public class SyntaxNode : ISyntaxNode where IN : struct public bool IsExpressionNode => Operation != null; - public bool IsBinaryOperationNode => IsExpressionNode ? Operation.Arity == 2 : false; - public bool IsUnaryOperationNode => IsExpressionNode ? Operation.Arity == 1 : false; + public bool IsBinaryOperationNode => IsExpressionNode ? Operation.Affix == Affix.InFix : false; + public bool IsUnaryOperationNode => IsExpressionNode ? Operation.Affix != Affix.InFix : false; public int Precedence => IsExpressionNode ? Operation.Precedence : -1; public Associativity Associativity => IsExpressionNode && IsBinaryOperationNode ? Operation.Associativity : Associativity.None;