Всегда ли нужно использовать то, что предлагают?

Разобравшись с посетителем и слушателем, пришло время попробовать «Велосипед». Изучая способы построения внешних DSL я столкнулся с проблемой получения практики. Не буду далеко ходить книга Фаулера про то, как построить язык мечты «DSL», к сожалению, оказалась совсем не обезболивающим, а скорее на оборот тропой в кустах крапивы.

Глава 12 Symbol Table
Место для хранения во время синтаксического анализа всех
идентифицируемых объектов для разрешения ссылок.

Понятно? Конечно понятно, к описанию претензий нет.
Многим языкам приходится ссылаться на объекты во многих точках кода. Если есть
язык, который определяет конфигурацию задач и их зависимости, значит, нужен способ,
которым одна задача могла бы сослаться на зависимые задачи в своем определении.


В книге представлена задача по сбору на работу
go_to_work -> drink_coffee dress
drink_coffee -> make_coffee wash
dress -> wash

И вот магия DSL, то для чего он создается, а именно код не нужно комментировать, достаточно просто прочитать, что бы определить зависимости между задачами.
Но я кофе не пью, и подвернулась книга «руководство microsoft по проектированию архитектуры приложений». Отличная задача – Разработать приложение под винду, чем сейчас и займемся.
И так MS уверяет

Данное руководство поможет:
• Понять базовые принципы и шаблоны построения архитектуры и дизайна для разработки успешных решений на платформе Microsoft.
• Правильно выбрать стратегии и шаблоны проектирования, которые помогут при проектировании слоев, компонентов и сервисов решения.
• Определить и реализовать ключевые технические решения.
• Определить и реализовать основные показатели качества и сквозные функции для решения.
• Правильно выбрать технологии для реализации решения.
• Создать возможный вариант базовой архитектуры решения.
• Правильно выбрать предлагаемые группой patterns & practices решения и руководства, которые помогут в реализации решения.

Отлично. У нас есть бесплатный список задач, который после реорганизации имеет вид
1. Понять_базовые_принципы
2. Выбрать_стратегии_и_шаблоны
3. Реализовать_ключевые_технические_решения
4. Реализовать_основные_показатели_качества
5. Выбрать_технологии
6. Создать_возможный_вариант_базовой_архитектуры
7. Выбрать_предлагаемые_решения_и_руководства

Ну и зависимости между ними
6 -> 3 4 5
2 -> 1 7
5 -> 7
7 -> 1
3 -> 2 5
4 -> 3

Пока все идет хорошо.
Грамматика этого DSL исключительно проста.
grammar file...
network : SEP? dependency (SEP dependency)* SEP?;
dependency
: lhs=ID '->' rhs+=ID+
{helper.recognizedDependency($lhs, $rhs);}
;

И это все? Что такое SEP, что такое ID? Не ну действительно я только недавно начал разбираться с грамматикой, да вообще с внешним DSL отличным от простого XML. За этим и пришел сюда – яма. Через 60 страниц листания, я узрел
SEP : ('-' | ' ' );

Через еще 50
fragment LETTER : ('a'..'z' | 'A'..'Z' | '_');
fragment DIGIT : ('0'..'9');
ID : LETTER (LETTER | DIGIT)* ;

Задачи описаны на русском, эх а пусть и DSL будет на нем же!
Собрав все вместе и изрядно поплясав вокруг кодировки для поддержки кириллицы
grammar WorkGrammar;

network : SEP? dependency (SEP dependency)* SEP?;

dependency
: lhs=ID '->' rhs+=ID+;

ID : LETTER (LETTER | DIGIT)* ;

fragment LETTER : ( 'а'..'я' | 'А'..'Я' | '_' );
fragment DIGIT : ('0'..'9');

 SEP : ('-' | ' ' );

 WS : [\t\r]+->skip;

Я конечно не писатель (не разработчик), а читатель (пользователь), и мне как пользователю это доставило не самые приятные проблемы (и в тех поддержку не позвонишь, в запросах к Google раздавались гудки, ни кто не брал).
С грамматикой видимо все! F5 породил Lexer и Parser!
Task что то называть класс ну очень не очень, поэтому просто Work
class Work
	{
		List<Work> _dep;

		public Work( string name )
		{
			Name = name;
			_dep = new List<Work>();
		}

		public string Name { get; private set; }

		public IEnumerable<Work> Dependencies { get { return _dep; } }

		public bool Completed { get; set; }

		public void AddDependency( Work dep )
		{
			_dep.Add( dep );
		}

	}

