Написание DSL на Nemerle и создание Enum в макросах
От: CodingUnit Россия  
Дата: 03.02.10 09:37
Оценка:
Здравствуйте, тут встала проблема написания сложного кода автоматически, за основу взяты макросы Nemerle, при это когда создаются новые ключевые слова их нельзя использовать в пространстве имен или в классе, видимо только в методе, иначе пишется сообщение об ошибке, так ли это?:
namespace TestFSM
{
  // ошибка пишет что требуется тип, макрос определен правильно
  statemachine Test
  {
  }

  class TestClass
  {
    // тоже самое
    statemachine Test
    {
    }
  }
}


Еще встала другая проблема, надо определить во время компиляции в макросе перечисление на основе кода, например так:
[MacroUsage(MacroPhase.BeforeTypedMembers,MacroTargets.Class,Inherited=true)]  
macro statemachine(ty : TypeBuilder)
{

// определяем перечисление, нормально
def stateid=ty.DefineNestedType(<[ decl:
  public enum StateID
  {
  }
  ]>);

..
def states : list[TypeBuilder]= ty.DeclaredNestedTypes; // получаем лист TypeBuilder-ов для внутренних типов

// для каждого типа добавляем имя в качестве константы к перечислению
states.Iter( x => stateid.Define(<[ $(x.Name)]>)); // не компилируется

// также просто если попробовать
stateid.Define(<[decl: | ABC ]>); // компилируется, но при выполнении ничего не происходит, новый член не добавляется
}

stateid.Compile(); // компилируем тип



// при этом если написать сразу список членов, то он определяется правильно:
def stateid=ty.DefineNestedType(<[ decl:
  public enum StateID
  {
  | ABC
  | BCD
  }
  ]>); // если бы была возможность как то в цитату вставить строки с описанием членов


Вопрос такой, как определить перечисление в макросе и добавить как подтип в классе? Как добавить динамически в макросе элементы в список перечисления, есть ли такая возможность?
И последний вопрос, если писать DSL то потребуется много ключевых слов, при этом они как то должны взаимодействовать между собой, проходит синтаксические и семантические проверки, для этого надо чтобы текст разбирался и получалось некое описание на DSL, для этого нужен какой то объект который хранит в себе все состояние описанной структуры, то есть должен быть доступен на этапе компиляции и при вызове макроса через помеченные объекты или описание в методе, добавлять информацию в этот объект, то есть должен создаваться один раз и жить в остальное время. Еще может потребоваться некоторые члены удалять, которые не несут в себе никакой информации для приложения, например классы для описания вложенной иерархии нужные макросу, но ненужные в рантайме, было бы хорошо удалить чтобы они были не видны, но TypeBuilder допускает объявлять члены, но не допускает удалять и многие внутренние поля компилятора read-only, можно ли решить как то эту задачу?
Re: Написание DSL на Nemerle и создание Enum в макросах
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.02.10 12:33
Оценка:
Здравствуйте, CodingUnit, Вы писали:

CU>namespace TestFSM
CU>{
CU>// ошибка пишет что требуется тип, макрос определен правильно
CU>statemachine Test
CU>{
CU>}


Так, действительно, в текущей версии делать нельзя.

Но можно делать например так:
class Test statemachine
{
}

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

В качестве примерами я написал небольшой макрос "test" который добавляет реализацию метода к классу. Собственно код макроса:
using Nemerle;
using Nemerle.Compiler;

namespace MacroLibrary2
{
  [Nemerle.MacroUsage(
    Nemerle.MacroPhase.BeforeInheritance, 
    Nemerle.MacroTargets.Class // указываем, что макрос - это макроатрибут уровня класса
  )]
  macro @MyTest(type_builder : TypeBuilder) 
    syntax("test") // задаем синтаксис
  {
    Helper.MyTest(type_builder);
  }
  
  module Helper
  {
    public MyTest(type_builder : TypeBuilder) : void
    {
      // добавляем в класс которому применен этот макрос реализацию метода "Test() : void"
      _ = type_builder.DefineWithSource(
        <[ decl:
            public Test() : void 
            {
              System.Console.WriteLine("The test happen! :)");
            }
        ]>);
    }
  }
}

А вот использование этого макроса:
using System;
using System.Console;
using Nemerle.Utility;

using MacroLibrary2;

class A test // собственно вот оно
{
}

module Program
{
  Main() : void
  {
    def a = A();
    a.Test(); // а это использование сгенерированного метода.
    _ = ReadLine();
  }
}
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Написание DSL на Nemerle и создание Enum в макросах
От: CodingUnit Россия  
Дата: 03.02.10 12:46
Оценка:
Здравствуйте, VladD2, Вы писали:


Да генерировать методы легко, хорошо что синтаксис можно добавить после класса, но не могли бы Вы Владислав написать есть ли возможность создать в классе перечисление и добавить в него имена констант на основе динамических данных, как я описал здесь, почему не компилируется определение перечисления и не добавляется даже статическое константное поле в перечисление, есть какие то ограничения у компилятора?:


MacroUsage(MacroPhase.BeforeTypedMembers,MacroTargets.Class,Inherited=true)]  
macro statemachine(ty : TypeBuilder)
{

// определяем перечисление, нормально
def stateid=ty.DefineNestedType(<[ decl:
  public enum StateID
  {
  }
  ]>);

..
def states : list[TypeBuilder]= ty.DeclaredNestedTypes; // получаем лист TypeBuilder-ов для внутренних типов

// для каждого типа добавляем имя в качестве константы к перечислению
states.Iter( x => stateid.Define(<[ | $(x.Name)]>)); // не компилируется

// также просто если попробовать
stateid.Define(<[decl: | ABC ]>); // компилируется, но при выполнении ничего не происходит, новый член не добавляется
}

stateid.Compile(); // компилируем тип




