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 через некоторое время появятся сообщения об ошибки. Но проект, тем не менее, будет компилироваться.
В ближайшее время постараюсь починить это дело и добавить макрос в стандартную библиотеке.