Как выяснилось в Битва «Слушатель vs Посетитель» на стадионе antlr4 посетитель и слушатель довольно не малы и тяжеловесны (хотя можно и их использовать), а так же в целях самообразования, пробуем обойти дерево на «Велосипеде».
class WorksLoader
	{
		public Dictionary<string, Work> Load( WorkGrammarParser.NetworkContext context )
		{
			Dictionary<string, Work> result = new Dictionary<string, Work>();

			foreach ( var dep in context.dependency() )
			{
				var work = GetWork( dep.lhs.Text, result );

				foreach ( var right in dep._rhs )
				{
					var depWork = GetWork( right.Text, result );
					work.AddDependency( depWork );
				}
			}

			return result;
		}

		Work GetWork( string name, Dictionary<string, Work> works )
		{
			if ( !works.ContainsKey( name ) )
				works[name] = new Work( name );

			return works[name];
		}
	}

Возможности Antlr4 по генерации контекстов радуют, сильно упрощает обход.
Класс с методом Load возвращающий таблицу символов (словарь работ) с уже определенными зависимостями.
Пришло время самому интересному, исходнику
Создать_возможный_вариант_базовой_архитектуры -> Реализовать_ключевые_технические_решения Реализовать_основные_показатели_качества Выбрать_технологии
Выбрать_стратегии_и_шаблоны -> Понять_базовые_принципы Выбрать_предлагаемые_решения_и_руководства
Выбрать_технологии -> Выбрать_предлагаемые_решения_и_руководства
Выбрать_предлагаемые_решения_и_руководства -> Понять_базовые_принципы
Реализовать_ключевые_технические_решения -> Выбрать_стратегии_и_шаблоны Выбрать_технологии
Реализовать_основные_показатели_качества -> Реализовать_ключевые_технические_решения

Не очень читаемые разделители (если укоротить названия работ, то вполне) ну да ладно, это во много раз лучше A -> B C
Определив текст нашего языка в ресурсы, можно получить список работ
class Program
	{
		static void Main( string[] args )
		{

			AntlrInputStream input = new AntlrInputStream(Resource.Input);
			WorkGrammarLexer lexer = new WorkGrammarLexer( input );
			CommonTokenStream tokens = new CommonTokenStream( lexer );
			WorkGrammarParser parser = new WorkGrammarParser( tokens );
			var works = new WorksLoader().Load( parser.network());

			var ready = GetReadyWorks(works);
		}

		static Dictionary<string, Work> GetReadyWorks( Dictionary<string, Work> works )
		{
			Dictionary<string, Work> ready = new Dictionary<string, Work>();

			foreach ( var currentWork in works.Select(e=>e.Value) )
			{
				if ( currentWork.Dependencies.Count() == 0 || currentWork.Dependencies.All( e => e.Completed ) )
					ready[currentWork.Name] = currentWork;
			}

			return ready;
		}
	}

F5! Работает! Если бы… Результат выдается не тот который ожидался. Потратив изрядное время на выявление проблемы -> засада расположилась там где ее ни кто не ожидал -> грамматика. Вообще не реально, мало того что она дается не полная и нет ни каких исходных материалов, так она еще и с ошибками. Возмутительно!
Рабочая грамматика
grammar WorkGrammar;

network : SEP? dependency ('\n' dependency)*;

dependency
: lhs=ID '->' rhs+=ID+;

ID : LETTER (LETTER | DIGIT)* ;

fragment LETTER : ( 'а'..'я' | 'А'..'Я' | '_' );
fragment DIGIT : ('0'..'9');

 SEP : ('-' | ' ' );

 WS : [\t\r]+->skip;


При разработке DSL на Antlr4 по книге Фаулера чувства двухбокие, вкус с горчинкой, с одного бока описание, текст на высоте, с другого же по мимо того что примеров на C# не найти, так еще изучение затрудняется проблемами поиска частей описания по сем разделам, и багами которые разработчик заботливо раскидал то тут то там.

P.S


Всегда ли нужно использовать то, что предлагают?
Мое мнение, что если позволяет время, то лучше написать свои классы по обходу деревьев! Хотя многое зависит от самой задачи.

Комментарии (0)