// при этом если написать сразу список членов, то он определяется правильно:
def stateid=ty.DefineNestedType(<[ decl:
  public enum StateID
  {
  | ABC
  | BCD
  }
  ]>); // если бы была возможность как то в цитату вставить строки с описанием членов


И еще вопрос можно ли удалять объявления например подклассов из класса? например убрать объявление SubTest, чтобы он присутствовал во время работы макроса и удалялся из конечного кода?


class Test
{

   class SubTest
   {
   }
}
Re: Написание DSL на Nemerle и создание Enum в макросах
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.02.10 14:36
Оценка:
Здравствуйте, CodingUnit, Вы писали:

CU>Еще встала другая проблема, надо определить во время компиляции в макросе перечисление на основе кода, например так:

CU>
CU>[MacroUsage(MacroPhase.BeforeTypedMembers,MacroTargets.Class,Inherited=true)]  
CU>macro statemachine(ty : TypeBuilder)
CU>{

CU>// определяем перечисление, нормально
CU>def stateid=ty.DefineNestedType(<[ decl:
CU>  public enum StateID
CU>  {
CU>  }
CU>  ]>);

CU>..
CU>def states : list[TypeBuilder]= ty.DeclaredNestedTypes; // получаем лист TypeBuilder-ов для внутренних типов

CU>// для каждого типа добавляем имя в качестве константы к перечислению
CU>states.Iter( x => stateid.Define(<[ $(x.Name)]>)); // не компилируется

CU>// также просто если попробовать
CU>stateid.Define(<[decl: | ABC ]>); // компилируется, но при выполнении ничего не происходит, новый член не добавляется
CU>}
CU>stateid.Compile(); // компилируем тип
CU>


CU>
CU>// при этом если написать сразу список членов, то он определяется правильно:
CU>def stateid=ty.DefineNestedType(<[ decl:
CU>  public enum StateID
CU>  {
CU>  | ABC
CU>  | BCD
CU>  }
CU>  ]>); // если бы была возможность как то в цитату вставить строки с описанием членов
CU>


CU>Вопрос такой, как определить перечисление в макросе и добавить как подтип в классе?


Что-то вроде этого:
[MacroUsage(MacroPhase.BeforeTypedMembers,MacroTargets.Class,Inherited=true)]  
macro statemachine(ty : TypeBuilder)
{
  // генерируем список определений полей перечисления
  def cases = ty.DeclaredNestedTypes.Map(t => <[ decl: | $(t.ParsedName : name); ]>);

  def enumTypeBuilder = ty.DefineNestedType( // вложенные типы нужно добавлять только с помощью DefineNestedType
    <[ decl:
        public enum StateID
        {
          ..$cases
        }
    ]>, true);
  
  enumTypeBuilder.Compile();
}


CU> Как добавить динамически в макросе элементы в список перечисления, есть ли такая возможность?


Или как показал я, или с помощью методов DefineXxx() класса TypeBuilder.

CU>И последний вопрос, если писать DSL то потребуется много ключевых слов, при этом они как то должны взаимодействовать между собой, проходит синтаксические и семантические проверки, для этого надо чтобы текст разбирался и получалось некое описание на DSL, для этого нужен какой то объект который хранит в себе все состояние описанной структуры, то есть должен быть доступен на этапе компиляции и при вызове макроса через помеченные объекты или описание в методе, добавлять информацию в этот объект, то есть должен создаваться один раз и жить в остальное время.


Это все очень сильно зависит от задачи говорить о чем-то в ообщем невозможно.

CU>Еще может потребоваться некоторые члены удалять, которые не несут в себе никакой информации для приложения, например классы для описания вложенной иерархии нужные макросу, но ненужные в рантайме, было бы хорошо удалить чтобы они были не видны, но TypeBuilder допускает объявлять члены, но не допускает удалять и многие внутренние поля компилятора read-only, можно ли решить как то эту задачу?


Удалаять нельзя. Но нужную метаинформацию можно описать в другом виде. Например в виде доп-синтаксиса, или в виде мета-атрибутов.

В общем, лучше опиши стоящую задачу (без ее реализации), а мы попробуем как ее лучше реализовать.
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Написание DSL на Nemerle и создание Enum в макросах
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.02.10 14:39
Оценка:
Здравствуйте, CodingUnit, Вы писали:

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


Извиняюсь. Был занят. Ответ на этот вопрос здесь
Автор: VladD2
Дата: 03.02.10
.

CU>И еще вопрос можно ли удалять объявления например подклассов из класса?


Нет. Я бы описал нужные данные в метаатрибуте или расширенном синтаксисе и не стал бы создавать не нужных вложенных типов.
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Написание DSL на Nemerle и создание Enum в макросах
От: CodingUnit Россия  
Дата: 03.02.10 15:36
Оценка:
Здравствуйте, VladD2, Вы писали:

CU>>Вопрос такой, как определить перечисление в макросе и добавить как подтип в классе?


VD>Что-то вроде этого:

VD>
VD>[MacroUsage(MacroPhase.BeforeTypedMembers,MacroTargets.Class,Inherited=true)]  
VD>macro statemachine(ty : TypeBuilder)
VD>{
VD>  // генерируем список определений полей перечисления
VD>  def cases = ty.DeclaredNestedTypes.Map(t => <[ decl: | $(t.ParsedName : name); ]>);

VD>  def enumTypeBuilder = ty.DefineNestedType( // вложенные типы нужно добавлять только с помощью DefineNestedType
VD>    <[ decl:
VD>        public enum StateID
VD>        {
VD>          ..$cases
VD>        }
VD>    ]>, true);
  
VD>  enumTypeBuilder.Compile();
VD>}
VD>


спасибо, работает!

