ExtensionPattern
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.08.11 04:29
Оценка: 82 (4)
ExtensionPattern наверно самая плохо освещенная фича Nemerle. ExtensionPattern — это макрос, который позволяет описать свой паттерн для произвольного типа. К сожалению реализация ExtensionPattern входящая в стандартную библиотеку макросов позволяет задавать паттерны-расширения только для описываемых в проекте типов.

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

Вот как выглядит применение нового макроса для задания паттернов для System.Linq.Expressions.Expression (деревьев выражений МС):
using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Console;
using System.Linq;
using System.Linq.Expressions;
using MacroLibrary;

[assembly: ExtensionPattern(Expression, Lambda(delegateType, name, body, tailCall, parameters)
  = LambdaExpression where(Type=delegateType, Name=name, Body=body, TailCall=tailCall, Parameters=parameters))]
[assembly: ExtensionPattern(Expression, Add(left, right)
  = BinaryExpression where(NodeType=ExpressionType.Add, Left=left, Right=right))]
[assembly: ExtensionPattern(Expression, AddChecked(left, right)
  = BinaryExpression where(NodeType=ExpressionType.AddChecked, Left=left, Right=right))]

module Program
{
  TestExpressionTree[T1, T2](expr : Expression[Func[T1, T2]], value : T1) : void
  {
    match (expr)
    {
       | Lambda(_, _, Add(Add(e1, e2), e3), _, parameters) => 
        WriteLine($"Мы таки нашли выражение x + y + z! Вот оно: $e1 + $e2 + $e3");
        foreach (p in parameters)
          WriteLine($"  Параметр: $(p.Name) : $(p.Type)");

       | Lambda(_, name, body, tailCall, parameters) => 
        WriteLine($"Lambda(name='$name', body='$body', tailCall=$tailCall)");
        foreach (p in parameters)
          WriteLine($"  Параметр: $(p.Name) : $(p.Type)");
        
      | _ => WriteLine("unknown :(");
    }
    def f = expr.Compile();
    def res = f(value);
    WriteLine(res);
  }
  
  Main() : void
  {
    TestExpressionTree(x => x + 2 + 3, 42);
    TestExpressionTree(x => x - 2L, 7L);
    _ = ReadLine();
  }
}

Этот код выводит на консоль следующее:
Мы таки нашли выражение x + y + z! Вот оно: x + 2 + 3
  Параметр: x : System.Int32
47
Lambda(name='', body='(x - Convert(2))', tailCall=False)
  Параметр: x : System.Int64
5

А вот реализация макроса (она очень проста):
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;
using Nemerle.Imperative;

namespace MacroLibrary
{
  [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Assembly)]
  macro ExtensionPattern(typeName : PExpr, defenition : PExpr)
  {
    ExtensionPatternImpl.DoTransform(Macros.ImplicitCTX(), typeName, defenition)
  }
  
  module ExtensionPatternImpl
  {
    public DoTransform(typer : Typer, typeName : PExpr, defenition : PExpr) : void
    {
      Macros.DefineCTX(typer);
      def ty = typer.BindFixedType(typeName);
      
      when (ty.Equals(typer.InternalType.Object))
        return;
        
      when (ty.TypeInfo == null)
        Message.Error(typeName.Location, "The 'typeName' must be a name of user defined type (like class).");
      
      AddExtensionPattern(ty.TypeInfo, defenition);

    }

    AddExtensionPattern(type : TypeInfo, defenition : PExpr) : void
    {
      def get_name (defenition)
      {
        | <[ $(id : dyn) ]> => Some (id)
        | _ => None ()
      }

      match (defenition)
      {
        | <[ $(id : dyn) ( .. $ids ) = $pat ]> when ids.ForAll(x => get_name(x).IsSome) =>
          def ids = ids.Map(get_name).Map (Option.UnSome);
          def ext =
            ExtensionPattern (parent      = type,
                              name        = id,
                              pattern     = pat,
                              identifiers = ids);
          type.AddExtensionPattern(ext)

        | _ => Message.FatalError("ExtensionPattern syntax is ``name (id_1, ..., id_n) = pattern''")
      }
    }
  }
}


Единственная проблема имеющаяся на сегодня — макрос не работает как следует в IDE. Метод AddExtensionPattern добавляет паттерн-расширение в поле TypeInfo, а TypeInfo для внешних типов кэшируеются (не пересоздаются) пока не изменится окружение (ссылочные сборки или другие настройки проекта). Так что в IDE через некоторое время появятся сообщения об ошибки. Но проект, тем не менее, будет компилироваться.

В ближайшее время постараюсь починить это дело и добавить макрос в стандартную библиотеке.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.