Irony — средство описания грамматик на коде .NET

Здравствуйте, в этой статье хочу рассказать об одном средстве, которое проводит лексический и синтаксический анализ, позволяя описывать грамматики языков в коде C#. Речь пойдет об Irony — .NET Language Implementation Kit. Еще можно встретить название Irony — .NET Compiler Constructon Kit, например в этой статье автора.



Анотация с сайта разработчика


Irony это средство разработки позволяющее реализовывать языки на .NET платформе. В отличии от множества существующих решений вроде yacc/lex генераторов, Irony не основан на генерации кода сканеров(лексических анализаторов) или парсеров(синтаксических анализаторов) из описаний грамматик на специальном мета-языке. Irony ориентирован на написание кода, который описывает грамматику языка. Для быстрого конструирования грамматик код описания пишется с использованием перегрузки операторов. Модули сканнеров и парсеров в Irony используют грамматику, написанную как код класса C#, управляющий процессом разбора.

Таким образом автор подчеркивает, что данное средство позволяет описывать грамматику на коде C#, и обойтись без использования генераторов синтаксических и грамматических анализаторов. Кроме самого кода сборки к Irony прилагается средство просмотра грамматик, описанных через него. Это средство называется Irony Grammar Explorer. Со сборкой этого инструмента приводятся примеры описания грамматик для языков C#, Scheme, SQL, GwBasic, JSON и других. Кроме того, что пишет автор, можно посмотреть интересный пример в этой статье. Где показано, как написать языковой сервис для IDE Visual Studio 2008 используя Irony. Данный пример был создан на основе стандартного примера Visual Studio 2008 SDK, который называется ManagedMyC. Правда пример не до конца описывает грамматику языка Си (в частности не все стандартные типы языка, перечисления и т.д.), но все равно достаточно хорошо показывает, как интегрировать Irony в качестве анализатора языкового сервиса среды Visual Studio. Изначально в примере ManagedMyC использовался стандартный Framework из VS.SDK называемый Managed Babel System, который основан на использовании инструментов генерации MPLex и MPPG.

Описание и пример кода


В Irony грамматика описывается используя нотацию Бэкуса-Наура внутри C# класса, который наследуется от Irony.Parsing.Grammar. Это явное отличие от множества языковых инструментов, подобных lex и yacc. Операторы “|” и “+” перегружены так, что вы можете вместе соединять терминальные обозначения, определяя правила подстановки. Определенный таким образом класс может быть использовал для разбора языка и в тоже время, вместе с другими возможностями Irony, для его интерпретации. Обычно для описания языка в Irony требуется описать следующие секции: языковые свойства и флаги, терминальные символы, нетерминальные символы, правила подстановки в форме Бэкуса-Наура, записи об операторах и ключевых словах.



Приведу пример описания разбора простых выражений с сайта автора Irony (изменены некоторые названия под последнюю версию сборки Irony).

Класс описания грамматики


using System;
using System.Collections.Generic;
using System.Text;
using Irony.Parsing;
using Irony.Ast;

namespace Irony.Samples {
 [Language("ExpressionEvaluator", "1.0", "Multi-line expression evaluator")]
  public class ExpressionEvaluatorGrammar : Irony.Parsing.Grammar
  {
    public ExpressionEvaluatorGrammar()
    {
      // 1. Terminals
      var number = new NumberLiteral("number");
      //Let's allow big integers (with unlimited number of digits):
      number.DefaultIntTypes = new TypeCode[] { TypeCode.Int32, TypeCode.Int64, NumberLiteral.TypeCodeBigInt };
      var identifier = new IdentifierTerminal("identifier");
      var comment = new CommentTerminal("comment", "#", "\n", "\r");
      //comment must to be added to NonGrammarTerminals list; it is not used directly in grammar rules,
      // so we add it to this list to let Scanner know that it is also a valid terminal.
      base.NonGrammarTerminals.Add(comment);

      // 2. Non-terminals
      var Expr = new NonTerminal("Expr");
      var Term = new NonTerminal("Term");
      var BinExpr = new NonTerminal("BinExpr", typeof(BinaryOperationNode));
      var ParExpr = new NonTerminal("ParExpr");
      var UnExpr = new NonTerminal("UnExpr", typeof(UnaryOperationNode));
      var UnOp = new NonTerminal("UnOp");
      var BinOp = new NonTerminal("BinOp", "operator");
      var PostFixExpr = new NonTerminal("PostFixExpr", typeof(UnaryOperationNode));
      var PostFixOp = new NonTerminal("PostFixOp");
      var AssignmentStmt = new NonTerminal("AssignmentStmt", typeof(AssignmentNode));
      var AssignmentOp = new NonTerminal("AssignmentOp", "assignment operator");
      var Statement = new NonTerminal("Statement");
      var ProgramLine = new NonTerminal("ProgramLine");
      var Program = new NonTerminal("Program", typeof(StatementListNode));

      // 3. BNF rules
      Expr.Rule = Term | UnExpr | BinExpr | PostFixExpr;
      Term.Rule = number | ParExpr | identifier;
      ParExpr.Rule = "(" + Expr + ")";
      UnExpr.Rule = UnOp + Term;
      UnOp.Rule = ToTerm("+") | "-" | "++" | "--";
      BinExpr.Rule = Expr + BinOp + Expr;
      BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**";
      PostFixExpr.Rule = Term + PostFixOp;
      PostFixOp.Rule = ToTerm("++") | "--";
      AssignmentStmt.Rule = identifier + AssignmentOp + Expr;
      AssignmentOp.Rule = ToTerm("=") | "+=" | "-=" | "*=" | "/=";
      Statement.Rule = AssignmentStmt | Expr | Empty;
      ProgramLine.Rule = Statement + NewLine;
      Program.Rule = MakeStarRule(Program, ProgramLine);
      this.Root = Program;    // Set grammar root

      // 4. Operators precedence
      RegisterOperators(1, "+", "-");
      RegisterOperators(2, "*", "/");
      RegisterOperators(3, Associativity.Right, "**");

      // 5. Punctuation and transient terms
      MarkPunctuation("(", ")");
      RegisterBracePair("(", ")");
      MarkTransient(Term, Expr, Statement, BinOp, UnOp, PostFixOp, AssignmentOp, ProgramLine, ParExpr);

      //automatically add NewLine before EOF so that our BNF rules work correctly when there's no final line break in source
      this.LanguageFlags = LanguageFlags.CreateAst | LanguageFlags.NewLineBeforeEOF | LanguageFlags.CanRunSample;

    }
  }
}

Код интерпретации описанной грамматики
using Irony.Samples;
using Irony.Interpreter;
namespace TestIrony
{
  class ProgramMain
  {
    static void Main()
    {
      ExpressionEvaluatorGrammar grammar = new ExpressionEvaluatorGrammar();
      var commandLine = new CommandLine(grammar);
      commandLine.Run();
    }
  }
}

Теги:
irony, .net, c#, грамматический разбор

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.