CU>>И последний вопрос, если писать DSL то потребуется много ключевых слов, при этом они как то должны взаимодействовать между собой, проходит синтаксические и семантические проверки, для этого надо чтобы текст разбирался и получалось некое описание на DSL, для этого нужен какой то объект который хранит в себе все состояние описанной структуры, то есть должен быть доступен на этапе компиляции и при вызове макроса через помеченные объекты или описание в методе, добавлять информацию в этот объект, то есть должен создаваться один раз и жить в остальное время.


VD>Это все очень сильно зависит от задачи говорить о чем-то в ообщем невозможно.


CU>>Еще может потребоваться некоторые члены удалять, которые не несут в себе никакой информации для приложения, например классы для описания вложенной иерархии нужные макросу, но ненужные в рантайме, было бы хорошо удалить чтобы они были не видны, но TypeBuilder допускает объявлять члены, но не допускает удалять и многие внутренние поля компилятора read-only, можно ли решить как то эту задачу?


VD>Удалаять нельзя. Но нужную метаинформацию можно описать в другом виде. Например в виде доп-синтаксиса, или в виде мета-атрибутов.


VD>В общем, лучше опиши стоящую задачу (без ее реализации), а мы попробуем как ее лучше реализовать.


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


[statemachine]
class TestFSM
{

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


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

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

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

state @CarDestroyed


хотелось бы что то подобное создать и переводить все это на этапе компиляции в вызовы соответствующих методов в библиотеке автомата для инициализации, главная проблема в легком описании на каком то DSL или псевдоязыке, метааттрибутов, какие могут быть возможнные решения?
[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
  {
  }
}
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Nemerle DSL FSM КА ДКА
Re: [Nemerle DSL] Описание конечного автомата
От: CodingUnit Россия  
Дата: 04.02.10 10:52
Оценка:
Здравствуйте, VladD2, Вы писали:



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


VD>Если бы я решал подобную задачу, то скорее всего я выбрал бы следующий подход:

VD>1. Каждый автомат я описывал бы как отдельный класс.
VD>2. Список состояний я описывал бы в метаатрибуте которым помечал бы этот класс.
VD>3. Синтаксис сделал бы похожим на описание локальных функций.
VD>4. Реакции на переходы описывал бы как методы внутри этого класса.

VD>Вот как мог бы выглядеть приведенный выше автомат:

VD>
VD>


Спасибо за ответ Владислав и подробное описание, хотел заметить что автомат иерархический, в Boo был описан плоский автомат, на практике чаще автоматы нужны сложные, то есть когда состояния могут быть вложенными друг в друга, как я описал здесь:

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

CU>>  [state]
CU>>  class Top // самое верхнее состояние
CU>>  {
    
CU>>    [state]
CU>>    class Start // подсостояние Top
CU>>    {
       
CU>>      [state]
CU>>      class Sub1 // подсостояние Start
CU>>      {
            [state]  
            class Sub2 // подсостояние Sub1
            {

            }
CU>>      } 
CU>>    }
    
CU>>    [state]
CU>>    class Next // подсостояние Top
CU>>    {

CU>>    }
CU>>  }
CU>>}
CU>>


графически они выглядят примерно так
http://upload.wikimedia.org/wikipedia/en/thumb/2/2e/UML_state_machine_Fig7.png/540px-UML_state_machine_Fig7.png
http://upload.wikimedia.org/wikipedia/en/thumb/2/20/UML_state_machine_Fig5.png/540px-UML_state_machine_Fig5.png

В принципе с метаатрибутом описать весь автомат можно, лишь бы это не сложно было сделать, тогда описание будет выглядеть примерно так:
 [FsmDef(
 {
 state Top
  {
        state Start
        {
        | Event1 => Next // переход по событию Event1

           // подостояние 1
           state Sub1
           {
           | entry => {
                      Action1 // действие при входе
                      }
           | exit => Action2 // действие при выходе

            // подсостояние 2            
             state Sub2
             {
             
             }
           }
        } 
       
        state Next
        {
        | Event2 => Start
        }
  }
})]
class MyFsm
{
  // обарботчик события перехода 
  EnterIn_IdleInGear_State(previosState : MyFsmState) : void
  {
    ... код обработки 
  }


сложно ли такую структуру считать в макросе? Там видимо понадобится дополнительный рекурсивный разбор каждого вложенного состояния и создание иерархии, при этом надо как то выделить части | name, и вложенные state. Hадо еще чтобы какой то класс хранил состояние структуру, то есть создавался однократно и использовался как синглтон в дальнейшем. Сложность пока только в том чтобы распознать конструкции | и state на одном уровне, остальное дело техники, не могли бы вы Владислав описать примерно как такое можно сделать, и что такое ..$ в цитате, в прошлом посте оно позволило развернуть лист выражений в цитате? В этих конструкциях match с чем конкретно он сравнивает, после switch такая конструкция выглядит странно, как я понимаю там списки из Expression, то есть он сравнивает списки экземпляров Expression?

VD>

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

VD>Вот как могут выглядеть заглушки (т.е. без реализации) этих макросов:

VD>
VD>  [Nemerle.MacroUsage(Nemerle.MacroPhase.BeforeTypedMembers, Nemerle.MacroTargets.Class)]
VD>  macro FsmDef(type_builder : TypeBuilder, body)
VD>  {
VD>  }

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


VD>Понимаю, что без опыта создания макросов распознать столь не тривиальную структуру будет не просто. По этому я потратил час, чтобы набросать примерную реализацию макросов. Вот что у меня получилось...

VD>Сами макросы:
VD>
VD>using Nemerle;
VD>using Nemerle.Compiler;
VD>using Nemerle.Compiler.Parsetree;
VD>using System.Diagnostics;

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

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

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

CU>Спасибо за ответ Владислав и подробное описание, хотел заметить что автомат иерархический, в Boo был описан плоский автомат, на практике чаще автоматы нужны сложные, то есть когда состояния могут быть вложенными друг в друга, как я описал здесь:...


Я показал как можно спроектировать синтаксис и как его разбирать. Думаю, что основные моменты я показал, так что будет не сложно переделать его в иерархический вариант.

CU>графически они выглядят примерно так

CU>здесь

CU>В принципе с метаатрибутом описать весь автомат можно, лишь бы это не сложно было сделать, тогда описание будет выглядеть примерно так:


CU>
...
CU>           state Sub1
CU>           {
CU>           | entry => {
CU>                      Action1 // действие при входе
CU>                      }
CU>           | exit => Action2 // действие при выходе
CU>})]
CU>


