[Nemerle DSL] Описание конечного автомата
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.02.10 17:13
Оценка: 1 (1)
#Имя: FAQ.nemerle.DSL001
Здравствуйте, CodingUnit, Вы писали:

CU>Задача такова, есть библиотека описания автоматов состояний в коде аналогичным в UML, проблема в том что описание структуры автомата сложно читаемое и создаваемое вручную, хотелось бы автоматизировать этот процессс за счет макросов и DSL, я уже видел решение подобной задачи в Boo, но хотелось бы попробовать сделать такое решение на Nemerle, под нужную библиотеку, там имеются разные процедуры для описания иерархии автомата, хотелось бы например сделать иерархическое визуальное описание иерархии, например:



CU>
CU>[statemachine]
CU>class TestFSM
CU>{

CU>  [state]
CU>  class Top
CU>  {
    
CU>    [state]
CU>    class Start
CU>    {
       
CU>      [state]
CU>      class Sub
CU>      {
CU>       entry() : void
CU>       {
CU>        // действие при входе
CU>       }
CU>       exit() : void
CU>       {
CU>        // действие при выходе
CU>       }
CU>      } 
CU>    }
    
CU>    [state]
CU>    class Next
CU>    {
CU>      transition("Sub",UserEvt) // описание перехода
CU>    }
CU>  }
CU>}
CU>


Примерно ясно. А почему решено именно классами их описывать?

CU>как то так, на Boo там вообще абстрактно описали автомат, типа


CU>
CU>state @Parked:
CU>    when @EngineStarted      >> @IdleInNeutral
    
CU>state @IdleInNeutral:
CU>    when @EngineKilled       >> @Parked
CU>    when @GearEngaged        >> @IdleInGear
    
CU>state @IdleInGear:
CU>    when @GearDisengaged     >> @IdleInNeutral
CU>    when @GasApplied         >> @RacingAlong

CU>state @RacingAlong:
CU>    when @BrakeApplied       >> @IdleInGear
CU>    when @CarCrashedIntoTree >> @CarDestroyed

CU>state @CarDestroyed
CU>


Такое описание лично мне намного больше нравится. Ну, может быть можно выбрать немного другой синтаксис, чтобы урпостить себе жизнь и сделать его более похожим на основной синтаксис Немерла, но что-то вроде того.

Если бы я решал подобную задачу, то скорее всего я выбрал бы следующий подход:
1. Каждый автомат я описывал бы как отдельный класс.
2. Список состояний я описывал бы в метаатрибуте которым помечал бы этот класс.
3. Синтаксис сделал бы похожим на описание локальных функций.
4. Реакции на переходы описывал бы как методы внутри этого класса.

Вот как мог бы выглядеть приведенный выше автомат:
[FsmDef(
{
  state Parked
  {
    | EngineStarted      => IdleInNeutral
  }
      
  state IdleInNeutral
  {
    | EngineKilled       => Parked
    | GearEngaged        => IdleInGear
  }
      
  state IdleInGear
  {
    | GearDisengaged     => IdleInNeutral
    | GasApplied         => RacingAlong
  }

  state RacingAlong
  {
    | BrakeApplied       => IdleInGear
    | CarCrashedIntoTree => CarDestroyed
  }

  state CarDestroyed { }
}
)]
class MyFsm
{
  // обарботчик события перехода 
  EnterIn_IdleInGear_State(previosState : MyFsmState) : void
  {
    ... код обработки 
  }
  ...
}


CU>хотелось бы что то подобное создать и переводить все это на этапе компиляции в вызовы соответствующих методов в библиотеке автомата для инициализации, главная проблема в легком описании на каком то DSL или псевдоязыке, метааттрибутов, какие могут быть возможнные решения?


Выше я описал возможный вариант.
Для реализации данного подхода нужно описать и реализовать два макроса:
1. FsmDef — метаатрибут. В нем должна производиться основная работа по построению конечного автомата.
2. state — макрос уровня выражения. Нужен только для того, чтобы немерле мог воспринимать синтаксис описания состояния — "state имяСостояния список состояний".

Вот как могут выглядеть заглушки (т.е. без реализации) этих макросов:
  [Nemerle.MacroUsage(Nemerle.MacroPhase.BeforeTypedMembers, Nemerle.MacroTargets.Class)]
  macro FsmDef(type_builder : TypeBuilder, body)
  {
  }

  macro State(stateName, body)
  syntax("state", stateName, body)
  {
    <[ () ]> // В этом макросе нам ничего делать не надо. Он нужет только чтобы немерле "пропустил" наш синтаксис.
  }


