[Nemerle] проблема с типами при написании макроса
public ChildrenProp : List[Child]
{
get
{
}
}
проблема -
1) надо из property.returnType достать его Generic параметр (в данном случае Child из List[Child])
2) чтобы в классе обьявить метод (.Define(<[...]>)) необходимо иметь TypeBuilder этого класса. (там коментарием псевдокодом написано что я хочу получить)
MacroTargets.Property)]
macro Children(t : TypeBuilder, p : ParsedProperty, qstring : string)
{
def property = p : ClassMember.Property;
def cached_instance = Macros.NewSymbol ("cached");
def GetFor = Macros.NewSymbol ("GetFor" + $"$(t.ParsedTypeName)");
t.Define(<[ decl: private static mutable $(cached_instance: name): $(t.ParsedTypeName) = null;]>); // кешируем полученное значение
// в классе ретурн типа пропорты обьявляем функцию GetFor.
(property.returnType./*GetGenericTypes[0].Typebuilder*/).Define(<[ decl: public static $(GetFor : name)(ID : Guid) : $(property.returnType)
{
GetByQuery($"$($qstring) = '$(ID)'");
}]>);
def accessor(accessor)
{
| Some(fn) => fn
| _ => Message.Error(property.Location, "Both getter and setter should be specified for dependency property."); null
};
def getter = accessor(property.getter);
getter.Body = <[ // засетиваем геттер нашей пропорты
if( $(cached_instance: name) != null)
$(cached_instance: name)
else
$(cached_instance: name) = ($(property.returnType))/*тож самое - нужный нам генерик тип сюда*/.$(GetFor : name)(ID);
]>;
}
1) надо из property.returnType достать его Generic параметр (в данном случае Child из List[Child])
2) чтобы в классе обьявить метод (.Define(<[...]>)) необходимо иметь TypeBuilder этого класса. (там коментарием псевдокодом написано что я хочу получить)
tb - билдер типа, в котором объявлено свойство pb.
Первый матч выцепляет единственный артумент типа
BindFixedType позволяет по PExpr получить FixedType (или не получить. если программист допутсил ошибку), из которого можно вытащить билдер.
def ty_arg = match(pb.returnType) {
| <[ $_[$ty_arg] ]> => ty_arg;
| _ => Message.FatalError("Improper usage of macro");
}
def ft = tb.BindFixedType(ty_arg);
match(ft?.TypeInfo) {
| child_tb is TypeBuilder => Message.Hint(child_tb.ToString());
| _ => Message.Hint("Type builder not found")
}
}
def ft = tb.BindFixedType(ty_arg);
tb, ty_arg не нулы, указывают на типы, даже с указанием неймспейса
не могу понять откуда возник иксепшн
в Nemerle.Compiler.TypeBuilder.BindFixedType(PExpr t)
а вообще круто! до таких вещей сам не допрешь. изучаю язык в основном читая его исходники
Для отладки макроса можно использовать инструкцию:
_ = System.Diagnostics.Debugger.Launch(); // или assert2(false);
Она запустит отладчик прямо во время компиляции проекта.
def ft = tb.BindFixedType(ty_arg);
tb, ty_arg не нулы, указывают на типы, даже с указанием неймспейса
не могу понять откуда возник иксепшн
в Nemerle.Compiler.TypeBuilder.BindFixedType(PExpr t)
Нда, писал второпях. К сожалению забыл я сказать, что у BindFixedType на стадии BeforeInheritance какие-то проблемы, макрос нужно сдвигать на стадию BeforeTypedMembers.
Весь код макроса:
using SCG = System.Collections.Generic;
using Nemerle;
using Nemerle.Assertions;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Utility;
using PT = Nemerle.Compiler.Parsetree;
using TT = Nemerle.Compiler.Typedtree;
namespace ChildrenMacro {
[MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Property)]
macro Children(tb : TypeBuilder, pb : ParsedProperty, q : string) {
ChildrenImpl.DoWork(tb, pb, q);
}
module ChildrenImpl {
GetChildTypeBuilder(tb : TypeBuilder, pb : PT.ClassMember.Property) : option[TypeBuilder] {
//assert2(false); // позволяет отлаживаться
def ty_arg = match(pb.returnType) {
| <[ $_[$ty_arg] ]> => ty_arg;
| _ => Message.FatalError("Type with single type argument expected.");
}
def ft = tb.BindFixedType(ty_arg);
match(ft?.TypeInfo) {
| child_tb is TypeBuilder => Some(child_tb)
| _ => None()
}
}
public DoWork(tb : TypeBuilder, pb : PT.ClassMember.Property, q : string) : void {
match(GetChildTypeBuilder(tb, pb)) {
| Some(child_tb) =>
def cached_instance = Macros.NewSymbol("cached");
tb.Define(<[ decl:
private static mutable $(cached_instance : name) : $(pb.returnType) = null;
]>);
def get_for = Macros.NewSymbol("GetFor");
child_tb.Define(<[ decl:
internal static $(get_for : name) (id : System.Guid) : $(pb.returnType) {
def q = $(q : string);
GetByQuery($"$q = '$id'");
}
]>);
def get_accessor(_) {
| Some(f) => f;
| _ => Message.FatalError(pb.Location, "Both getter and setter should be specified for dependency property.");
}
def getter = get_accessor(pb.getter);
getter.Body = <[
when(null == $(cached_instance : name))
$(cached_instance : name) = $(child_tb.ParsedName : name).$(get_for : name)(ID);
$(cached_instance : name)
]>;
| _ => Message.FatalError("Type must be declared in this project.");
}
}
}
}
Это пожалуй главная проблема - крайне мало документации. :(
[TransitLink2("TableName", "ParentID", "ChildID")]
public Children:MultyLink[Child]
{get{null}}
используется при связи много ко много - требует в БД таблицы развязки, и соотв класса
в данном макросе создаю этот класс-связку и потом к нему обращаюсь, прописываю геттер пропорты, методы обьявляю в тек классе итд, неважно
в строке curenv.Define(LinkClass); вылетает иксепшн "Internal compiler error 'env is null for Data.NewEngine.TableName__", в дебаге LinkClass.Env равен GlobalEnv (собсно тому, что засетил)
идеи как создать в макросе класс передирал с Nemerle.Utility.Accessor
using SCG = System.Collections.Generic;
using Nemerle;
using Nemerle.Assertions;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Utility;
using System.Linq;
using PT = Nemerle.Compiler.Parsetree;
using TT = Nemerle.Compiler.Typedtree;
namespace DbEngine.Macroses
{
[MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Property)]
macro TransitLink2(tb : TypeBuilder, pb : ParsedProperty, table : string, idMy : string, idChild : string)
{
unless(tb.GlobalEnv.Manager.IsIntelliSenseMode)
TransitLink2Impl.DoWork(tb, pb, table, idMy, idChild)
}
module TransitLink2Impl
{
public DoWork(tb : TypeBuilder, pb : PT.ClassMember.Property, table : string, idMy : string, idChild : string) : void
{
def GetLinkTB()
{
def firstName = ...;
def secondName= ...;
def parentIdNo = ...;
def linkClassName = Compiler.Parsetree.Name($"$(table)__");
def curenv = tb.GlobalEnv;
match(curenv.BindFixedType(<[$(linkClassName : name)]>)?.TypeInfo) // check if already exists
{
| child_tb is TypeBuilder => (parentIdNo, Some(child_tb))
| _=>
def LinkClass =
<[ decl:
[Table($"$(table : string)")]
internal class $(linkClassName : name) : BaseClass[$(linkClassName : name)], ILink
{
public this(ID1 : Guid, ID2 : Guid)
{
base();
reader[$"$(firstName : string)"] = ID1;
reader[$"$(secondName : string)"] = ID2;
}
[ParentLink($"$(firstName : string)")] // свой другой макрос
public FirstClass : BaseClass {get{null}}
[ParentLink($"$(secondName : string)")]
public SecondClass : BaseClass {get{null}}
}
]>;
LinkClass.SetEnv(curenv);
def cc = curenv.Define(LinkClass);
cc.Compile();
(parentIdNo, Some(cc))
}
}
match(GetLinkTB())
{
| (parentIdNo, Some(link_tb)) =>
...
| _ => Message.FatalError("Error generating Link Class");
}
| _ => Message.FatalError("Type must be declared in this project.");
}
}
}
}
в строке curenv.Define(LinkClass); вылетает иксепшн "Internal compiler error 'env is null for Data.NewEngine.TableName__"
В цитатах объявляющих класс/структуру можно объявлять только конструкторы:
class X {
public this() { doSomth; }
}
]>
Остальные члены нужно объявлять либо отдельными вызовами Define
class MyClass { }
]>);
tb.Define(<[ decl : public Foo() : void { blabla } ]>);
tb.Define(<[ decl : public Bar() : void { blabla } ]>);
либо такой штукой:
<[ decl : public Foo() : void { blabla } ]>,
<[ decl : public Bar() : void { blabla } ]>
];
def tb = p.Define(<[ decl:
class MyClass { ..$members }
]>);
Да, это неудобно сам нарывался, но еще ни у кого руки не дошли исправить :(
<[ decl:
internal class $(linkClassName : name) : BaseClass[$(linkClassName : name)], ILink
{
}
]>;
пустой класс. но ему все равно не нравится.
в качестве env беру GlobalEnv билдера, в котором нахожусь
<[ decl:
internal class $(linkClassName : name) : BaseClass[$(linkClassName : name)], ILink
{
}
]>;
пустой класс. но ему все равно не нравится.
в качестве env беру GlobalEnv билдера, в котором нахожусь
А для чего делается вот это?
Класс и так будет объявлен в пространстве имен curenv.
но оно что с ним что без него не пашет
может еще дело в стадии компиляции макроса?
[MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Property)]
но оно что с ним что без него не пашет
может еще дело в стадии компиляции макроса?
[MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Property)]
Хмм не знаю, трудно смоделировать.
BindFixedType кстати тут совсем не нужно использовать. BindFixedType генерирует ошибку компиляции, если не смог найти тип, потому нужно использовать LookupType:
macro TransitLink2(tb : TypeBuilder, pb : ParsedProperty, table : string) {
unless(tb.GlobalEnv.Manager.IsIntelliSenseMode)
TransitLink2Impl.DoWork(tb, pb, table);
}
module TransitLink2Impl {
public DoWork(tb : TypeBuilder, pb : PT.ClassMember.Property, table : string) : void {
def linkClassName = "Blabla_" + table;
match(tb.GlobalEnv.LookupType([linkClassName])) // check if already exists
{
| Some(_ is TypeBuilder) => () // уже объявлен в этой сборке
| Some(_ is TypeInfo) => () // объявлен в другой сборке
| _ => // еще не объявлен
def LinkClass = <[ decl:
public class $(linkClassName : usesite) { }
]>;
def cc = tb.GlobalEnv.Define(LinkClass);
cc.Compile();
}
}
}
З.Ы. Использую компилятор версии r9037.
таки я не могу в это поверить, но проблема была в
$(linkClassName : usesite) (я же использовал Нейм)
а какая разница вообще?
$(linkClassName : usesite) (я же использовал Нейм)
а какая разница вообще?
PT.Name захватывает (или не захватывает, как в вашем случае) GlobalEnv в котором объявлен (поле context). Я не рекомендую изготавливать Name вручную - можно нарваться на самые веселые грабли:
class X {
public this() { doSomth; }
}
]>
Остальные члены нужно объявлять либо отдельными вызовами Define
class MyClass { }
]>);
tb.Define(<[ decl : public Foo() : void { blabla } ]>);
tb.Define(<[ decl : public Bar() : void { blabla } ]>);
либо такой штукой:
<[ decl : public Foo() : void { blabla } ]>,
<[ decl : public Bar() : void { blabla } ]>
];
def tb = p.Define(<[ decl:
class MyClass { ..$members }
]>);
Да, это неудобно сам нарывался, но еще ни у кого руки не дошли исправить :(
Исправил в r9043. Теперь члены классов можно объявлять в квазицитате с этим классом.