Как сделать и найти метку в дереве AST
От: FDSC Россия consp11.github.io блог
Дата: 05.08.11 23:32
Оценка:
Пытаюсь в очередной раз изучить макросы, но не получается.

Нужно написать макросы, которые будут позволять:
первый макрос
1. Поставить на метод метку допустимого состояния вызова в некотором конечном автомате
2. Сгенерировать runtime-проверку в методе: что он вызван там, где надо

Второй макрос:
1. Поставить метку на часть кода, например, mark("имя метки") {делаем_нечто_страшное}
2. При компиляции метода найти выделенный этой меткой код и проанализировать его AST (например, чтобы вычислить правильную последовательность, заданную первым макросом не в runtime, а во время компиляции)

На второй макрос даже не представляю куда копать. Подскажите.

С первым макросом пытаюсь сделать что-то такое

using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;


namespace marks
{
    // Этим макросом пытаемся поставить метку на AST
  macro verificationMarkLogic (vlMarkName, logicTest, body)
  syntax ("vlMark", "(", vlMarkName, logicTest,  ")", body)
  {
      // Делаем кортеж из данных метки и тела метода
      def typle = vlMarkName::logicTest::body::[];
      <[
        def result = $(PExpr.Tuple.Create
        (
            typle
        ));

        // Возвращаем результат вычисления метода
        result[2]
        ]>
  }
  

    // Определяем макрос-атрибут метода
  [Nemerle.MacroUsage (Nemerle.MacroPhase.WithTypedMembers,
  Nemerle.MacroTargets.Method, Inherited = true)]
  // У макроса должно быть два параметра:
  //   строка-имя состояния конечного автомата, в которое переводится автомат после выполнения данного метода
  //   и список допустимых состояний, в которых можно вызывать данный метод
  macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, vlStateName, vlAllowableStates)
  {
      // Если членов класса почему-то нет, то считаем это ошибкой
      if (!tb.LookupMemberAvailable)
      {
          def msg = "lookup member not available for class $(tb.Name) in the macro verificationMarkState";
          Message.Error(meth.Location, msg);
      }
      else
      {
          // Вводим служебную переменную
          def serviceMemberName = tb.Name + "_verificationMark_state";
          mutable flag = false;
          when (tb.LookupMember (serviceMemberName) == [])
            tb.Define (<[ decl: $serviceMemberName: string = "" ]>);

          // Генерируем новое тело метода
          def msg = "Not allowed state for class $(tb.Name) in method $(meth.Name)";
          meth.Body =
          <[
            // Проверяем, что текущее состояние позволяет вызывать данный метод
            def temporary_verification_flag = false;
            foreach (allowableState in vlAllowableStates)
            {
                when ( $serviceMemberName == allowableState)
                {
                    temporary_verification_flag = true;
                    break;
                }
            }
            assert(temporary_verification_flag, $msg);

            // Выполняем вычисления и меняем состояние, если не произошло исключение
            $(meth.Body);
            $serviceMemberName = $vlStateName;
          ]>
      }
  }


  public partial class MainForm : Form
  {
    public this()
    {
      InitializeComponent();
    }
  
    private button1_Click (_ : object,  _ : System.EventArgs) : void
    {
        markTest();
    }

    class File
    {
        [verificationMarkState("initialized", "":[])]
        public this()
        {
            _ = MessageBox.Show("File");
        }

        [verificationMarkState("opened", "initialized":[])]
        public open(): void
        {
            _ = MessageBox.Show("open");
        }

        public doing(): void
        {
            _ = MessageBox.Show("doing");
        }

        [verificationMarkState("closed", "opened":[])]
        public close(): void
        {
            _ = MessageBox.Show("Close");
        }
    }
    
    public markTest(): void
    {
        def f = File();
        f.open();
        f.doing();
        f.close();
        f.close();
    }
  }
}


Этот код при компиляции выдаёт

Build FAILED.

"D:\works\programming\AITryes\macroTryes\marks\marks.nproj" (Build target) (1)
->
(CoreCompile target) ->
MainForm.n(97,10,97,53): error : the custom attribute `verificationMarkState(
"initialized", ("" : []))' could not be found or is invalid [D:\works\programmi
ng\AITryes\macroTryes\marks\marks.nproj]

0 Warning(s)
1 Error(s)

Time Elapsed 00:00:00.83

верификация конечный_автомат
Re: Как сделать и найти метку в дереве AST
От: FDSC Россия consp11.github.io блог
Дата: 05.08.11 23:39
Оценка:
Кажется, так правильнее, но всё равно не помогает


  macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, params opts : list [PExpr])//, vlStateName, vlAllowableStates)
  {
      def vlStateName       = opts[0];
      def vlAllowableStates = opts[1];
Re: Как сделать и найти метку в дереве AST
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.08.11 02:23
Оценка: 1 (1)
Здравствуйте, FDSC, Вы писали:

FDS> MainForm.n(97,10,97,53): error : the custom attribute `verificationMarkState(

FDS>"initialized", ("" : []))' could not be found or is invalid [D:\works\programmi
FDS>ng\AITryes\macroTryes\marks\marks.nproj]

Дык, нужно же открыть пространство имен в котором определен макрос.

Плюс макрос должен быть скомпилирован в отдельную макро-сборку и подключен к проекту его использующему через ссылку на сборку.

Просто помни, что макрос — это плагин к компилятору и работает он во время компиляции, и все встанет на свои места.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Как сделать и найти метку в дереве AST
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.08.11 02:25
Оценка:
Здравствуйте, FDSC, Вы писали:

FDS>Кажется, так правильнее, но всё равно не помогает

