Прочитав книжку 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. Заключительный скриншот редактора
Не уверен, что все это сильно жизнеспособно, ну да ладно. Хотел немного поиграться;)