From 65041d171f68c449ef07e229c0d4002feb43a0a8 Mon Sep 17 00:00:00 2001 From: Sanchime Date: Thu, 11 Aug 2022 12:11:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0e165f --- /dev/null +++ b/README.md @@ -0,0 +1,177 @@ +# Irony +A modified version of the Irony project ([https://irony.codeplex.com](https://irony.codeplex.com)) with .NET Core support. + +[![Build status](https://dev.azure.com/sunnycoding/Irony/_apis/build/status/Irony-Build-Pipeline)](https://dev.azure.com/sunnycoding/Irony/_build/latest?definitionId=4) + +Irony is a .NET Language Implementation Kit written originally by Roman Ivantsov, you should be able to find his blog related to Irony via [http://irony-roman.blogspot.com/](http://irony-roman.blogspot.com/). He also developed an ORM framework, VITA, which can be found [here](http://vita.codeplex.com/ "here"). + +Based on the fact that the project on its official site hasn't been updated for a long time (last commit was on Dec 13th 2013) and cannot support .NET Core, I just made a copy of the project and made some modifications in order to support .NET Core. I still kept the MIT license and made the project to be licensed under Roman's name. + +## Major Changes +- Fixed the compile issues found during .NET Core migration + - Changed `StringComparer.InvariantCulture(IgnoreCase)` to `StringComparer.CurrentCulture(IgnoreCase)` + - Changed `char.GetUnicodeCategory()` to `CharUnicodeInfo.GetUnicodeCategory(current)` + - Temporary removed `ParseTreeExtensions` implementation + - Migrated the unit test project to xUnit + - Removed the original `Test`, `Sample`, `GrammarExplorer` projects from the Visual Studio solution. And the GrammarExplorer is supposed to be provided in another repo + +## Adding the NuGet Package +The Irony and Irony.Interpreter packages have been published to NuGet, with the package id `Irony.NetCore` and `Irony.Interpreter.NetCore`, in distinguishing from the original `Irony` and `Irony.Interpreter` packages published by Roman. + + +## Example +This repo contains a full example of an arithmetic expression evaluator, which accepts an arithmetic expression as a string and evaluates and calculates the result. You can find the source code under `Irony.SampleApp` folder. The expression grammar can be represented by the following C# class: + +```cs +using Irony.Interpreter.Ast; +using Irony.Parsing; +using System; + +namespace Irony.SampleApp +{ + /// + /// Represents the grammar of a custom expression. + /// + /// + [Language("Expression Grammar", "1.0", "abc")] + public class ExpressionGrammar : Grammar + { + /// + /// Initializes a new instance of the class. + /// + public ExpressionGrammar() : base(false) + { + var number = new NumberLiteral("Number"); + number.DefaultIntTypes = new TypeCode[] { TypeCode.Int16, TypeCode.Int32, TypeCode.Int64 }; + number.DefaultFloatType = TypeCode.Single; + + var identifier = new IdentifierTerminal("Identifier"); + var comma = ToTerm(","); + + var BinOp = new NonTerminal("BinaryOperator", "operator"); + var ParExpr = new NonTerminal("ParenthesisExpression"); + var BinExpr = new NonTerminal("BinaryExpression", typeof(BinaryOperationNode)); + var Expr = new NonTerminal("Expression"); + var Term = new NonTerminal("Term"); + + var Program = new NonTerminal("Program", typeof(StatementListNode)); + + Expr.Rule = Term | ParExpr | BinExpr; + Term.Rule = number | identifier; + + ParExpr.Rule = "(" + Expr + ")"; + BinExpr.Rule = Expr + BinOp + Expr; + BinOp.Rule = ToTerm("+") | "-" | "*" | "/"; + + RegisterOperators(10, "+", "-"); + RegisterOperators(20, "*", "/"); + + MarkPunctuation("(", ")"); + RegisterBracePair("(", ")"); + MarkTransient(Expr, Term, BinOp, ParExpr); + + this.Root = Expr; + } + } +} + +``` +The following class diagram illustrates the object model that can represent an arithmetic expression, the classes shown in this diagram can be found under `Irony.SampleApp.Evaluations` namespace. + +![](https://raw.githubusercontent.com/daxnet/irony/master/doc/ClassDiagram.png) + +The `Evaluator` class under `Irony.SampleApp.Evaluations` namespace is responsible for creating the parser based on the above expression grammar definition and parse the input string and finally comes out the evaluated value. + +```cs +using Irony.Parsing; +using System; +using System.Text; + +namespace Irony.SampleApp.Evaluations +{ + internal sealed class Evaluator + { + public Evaluation Evaluate(string input) + { + var language = new LanguageData(new ExpressionGrammar()); + var parser = new Parser(language); + var syntaxTree = parser.Parse(input); + + if (syntaxTree.HasErrors()) + { + throw new InvalidOperationException(BuildParsingErrorMessage(syntaxTree.ParserMessages)); + } + + return PerformEvaluate(syntaxTree.Root); + } + + private Evaluation PerformEvaluate(ParseTreeNode node) + { + switch (node.Term.Name) + { + case "BinaryExpression": + var leftNode = node.ChildNodes[0]; + var opNode = node.ChildNodes[1]; + var rightNode = node.ChildNodes[2]; + Evaluation left = PerformEvaluate(leftNode); + Evaluation right = PerformEvaluate(rightNode); + BinaryOperation op = BinaryOperation.Add; + switch (opNode.Term.Name) + { + case "+": + op = BinaryOperation.Add; + break; + case "-": + op = BinaryOperation.Sub; + break; + case "*": + op = BinaryOperation.Mul; + break; + case "/": + op = BinaryOperation.Div; + break; + } + return new BinaryEvaluation(left, right, op); + case "Number": + var value = Convert.ToSingle(node.Token.Text); + return new ConstantEvaluation(value); + } + + throw new InvalidOperationException($"Unrecognizable term {node.Term.Name}."); + } + + private static string BuildParsingErrorMessage(LogMessageList messages) + { + var sb = new StringBuilder(); + sb.AppendLine("Parsing failed with the following errors:"); + messages.ForEach(msg => sb.AppendLine($"\t{msg.Message}")); + return sb.ToString(); + } + } +} + +``` +And the `Program.Main` method simply creates the evaluator and output the evaluated value: + +```cs +using Irony.SampleApp.Evaluations; +using System; + +namespace Irony.SampleApp +{ + public class Program + { + public static void Main(string[] args) + { + var evaluator = new Evaluator(); + var evaluation = evaluator.Evaluate("2.5+(3-1)*5"); + Console.WriteLine(evaluation.Value); + } + } +} + +``` + +Program output: + +![](https://raw.githubusercontent.com/daxnet/irony/master/doc/ProgramOutput.png)