FDS>
FDS>  macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, params opts : list [PExpr])//, vlStateName, vlAllowableStates)
FDS>  {
FDS>      def vlStateName       = opts[0];
FDS>      def vlAllowableStates = opts[1];
FDS>


Чтобы использовать доступ по индексу нужно использовать array, а не list.

Что же касается твой задачи, то сначала ее нужно как следует описать. Пока что я смутно понимаю что тебе надо.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Как сделать и найти метку в дереве AST
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.08.11 02:30
Оценка:
Здравствуйте, FDSC, Вы писали:

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


FDS>Нужно написать макросы, которые будут позволять:

FDS>первый макрос
FDS>1. Поставить на метод метку допустимого состояния вызова в некотором конечном автомате

Это слишком расплывчатое требование. Объясни подробнее, что ты имеешь в виду под меткой и как ты это себе видишь. Оптимально было бы привести код без макросов (с ручной реализацией), чтобы было понятно как его сгенерировать макросом.

FDS>2. Сгенерировать runtime-проверку в методе: что он вызван там, где надо


Для этого нужно сделать макро-атрибует и в нем сгенерировать нужные проверки. Ну, а чтобы ответить, что конкретно нужно делать, нужно понять саму идею.

CodingUnit тут создает целую библиотеку для работы с КА. Возможно стоит попробовать использовать ее.

FDS>Второй макрос:

FDS>1. Поставить метку на часть кода, например, mark("имя метки") {делаем_нечто_страшное}
FDS>2. При компиляции метода найти выделенный этой меткой код и проанализировать его AST (например, чтобы вычислить правильную последовательность, заданную первым макросом не в runtime, а во время компиляции)

Это какой-то слишком сложный план. Уверен, что все можно сделать намного проще.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Как сделать и найти метку в дереве AST
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.08.11 12:50
Оценка:
Здравствуйте, FDSC, Вы писали:

Посмотрел код на свежею голову... Нашел множество ошибок (я так думаю).

Разберу код приведенный ниже по частям.

FDS>namespace marks
FDS>{
FDS>    // Этим макросом пытаемся поставить метку на AST
FDS>  macro verificationMarkLogic (vlMarkName, logicTest, body)
FDS>  syntax ("vlMark", "(", vlMarkName, logicTest,  ")", body)
FDS>  {
FDS>      // Делаем кортеж из данных метки и тела метода
FDS>      def typle = vlMarkName::logicTest::body::[];
FDS>      <[
FDS>        def result = $(PExpr.Tuple.Create
FDS>        (
FDS>            typle
FDS>        ));

FDS>        // Возвращаем результат вычисления метода
FDS>        result[2]
FDS>        ]>
FDS>  }


Здесь не ясно что в итоге должно получиться. Какой смысл засовывать что-то в кортеж и потом возвращать один из его элементов?

Вместо vlMarkName::logicTest::body::[] можно использовать синтаксис литерала списка [vlMarkName, logicTest, body].


FDS>    // Определяем макрос-атрибут метода
FDS>  [Nemerle.MacroUsage (Nemerle.MacroPhase.WithTypedMembers,
FDS>  Nemerle.MacroTargets.Method, Inherited = true)]
FDS>  // У макроса должно быть два параметра:
FDS>  //   строка-имя состояния конечного автомата, в которое переводится автомат после выполнения данного метода
FDS>  //   и список допустимых состояний, в которых можно вызывать данный метод
FDS>  macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, vlStateName, vlAllowableStates)
FDS>  {
FDS>      // Если членов класса почему-то нет, то считаем это ошибкой
FDS>      if (!tb.LookupMemberAvailable)
FDS>      {
FDS>          def msg = "lookup member not available for class $(tb.Name) in the macro verificationMarkState";
FDS>          Message.Error(meth.Location, msg);
FDS>      }


tb.LookupMemberAvailable будет возвращать false только на фазах до MacroPhase.WithTypedMembers, так что эту проверку можно убрать, или заменить на ассерт.

FDS>      else
FDS>      {
FDS>          // Вводим служебную переменную
FDS>          def serviceMemberName = tb.Name + "_verificationMark_state";
FDS>          mutable flag = false;


Не вижу использования переменной flag.

FDS>          when (tb.LookupMember (serviceMemberName) == [])
FDS>            tb.Define (<[ decl: $serviceMemberName: string = "" ]>);

FDS>          // Генерируем новое тело метода
FDS>          def msg = "Not allowed state for class $(tb.Name) in method $(meth.Name)";
FDS>          meth.Body =
FDS>          <[
FDS>            // Проверяем, что текущее состояние позволяет вызывать данный метод


Пока все — ОК.
FDS>            def temporary_verification_flag = false;


Вот это просто бессмысленный код. По сути это объявление константы. Эту переменную нельзя будет изменить. Причем макрос скомпилируется, так как синтаксически эта конструкция корректна, но при его раскрытии (в проекте использующем макрос) будет выдано сообщение об ошибке. А так как код сгенерирован макросом, то указывать это сообщение будет на макро-атрибут, что затруднит поиск ошибки.

FDS>            foreach (allowableState in vlAllowableStates)


А вот здесь похоже на то, что перед "vlAllowableStates" забыли поставить "$". Иначе переменная с этим именем должна быть добавлена в макросе (вызовом tb.Define()).
Кроме того нужно понимать, что макросы "гигиеничные", то есть все имена объявленные внутри них не могут конфликтовать с такими же объявленными в других местах (в том числе, в рукописном коде и других макросах). Если нужно сослаться на имя из внешнего контекста, то нужно использовать конструкции $(строка_с_именем : usesite) или $(строка_с_именем : dyn). Последнее нужно применять с осторожность, так как оно полностью нарушает гигиену и использует имя из любого контекста.

Если я правильно понял задумку, то данный код должен выглядеть следующим образом:

          meth.Body =
          <[
            // Проверяем, что текущее состояние позволяет вызывать данный метод
            mutable temporary_verification_flag = false;

            foreach (allowableState in $vlAllowableStates)
            {
              ...


Идем дальше.

FDS>            {
FDS>                when ( $serviceMemberName == allowableState)
FDS>                {
FDS>                    temporary_verification_flag = true;
FDS>                    break;


break — это не оператор языка, а синтаксический макрос. И как любой другой макрос он должен быть "открыт" перед использованием. По этому, в начале файла, нужно добавить строку:
using Nemerle.Imperative;

или не использовать циклы (их можно заменить рекурсивной функцие:
          <[
            // Проверяем, что текущее состояние позволяет вызывать данный метод
            def ferify(vlAllowableStates)
            {
              | head :: _ when $serviceMemberName == head => true
              | head :: tail                              => ferify(tail)
              | _                                         => false
            }
            assert(ferify($vlAllowableStates), $msg);

Или, что еще лучше, с использованием функций высшего порядка:
assert($vlAllowableStates.Exist(x => x == $serviceMemberName), $msg);

Напоследок, еще один вариант того же самого:
assert($vlAllowableStates.Exist(_ == $serviceMemberName), $msg);

Это называется "частичное применение операторов". Я не привел его сразу, чтобы не путать. Этот код эквивалентен предыдущему примеру.
Кроме того можно вместо Exist (из стандартной библиотеки немерла) использовать Any (из Linq). Но Exist немного эффективнее, так как работает со списком немерла, а не с перечислителем, а так же принимает немерловый функциональный объект, а не делегат. Кроме того Linq нужно импортировать с помощью "using System.Linq;", а Exist для списка доступен всегда.

...
FDS> [verificationMarkState("initialized", "":[])]

Конструкция "":[] сама по себе корректная. Она рассматривается парсером немерла как оператор ":" которому в качестве параметра переданы строковый литерал и литерал пустого списка.
Вот только эта конструкция совершенно бессмысленна с точки зрения семантики немерла. Оператор ":" используется в немерле для уточнения или задания типа. При этом [] не является корректным типом. Таким образом будучи подставленным в код это выражение приведет к генерации следующего кода (с учетом моих исправлений):
assert(("":[]).Exist(_ == $serviceMemberName), $msg);

что, конечно же, не является корректным кодом.

Предполагаю, что ты ошибся с синтаксисом и вместо ":" хотел использовать "::" (конструктор списка). В прочем, опять же, тут лучше использовать синтаксис литерала списка. Таким образом твой код, видимо, должен выглядеть вот так:
[verificationMarkState("initialized", [""])]


FDS>Этот код при компиляции выдаёт


FDS>

FDS>Build FAILED.

FDS>"D:\works\programming\AITryes\macroTryes\marks\marks.nproj" (Build target) (1)
->>
FDS>(CoreCompile target) ->
FDS> MainForm.n(97,10,97,53): error : the custom attribute `verificationMarkState(
FDS>"initialized", ("" : []))' could not be found or is invalid [D:\works\programmi
FDS>ng\AITryes\macroTryes\marks\marks.nproj]


Однако, данное сообщение об ошибке указывает на то, что макрос просто не виден в данной области видимости.
Как я уже говорил, тут может быть
* Макрос не помещен в отдельную сборку. На что указывает размещение кода одной пачкой (в прочем, возможно это просто получилось при публикации кода в сообщении).
* Не открыто пространство имен marks.
* Сборка в которой находится макрос не подключена к проекту в котором используется макрос.

ЗЫ

Все указанные ошибки являются или опечатками, или непониманием базовых принципов которые сто раз описаны в документации. Так что наличие лучшей документации тут вряд ли помогла.

Несколько советов по тому как избегать подобных ошибок.

Первое что нужно понимать — макрос это не функция, а плагин к компилятору. Так как немерл является компилируемым языком, он не может быть интерпретировать свой код в процессе компиляции. По сему макросы должны компилироваться в отдельные сборки и подключаться к проекту на подобии библиотек. Макросы создаются в виде объектов во время компиляции и раскрываются (а не вызываются) порождая новый код в проекте к которому они подключены.

Отсюда следует еще один вывод. Макросы не могут изменять сами себя (однако могут использовать свои старые версии).

Далее нужно понимать, что макрос работает до типизации. По этому ошибки связанные с типами мы не можем получить до момента когда сгенерированный макросом код не начнет типизируются в целевом проекте (проекте где используется макрос). По сему очень легко создать макрос который породит некорректный код.

Чтобы макрос порождал корректный код, на первых этапах (пока мало опыта) имеет смысл сначала написать код который должен порождать макрос вручную. Это даст возможность вдеть что и как должно быть и не допустить ошибок связанных с типами. Далее, имея работающий код, нужно всего лишь сформировать код макроса который породит аналог.

Как и любая другая сложная программа макрос может требовать отладки. Для этого проще всего вставить в код макроса assert2(false) (или Debug.Assert(false)) и скомпилировать проект использующий макрос (так как макрос запускается именно в это время). В появившемся диалоге нужно выбрать "Retry". Это приведет к процедуре запуска еще одной копии студии (если мы работам из под нее) и переходу в режим отладки макроса. Далее будут доступны все возможности отладчика. Формируемый код отлично видно в окнах Local и WatchX.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Runtime с макроатрибутами
От: FDSC Россия consp11.github.io блог
Дата: 06.08.11 13:00
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Что же касается твой задачи, то сначала ее нужно как следует описать. Пока что я смутно понимаю что тебе надо.


Вот пример использования того, что я сейчас хочу получить на первом этапе
    // Тестовый класс, имеет методы, которые можно вызывать в различных состояниях
    class File
    {
        // Входной переход на initialized
        [verificationMarkState("initialized", "":[])]
        public this()
        {
            _ = MessageBox.Show("File");
        }

        // Это можно вызывать в состоянии  "initialized"
        // После вызова метода объект перейдёт в состояние "opened"
        [verificationMarkState("opened", "initialized"::[])]
        public open(): void
        {
            _ = MessageBox.Show("open");
        }

        [verificationMarkState("opened", "opened"::[])]
        public doing(): void
        {
            _ = MessageBox.Show("doing");
        }

        [verificationMarkState("closed", "opened"::[])]
        public close(): void
        {
            _ = MessageBox.Show("Close");
        }
    }

    // Здесь, собственно, тестовый код
    // В runtime должно сработать assert при втором close
    public markTest(): void
    {
        def f = File();
        f.open();
        f.doing();
        f.close();
        f.close();
    }


Но макрос у меня не компилируется.

------ Build started: Project: VerificationMacroses, Configuration: Debug Any CPU ------
        C:\Program Files (x86)\Nemerle\Net-4.0\ncc.exe 
/no-color 
/no-stdlib 
/greedy-references:- 
/define:DEBUG;TRACE 
/target:library 
/debug+ 
/project-path:D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\VerificationMacroses.nproj 
/root-namespace:VerificationMacroses

 Macro1.n
Properties\AssemblyInfo.n

/ref:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll" 
/ref:"C:\Program Files (x86)\Nemerle\Net-4.0\Nemerle.Compiler.dll" 
/ref:"C:\Program Files (x86)\Nemerle\Net-4.0\Nemerle.dll" 
/ref:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Core.dll" 
/ref:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Data.DataSetExtensions.dll" 
/ref:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Data.dll" 
/ref:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll" 
/ref:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Xml.dll" 
/ref:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Xml.Linq.dll" 
/macros:"C:\Program Files (x86)\Nemerle\Net-4.0\Nemerle.Linq.dll" 

/out:obj\Debug\VerificationMacroses.dll

D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(74,31): error : in argument #2 (tl), needed a list[string-], got list[Nemerle.Compiler.Parsetree.PExpr.Ref-]: common super type of types [string, Nemerle.Compiler.Parsetree.PExpr.Ref] is just `object', please upcast one of the types to `object' if this is desired
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(74,31): error : in argument #2 (tl), needed a list[Nemerle.Compiler.Parsetree.PExpr.Ref-], got list[string-]: common super type of types [Nemerle.Compiler.Parsetree.PExpr.Ref, string] is just `object', please upcast one of the types to `object' if this is desired
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(95,21): error : in argument #1 (resolve), needed a bool -> option[Nemerle.Compiler.Parsetree.PExpr], got bool+ -> void -> option.None[?]-: void -> option.None[?] is not a subtype of option[Nemerle.Compiler.Parsetree.PExpr] [simple require]

Done building project "VerificationMacroses.nproj" -- FAILED.


Что ему надо, никак не пойму (точнее, нужно ему вместо string PExpr, но вот как от этого уйти я не понимаю)

Сам код макроса в нынешнем варианте таков:

using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;

using Nemerle.Compiler.Parsetree;

namespace VerificationMacroses
{
  // Определяем макрос-атрибут метода
  [Nemerle.MacroUsage (Nemerle.MacroPhase.WithTypedMembers,
  Nemerle.MacroTargets.Method, Inherited = true)]
  // У макроса должно быть два параметра:
  //   строка-имя состояния конечного автомата, в которое переводится автомат после выполнения данного метода
  //   и список допустимых состояний, в которых можно вызывать данный метод
  macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, params opts : list [PExpr])//, vlStateName, vlAllowableStates)
  {
      def typer : Typer = Macros.ImplicitCTX();

      match (opts)
      {
        | vlStateName::vlAllowableStates::[] => 
                def tvlStateName       = typer.TypeExpr(vlStateName);
                def tvlAllowableStates = typer.TypeExpr(vlAllowableStates);

                def checkParamsTypes()
                {
                    def vlsnCheck = 
                    match (vlStateName)
                    {
                        | <[ ($vlsn: string) ]> => (true, vlsn)
                        | _ => (false, PExpr.Void())
                    }

                    def vlasCheck = 
                    match (vlAllowableStates)
                    {
                        | <[ ($vlasList : list[string]) ]> => (true, vlasList)
                        | _ => (false, PExpr.Void())
                    }

                    (vlsnCheck[0] && vlasCheck[0], vlsnCheck[1], vlasCheck[1])
                }
                
                def mainCalculation(vlStateNamExpr, vlAllowableStatesExpr)
                {
                    // Если членов класса почему-то нет, то считаем это ошибкой
                    if (!tb.LookupMemberAvailable)
                    {
                        def msg = "lookup member not available for class $(tb.Name) in the macro verificationMarkState";
                        Message.Error(meth.Location, msg);
                    }
                    else
                    {
                        // Вводим служебную переменную
                        def serviceMemberName = tb.Name + "_verificationMark_state";
                        mutable flag = false;
        /*                when (tb.LookupMember (serviceMemberName) == [])
                          tb.Define (<[ decl: protected mutable  $serviceMemberName: string = ""; ]>);
        */
                        // Генерируем новое тело метода
                        def msg = "Not allowed state for class $(tb.Name) in method $(meth.Name)";
                        meth.Body =
                        Util.locate
                        (
                            meth.Body.Location,
                            <[
                              // Проверяем, что текущее состояние позволяет вызывать данный метод
                              def temporary_verification_flag = false;
                              foreach (allowableState in $vlAllowableStatesExpr)
                              {
                                  when ( $serviceMemberName == allowableState)
                                  {
                                      temporary_verification_flag = true;
                                      break;
                                  }
                              }
                              assert(temporary_verification_flag, $msg);

                              // Выполняем вычисления и меняем состояние, если не произошло исключение
                              $(meth.Body);
                              $(serviceMemberName: string) = $vlStateNamExpr;
                            ]>
                        );
                    }
                };

                typer.DelayMacro
                (
                    lastTry =>
                    {
                        if (tvlStateName.Type.Hint.IsSome && tvlAllowableStates.Type.Hint.IsSome)
                        {
                            def (isCorrectTypes, vlStateNamExpr, vlAllowableStatesExpr) = checkParamsTypes();
                            if (isCorrectTypes)
                                mainCalculation(vlStateNamExpr, vlAllowableStatesExpr);
                             else
                                Message.Error($"Типы выражений ‘$vlStateName’ или ‘$vlAllowableStates’ неверны (должны быть string, list[string])");
                        }
                        else
                        {
                            when (lastTry)
                                Message.Error($"Типы выражений ‘$vlStateName’ или ‘$vlAllowableStates’ вывести не удалось");
                        }
                        option.None
                    }
                )

        | _ => Message.Error(meth.Location, "not correct arguments in macro verificationMarkState for class $(tb.Name) on method $(meth.Name)");
      }
  }


}
Re[4]: Runtime с макроатрибутами
От: FDSC Россия consp11.github.io блог
Дата: 06.08.11 13:05
Оценка:
Обшибся

Вот ошибки


/out:obj\Debug\VerificationMacroses.dll
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(64,41): error : each overload has an error during call:
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(64,41): error : overload #1, "constructor Nemerle.Compiler.Parsetree.ClassMember.Field..ctor(loc : Nemerle.Compiler.Location, name : Nemerle.Compiler.Parsetree.Splicable, modifiers : Nemerle.Compiler.Modifiers, ty : Nemerle.Compiler.Parsetree.PExpr) : Nemerle.Compiler.Parsetree.ClassMember.Field" fail because:
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(64,41): error : unresolved named parameters: `loc'
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(64,41): error : overload #2, "constructor Nemerle.Compiler.Parsetree.ClassMember.Field..ctor(loc : Nemerle.Compiler.Location, name : Nemerle.Compiler.Parsetree.Splicable, modifiers : Nemerle.Compiler.Modifiers, ty : Nemerle.Compiler.Parsetree.PExpr, initializer : Nemerle.Compiler.Parsetree.PExpr) : Nemerle.Compiler.Parsetree.ClassMember.Field" fail because:
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(64,41): error : unresolved named parameters: `loc', `initializer'
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(64,41): error : overload #3, "constructor Nemerle.Compiler.Parsetree.ClassMember.Field..ctor(name : Nemerle.Compiler.Parsetree.Splicable, modifiers : Nemerle.Compiler.Modifiers, ty : Nemerle.Compiler.Parsetree.PExpr) : Nemerle.Compiler.Parsetree.ClassMember.Field" fail because:
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(64,67): error : in argument #1 (name), needed a Nemerle.Compiler.Parsetree.Splicable, got string: System.String is not a subtype of Nemerle.Compiler.Parsetree.Splicable [simple require]
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(74,31): error : in argument #2 (tl), needed a list[string-], got list[Nemerle.Compiler.Parsetree.PExpr.Ref-]: common super type of types [string, Nemerle.Compiler.Parsetree.PExpr.Ref] is just `object', please upcast one of the types to `object' if this is desired
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(74,31): error : in argument #2 (tl), needed a list[Nemerle.Compiler.Parsetree.PExpr.Ref-], got list[string-]: common super type of types [Nemerle.Compiler.Parsetree.PExpr.Ref, string] is just `object', please upcast one of the types to `object' if this is desired
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(95,21): error : in argument #1 (resolve), needed a bool -> option[Nemerle.Compiler.Parsetree.PExpr], got bool+ -> void -> option.None[?]-: void -> option.None[?] is not a subtype of option[Nemerle.Compiler.Parsetree.PExpr] [simple require]
Done building project "VerificationMacroses.nproj" -- FAILED.


А в коде
 when (tb.LookupMember (serviceMemberName) == [])
     tb.Define (<[ decl: protected mutable  $serviceMemberName: string = ""; ]>);

надо раскомментировать
Re[2]: Как сделать и найти метку в дереве AST
От: FDSC Россия consp11.github.io блог
Дата: 06.08.11 13:29
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Кроме того нужно понимать, что макросы "гигиеничные", то есть все имена объявленные внутри них не могут конфликтовать с такими же объявленными в других местах (в том числе, в рукописном коде и других макросах). Если нужно сослаться на имя из внешнего контекста, то нужно использовать конструкции $(строка_с_именем : usesite) или $(строка_с_именем : dyn). Последнее нужно применять с осторожность, так как оно полностью нарушает гигиену и использует имя из любого контекста.



А где про это можно прочитать? В твоих статьях usesite практически не раскрывается, а dyn я вообще не помню.

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


Ну, usesites и отдельные сборки для макросов — не скажу, что сто раз описаны. Остальное — от долгого перерыва в использовании Nemerle

Странно, что раньше я создавал простейшие макросы прямо в коде, а не в отдельной сборке, и они работали. С версии 9.6 (или что-то в этом роде) что-то изменилось?

VD>Далее нужно понимать, что макрос работает до типизации. По этому ошибки связанные с типами мы не можем получить до момента когда сгенерированный макросом код не начнет типизируются в целевом проекте (проекте где используется макрос). По сему очень легко создать макрос который породит некорректный код.


И ещё совершенно непонятно и, насколько я видел, не описано в документации, что же будет делать компилятор, когда я на этапе типизированного AST создам новый код в макросе


VD>Как и любая другая сложная программа макрос может требовать отладки. Для этого проще всего вставить в код макроса assert2(false) (или Debug.Assert(false)) и скомпилировать проект использующий макрос (так как макрос запускается именно в это время). В появившемся диалоге нужно выбрать "Retry". Это приведет к процедуре запуска еще одной копии студии (если мы работам из под нее) и переходу в режим отладки макроса. Далее будут доступны все возможности отладчика. Формируемый код отлично видно в окнах Local и WatchX.


Спасибо, не знал.
Re[5]: Runtime с макроатрибутами
От: FDSC Россия consp11.github.io блог
Дата: 06.08.11 13:30
Оценка:
Новая версия кода с незначительными исправлениями


using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;

using Nemerle.Imperative;
using Nemerle.Compiler.Parsetree;

namespace VerificationMacroses
{
  // Определяем макрос-атрибут метода
  [Nemerle.MacroUsage (Nemerle.MacroPhase.WithTypedMembers,
  Nemerle.MacroTargets.Method, Inherited = true)]
  // У макроса должно быть два параметра:
  //   строка-имя состояния конечного автомата, в которое переводится автомат после выполнения данного метода
  //   и список допустимых состояний, в которых можно вызывать данный метод
  macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, params opts : list [PExpr])//, vlStateName, vlAllowableStates)
  {
      def typer : Typer = Macros.ImplicitCTX();

      match (opts)
      {
        | vlStateName::vlAllowableStates::[] => 
                def tvlStateName       = typer.TypeExpr(vlStateName);
                def tvlAllowableStates = typer.TypeExpr(vlAllowableStates);

                def checkParamsTypes()
                {
                    def vlsnCheck = 
                    match (vlStateName)
                    {
                        | <[ ($vlsn: string) ]> => (true, vlsn)
                        | _ => (false, PExpr.Void())
                    }

                    def vlasCheck = 
                    match (vlAllowableStates)
                    {
                        | <[ ($vlasList : list[string]) ]> => (true, vlasList)
                        | _ => (false, PExpr.Void())
                    }

                    (vlsnCheck[0] && vlasCheck[0], vlsnCheck[1], vlasCheck[1])
                }

                def mainCalculation(vlStateNamExpr, vlAllowableStatesExpr)
                {
                    // Если членов класса почему-то нет, то считаем это ошибкой
                    if (!tb.LookupMemberAvailable)
                    {
                        def msg = "lookup member not available for class $(tb.Name) in the macro verificationMarkState";
                        Message.Error(meth.Location, msg);
                    }
                    else
                    {
                        // Вводим служебную переменную
                        def serviceMemberName = tb.Name + "_verificationMark_state";
                        when (tb.LookupMember (serviceMemberName) == [])
                          tb.Define (<[ decl: protected mutable  $serviceMemberName: string = ""; ]>);

                        // Генерируем новое тело метода
                        def msg = "Not allowed state for class $(tb.Name) in method $(meth.Name)";
                        meth.Body =
                        Util.locate
                        (
                            meth.Body.Location,
                            <[
                              // Проверяем, что текущее состояние позволяет вызывать данный метод
                               assert($vlAllowableStatesExpr.Exist(_ == $serviceMemberName), $msg);

                              // Выполняем вычисления и меняем состояние, если не произошло исключение
                              $(meth.Body);
                              $(serviceMemberName: string) = $vlStateNamExpr;
                            ]>
                        );
                    }
                };

                typer.DelayMacro
                (
                    lastTry =>
                    {
                        if (tvlStateName.Type.Hint.IsSome && tvlAllowableStates.Type.Hint.IsSome)
                        {
                            def (isCorrectTypes, vlStateNamExpr, vlAllowableStatesExpr) = checkParamsTypes();
                            if (isCorrectTypes)
                                mainCalculation(vlStateNamExpr, vlAllowableStatesExpr);
                             else
                                Message.Error($"Типы выражений ‘$vlStateName’ или ‘$vlAllowableStates’ неверны (должны быть string, list[string])");
                        }
                        else
                        {
                            when (lastTry)
                                Message.Error($"Типы выражений ‘$vlStateName’ или ‘$vlAllowableStates’ вывести не удалось");
                        }
                        option.None
                    }
                )

        | _ => Message.Error(meth.Location, "not correct arguments in macro verificationMarkState for class $(tb.Name) on method $(meth.Name)");
      }
  }


}



using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace marks
{
  public partial class MainForm : Form
  {
    public this()
    {
      InitializeComponent();
    }

    private button1_Click (_ : object,  _ : System.EventArgs) : void
    {
        markTest();
    }

    // Тестовый класс, имеет методы, которые можно вызывать в различных состояниях
    class File
    {
        // Входной переход на initialized
        [verificationMarkState("initialized", ""::[])]
        public this()
        {
            _ = MessageBox.Show("File");
        }

        // Это можно вызывать в состоянии  "initialized"
        // После вызова метода объект перейдёт в состояние "opened"
        [verificationMarkState("opened", ["initialized"])]
        public open(): void
        {
            _ = MessageBox.Show("open");
        }

        [verificationMarkState("opened", ["opened"])]
        public doing(): void
        {
            _ = MessageBox.Show("doing");
        }

        [verificationMarkState("closed", ["opened"])]
        public close(): void
        {
            _ = MessageBox.Show("Close");
        }
    }

    // Здесь, собственно, тестовый код
    // В runtime должно сработать assert при втором close
    public markTest(): void
    {
        def f = File();
        f.open();
        f.doing();
        f.close();
        f.close();
    }
  }
}
Re[3]: usesite
От: FDSC Россия consp11.github.io блог
Дата: 06.08.11 13:45
Оценка:
Здравствуйте, FDSC, Вы писали:

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


VD>>Если нужно сослаться на имя из внешнего контекста, то нужно использовать конструкции $(строка_с_именем : usesite) или $(строка_с_именем : dyn). Последнее нужно применять с осторожность, так как оно полностью нарушает гигиену и использует имя из любого контекста.


Я правильно понял, что нужно так


                        def serviceMemberName = tb.Name + "_verificationMark_state";
                        when (tb.LookupMember (serviceMemberName) == [])
                          tb.Define (<[ decl: protected mutable  $(serviceMemberName: usesite): string = ""; ]>);

                        // Генерируем новое тело метода
                        def msg = "Not allowed state for class $(tb.Name) in method $(meth.Name)";
                        meth.Body =
                        Util.locate
                        (
                            meth.Body.Location,
                            <[
                              // Проверяем, что текущее состояние позволяет вызывать данный метод
                               assert($vlAllowableStatesExpr.Exist(_ == $(serviceMemberName: usesite)), $msg);

                              // Выполняем вычисления и меняем состояние, если не произошло исключение
                              $(meth.Body);
                              $(serviceMemberName: usesite) = $vlStateNamExpr;
                            ]>
                        );


Тогда ошибки становятся такими


/out:obj\Debug\VerificationMacroses.dll

D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(74,32): error : in argument #2 (tl), needed a list[Nemerle.Compiler.Parsetree.PExpr.Call-], got list[string-]: common super type of types [Nemerle.Compiler.Parsetree.PExpr.Call, string] is just `object', please upcast one of the types to `object' if this is desired
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(86,21): error : in argument #1 (resolve), needed a bool -> option[Nemerle.Compiler.Parsetree.PExpr], got bool+ -> void -> option.None[?]-: void -> option.None[?] is not a subtype of option[Nemerle.Compiler.Parsetree.PExpr] [simple require]

Done building project "VerificationMacroses.nproj" -- FAILED.
Re[3]: Как сделать и найти метку в дереве AST
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.08.11 14:25
Оценка:
Здравствуйте, FDSC, Вы писали:

VD>>Кроме того нужно понимать, что макросы "гигиеничные", то есть все имена объявленные внутри них не могут конфликтовать с такими же объявленными в других местах (в том числе, в рукописном коде и других макросах). Если нужно сослаться на имя из внешнего контекста, то нужно использовать конструкции $(строка_с_именем : usesite) или $(строка_с_именем : dyn). Последнее нужно применять с осторожность, так как оно полностью нарушает гигиену и использует имя из любого контекста.


FDS>А где про это можно прочитать? В твоих статьях usesite практически не раскрывается, а dyn я вообще не помню.


Ну, как всегда здесь и здесь.

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


FDS>Ну, usesites и отдельные сборки для макросов — не скажу, что сто раз описаны. Остальное — от долгого перерыва в использовании Nemerle


usesites тебе особо не нужен был, хотя он тоже описан, ну, а про раздельные сборки говорится вообще везде и всюду по сто раз. Это было очень не просто пропустить. Надо было взять любое введение (коих куча) и проделать то что там написано.

FDS>Странно, что раньше я создавал простейшие макросы прямо в коде, а не в отдельной сборке, и они работали. С версии 9.6 (или что-то в этом роде) что-то изменилось?


Это не могло быть в принципе. Даже в самых ранних версиях макросы нужно было подключать как сборку. Можно заглянуть в историю этой страницы http://nemerle.org/Macros_tutorial и увидеть, что раздел "Compiling a simplest macro" был еще в 2005-ом году.

VD>>Далее нужно понимать, что макрос работает до типизации. По этому ошибки связанные с типами мы не можем получить до момента когда сгенерированный макросом код не начнет типизируются в целевом проекте (проекте где используется макрос). По сему очень легко создать макрос который породит некорректный код.


FDS>И ещё совершенно непонятно и, насколько я видел, не описано в документации, что же будет делать компилятор, когда я на этапе типизированного AST создам новый код в макросе


Это тоже где-то описано. Любой код подвергается типизации (считай — компиляции). Не важно когда ты его создал, компилятор его все равно будет типизировать. Кроме того ты можешь сделать это и сам (чтобы получить информацию о типах). Там есть свои сложности, но я бы оставил это до момента когда ты разберешься в азах.

ЗЫ

В общем, я бы на твоем месте начал бы с того, чтобы сформировал нормальную рабочую среду. Для этого нужно или добиться полной компиляции интеграции и компилятора с исходников (лучший вариарт, так как при этом исходники компилятора будут доступны для отладки), или поставить стабильную версию интеграции с VS 2008. Если нет полной 2008-ой студии, то можно поставить "изолетед мод" (аналог Экспрессов).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Runtime с макроатрибутами
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.08.11 17:04
Оценка:
Здравствуйте, FDSC, Вы писали:

FDS>Вот пример использования того, что я сейчас хочу получить на первом этапе...


Тогда ты на правильном пути. Только вот тут:

FDS>[c#]

FDS> [verificationMarkState("initialized", "":[])]

У тебя снова ":", а не "::".

Я бы вообще посоветовал тебе вместо передачи списка сделать у макроса последний параметр массивом параметров (как в шарпе). Для этого нужно пометить его модификатором "params":
  [Nemerle.MacroUsage(Nemerle.MacroPhase.WithTypedMembers, Nemerle.MacroTargets.Method)]
  // У макроса должно быть два параметра:
  //   строка-имя состояния конечного автомата, в которое переводится автомат после выполнения данного метода
  //   и список допустимых состояний, в которых можно вызывать данный метод
  macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, vlStateName, params vlAllowableStates : list[PExpr])
  {

Примечание: для более старых версий вместо "list[PExpr]" нужно задавать "list[expr]".


FDS>Но макрос у меня не компилируется.


FDS>D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(74,31): error : in argument #2 (tl), needed a list[string-], got list[Nemerle.Compiler.Parsetree.PExpr.Ref-]: common super type of types [string, Nemerle.Compiler.Parsetree.PExpr.Ref] is just `object', please upcast one of the types to `object' if this is desired


Ты где-то перепутал типы. Скорее всего при конкатинации списков. Смотри что у тебя в сроке 74 файла Macro1.n. PExpr.Ref это представление ссылки на имени в кода. Видимо, ты пытаешься к списку строк прицепить имя.

А еще лучше использовать IDE и вынести код макроса в отдельную функцию. Тогда будет доступен интеллисенс и ошибиться будет очень сложно.
В современной версии интеграции с VS 2010 (пока что собирается только с исходников) есть отличный визард для макросов. Он сильно упрощает их создание.

FDS>D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(74,31): error : in argument #2 (tl), needed a list[Nemerle.Compiler.Parsetree.PExpr.Ref-], got list[string-]: common super type of types [Nemerle.Compiler.Parsetree.PExpr.Ref, string] is just `object', please upcast one of the types to `object' if this is desired

FDS>D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Macro1.n(95,21): error : in argument #1 (resolve), needed a bool -> option[Nemerle.Compiler.Parsetree.PExpr], got bool+ -> void -> option.None[?]-: void -> option.None[?] is not a subtype of option[Nemerle.Compiler.Parsetree.PExpr] [simple require]

FDS>Done building project "VerificationMacroses.nproj" -- FAILED.

FDS>[/code]

FDS>Что ему надо, никак не пойму (точнее, нужно ему вместо string PExpr, но вот как от этого уйти я не понимаю)


FDS>Сам код макроса в нынешнем варианте таков:


FDS> macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, params opts : list [PExpr])//, vlStateName, vlAllowableStates)

FDS> {
FDS> def typer : Typer = Macros.ImplicitCTX();

FDS> match (opts)

FDS> {
FDS> | vlStateName::vlAllowableStates::[] =>

Данный паттерн (x :: y :: []) отлавливает список состоящий из двух элементов, а ты, скорее всего, хочешь отловить список и его конец. Это делается так head :: tail, где head — это первый элемент, а tail хвост списка (возможно пустой, но это можно проверить отдельно).
В твоем же случае нет смысл заниматься разбором списка вручную. Первый параметр можно описать отдельно. Наличие params у последнего параметра не препятствует этому.

Кроме того, если в макрос должны передаваться литералы, то это можно явно указать задав тип параметра как string.

FDS> def tvlStateName = typer.TypeExpr(vlStateName);

FDS> def tvlAllowableStates = typer.TypeExpr(vlAllowableStates);

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

FDS> def msg = "Not allowed state for class $(tb.Name) in method $(meth.Name)";

FDS> ...
FDS> <[
FDS> // Проверяем, что текущее состояние позволяет вызывать данный метод
FDS> assert(temporary_verification_flag, $msg);

Ну, а вот и ошибочка! Ты подставляешь строку в место где требуется АСТ. Нужно писать так:
                        def msg = "Not allowed state for class $(tb.Name) in method $(meth.Name)";
                        ...
                         <[
                            // Проверяем, что текущее состояние позволяет вызывать данный метод
                            assert(temporary_verification_flag, $(msg : string));

Цитата $(msg : string) указывает компилятору, что нужно превратить строку msg в АСТ строкового литерала.

Ниже приведен твой пример переработанный мной:
using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;


namespace TestMacro
{
  [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Method)]
  macro VerificationMarkState(typeBuilder : TypeBuilder, method : MethodBuilder, vlStateName : string, params vlAllowableStates : list[PExpr])
  {
    VerificationMarkStateImpl.DoTransform(Macros.ImplicitCTX(), typeBuilder, method, vlStateName, vlAllowableStates)
  }
  
  module VerificationMarkStateImpl
  {
    public DoTransform(typer : Typer, typeBuilder : TypeBuilder, method : MethodBuilder, vlStateName : string, vlAllowableStates : list[PExpr]) : void
    {
      Macros.DefineCTX(typer);
      
      // Как я понимаю, в параметрах макро-атрибута допустимы только строковые литералы. Для проверки этого
      // нам не нужна типизаци. Более того она тут не даст желаемого результата, так как пропустит,
      // например, вызов функции возвращающей строку, а нам нужны торлько литералы. Если это не так, то 
      // нужно смотреть расширенные варианты использования.
      def result = vlAllowableStates.Find(p => !(p is <[ $(_ : string) ]>)); // Тот же самый паттерн, но в виде АСТ: PExpr.Literal(Literal.String)
      
      // Указание badParam.Location приводит к тому, что компилятор будет указывать не первый параметр не являющийся строковым литералом.
      when (result is Some(badParam))
        Message.FatalError(badParam.Location, "All parameters if VerificationMarkState macro must be string literals.");

        // Вводим поле хранящее текущее состояне
        //def currentStateName = "_currentState";
        //def currentState = <[ $(currentStateName : usesite) ]>;
        when (typeBuilder.LookupMember("_currentState").IsEmpty)
          typeBuilder.Define(<[ decl: protected mutable _currentState : string = ""; ]>);
          
        // Генерируем новое тело метода
        def msg = $"Not allowed state for class '$(typeBuilder.Name)' in method '$(method.Name)'.";
        method.Body =
        Util.locate(method.Body.Location,
            <[
              // Проверяем, что текущее состояние позволяет вызывать данный метод
              assert(array[..$vlAllowableStates].Contains(_currentState), $(msg : string));

              // Выполняем вычисления и меняем состояние, если не произошло исключение
              $(method.Body);
              _currentState = $(vlStateName : string);
            ]>
        );
      }
  }
}

Тест использования:
using System.Console;
using TestMacro;

class File
{
  // Входной переход на initialized
  [VerificationMarkState("initialized", "")]
  public this() { WriteLine("File"); }

  // Это можно вызывать в состоянии  "initialized"
  // После вызова метода объект перейдёт в состояние "opened"
  [VerificationMarkState("opened", "initialized")]
  public open(): void { WriteLine("open"); }

  [VerificationMarkState("opened", "opened")]
  public doing(): void { WriteLine("doing"); }

  [VerificationMarkState("closed", "opened")]
  public close(): void { WriteLine("Close"); }
}

module Program
{
  Main() : void
  {
    try
    {
      def f = File();
      f.open();
      f.doing();
      f.close();
      f.close();
    }
    catch { | e => WriteLine(e.Message); }
      
    _ = ReadLine();
  }
}

А это консольный вывод:
File
open
doing
Close
assertion ``array ["opened"].Contains(_currentState)'' failed in file Main.n, li
ne 19: Not allowed state for class 'File' in method 'close'.


Могу сразу предложить несколько улучшений. Во первых проверку можно оптимизировать одним из двух способов:
1. Создавать для каждого метода по полю содержащему Set[string] заполняемый один раз при их инициализации. В программе же только проверять вхождение в этот набор.
2. Развернуть проверку в набор выражений. Это может оказаться быстрее при малом количестве состояний.

Кроме того, для идентификации состояний, логично было использовать не строки, а целые или перечисления. При этом пункт 2 оказался бы шустрее.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Как сделать и найти метку в дереве AST
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.08.11 18:21
Оценка: 6 (1)
Здравствуйте, FDSC, Вы писали:

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


FDS>Нужно написать макросы, которые будут позволять:

FDS>первый макрос
FDS>1. Поставить на метод метку допустимого состояния вызова в некотором конечном автомате

Первый пункт по прежнему я полностью понять не могу. Приведи пример кода который должен получиться в итоге.

FDS>2. Сгенерировать runtime-проверку в методе: что он вызван там, где надо


Если я правильно понял, то близкая к оптимальной версия макросов должна выглядеть как показано ниже.
Я ввел еще один макрос DefineVerificationState который должен вешаться на класс и задавать перечисление в котором описываются допустимые состояния.
Введение дополнительного макроса полезно по двум соображениям. Во первых в нем можно производить любую необходимую инициализацию. Так в нем добавляется поле _currentState и проверяется, что выражение переданное в качестве параметра является перечислением. Тут же можно делать и другие перечисления. Например, для ускорения вычислений лучше использовать перечисление содержащее не пересекающиеся по битам флаги. Тогда проверка сведется до одной булевой операции. В DefineVerificationState можно проверять, что все элементы перечисления не имеют уникальное битовое представление.
Использование данной версии макроса выглядит следующим образом:
using System.Console;
using TestMacro;

enum FileStates
{
  | None
  | Initialized
  | Opened
  | Closed
}

[DefineVerificationState(FileStates)]
class File
{
  // Входной переход на initialized
  [VerificationMarkState(Initialized, None)]
  public this() { WriteLine("File"); }

  // Это можно вызывать в состоянии  "initialized"
  // После вызова метода объект перейдёт в состояние "opened"
  [VerificationMarkState(Opened, Initialized)]
  public open(): void { WriteLine("open"); }

  [VerificationMarkState(Opened, Opened)]
  public doing(): void { WriteLine("doing"); }

  [VerificationMarkState(Closed, Opened)]
  public close(): void { WriteLine("Close"); }
}

module Program
{
  Main() : void
  {
    try
    {
      def f = File();
      f.open();
      f.doing();
      f.close();
      f.close();
    }
    catch { | e => WriteLine(e.Message); }
      
    _ = ReadLine();
  }
}

Если ошибиться в имени состояния, то будет выдано сообщение об ошибке.

Ниже приведен код реализации обоих макросов. Надеюсь он кому-то будет полезен.
Я снабдил код максимальным количеством комментариев. Но если что-то не ясно, то задавайте вопросы.
VerificationMarkState.n
  Скрытый текст
using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;


namespace TestMacro
{
  [MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Class)]
  macro DefineVerificationState(typeBuilder : TypeBuilder, enumName : PExpr)
  {
    DefineVerificationStateImpl.DoTransform(Macros.ImplicitCTX(), typeBuilder, enumName)
  }
  
  module DefineVerificationStateImpl
  {
    public DoTransform(typer : Typer, typeBuilder : TypeBuilder, enumName : PExpr) : void
    {
      Macros.DefineCTX(typer);
      
      if (typer.BindFixedType(enumName).IsEnum) // "связываем" тип и проверяем, что это перечисление
      {
        typeBuilder.UserData["VerificationState"] = enumName; // запоминаем значение в словаре доступном в других макросах
        typeBuilder.Define(<[ decl: protected mutable _currentState : $enumName; ]>); // добавляем поле _currentState
      }
      else
        Message.Error(enumName.Location, "Expecter enum which define states.");
        
    }
  }
}


VerificationMarkState.n
  Скрытый текст
using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;


namespace TestMacro
{
  [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Method)]
  macro VerificationMarkState(typeBuilder : TypeBuilder, method : MethodBuilder, vlStateName : PExpr, params vlAllowableStates : list[PExpr])
  {
    VerificationMarkStateImpl.DoTransform(Macros.ImplicitCTX(), typeBuilder, method, vlStateName, vlAllowableStates)
  }
  
  module VerificationMarkStateImpl
  {
    public DoTransform(typer : Typer, typeBuilder : TypeBuilder, method : MethodBuilder, vlStateName : PExpr, vlAllowableStates : list[PExpr]) : void
    {
      Macros.DefineCTX(typer);
      
      def states = vlStateName :: vlAllowableStates; // finst state is vlStateName!
      def result = states.Find(p => !(p is PExpr.Ref));
      
      when (result is Some(badParam)) // Все параметры должны быть ссылками на имя (PExpr.Ref)
        Message.FatalError(badParam.Location, "All parameters if VerificationMarkState macro must be string literals."); // FatalError выдаст исключение

      def stateNames  = states.Map(e => (e :> PExpr.Ref).name.Id); // преобразуем список PExpr.Ref в список имен (строк)
      def enumName    = typeBuilder.UserData["VerificationState"] :> PExpr; // получаем имя enum-а запомненное в макросе DefineVerificationState

      when (enumName == null)
        Message.FatalError(typeBuilder.NameLocation, $"You must define DefineVerificationState attribute on $(typeBuilder.Name) type.");
        
      def statesAst   = stateNames.Map(name => <[ $enumName.$(name : usesite) ]>); // преобразуем список имен в список ссылок на значения enum-а
      // генерируем выражения проверки вида state1 == _currentState && state2 == _currentState && true. 
      // "&& true" нужно как начальное значение. Оно бдует выкинуто аптимизатором.
      // Выражения генерируем не для всего списка, а только для хвоста (за исключением первоего элемента), так как вначале у нас vlStateName.
      def checkStates = statesAst.Tail.Fold(<[ true ]>, (state, acc) => <[ $state == _currentState && $acc ]>);

      // Генерируем новое тело метода
      def msg = $"Not allowed state for class '$(typeBuilder.Name)' in method '$(method.Name)'.";
      method.Body =
      Util.locate(method.Body.Location, <[
            // Проверяем, что текущее состояние позволяет вызывать данный метод
            assert($checkStates, $(msg : string));

            // Выполняем вычисления и меняем состояние, если не произошло исключение
            $(method.Body);
            _currentState = $(statesAst.Head);
          ]>
      );
      }
  }
}
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Runtime с макроатрибутами
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.08.11 18:31
Оценка:
Здравствуйте, FDSC, Вы писали:

FDS>Вот пример использования того, что я сейчас хочу получить на первом этапе


Кажется я немного ввожу в заблуждение. Хотя цвета разные, но при обращении к полям вроде бы они игнорируются. Так что можно генерировать код доступа к полям и без возни с usesite.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Как сделать и найти метку в дереве AST
От: FDSC Россия consp11.github.io блог
Дата: 06.08.11 20:55
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Если я правильно понял, то близкая к оптимальной версия макросов должна выглядеть как показано ниже.


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

   def result = states.Find(p => !(p is PExpr.Ref));

Я так понимаю, мы ищем здесь все не PExpr.Ref. А как узнать вообще, какому именно PExpr будет соответствовать какая операция в коде? В документации по PExpr на nemerle.org что называется пустота (точнее, не больше, чем в самом коде)?

      when (result is Some(badParam)) // Все параметры должны быть ссылками на имя (PExpr.Ref)

Насколько я понимаю, мы здесь смотрим, что если в result есть некоторый badParam, проще говоря, есть хоть один элемент, то это ошибка — не хотим такого
Однако, признаться, я не очень понял
1. Как работает Some и где он описан в Nemerle (хотя в статьях он используется напропалую, но — не дошло )?
2.
  macro whenmacro (cond, body)
  syntax ("when", "(", cond, ")", body)
  {

    def res1 = Util.locate(cond.Location,
      match (cond)
      {
        | <[ $subCond is $pattern ]> with guard = null
        | <[ $subCond is $pattern when $guard ]> =>
          def res2 = match (pattern)
          {
            | PT.PExpr.Call when guard != null =>
              <[ match ($subCond) { | $pattern when $guard => $body : void | _ => () } ]>
            | PT.PExpr.Call =>
              <[ match ($subCond) { | $pattern => $body : void | _ => () } ]>
            | _ => <[ match ($cond) { | true => $body : void | _ => () } ]>
          }
          res2
        | _ => <[ match ($cond : bool) { | true => $body : void | _ => () } ]>
      });

    res1
  }

Выделил жирным кусок кода, до которого, как я понимаю, должна дойти обработка, а вот куда он дальше пойдёт? Опять же, где узнать, что такое PExpr.Call?

def stateNames  = states.Map(e => (e :> PExpr.Ref).name.Id); // преобразуем список PExpr.Ref в список имен (строк)

Зачем нам
1. Указывать (e :> PExpr.Ref) — ведь это явный каст к типу, который и так предок типа e, разве нет?
2. Зачем нам делать маппинг, вместо того, чтобы просто написать в Fold
вместо этого
def checkStates = statesAst.Tail.Fold(<[ true ]>, (state, acc) => <[ $state == _currentState && $acc ]>);

это
def checkStates = states.Tail.Fold(<[ true ]>, (state, acc) => <[ $state == _currentState && $acc ]>);


Далее, что мы делаем здесь
def statesAst   = stateNames.Map(name => <[ $enumName.$(name : usesite) ]>); // преобразуем список имен в список ссылок на значения enum-а

Я так понимаю, что в сумме получится для doing список
[FileStates.Opened, FileStates.Opened]


Но зачем указывать $(name : usesite) ?






По моему коду

using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;

using Nemerle.Imperative;
using Nemerle.Compiler.Parsetree;

namespace VerificationMacroses
{
  public module Runtime
  {
      public Exists(memberList: list[string], serviceMemberName: string): bool
      {
          memberList.Exists(_ == serviceMemberName)
      }
  }

  // Определяем макрос-атрибут метода
  [Nemerle.MacroUsage (Nemerle.MacroPhase.WithTypedMembers,
  Nemerle.MacroTargets.Method, Inherited = true)]
  // У макроса должно быть два параметра:
  //   строка-имя состояния конечного автомата, в которое переводится автомат после выполнения данного метода
  //   и список допустимых состояний, в которых можно вызывать данный метод
  macro verificationMarkState (tb : TypeBuilder, meth : MethodBuilder, params opts : list [PExpr])//, vlStateName, vlAllowableStates)
  {
      // assert2(false);
      def typer : Typer = Macros.ImplicitCTX();

      match (opts)
      {
        | vlStateName::vlAllowableStates::[] => 
            {
                def tvlStateName       = typer.TypeExpr(vlStateName);
                def tvlAllowableStates = typer.TypeExpr(vlAllowableStates);

                def checkParamsTypes()
                {
                    def vlsnCheck = 
                    match (vlStateName)
                    {
                        | <[ $vlsn: string ]> => (true, vlsn)
                        | _ => Console.WriteLine(tvlStateName.Type.Hint); (false, PExpr.Void())
                    }
                    match (tvlStateName.Type.Hint)
                    {
                        | Some(int) => Console.WriteLine("Распознал int-");
                        | Some(string) => Console.WriteLine("Распознал-");
                        | _            => Console.WriteLine("Фигово-");
                    }
                    match (tvlStateName.Type.Hint)
                    {
                        | int => Console.WriteLine("Распознал int");
                        | string => Console.WriteLine("Распознал");
                        | _            => Console.WriteLine("Фигово");
                    }
                    match (tvlStateName.Type)
                    {
                        | int => Console.WriteLine("Распознал int|");
                        | string => Console.WriteLine("Распознал|");
                        | _            => Console.WriteLine("Фигово|");
                    }

                    def vlasCheck = 
                    match (vlAllowableStates)
                    {
                        | <[ ($vlasList : list[string]) ]> => (true, vlasList)
                        | _ => (false, PExpr.Void())
                    }

                    (vlsnCheck[0] && vlasCheck[0], vlsnCheck[1], vlasCheck[1], vlsnCheck[0], vlasCheck[0])
                }

                def mainCalculation(vlStateNamExpr, _vlAllowableStatesExpr)
                {
                    // Если членов класса почему-то нет, то считаем это ошибкой
                    if (!tb.LookupMemberAvailable)
                    {
                        def msg = "lookup member not available for class $(tb.Name) in the macro verificationMarkState";
                        Message.Error(meth.Location, msg);
                    }
                    else
                    {
                        // Вводим служебную переменную
                        def serviceMemberName = tb.Name + "_verificationMark_state";
                        when (tb.LookupMember (serviceMemberName) == [])
                          tb.Define (<[ decl: protected mutable  $(serviceMemberName: dyn): string = ""; ]>);

                        // Генерируем новое тело метода
                        def msg = "verificationMarkState: Not allowed state for class $(tb.Name) in method $(meth.Name)";
                        meth.Body =
                        Util.locate
                        (
                            meth.Body.Location,
                            <[
                            assert
                            (
                                Runtime.Exists(  $vlAllowableStates, $(serviceMemberName: dyn)  ),
                                $(msg: string)
                            );

                              // Выполняем вычисления и меняем состояние, если не произошло исключение
                              $(meth.Body);
                              $(serviceMemberName: dyn) = $vlStateNamExpr;
                            ]>
                        );
                    }
                };

                typer.DelayMacro
                (
                    lastTry =>
                    {
                        if (tvlStateName.Type.Hint.IsSome && tvlAllowableStates.Type.Hint.IsSome)
                        {
                            def (isCorrectTypes, vlStateNamExpr, vlAllowableStatesExpr, vlsnCheck, vlasCheck) =
                                checkParamsTypes();

                            if (isCorrectTypes)
                                mainCalculation(vlStateNamExpr, vlAllowableStatesExpr);
                             else
                             {
                                unless (vlsnCheck)
                                    Message.Error(vlStateName.Location, $"verificationMarkState: Тип выражения ‘$vlStateName’неверен (должен быть string)");
                                unless (vlasCheck)
                                    Message.Error(vlAllowableStates.Location, $"verificationMarkState: Тип выражения ‘$vlAllowableStates’неверен (должен быть list[string])");
                            }
                        }
                        else
                        {
                            when (lastTry)
                                Message.Error(vlStateName.Location, $"verificationMarkState: Типы выражений ‘$vlStateName’ или ‘$vlAllowableStates’ вывести не удалось");
                        }
                        None()
                    }
                )
            }

        | _ => 
            {
                Message.Error(meth.Location, "not correct arguments in macro verificationMarkState for class $(tb.Name) on method $(meth.Name)");
                //PExpr.Error(meth.Location)
            }
      }
  }


}


1. В функции checkParamsTypes при отладке не отображаются внешние переменные, например, vlStateName и tvlStateName (пишет, что вне области видимости) — почему?
2. Если оставить так, как указано выше, с закомментированной последней строкой макроса (не считая скобок)

//PExpr.Error(meth.Location)

то получаем следующие сообщения


D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Runtime.n(145,13): error : expected Nemerle.Compiler.Parsetree.PExpr-, got void in computation branch: Nemerle.Compiler.Parsetree.PExpr is not a subtype of void [simple require]
D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Runtime.n(145,13): warning : hint: this means two branches of ``if'' or ``match'' have different types


Что не нравится компилятору?


3. В функции checkParamsTypes я получаю от match такой вывод на консоль


Some (string)
        Распознал int-
        Распознал int
        Распознал int|
D:\works\programming\AITryes\macroTryes_runtime\marks\MainForm.n(35,32): error : verificationMarkState: Тип выражения '"initialized"'неверен (должен быть string)
D:\works\programming\AITryes\macroTryes_runtime\marks\MainForm.n(35,47): error : verificationMarkState: Тип выражения '[""]'неверен (должен быть list[string])



(для одного из срабатываний)

Почему?
usesite some pexpr.ref pexpr.call pexpr when
Re[3]: Как сделать и найти метку в дереве AST
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.08.11 01:24
Оценка:
Здравствуйте, FDSC, Вы писали:

FDS>Да, что-то похожее. Сейчас надо будет попробовать так же сделать самому, не смотря в "подсказку".


На самом деле самый эффективный способ учиться — это, как раз, делать что-то самому (а не повторять сделанное) и периодически показывать результаты своего труда тем кто более опытен (для ревизии). Освоить все за раз все равно не удастся. Нужно писать код и постоянно его улучшать. А улучшения как раз могут предложить те кто опытнее.

FDS>Но остались вопросы и по моему коду.


Ни вопрос. Ответим.

FDS>
FDS>   def result = states.Find(p => !(p is PExpr.Ref));
FDS>

FDS>Я так понимаю, мы ищем здесь все не PExpr.Ref.

Так, точно.

FDS>А как узнать вообще, какому именно PExpr будет соответствовать какая операция в коде?


Ну, вообще-то из описания обычно довльно понятно. Но можно просто подсматривать в отладчике (так все и дела).
Просто создай тестовый проект с макросом которому в качестве параметра передается выражение. В макросе разбирай код и выводи описание на консоль. Думаю, что через пару часов ты все поймешь сам. При этом полезно поставить в макру assert2(false), перейти в отладчик и поглядеть код под отлачиком (раскрывая объекты АСТ и смотря их устройство).

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

FDS>В документации по PExpr на nemerle.org что называется пустота (точнее, не больше, чем в самом коде)?


Авторы предполагали, что писателям макросов будет достаточно квази-цитирвания. Оно же ведь работает в две стороны (и как конструктор, и как декоструктор). Вот только не все случаи решаются кваз-цитатами. Например, в квази-цитировании нет средств (ну, или я их не знаю) для распознования ссылки на простое имя (PExpr.Ref). В прочем, для данного кода можно было бы обойтись вот такой квази-цитатой:
states.Find(p => !(p is <[ $(_ : name) ]>))

Она залезает чуть глубже, но для данной задачи это даже хорошо. Я просто ступил немного.

FDS>
FDS>      when (result is Some(badParam)) // Все параметры должны быть ссылками на имя (PExpr.Ref)
FDS>

FDS>Насколько я понимаю, мы здесь смотрим, что если в result есть некоторый badParam, проще говоря, есть хоть один элемент, то это ошибка — не хотим такого

Ага.

FDS>Однако, признаться, я не очень понял

FDS>1. Как работает Some и где он описан в Nemerle (хотя в статьях он используется напропалую, но — не дошло )?

Находится он здесь.
Это банальный вариантный дженерик-тип option[T] из двух вхождений. Одно — None() — описывает отсутствие результата. Другое — Some(value : T) — описывает присутствие результата, и по совместительству, сам результат. Не плохо описан здесь
Автор(ы): Чистяков Владислав Юрьевич
Дата: 25.07.2010
Неформальное введение в язык программирования Nemerle. В этой части, на базе примера «калькулятор», описываются типы данных variant и class.
. Собственно я бы посоветовал прочесть весь Язык Nemerle, чтобы познакомиться с особенностями языка и проникнуться его духом. Оставшиеся части — это ООП и макросы. В области ООП немерл ничего нового не привносит (если не считать те же макросы), так что зная Яву или Шарп можно сказать, что ты знаком и с ООП в Немерле. Ну, а макросы ты уже начал осваивать. Их кроме как на практике не освоить. Даже сто-пятьсот умных книг практики не заменят.

FDS>2.

FDS>
FDS>  macro whenmacro (cond, body)
FDS>  syntax ("when", "(", cond, ")", body)
FDS>  {

FDS>    def res1 = Util.locate(cond.Location,
FDS>      match (cond)
FDS>      {
FDS>        | <[ $subCond is $pattern ]> with guard = null
FDS>        | <[ $subCond is $pattern when $guard ]> =>
FDS>          def res2 = match (pattern)
FDS>          {
FDS>            | PT.PExpr.Call when guard != null =>
FDS>              <[ match ($subCond) { | $pattern when $guard => $body : void | _ => () } ]>
FDS>            | PT.PExpr.Call =>
FDS>              <[ match ($subCond) { | $pattern => $body : void | _ => () } ]>
FDS>            | _ => <[ match ($cond) { | true => $body : void | _ => () } ]>
FDS>          }
FDS>          res2
FDS>        | _ => <[ match ($cond : bool) { | true => $body : void | _ => () } ]>
FDS>      });

FDS>    res1
FDS>  }
FDS>


Ты верно мыслишь! Чтобы понять как действует "магия" — нужно смотреть ее исходники .

FDS>Выделил жирным кусок кода, до которого, как я понимаю, должна дойти обработка, а вот куда он дальше пойдёт?


Что значит "пойдет"?

Это сопоставление с образцом. Опять же описано сто раз. Тот же Язык Немерле посвящает этому вопросу кучу времени и разжевывает его в самой доступной форме.
На входе мы имеем АСТ выражения условия "cond". Далее производится его разбор на составляющие. В итоге код (АСТ) выражения when переписывается в вызов match. Таким образом выражение:
when (result is Some(badParam))
  Message.FatalError(badParam.Location, "All parameters if VerificationMarkState macro must be string literals."); )

преобразуется в:
match (result)
{
  | Some(badParam) => Message.FatalError(badParam.Location, "All parameters if VerificationMarkState macro must be string literals.") : void
  | | _ => ()
}

" : void" добавляется чтобы предотвратить непонятные сообщения об ошибках в случае, если кто-то ничаянно (или по незнанию) попытается возвратить из when значение. Ведь when не может возвращать значения, так как в случае неудачи проверки будет нечего вернуть.

"()" — это так называемый void-литерал. Описывает выражение которое ничего не делает и имеет тип void. В Немерле все является выражением. Так что допустимы даже void-выражения. Это может звучать непривычно, но можно делать даже так:
def x : void = ();

Сделать с такой переменной будет ничего невозможно (кроме как возвратить там где требуется void), но это делает язык концептуально чистым.

FDS>Опять же, где узнать, что такое PExpr.Call?


Это опять небольшая халтурка. Вместо PExpr.Call, можно было бы использовать квази-цитату: <[ $_($_) ]>. Она описывает "синтаксис вызова функции". Т.е. все что выглядит как F(...) им разбирается. Соответственно и ветка АСТ имеет имя Call — вызов. Конструкция "$_", кстати, означат "любое выражение.

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

FDS>
FDS>def stateNames  = states.Map(e => (e :> PExpr.Ref).name.Id); // преобразуем список PExpr.Ref в список имен (строк)
FDS>

FDS>Зачем нам
FDS>1. Указывать (e :> PExpr.Ref) — ведь это явный каст к типу, который и так предок типа e, разве нет?

Нет. Тип e — PExpr. PExpr — это вариантный тип. Он позволят полиморфно хранить разные составные типы входящие в его состав.

На самом деле я немного ступил и этот код можно написать не прибегая к прямому использованию типов АСТ. Вот как можно записать тот же код с квази-цитированием:
def states = vlStateName :: vlAllowableStates; // finst state is vlStateName!
def result = states.Find(p => !(p is <[ $(_ : name) ]>));
      
when (result is Some(badParam)) // Все параметры должны быть ссылками на имя (PExpr.Ref)
  Message.FatalError(badParam.Location, "All parameters if VerificationMarkState macro must be enum literals."); // FatalError выдаст исключение

def convertToStr(expr : PExpr) : string
{
  | <[ $(x : name) ]> => x.Id
  | _ => assert(false)
}
def stateNames  = states.Map(convertToStr); // преобразуем список PExpr.Ref в список имен (строк)

Основные отличия выделены жирным.

FDS>2. Зачем нам делать маппинг, вместо того, чтобы просто написать в Fold

FDS>вместо этого
FDS>
FDS>def checkStates = statesAst.Tail.Fold(<[ true ]>, (state, acc) => <[ $state == _currentState && $acc ]>);
FDS>

FDS>это
FDS>
FDS>def checkStates = states.Tail.Fold(<[ true ]>, (state, acc) => <[ $state == _currentState && $acc ]>);
FDS>


Отдельный Map сделан для упрощения отдельных операций. Зачастую бывает понятнее когда на сначала данные преобразуются в удобную форму, а потом уже обрабатываются.
Что же касается того зачем пришлось это делать, то это происходит из-за особенностей работы квази-цитаты:
<[ $object.$name ]>

Это квази-цитата доступа к члену. Ну, скажем str.Length — это доступ к члену.
Проблема в том, что вместо object можно подставить любое выражение, но вместо name должен подставляться объект типа Name, описывающий ссылку на имя. По немпонятным для меня причинам вместо него нельзя подставить PExpr.Ref (хотя в нем находится то же объект Name.
В принципе, этот можно было бы записать:
def convertToName(expr : PExpr)
{
  | <[ $(x : name) ]> => x
  | _ => assert(false)
}
def stateNames  = states.Map(convertToName); // преобразуем список PExpr.Ref в список имен (строк)
def enumName    = typeBuilder.UserData["VerificationState"] :> PExpr; // получаем имя enum-а запомненное в макросе DefineVerificationState

when (enumName == null)
  Message.FatalError(typeBuilder.NameLocation, $"You must define DefineVerificationState attribute on $(typeBuilder.Name) type.");
        
def statesAst   = stateNames.Map(name => <[ $enumName.$(name : name) ]>); // преобразуем список имен в список ссылок на значения enum-а

Она даже стал бы чуточку эффективнее, так как не потребовалось бы создавать новые объекты типа Name.
Однако Map я все равно оставил бы, так как иначе код стане менее читабельным.

Кстати, объекты типа Name и хранят информацию о тех самых цветах. И именно по этому используются именно они, а не просто строки.

В прочем... я тут подумал. Возможно ты прав и самым логичным вариантом был бы такой:
def states = vlStateName :: vlAllowableStates; // finst state is vlStateName!
def result = states.Find(p => !(p is <[ $(_ : name) ]>));
      
when (result is Some(badParam)) // Все параметры должны быть ссылками на имя (PExpr.Ref)
  Message.FatalError(badParam.Location, "All parameters if VerificationMarkState macro must be enum literals."); // FatalError выдаст исключение

def enumName    = typeBuilder.UserData["VerificationState"] :> PExpr; // получаем имя enum-а запомненное в макросе DefineVerificationState

when (enumName == null)
  Message.FatalError(typeBuilder.NameLocation, $"You must define DefineVerificationState attribute on $(typeBuilder.Name) type.");
        
def convertToEnumFieldRef(expr : PExpr)
{
  | <[ $(x : name) ]> => <[ $enumName.$(x : name) ]>
  | _ => assert(false)
}
def statesAst   = states.Map(convertToEnumFieldRef); // преобразуем список имен в список ссылок на значения enum-а
// генерируем выражения проверки вида state1 == _currentState && state2 == _currentState && true. 
// "&& true" нужно как начальное значение. Оно бдует выкинуто аптимизатором.
// Выражения генерируем не для всего списка, а только для хвоста (за исключением первоего элемента), так как вначале у нас vlStateName.
def checkStates = statesAst.Tail.Fold(<[ true ]>, (state, acc) => <[ $state == _currentState && $acc ]>);

то есть, с преобразованием сразу из PExpr описывающего короткие имена в ссылки на поля enum-а.

FDS>Далее, что мы делаем здесь

FDS>
FDS>def statesAst   = stateNames.Map(name => <[ $enumName.$(name : usesite) ]>); // преобразуем список имен в список ссылок на значения enum-а
FDS>

FDS>Я так понимаю, что в сумме получится для doing список

Э... а что значит doing?

FDS>
FDS>[FileStates.Opened, FileStates.Opened]
FDS>


FDS>Но зачем указывать $(name : usesite) ?


Для преобразования строки в имя с цветом таким как у места использования (usesite-а).
Как я уже написал выше, можно было обойтись без преобразования в строки и обратно.

FDS>


FDS>По моему коду


FDS>1. В функции checkParamsTypes при отладке не отображаются внешние переменные, например, vlStateName и tvlStateName (пишет, что вне области видимости) — почему?


Отладчик (поддержка в VS), к сожалению не совершенен. Скорее всего функция попадает в замыкание. При этом переменные копируются в специальный объект. Зачастую его можно увидеть в отладчике. Если залезть в него, то можно увидеть свои переменные, но, обычно, с разными префиксами и суфиксами.

FDS>2. Если оставить так, как указано выше, с закомментированной последней строкой макроса (не считая скобок)


FDS>//PExpr.Error(meth.Location)


FDS>то получаем следующие сообщения



FDS>
FDS>D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Runtime.n(145,13): error : expected Nemerle.Compiler.Parsetree.PExpr-, got void in computation branch: Nemerle.Compiler.Parsetree.PExpr is not a subtype of void [simple require]
FDS>D:\works\programming\AITryes\macroTryes_runtime\VerificationMacroses\Runtime.n(145,13): warning : hint: this means two branches of ``if'' or ``match'' have different types
FDS>


FDS>Что не нравится компилятору?


Компилятор видит, что разные вхождения оператора match возвращают значения разных типов.
Если не игнорировать предупреждения, а их у твоем коде море, то ты заметишь, что среди прочих есть предупреждение:

warning : N10005: ignored computed value of type Nemerle.Compiler.Parsetree.PExpr

Это значит, что компилятор вывел, что оператор match имеет возвращаемое значение отличное от void.
Происходит это потому, что последним выражением в первом вхождении match-а у тебя является вызов typer.DelayMacro(). А этот метод имеет возвращаемое значение типа PExpr.
Ну, и так как первое вхождение match-а имеет тип PExpr, то компилятор требует, чтобы все остальные вхождения так же имели бы этот тип или его супертип (базовый класс).

Чтобы избавиться, как от этой проблемы, так и от предупреждения достаточно поставить перед вызовом метода "_ = ". Это своеобразный DevNull. Присвоение в никуда. У присвоения тип void, так что все выражение так же будет иметь тип void.

В отличии от компилятора шарпа, немерл отслеживает потерянные значения. Так что написать на немерле ошибочный код вроде:
str.Remove(1, 2)

не удастся.

Кроме того в немерле возвращаемым значением является последнее значение в любом блоке кода. И их потери могут иметь печальные последствия.
В общем, к этому нужно привыкнуть. Зато потом будет бесить то как это реализовано в шарпе и яве.

FDS>3. В функции checkParamsTypes я получаю от match такой вывод на консоль


FDS>
FDS>Some (string)
FDS>        Распознал int-
FDS>        Распознал int
FDS>        Распознал int|
FDS>D:\works\programming\AITryes\macroTryes_runtime\marks\MainForm.n(35,32): error : verificationMarkState: Тип выражения '"initialized"'неверен (должен быть string)
FDS>D:\works\programming\AITryes\macroTryes_runtime\marks\MainForm.n(35,47): error : verificationMarkState: Тип выражения '[""]'неверен (должен быть list[string])
FDS>


Тут еще раз стоит упомянуть о том, что в Немерле нельзя игнорировать предупреждения. Если внимательно присмотреться к ним, то будет ясно, в чем проблема.

На строки:
match (tvlStateName.Type.Hint)
{
  | Some(int) => Console.WriteLine("Распознал int-");
  | Some(string) => Console.WriteLine("Распознал-");
  | _            => Console.WriteLine("Фигово-");
}

Компилятор выдает предупреждения:

warning : N168: a value bound in pattern string was never used

Происходит это потому, что компилятор воспринимает имена int и string за имена переменных. А так как эти переменные нигде не используются, то получается предупреждение.

Твой код ничем не изменился бы если бы был записан так:
match (tvlStateName.Type.Hint)
{
  | Some(x) => Console.WriteLine("Распознал int-");
  | Some(x) => Console.WriteLine("Распознал-");
  | _       => Console.WriteLine("Фигово-");
}

Более того, он бессмысленнее, так как второй паттерн никогда не сработает.
Подробности смотри в описании паттерна "переменная".

Что же касается до задачи которую ты, по всей видимости, хочешь тут решить, то так просто она, к сожалению, не решается. Рабочий код будет выглядеть так:
match (tvlStateName.Type.Hint)
{
  | Some(type) when typer.InternalType.Int32.Equals(type)  => Console.WriteLine("Распознал int-");
  | Some(type) when typer.InternalType.String.Equals(type) => Console.WriteLine("Распознал-");
  | _            => Console.WriteLine("Фигово-");
}

Для не предопределенных типов все будет еще сложнее, так как придется сначала получить ссылку на тип. Это можно сделать следующими способами.
1. С помощью квази-цитаты ttype:
<[ ttype: System.Collections.Generic.List[int] ]>

2. С помощью typer.Manager.LookupTypeInfo(). Но это еще сложнее, так как при этом получается только TypeInfo, а сам тип придется конструировать вручную.
3. Связать тип с помощью функций typer.BindFixedType() или typer.BindType(). Например:
typer.BindFixedType(<[ System.Collections.Generic.List[_] ]>)

Это пример интересен тем, что возвращает тип параметр типов которого является свободной переменной. Такой тип можно использовать для унификации с другими типами. Унификация это очень крутая штука используемая в выводе типов немерла. Унификация это фича Пролога. Можно считать, что унификация — это как паттер-матчинг, но двусторонний, т.е. влияет как на значение, так и на образец. С помощью унификации можно за один прием определить может ли быть один тип подтипом другого. Об этом можно прочесть здесь
Автор(ы): Владислав Юрьевич Чистяков
Дата: 03.09.2009
В данной части статьи рассказывается о том, как работает система вывода типов Nemerle, о том, как с ней могут взаимодействовать макросы Nemerle, и что это дает
. Но это уже совсем продвинутый курс. Ты забегаешь вперед. Очень многое можно делать вообще без типизации. Как видишь, я реализовал твой пример без типизации и других вывертов.

По уму это конечно следовало бы "засахарить". В общем-то, ttype-цитата и есть такой сахан, но не очень удобный. Хорош было бы сделать макрос который позволял бы производить сопоставление с образцом примерно так как это пытался сделать ты.

Кстати, это отличная тренировочная задача! Решив ее, ты будешь знать о макросах намного больше!

Можно реализовать макрос который можно будет использовать примерно так:
using System.Collections.Generic;
...
ttype match (tvlStateName.Type.Hint)
{
  | ty is int       => тут ty - это переменная описывающая тип int
  | ty is string    => тут ty - это переменная описывающая тип string
  | ty is List[int] => тут ty - это переменная описывающая тип System.Collections.Generic.List[int]
  | ty is List[$p]  => тут ty - это переменная описывающая тип System.Collections.Generic.List[_] с любым типом помещаемым в переменную p.
}
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Как сделать и найти метку в дереве AST
От: FDSC Россия consp11.github.io блог
Дата: 07.08.11 01:40
Оценка: 48 (1)
Здравствуйте, VladD2, Вы писали:

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


Могу поучаствовать в документировании AST, если удастся найти методику работы, так чтобы изучать и документировать одновременно — мне всё равно хочется это дело изучить (и пока есть время), плюс я уже когда-то документировал AST компилятора gcc (там я просто взял исходники Linux, скомпилировал и всё AST автоматом проанализировал на типы узлов и варианты наборов параметров — но это долго возиться надо было)
Re[5]: Как сделать и найти метку в дереве AST
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.08.11 02:13
Оценка:
Здравствуйте, FDSC, Вы писали:

FDS>Могу поучаствовать в документировании AST,


Давай.

FDS>если удастся найти методику работы, так чтобы изучать и документировать одновременно


Как я уже упоминал, лучшая методика тут — использование отладчика и просмотр примеров кода (из компилятора и библиотек).

FDS> — мне всё равно хочется это дело изучить (и пока есть время), плюс я уже когда-то документировал AST компилятора gcc (там я просто взял исходники Linux, скомпилировал и всё AST автоматом проанализировал на типы узлов и варианты наборов параметров — но это долго возиться надо было)


Весь АСТ описан здесь http://github.com/rsdn/nemerle/blob/master/ncc/parsing/ParseTree.n
Еще есть так называемый типизированный АСТ: http://github.com/rsdn/nemerle/blob/master/ncc/typing/TypedTree.n
Но это уже для совсем не тривиальных задач приходится использовать.
Вся (ну, почти вся) логика работы с квази-цитатами находится тут: http://github.com/rsdn/nemerle/blob/master/ncc/typing/Macros.n
Из этого файла можно понять, как пакуются и распаковываются квази-цитаты.

В купе это ~ 150К кода. Причем кода довольно простого, так как это в основном декларации.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Как сделать и найти метку в дереве AST
От: FDSC Россия consp11.github.io блог
Дата: 07.08.11 02:35
Оценка:
Здравствуйте, VladD2, Вы писали:


VD>Кстати, объекты типа Name и хранят информацию о тех самых цветах. И именно по этому используются именно они, а не просто строки.


FDS>>Далее, что мы делаем здесь

FDS>>
FDS>>def statesAst   = stateNames.Map(name => <[ $enumName.$(name : usesite) ]>); // преобразуем список имен в список ссылок на значения enum-а
FDS>>

FDS>>Я так понимаю, что в сумме получится для doing список

VD>Э... а что значит doing?


При обработке метода doing тестового класса

FDS>>
FDS>>[FileStates.Opened, FileStates.Opened]
FDS>>


FDS>>Но зачем указывать $(name : usesite) ?


VD>Для преобразования строки в имя с цветом таким как у места использования (usesite-а).

VD>Как я уже написал выше, можно было обойтись без преобразования в строки и обратно.

Я вот не очень понял, что в этот момент происходит, что такое "цвет". Мне всегда казалось, что если я напишу
def name = "x";
<[$(name: usesite) = 984]>

То вернётся что-то типа
<[x_1 = 984]>

т.е. будет произведена подстановка на место переменной какой-то другой, чтобы скрыть её от повторения

Но тогда непонятно, собственно, зачем это делать сейчас, ведь $name — это идентификатор состояния, например, "Opened", который подставляется к "FileStates." и с ним ничего такого не нужно делать. Или нет?


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


VD>Происходит это потому, что компилятор воспринимает имена int и string за имена переменных. А так как эти переменные нигде не используются, то получается предупреждение.


Было у меня такое подозрение Только как-то это странно: непонятно, какой логикой он руководствуется. Здесь ему так нравится, тут — сяк...

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

1. С помощью квази-цитаты ttype:
<[ ttype: System.Collections.Generic.List[int] ]>


Влад, а почему вот это не работает (этот кусок изначально был [немного его поправил], просто погряз в других math):
def checkParamsTypes()
                {
                    def vlsnCheck = 
                    match (vlStateName)
                    {
                        | <[ $vlsn: string ]> => (true, vlsn)
                        | _ => Console.WriteLine(tvlStateName.Type); (false, PExpr.Void())
                    }

                    def vlasCheck = 
                    match (vlAllowableStates)
                    {
                        | <[ ($vlasList : list[string]) ]> => (true, vlasList)
                        | _ => Console.WriteLine(tvlAllowableStates.Type); (false, PExpr.Void())
                    }

                    (vlsnCheck[0] && vlasCheck[0], vlsnCheck[1], vlasCheck[1], vlsnCheck[0], vlasCheck[0])
                }

Разве это не то самое?
Выдаёт

        string
        list[string-]
D:\works\programming\AITryes\macroTryes_runtime\marks\MainForm.n(35,32): error : verificationMarkState: Тип выражения '"initialized"'неверен (должен быть string)
D:\works\programming\AITryes\macroTryes_runtime\marks\MainForm.n(35,47): error : verificationMarkState: Тип выражения '[""]'неверен (должен быть list[string])

Т.е. он не определяет String

VD>Кстати, это отличная тренировочная задача! Решив ее, ты будешь знать о макросах намного больше!


Попытаюсь завтра
usesite match квазицитирование
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.