Так выглядит красивее. Только одно замечание. Вот эти вот "entry" и "exit" ведь к самому автомату отношения не имеют, да? Это ведь реакция?
На мой взгляд лучше не забивать описание автомата действиями которые должны осуществляться при переходах. Действия лучше помещать в методы внутри класса. Метод EnterIn_IdleInGear_State(), который я изобразил в своем примере как раз и был таким действием. Их можно помечать атрибутами или распознавать по имени. В моем случае подразумевалось, что он будет распознаваться оп имени. Можно их описывать и на основании атрибутов. Например, для приведенного выше фрагмента можно описать реакции так:
[OnEnter(State=Sub1)]
{
  Action1 // действие при входе
}

[OnExit(State=Sub1)]
{
  Action2 // действие при выходе
}


Это позволит разгрузить описание самого автомат и иметь интеллисенс (дополнение при вводе, подсветку, хинты, ...) внутри методов.

CU>сложно ли такую структуру считать в макросе?


Макросам без разницы. Они разбирают абстрактный код. Принцип я показал. Если будут какие-то непонимания или вопросы, то можно обращаться сюда или прямо ко мне на Skype (имя в скапе совпадает с ником на форуме).

Естественно, имеет смысл почитать статьи посвященные написанию макросов.

CU> Там видимо понадобится дополнительный рекурсивный разбор каждого вложенного состояния и создание иерархии, при этом надо как то выделить части | name, и вложенные state.


Да, конечно. Вам нужно расширить функцию makeTransition (Ее, собственно, по любому нужно развивать.):
def makeTransition(transitionDef : MatchCase) : void
{
  Message.Hint(transitionDef.Location, $"  Events=$(transitionDef.patterns) Transition=$(transitionDef.body)")
}

В transitionDef.body может находиться произвольный Nemerle-код. Так как мы объявили макрос state, то в transitionDef.body может находиться и он (или даже несколько обращений к нему).

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

CU>Hадо еще чтобы какой то класс хранил состояние структуру, то есть создавался однократно и использовался как синглтон в дальнейшем.


Это предложение я не понял.
Речь идет о создании объектной модели автомата?
Или о том, что описание одного автомата может использоваться для разных задач?

CU> Сложность пока только в том чтобы распознать конструкции | и state на одном уровне,


Что значит на одном уровне?
Как распознавать state и его вхождения я показал. Вам нужно только немного расширить это распознавание сделав его рекурсивным. Сейчас распознование конструкции "| X => Y" выливается в банальный вывод текста в вывод компилятора:
def makeTransition(transitionDef : MatchCase) : void
{
  Message.Hint(transitionDef.Location, $"  Events=$(transitionDef.patterns) Transition=$(transitionDef.body)")
}

Вам же нужно развить это дело.

CU> остальное дело техники, не могли бы вы Владислав описать примерно как такое можно сделать, и что такое ..$ в цитате, в прошлом посте оно позволило развернуть лист выражений в цитате?


Это синтаксис квази-цирирования. Оно работает в две стороны (на распознавание и на композицию кода). Конкретно "..$x" позволяет распознать набор повторяющихся участов кода в список или сгенерировать код из списока. Такая конструкция может применятся только в скобках (в данном случсе в фигурных). Более подробно о квази-цитировании написано во второй статье
Автор(ы): Чистяков Владислав Юрьевич
Дата: 18.08.2011
Во второй части статьи о макросах Nemerle речь пойдет о макросах уровня выражения, о макросах, изменяющих синтаксис языка, а также о контексте компиляции, доступном в макросах, и тех возможностях, которые он предоставляет (типизации выражений, получении доступа к описанию типов проекта, информации о методах и т.п.).
курса Макросы Nemerle &mdash; расширенный курс (см. раздел "Работа с квази-цитированием").

Перед тем как браться за серьезные макросы, а данный тип макроса несомненно не самый простой, следует прочитать этот курс. По крайней мере первые две статьи.

CU>В этих конструкциях match с чем конкретно он сравнивает, после switch такая конструкция выглядит странно, как я понимаю там списки из Expression, то есть он сравнивает списки экземпляров Expression?


Там список так называемых PExpr. PExpr — это вариантный тип описывающих код немерла. Грубо говоря — это АСТ немерла. Чтобы не нужно было знать все типы данных в него входящий и писать сложный и громоздкий код была придумана система квази-цитирования. В общем, в указанном курсе все это описано.

ЗЫ

Один полезный совет... Чтобы лучше понимать происходящее на работу макросов лучше смотреть из под отладчика. Просто добавьте в некоторый фрагмент кода макроса:
assert2(false);

и скомпилируйте тестовый проект. Когда появится окно ассерат нажмите Retry и вы окажетесь в отладчике (нужно открыть его в отдельной ссесси, не той в котрой идет компиляция). Далее можно будет идти по коду макроса отладчиком и смоетреть значения и тип переменных. Это резко упрощает понимание (особенно чужого кода).
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Nemerle DSL] Описание конечного автомата
От: CodingUnit Россия  
Дата: 16.02.10 13:56
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, CodingUnit, Вы писали:


CU>>Спасибо за ответ Владислав и подробное описание, хотел заметить что автомат иерархический, в Boo был описан плоский автомат, на практике чаще автоматы нужны сложные, то есть когда состояния могут быть вложенными друг в друга, как я описал здесь:...


VD>Я показал как можно спроектировать синтаксис и как его разбирать. Думаю, что основные моменты я показал, так что будет не сложно переделать его в иерархический вариант.


CU>>графически они выглядят примерно так

CU>>здесь

CU>>В принципе с метаатрибутом описать весь автомат можно, лишь бы это не сложно было сделать, тогда описание будет выглядеть примерно так:


CU>>
VD>...
CU>>           state Sub1
CU>>           {
CU>>           | entry => {
CU>>                      Action1 // действие при входе
CU>>                      }
CU>>           | exit => Action2 // действие при выходе
CU>>})]
CU>>


VD>Так выглядит красивее. Только одно замечание. Вот эти вот "entry" и "exit" ведь к самому автомату отношения не имеют, да? Это ведь реакция?

VD>На мой взгляд лучше не забивать описание автомата действиями которые должны осуществляться при переходах. Действия лучше помещать в методы внутри класса. Метод EnterIn_IdleInGear_State(), который я изобразил в своем примере как раз и был таким действием. Их можно помечать атрибутами или распознавать по имени. В моем случае подразумевалось, что он будет распознаваться оп имени. Можно их описывать и на основании атрибутов. Например, для приведенного выше фрагмента можно описать реакции так:
VD>[c#]

Все работает замечательно, на Nemerle можно строить неплохие DSL, но вот беда, почему то не хотят создаваться в цикле объявления событий, то есть создаются правильные PExpr объявления члена класса события, но когда начинается конечная компиляция оказывается что члены поля и методов add,remove генерируются с одинаковыми именами, соответствующих названию в той строчке в которой производилось объявление, например если делаешь так:


// функция которая создает события из описания где то списка событий
def create_events(model)
{
model.Events.Map(x =>
 {
 def ename : string =x.Name; // это имя члена
 <[ decl: public event $(ename : usesite) : EventHandler; ]> // на выходе объявление члена события
 });
}

def defs=create_events(model); // создаем список объявлений
defs.Iter(x => typebuilder.Define(x)); // добавляем в typebuilder


при этом возникает ошибка в духе: _N2_ename_usesite_field duplicated identifier, тоже самое и с методами add,remove, исследовав этот вопрос оказывается когда создается с помощью цитаты объявление события оно всегда использует имя объявления в данном случае текст $(ename : usesite), и с помощью него создает имя членов, видимо подразумевая что событие должно объявлятся с константным именем и всегда разным, но что делать если надо объявить на основе динамических данных? Я решил эту задачу только с помощью специального метода, который берет объявление с помощью цитаты, которое преобразуется в ClassMember.Event и создает новый класс ClassMember.Event заменяя имена на новые и оставляя тела такие же как в старом созданном с помощью цитаты, тогда сообщение об ошибке не возникает, но это не выход, надо видимо чтобы имя полей и методов add,remove, при объявлении квази цитаты использовалось уникальное на основе имени которое берется из выражения, а не на основе текста выражения, это надо править компилятор, где бы найти файл где вся эта логика распознавания цитат реализуется? Может ли помочь наш всемогущий Владислав?
Re[4]: [Nemerle DSL] Описание конечного автомата
От: hardcase Пират http://nemerle.org
Дата: 16.02.10 14:55
Оценка:
Здравствуйте, CodingUnit, Вы писали:

CU>при этом возникает ошибка в духе: _N2_ename_usesite_field duplicated identifier, тоже самое и с методами add,remove


Вот такой хак насильно изготавливает event-ы:

using Nemerle;
using Nemerle.Compiler;
using PT = Nemerle.Compiler.Parsetree;

namespace MacroLibrary3 {

    [MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Class)]
    public macro CreateEvents(tb : TypeBuilder) {
        Helper.CreateEvents(tb, [ "Foo", "Bar" ]);
    }

    module Helper {
        public CreateEvents(tb :TypeBuilder, events : list[string]) : void {
            foreach(name in events) {
                BuildEvent(tb, name, <[ System.EventHandler ]>);
            }
        }
        
        private BuildEvent(tb : TypeBuilder, name : string, evt_type : PT.PExpr) : void {
            def evt_field_name = PT.Name("_N_" + name);
            def evt_field = PT.Splicable.Name(evt_field_name);
            def field = <[ decl : private mutable $evt_field : $evt_type; ]>;

            def add_proc = <[ decl :
                private $("add_" + name : usesite)(value : $evt_type) : void {
                    if(null == $(evt_field_name : name)) {
                        $(evt_field_name : name) = value;
                    } else {
                        $(evt_field_name : name) = System.Delegate.Combine($(evt_field_name : name), value) :> $evt_type;
                    }
                }
            ]>;

            def remove_proc = <[ decl :
                private $("remove_" + name : usesite)(value : $evt_type) : void {
                    when(null != $(evt_field_name : name)) {
                        $(evt_field_name : name) = System.Delegate.Remove($(evt_field_name : name), value) :> $evt_type;
                    }
                }
            ]>;

            def evt_mods = Modifiers(NemerleAttributes.Public, []);
            def evt_name = PT.Splicable.Name(PT.Name(name));
            def evt = PT.ClassMember.Event(evt_name, evt_mods, tb.ParsedTypeName, field, add_proc, remove_proc);

            tb.Define(evt);
        }
    }

}



Проблема в том, что несмотря на NemerleAttributes.Public событие остается привытным.

Дойду до дому — повожусь еще.
http://nemerle.org/Banners/?t=Developer!&g=dark /* иЗвиНите зА неРовнЫй поЧерК */
Re[4]: [Nemerle DSL] Описание конечного автомата
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.02.10 15:22
Оценка:
Здравствуйте, CodingUnit, Вы писали:

CU>при этом возникает ошибка в духе: _N2_ename_usesite_field duplicated identifier, тоже самое и с методами add,remove, исследовав этот вопрос оказывается когда создается с помощью цитаты объявление события оно всегда использует имя объявления в данном случае текст $(ename : usesite), и с помощью него создает имя членов, видимо подразумевая что событие должно объявлятся с константным именем и всегда разным, но что делать если надо объявить на основе динамических данных? Я решил эту задачу только с помощью специального метода, который берет объявление с помощью цитаты, которое преобразуется в ClassMember.Event и создает новый класс ClassMember.Event заменяя имена на новые и оставляя тела такие же как в старом созданном с помощью цитаты, тогда сообщение об ошибке не возникает, но это не выход, надо видимо чтобы имя полей и методов add,remove, при объявлении квази цитаты использовалось уникальное на основе имени которое берется из выражения, а не на основе текста выражения, это надо править компилятор, где бы найти файл где вся эта логика распознавания цитат реализуется? Может ли помочь наш всемогущий Владислав?


А можно прислать мне проект воспроизводящий ошибку? Желательно, чтобы в нем не было ничего лишнего, только описанная проблема. Тогда я постараюсь посмотреть, что происходит, и если получится, поправить баг в компиляторе или объяснить как нужно действовать.
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [Nemerle DSL] Описание конечного автомата
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.02.10 15:43
Оценка:
Здравствуйте, CodingUnit, Вы писали:

Да... Пока я (или кто-то еще) не разобрался в ошибке можно обойти проблему описывая свойство явно. Что-то вроде:
def fieldName = Macros.NewSymbol(field.Name);

tb.Define(<[ decl: private mutable $(fieldName : name) : EventHandler; ]>);

tb.Define(<[ decl: 
  public event $(ename : usesite) : EventHandler
  {
    add    { $(fieldName : name) += value; }
    remove { $(fieldName : name) -= value; } 
  }
]>);
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Nemerle DSL] Описание конечного автомата
От: CodingUnit Россия  
Дата: 16.02.10 15:46
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, CodingUnit, Вы писали:


CU>>при этом возникает ошибка в духе: _N2_ename_usesite_field duplicated identifier, тоже самое и с методами add,remove, исследовав этот вопрос оказывается когда создается с помощью цитаты объявление события оно всегда использует имя объявления в данном случае текст $(ename : usesite), и с помощью него создает имя членов, видимо подразумевая что событие должно объявлятся с константным именем и всегда разным, но что делать если надо объявить на основе динамических данных? Я решил эту задачу только с помощью специального метода, который берет объявление с помощью цитаты, которое преобразуется в ClassMember.Event и создает новый класс ClassMember.Event заменяя имена на новые и оставляя тела такие же как в старом созданном с помощью цитаты, тогда сообщение об ошибке не возникает, но это не выход, надо видимо чтобы имя полей и методов add,remove, при объявлении квази цитаты использовалось уникальное на основе имени которое берется из выражения, а не на основе текста выражения, это надо править компилятор, где бы найти файл где вся эта логика распознавания цитат реализуется? Может ли помочь наш всемогущий Владислав?


VD>А можно прислать мне проект воспроизводящий ошибку? Желательно, чтобы в нем не было ничего лишнего, только описанная проблема. Тогда я постараюсь посмотреть, что происходит, и если получится, поправить баг в компиляторе или объяснить как нужно действовать.


У меня сейчас интеграция вылетела, не хочет переставлятся, нужно будет студию переставлять, сложно будет собрать внятный проект, но описанную проблему легко воспроизвести, достаточно создать макроаттрибут который добавляет члены к классу, далее сделать массив строк имен эвентов и преобразовать в цитатное объявление события так:


void MacroHelper(typebuilder : TypeBuilder)
 {

def create_events(names : list[string])
{
names.Map(x =>
 {
 def ename : string =x; // это имя члена
 <[ decl: public event $(ename : usesite) : EventHandler; ]> // на выходе объявление члена события
 });
}

def names="abc" :: "cda" :: []; // cоздаем массив хотя бы двух имен
def defs=create_events(names); // создаем список объявлений
defs.Iter(x => typebuilder.Define(x)); // добавляем в typebuilder
}


потом добавляем макроаттрибут к какому нибудь классу, и собираем проект с применением макроса и проблема себя проявит сообщением о сдублированных идентификаторов генерируемых компилятором для закрытого поля события и методов add,remove которые все будут именоваться одним именем имеющим в себе название "ename_usesite" по объявлению, кроме как хаком пока такая проблема не решается, хорошо бы глянуть поглубже, если че то не будет работать, то я завтра сделаю проект.
Re[5]: [Nemerle DSL] Описание конечного автомата
От: hardcase Пират http://nemerle.org
Дата: 16.02.10 16:12
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>А можно прислать мне проект воспроизводящий ошибку? Желательно, чтобы в нем не было ничего лишнего, только описанная проблема. Тогда я постараюсь посмотреть, что происходит, и если получится, поправить баг в компиляторе или объяснить как нужно действовать.


Макрос:
    [MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Class)]
    public macro CreateEvents(tb : TypeBuilder) {
        Helper.CreateEvents(tb, [ "Foo", "Bar" ]);
    }

    module Helper {
        public CreateEvents(tb : TypeBuilder, events : list[string]) : void {
            foreach(name in events) {
                BuildEvent2(tb, name, <[ System.EventHandler ]>);
            }
        }
        
        private BuildEvent2(tb : TypeBuilder, name : string, evt_type : PT.PExpr) : void {
            tb.Define(<[ decl:
                public event $(name : usesite) : $evt_type;
            ]>);
        }
    }

Использование:
[CreateEvents] class C1 { }