Понимаю, что без опыта создания макросов распознать столь не тривиальную структуру будет не просто. По этому я потратил час, чтобы набросать примерную реализацию макросов. Вот что у меня получилось...
Сами макросы:
using Nemerle;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using System.Diagnostics;

namespace MacroLibrary2
{
  [Nemerle.MacroUsage(Nemerle.MacroPhase.BeforeTypedMembers, Nemerle.MacroTargets.Class)]
  macro FsmDef(type_builder : TypeBuilder, body)
  {
    Helper.MakeFsm(type_builder, body);
  }

  macro State(stateName, body)
  syntax("state", stateName, body)
  {
    _ = stateName; // говорим компилятору, что мы намеренно не желаем использовать параметр
    _ = body;      // тоже самое
    <[ () ]> // В этом макросе нам ничего делать не надо. Он нужен только чтобы немерле "пропустил" наш синтаксис.
  }
  
  module Helper
  {
    public MakeFsm(ty : TypeBuilder, body : PExpr) : void
    {
      def makeTransition(transitionDef : MatchCase) : void
      {
          Message.Hint(transitionDef.Location, $"  Events=$(transitionDef.patterns) Transition=$(transitionDef.body)")
      }
    
      def makeSate(stateDef : PExpr) : void
      {   // ncc заворачивает обращение к макросу в конструкцию PExpr.MacroCall...
        | PExpr.MacroCall(name, _, parms) when name.Id == "state" =>
          
          match (parms)
          { // параметры (код передаваемый в них) макроса заворачиваются в SyntaxElement.Expression.
            // Пользователь должен передать два параметра...
            
              // имя и пустую группу...
            | [Expression(<[ $stateName ]>), Expression(<[ { } ]>)] => 
              Message.Hint(stateName.Location, $"Распознано описание состояния '$stateName' (без переходов!)");

              // или имя и список вхождений оператора match который компилятор превращает в полноценный оператор match...              
            | [Expression(<[ $stateName ]>), Expression(<[ match ($_) { ..$transitions } ]>)] =>
              Message.Hint(stateName.Location, $"Распознано описание состояния '$stateName'. Переходы:");
            
              foreach (transitionDef in transitions)
                makeTransition(transitionDef);
                
            | [Expression(<[ $_ ]>), Expression(<[ { $x } ]>)] =>
              Message.Error(x.Location, "Ожидается описание переходов в формате: { | Event => State | Event => State ... }");
              
            | _ => 
              Message.Error(stateDef.Location, 
                "Ожидается описание состояния в формате: state StateName { transitions }"); 
          }
        
        | _ =>
          Message.Error(stateDef.Location, 
            "Ожидается описание состояния в формате: state StateName { transitions }"); 
      }
      
      match (body)
      {
         // описания состояний заключены в группу (фигурные скобки)
        | <[ { ..$states } ]> => // матчим список выражений описывающих состояния
          foreach (state in states) 
            makeSate(state);
            
        | _ => 
          Message.Error(body.Location, 
            "Ожидается список состоянии в формате { state1 state2 ... }"); 
      }
    }
  }
}


Это только код распознавания. В нем нет построения самого КА и генерации кода его реализующего.
Вместо этого я просто выдаю подсказки (которые можно увидеть в окне Output в VS). Так что просто ищи код "Message.Hint" и думай как заменить его на нечто действительно полезное.

Пример использования:
[FsmDef(
{
  state Parked
  {
    | EngineStarted      => IdleInNeutral
  }
      
  state IdleInNeutral
  {
    | EngineKilled       => Parked
    | GearEngaged        => IdleInGear
  }
      
  state IdleInGear
  {
    | GearDisengaged     => IdleInNeutral
    | GasApplied         => RacingAlong
  }

  state RacingAlong
  {
    | BrakeApplied       => IdleInGear
    | CarCrashedIntoTree => CarDestroyed
  }

  state CarDestroyed { }
}
)]
class MyFsm
{
  // обработчик события перехода 
  EnterIn_IdleInGear_State() : void
  {
  }
}
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
nemerle dsl fsm ка дка
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.