Архив рубрики ‘DSL’

DSLs in Boo

Posted: 6 января, 2012 in .NET, DSL
Метки:,

Прочитав книжку DSLs in Boo, я захотел сделать небольшой DSL для своей предметной области. Ссылку на последние исходники можно найти в конце статьи. В самой статье я сделаю несколько комментариев.
В данный момент я работаю в компании, которая занимается снятием показаний с приборов учета специально обученными людьми на КПК с последующей отправкой на сервер. Иногда, для большего контроля, приходится просить, чтобы вместе с числовым значением показания, контролер еще делал фотографию прибора учета. Такие заявки поступают от разных людей, чаще всего они имеют вид

Прошу проставить на фотофиксацию показаний л/с ,где разница показаний с ноября по декабрь 2011г. составляет от 1кВт до 50кВт.

В целях более полной проверки достоверности снимаемых показаний прошу Вас включить 20% -ное принудительное фотографирование в январе месяце для следующих контролеров…

Прошу вас выставить 50% фотографирование на маршрут контролера Иванова И.И. № 7777 с сегодняшнего дня.

и т.д.

Все заявки имеют вид типа:

 Проставить на фотофиксацию
 с 1 января 2012 по 31 января 2012г
 для контролера Иванова И.И.
 100% фотографирование
 где никогда не производилась фотофиксация
 Перечень л/с скинуть на manager1@mail.ru

А это уже похоже на некоторый DSL. В качестве языков для построения DSL обычно выбирают языки, которые уже сами по себе немного похожи на естественный язык. В мире .NET популярностью пользуются F# и Boo, т.к. они позволяют делать вызов функций без скобок. А еще в Boo можно компилятору подсказывать что вы имели ввиду написав то, что не входит в стандартный язык. Не знаю есть ли в F# такое метапрограммирование, наверняка что-то можно придумать. Так вот, чтобы долго не думать, возьмем эту заявку в качестве эталона, заменим пробел на подчеркивание, выравняем код как в Boo и получим «программу» вида

 запрос "фотофиксация для контролера" :
    для_контролера_номер 7777
    начиная_с "01.01.2012"
    до "31.01.2012"
    установить_процент_снятия 50
    список_лицевых_выслать_на_почту "manager1@mail.ru"
    где :
       показания_не_снимались "01.12.2011", "31.12.2011"

Базовый класс в С# будет выглядеть как-то так

public abstract class BasePhotoRequest
    {
        public string Name { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public int ControllerId { get; set; }
        public int PhotoPercent { get; set; }
        public string NotifyEmail { get; set; }

        public abstract void Build();

        protected void запрос(string name, Action block)
        {
            Name = name;
            block();
        }

        public void PrintProperties()
        {
            …
        }

        public void начиная_с(string customDate)
        {
		…
        }

        public void где(Action block)
        {
            block();
        }

        public void показания_не_снимались(string date1, string date2)
        {
		//Создадим какое-нибудь правило Rule
            …
        }

        public void до(string customDate)
        {
            …
        }

        public void для_контролера_номер(int controllerId)
        {
            ControllerId = controllerId;
        }

        public void установить_процент_снятия(int percent)
        {
            PhotoPercent = percent;
        }

        public void список_лицевых_выслать_на_почту(string email)
        {
            NotifyEmail = email;
        }

    }

Еще я хотел бы завести класс Rule для блока «где». И на каждое правило создать свой класс. В конце концов я хотел бы, чтобы класс BasePhotoRequest выдавал SQL выражение, которое можно было быстренько исполнить в базе с помощью классов ADO.NET. А эти правила будут отражаться в SQL-блоке WHERE. Правила простенькие как например ограничение потребления электроэнергии

public class ConsumptionRule : Rule
    {
        private readonly double _val1;
        private readonly double _val2;

        public ConsumptionRule(double  val1, double val2)
        {
            _val1 = val1;
            _val2 = val2;
        }

        public override string ToString()
        {
            return string.Format("consumption between {0} and {1}", _val1, _val2);
        }
    }

Метод Build в BasePhotoRequest помечен как абстрактный. Здесь произойдет немного магии. Компилятор в Boo как конвейер: состоит из нескольких шагов. Мы этому компилятору скажем, чтобы на первом шаге он заталкал код из Boo в метод Build. Делается это таким образом

public class MyDslEngine: DslEngine
    {
       public MyDslEngine()
        {
           Storage = new FileSystemDslEngineStorage();
        }

        protected override void CustomizeCompiler(BooCompiler compiler,
                                                  CompilerPipeline pipeline,
                                                  string[] urls)
        {
            compiler.Parameters.Ducky = true;

             pipeline.Insert(1,
                            new AnonymousBaseClassCompilerStep(
                                typeof(BasePhotoRequest),
                                "Build",
                                "ConsoleApplication1"
                                ));
        }
    }

Далее где-нибудь в программе сделаем вызов

DslFactory dslFactory = new DslFactory();
dslFactory.Register<BasePhotoRequest>(new MyDslEngine());
dslFactory.BaseDirectory = Environment.CurrentDirectory;
var photo = dslFactory.TryCreate<BasePhotoRequest>("photo.boo");
photo.Build();

И наш класс BasePhotoRequest будет создан. На самом деле компилятор Boo с помощью библиотеки Rhino.DSL создаст нам класс-наследник от BasePhotoRequest (вот она польза от многоязычности CLR исполняющей все на IL), в метод Build засунет наш код, и вернет этот класс обратно. Мы лишь вызовем сам метод Build.
Наверное стоит сказать, что автор это книги также является автором библиотеки Rhino.DSL, которая включает в себя все рутины по созданию анонимных классов и настройки компилятора.
Ну вот мы создали какой-то DSL. Как теперь им пользоваться? Чтобы давать другим людям непрограммистам писать на нем, нужна какая-то среда. А то могут подумать, что можно писать что угодно через подчеркивание и программа все сделает как надо. В качестве редактора кода возьмем редактор кода от SharpDevelop. В нем легко настраивается подсветка и даже IntelliSense.
Для настройки подсветки нужно создать файл, например Dsl.xshd, и в нем все ключевые слова прописать. Для форматирования — отнаследовать от DefaultFormattingStrategy. Для IntelliSense — отнаследовать от ICompletionDataProvider. Заключительный скриншот редактора

Не уверен, что все это сильно жизнеспособно, ну да ладно. Хотел немного поиграться;)

Ссылка на исходники