# 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)