Ошибки вида:
mutable _N__N_event_field_of__name___usesite__3262 : System.EventHandler; redefined in 'C1'
redefinition of method C1.add_(name : usesite)(value : System.EventHandler) : void

Текст (name : usesite) наводит на определенные мысли...
http://nemerle.org/Banners/?t=Developer!&g=dark /* иЗвиНите зА неРовнЫй поЧерК */
Re[5]: [Nemerle DSL] Описание конечного автомата
От: hardcase Пират http://nemerle.org
Дата: 16.02.10 16:19
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, CodingUnit, Вы писали:


VD>Да... Пока я (или кто-то еще) не разобрался в ошибке можно обойти проблему описывая свойство явно. Что-то вроде:

VD>
VD>def fieldName = Macros.NewSymbol(field.Name);

VD>tb.Define(<[ decl: private mutable $(fieldName : name) : EventHandler; ]>);

VD>tb.Define(<[ decl: 
VD>  public event $(ename : usesite) : EventHandler
VD>  {
VD>    add    { $(fieldName : name) += value; }
VD>    remove { $(fieldName : name) -= value; } 
VD>  }
VD>]>);
VD>


Судя по всему неправильно формируются имена свойств и add/remove методов. Допилил предыдущий хак до такого вида:
public BuildEvent(tb : TypeBuilder, name : string, evt_type : PT.PExpr) : void {
    def evt_field_name = PT.Name("_" + name);
    def evt_field = PT.Splicable.Name(evt_field_name);
    def field = <[ decl : private mutable $evt_field : $evt_type; ]>;

    def adder = <[ decl :
        ..$(Modifiers(NemerleAttributes.Public | NemerleAttributes.SpecialName, []))
        $("add_" + name : usesite)(value : $evt_type) : void {
            $(evt_field_name : name) = System.Delegate.Combine($(evt_field_name : name), value) :> $evt_type;
        }
    ]>;

    def remover = <[ decl :
        ..$(Modifiers(NemerleAttributes.Public | NemerleAttributes.SpecialName, []))
        $("remove_" + name : usesite)(value : $evt_type) : void {
            $(evt_field_name : name) = System.Delegate.Remove($(evt_field_name : name), value) :> $evt_type;
        }
    ]>;

    def evt_mods = Modifiers(NemerleAttributes.Public, []);
    def evt_name = PT.Splicable.Name(PT.Name(name));
    def evt = PT.ClassMember.Event(evt_name, evt_mods, tb.ParsedTypeName, field, adder, remover);

    tb.Define(evt);
}

К соответствующему свойству для события Foo из класса можно обращаться через подчеркивание: _Foo, операторы += и -= не работают — нужно явно обращаться к add_Foo и remove_Foo.
http://nemerle.org/Banners/?t=Developer!&g=dark /* иЗвиНите зА неРовнЫй поЧерК */
Re[6]: [Nemerle DSL] Описание конечного автомата
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.02.10 17:07
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Текст (name : usesite) наводит на определенные мысли...


