Сообщение Re[5]: Pretty print для variants от 14.01.2016 2:59
Изменено 14.01.2016 3:01 VladD2
Здравствуйте, vaskir, Вы писали:
V>Оптимально ли так определять наличие атрибута (по строковому имени)?
Нормально. Другие пути сложнее.
V>То же самое для определения, есть ли неунаследованный переопределенный ToString (opt.GetDirectMembers().Any(x => x.Name == "ToString"))
Тут ты забыл проверить отсутствие аргументов. Твой макрос может ложно сработать на ToString с параметрами, а ToString-а без параметров может и не быть.
V>Получение имен аргументов через мэнчинг PExpr — ОК? Как-то низкоуровнево выглядит, учитываю, что у нас по идее уже есть типизированный метод.
ОК, но есть более простые подходы. Ниже я привел отрефакторнный и поправленный вариант твоего макроса с комментариями. Надеюсь они будут полезны.
V>Оптимально ли так определять наличие атрибута (по строковому имени)?
Нормально. Другие пути сложнее.
V>То же самое для определения, есть ли неунаследованный переопределенный ToString (opt.GetDirectMembers().Any(x => x.Name == "ToString"))
Тут ты забыл проверить отсутствие аргументов. Твой макрос может ложно сработать на ToString с параметрами, а ToString-а без параметров может и не быть.
V>Получение имен аргументов через мэнчинг PExpr — ОК? Как-то низкоуровнево выглядит, учитываю, что у нас по идее уже есть типизированный метод.
ОК, но есть более простые подходы. Ниже я привел отрефакторнный и поправленный вариант твоего макроса с комментариями. Надеюсь они будут полезны.
using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using System.Linq;
[MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
public macro Show(ty: TypeBuilder) {
ShowImpl.Transform(Macros.ImplicitCTX(), ty)
}
module ShowImpl
{
public Transform(typer : Typer, ty: TypeBuilder): void {
// Функция мелкая и не имеет смысла за пределами данного метода. Делаем ее локальной!
def getRecordCtorParams(tb: TypeBuilder): option[list[string]]
{
Macros.DefineCTX(typer); // нужно для "ttype:"
// Просим компилятор отдать нам FixedType для известного типа и берем у него TypeInfo.
def mappedMemberAttribute = <[ ttype: Nemerle.Internal.MappedMemberAttribute; ]>.TypeInfo;
// Если в лямбде много букв, имеет смысл вынести ее в локальную функцию. Так читабельнее выходит.
def extractFieldName(param)
{
// FindAttributeWithArgs находит нужные атрибут по TypeInfo и отдает результат в виде
// Some(имя_атрибута, [параметр_атрибута1, параметр_атрибута2, ...]).
match (param.AttributesAndModifiers.FindAttributeWithArgs(mappedMemberAttribute, tb.GlobalEnv))
{
// "размтачиваем" его одним залпом. У нас должен быть ровно один параметр типа "строковый литерал".
| Some((_, [<[ $(name : string) ]>])) => name
| _ => assert(false) // Параметр должен быть железно! Если мы попали сюда, то что-то пошло не так :)
}
}
tb.GetConstructors(BindingFlags.Public)
// Find() возвращает результат завернутый в option[T]. Если уж ты решил поупражняться в ФП, то так удобнее буедт :).
.Find(method => method.IsCustomAttributeDefined("Nemerle.Internal.RecordCtorAttribute"))
// Ну, тут ты понял. Для тех кто не понял поясню, что первый Map() вызывается на option[Е].
// Если в option есть значение, выполняется переданная функция и результат заворачивается в Some().
// Иначе возвращается None().
.Map(ctor => ctor.GetParameters().Map(extractFieldName))
}
match (ty)
{
| _ when ty.IsDelegate with n = "delegates"
| _ when ty.IsEnum with n = "enums"
| _ when ty.IsInterface with n = "interfaces"
| _ when ty.IsModule with n = "modules" =>
Message.Error(ty.Location, $"The Show macro does not support $n.");
| _ =>
// Надо проверять наличие только ToString без параметров. Луже запихнуть эту проверку в локальную функцию, чтбы код был понятнее.
def existsHandmadeToString(opt) { opt.GetDirectMembers().OfType.[IMethod]().Any(x => x.Name == "ToString" && x.Header.Parameters.IsEmpty) }
// Nemerle нормальный императивный язык. Iter() не нужны! Используем foreach :).
// К тому же он в Nemerle крутой! Он паттарн-матчинг поддерживает. В данном случае используем его
// гуард для фильтрации типов в которых метод ToString написан вручную.
foreach (opt when !existsHandmadeToString(opt) in ty.GetVariantOptions())
{
// Если match планируется обрабатывать только один случай паттерн-матчинга, то можно использовать макру when или if
when (getRecordCtorParams(opt) is Some(pars))
{
def fieldsExpr = pars.Fold(<[]>, (f, acc) => {
match (acc) {
| <[]> => <[ $(f: usesite).ToString() ]>
| _ => <[ $acc + ", " + $(f: usesite).ToString() ]>
}
});
def body =
match (fieldsExpr) {
| <[]> => <[ $(opt.Name) ]>
| _ => <[ $(opt.Name) + "(" + $fieldsExpr + ")" ]>
};
opt.Define(<[decl:
public override ToString(): string {
$body
}
]>);
}
}
}
}
}
Re[5]: Pretty print для variants
Здравствуйте, vaskir, Вы писали:
V>Оптимально ли так определять наличие атрибута (по строковому имени)?
Нормально. Другие пути сложнее.
V>То же самое для определения, есть ли неунаследованный переопределенный ToString (opt.GetDirectMembers().Any(x => x.Name == "ToString"))
Тут ты забыл проверить отсутствие аргументов. Твой макрос может ложно сработать на ToString с параметрами, а ToString-а без параметров может и не быть.
V>Получение имен аргументов через мэнчинг PExpr — ОК? Как-то низкоуровнево выглядит, учитываю, что у нас по идее уже есть типизированный метод.
ОК, но есть более простые подходы. Ниже я привел отрефакторнный и поправленный вариант твоего макроса с комментариями. Надеюсь они будут полезны.
V>Оптимально ли так определять наличие атрибута (по строковому имени)?
Нормально. Другие пути сложнее.
V>То же самое для определения, есть ли неунаследованный переопределенный ToString (opt.GetDirectMembers().Any(x => x.Name == "ToString"))
Тут ты забыл проверить отсутствие аргументов. Твой макрос может ложно сработать на ToString с параметрами, а ToString-а без параметров может и не быть.
V>Получение имен аргументов через мэнчинг PExpr — ОК? Как-то низкоуровнево выглядит, учитываю, что у нас по идее уже есть типизированный метод.
ОК, но есть более простые подходы. Ниже я привел отрефакторнный и поправленный вариант твоего макроса с комментариями. Надеюсь они будут полезны.
using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using System.Linq;
[MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
public macro Show(ty: TypeBuilder) {
ShowImpl.Transform(Macros.ImplicitCTX(), ty)
}
module ShowImpl
{
public Transform(typer : Typer, ty: TypeBuilder): void {
// Функция мелкая и не имеет смысла за пределами данного метода. Делаем ее локальной!
def getRecordCtorParams(tb: TypeBuilder): option[list[string]]
{
Macros.DefineCTX(typer); // нужно для "ttype:"
// Просим компилятор отдать нам FixedType для известного типа и берем у него TypeInfo.
def mappedMemberAttribute = <[ ttype: Nemerle.Internal.MappedMemberAttribute; ]>.TypeInfo;
// Если в лямбде много букв, имеет смысл вынести ее в локальную функцию. Так читабельнее выходит.
def extractFieldName(param)
{
// FindAttributeWithArgs находит нужный атрибут по TypeInfo и отдает результат в виде
// Some(имя_атрибута, [параметр_атрибута1, параметр_атрибута2, ...]).
match (param.AttributesAndModifiers.FindAttributeWithArgs(mappedMemberAttribute, tb.GlobalEnv))
{
// "размтачиваем" его одним залпом. У нас должен быть ровно один параметр типа "строковый литерал".
| Some((_, [<[ $(name : string) ]>])) => name
| _ => assert(false) // Параметр должен быть железно! Если мы попали сюда, то что-то пошло не так :)
}
}
tb.GetConstructors(BindingFlags.Public)
// Find() возвращает результат завернутый в option[T]. Если уж ты решил поупражняться в ФП, то так удобнее буедт :).
.Find(method => method.IsCustomAttributeDefined("Nemerle.Internal.RecordCtorAttribute"))
// Ну, тут ты понял. Для тех кто не понял поясню, что первый Map() вызывается на option[Е].
// Если в option есть значение, выполняется переданная функция и результат заворачивается в Some().
// Иначе возвращается None().
.Map(ctor => ctor.GetParameters().Map(extractFieldName))
}
match (ty)
{
| _ when ty.IsDelegate with n = "delegates"
| _ when ty.IsEnum with n = "enums"
| _ when ty.IsInterface with n = "interfaces"
| _ when ty.IsModule with n = "modules" =>
Message.Error(ty.Location, $"The Show macro does not support $n.");
| _ =>
// Надо проверять наличие только ToString без параметров. Луже запихнуть эту проверку в локальную функцию, чтбы код был понятнее.
def existsHandmadeToString(opt) { opt.GetDirectMembers().OfType.[IMethod]().Any(x => x.Name == "ToString" && x.Header.Parameters.IsEmpty) }
// Nemerle нормальный императивный язык. Iter() не нужны! Используем foreach :).
// К тому же он в Nemerle крутой! Он паттарн-матчинг поддерживает. В данном случае используем его
// гуард для фильтрации типов в которых метод ToString написан вручную.
foreach (opt when !existsHandmadeToString(opt) in ty.GetVariantOptions())
{
// Если match планируется обрабатывать только один случай паттерн-матчинга, то можно использовать макру when или if
when (getRecordCtorParams(opt) is Some(pars))
{
def fieldsExpr = pars.Fold(<[]>, (f, acc) => {
match (acc) {
| <[]> => <[ $(f: usesite).ToString() ]>
| _ => <[ $acc + ", " + $(f: usesite).ToString() ]>
}
});
def body =
match (fieldsExpr) {
| <[]> => <[ $(opt.Name) ]>
| _ => <[ $(opt.Name) + "(" + $fieldsExpr + ")" ]>
};
opt.Define(<[decl:
public override ToString(): string {
$body
}
]>);
}
}
}
}
}