Ага. Вот какой код генерируется в итоге (C# by Reflector):
tb.Define(
  new ClassMember.Event(
    new Splicable.Name(new Name(name, ManagerClass.Instance.MacroColors.UseColor, ManagerClass.Instance.MacroColors.UseContext)), 
    new Modifiers(NemerleAttributes.Public, list<PExpr>.Nil._N_constant_object),
    evt_type, 
    new ClassMember.Field(new Splicable.Name(Name.NameInCurrentColor("_N__N_event_field_of__name___usesite__3262", _N_MacroContexts.Get(1, ManagerClass.Instance))), new Modifiers(NemerleAttributes.Mutable | NemerleAttributes.Private, list<PExpr>.Nil._N_constant_object), evt_type),
    new ClassMember.Function(new Splicable.Name(Name.NameInCurrentColor("add_(name : usesite)", _N_MacroContexts.Get(1, ManagerClass.Instance))), new Modifiers(NemerleAttributes.Public, list<PExpr>.Nil._N_constant_object), new PFunHeader(new Typarms(list<Splicable>.Nil._N_constant_object, list<Constraint>.Nil._N_constant_object), new Splicable.Name(Name.NameInCurrentColor("add_(name : usesite)", _N_MacroContexts.Get(1, ManagerClass.Instance))), new PExpr.Void(), new list<PParameter>.Cons(new PParameter(new Splicable.Name(Name.NameInCurrentColor("value", _N_MacroContexts.Get(1, ManagerClass.Instance))), new Modifiers(NemerleAttributes.None, list<PExpr>.Nil._N_constant_object), evt_type), list<PParameter>.Nil._N_constant_object)), list<PExpr>.Nil._N_constant_object, FunBody.Abstract._N_constant_object), 
    new ClassMember.Function(new Splicable.Name(Name.NameInCurrentColor("remove_(name : usesite)", _N_MacroContexts.Get(1, ManagerClass.Instance))), new Modifiers(NemerleAttributes.Public, list<PExpr>.Nil._N_constant_object), new PFunHeader(new Typarms(list<Splicable>.Nil._N_constant_object, list<Constraint>.Nil._N_constant_object), new Splicable.Name(Name.NameInCurrentColor("remove_(name : usesite)", _N_MacroContexts.Get(1, ManagerClass.Instance))), new PExpr.Void(), new list<PParameter>.Cons(new PParameter(new Splicable.Name(Name.NameInCurrentColor("value", _N_MacroContexts.Get(1, ManagerClass.Instance))), new Modifiers(NemerleAttributes.None, list<PExpr>.Nil._N_constant_object), evt_type), list<PParameter>.Nil._N_constant_object)), list<PExpr>.Nil._N_constant_object, FunBody.Abstract._N_constant_object)
  )


Таким образом еще при формировании цитаты формируется некорректный код.
К сожалению искать место в компиляторе которое занимается лифтингом квази-цитат событий не так просто. На это уйдет время.
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: [Nemerle DSL] Описание конечного автомата
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.02.10 18:38
Оценка:
Здравствуйте, VladD2, Вы писали:

В общем, проблема возникает еще при парсинге квази цитаты (см. файл MainParser.n) строка 1619 и далее со строки 1696 и далее:
parse_event (mutable loc : Location, mods : Modifiers,
             mutable customs : list [Token.SquareGroup]) : ClassMember
{
  def id = get_splicable_id ();
  def ret_type = parse_return_type(false, Location.Default);
  loc += ret_type.Location;
  def plain_name = id.ToString();
  ...
      /// auto-generated field
      def fieldName = "_N_event_field_of_" + plain_name;
      def fmods = NemerleAttributes.Private %| NemerleAttributes.Mutable;
      def doRenaming = !Manager.IsIntelliSenseMode;
      def generatedLoc = loc.AsGenerated();
      def field_name = if (doRenaming) MkTempName(fieldName, generatedLoc) else MkName(fieldName, generatedLoc);
      def field_attrs = Modifiers (fmods, []);
      take_attributes_out (ref customs, System.AttributeTargets.Field, false, field_attrs);
      def field = ClassMember.Field (generatedLoc, Splicable.Name (field_name), field_attrs, ret_type);
      field.ParsedType = ret_type;

      def method_atts = Modifiers (mods.Attributes, []);                                         
      take_attributes_out (ref customs, System.AttributeTargets.Method, true, method_atts);

      def method_parms = [PParameter (val_n, ret_type, Modifiers ())];

      def name = MkSplicableName("add_" + plain_name, generatedLoc);
      def fh = PFunHeader (generatedLoc, name, PExpr.Void (generatedLoc), method_parms);
      // funbody is filled during typing
      add = ClassMember.Function (generatedLoc, name, method_atts, fh, [], null);
      add._env = env;

      def name = MkSplicableName("remove_" + plain_name, generatedLoc);
      def fh = PFunHeader (generatedLoc, name, PExpr.Void (generatedLoc), method_parms);
      remove = ClassMember.Function (generatedLoc, name, method_atts, fh, [], null);
      remove._env = env;

      ClassMember.Event (loc, id, mods, ret_type, field, add, remove)


В случае использования сплайсинга в имени свойства в id находится не имя, а выражение (для приведенного примера — это выражение "(name : usesite)". Оно тупо приводится к строке и далее используется для формирования имени поля, методов add_Xxx и remove_Xxx. Естественно, что в результате получается чушь.

Этот код нужно переписывать, так чтобы вместо генерации имени во время парсига, генерировался бы код который формировал бы имена во время компиляции исходника (т.е. раскрытия макроса в нашем случае).
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: [Nemerle DSL] Описание конечного автомата
От: hardcase Пират http://nemerle.org
Дата: 16.02.10 18:45
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, VladD2, Вы писали:


VD>В общем, проблема возникает еще при парсинге квази цитаты (см. файл MainParser.n) строка 1619 и далее со строки 1696 и далее:



Я правильно думаю что plain_name должен получаться также как и в parse_property (строка 1340):
def plain_name = match (id) { | Splicable.Name (n) => n.Id | _ => "" };
http://nemerle.org/Banners/?t=Developer!&g=dark /* иЗвиНите зА неРовнЫй поЧерК */
Re[9]: [Nemerle DSL] Описание конечного автомата
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.02.10 19:20
Оценка:
Здравствуйте, hardcase, Вы писали:


H>Я правильно думаю что plain_name должен получаться также как и в parse_property (строка 1340):

H>
H>def plain_name = match (id) { | Splicable.Name (n) => n.Id | _ => "" };
H>


"plain_name" вообще не должно быть. Вместо этого нужно генерировать код который сформирует этот самый plain_name динамически (во время компиляции).

Но что вот как это сделать я пока не понял. Уже устал. Да и задачка получается слишком высшего порядка.

По уму имя поля, add-ера и remove-ера нужно формировать динамически.

Splicable — это вариант который описан следующим образом:
  public variant Splicable : Located
  {
    | Name { body : Parsetree.Name; }
    | Expression
      {
        expr : PExpr;
        [RecordIgnore] public mutable env : GlobalEnv;
        public this(expr : PExpr, env : GlobalEnv) { this.expr = expr; this.env = env; }
        public this(loc : Location, expr : PExpr, env : GlobalEnv)
        {
          this.loc = loc; this.expr = expr; this.env = env;
        }
      }


Expression как раз и предназначен для формирования имени динамически.
Посему для формирования имен поля, add-ера и remove-ера нужно сформировать экзепляры Splicable.Expression() которые будут содержать код из имени свойства плюс дополнительный код модифицирующий имя так чтобы из него получались требуемые имена.

Сплайс вида "$(name : usesite)" на этапе компиляции квази-цитаты преобразуется в:
Splicable.Expression(<[ name : usesite ]>, GlobalEnv(...))

Думается, что нужно разобрать выражение <[ name : usesite ]> "на запчасти" и вцепить из него выржение (в данном случае "name"), тип (в данном случае "usesite") и сформировать новые выражение которое будут выглядеть как-то так:
<[ ("_N_event_field_of_" + name) : usesite ]> // для имени поля 
<[ ("add_" + name) : usesite ]>               // для имени add-ера
<[ ("remove_" + name) : usesite ]>            // для имени remove-ра

ну, и далее сформировать на их основе новые варианты Splicable.Expression() которые и использовать для формирования имен соответствующих сущностный.

ЗЫ

Откровенно говоря у меня сейчас ужасный цейтнот, так что заняться в ближайшее время я этим наверно не смогу. Буду рад, если кто-то попытается исправить этот баг.
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.