Re: Изучаем макросы Nemerle
От: ie Россия http://ziez.blogspot.com/
Дата: 31.03.06 19:39
Оценка:
В процессе изучения этих самых макросов, столкнулся с задачей. Хочется понять есть ли у класса, к которому применяется макрос, тот или иной атрибут. И если есть, выдернуть из него значения. Проблема в том, что для модифицируемого класса не могу получить Type:

    [Nemerle.MacroUsage(Nemerle.MacroPhase.BeforeInheritance,
                        Nemerle.MacroTargets.Class)]
    macro TestMacro(tb : TypeBuilder)
    {
        def c = tb.SystemType;
        System.Console.WriteLine(c.ToString()); // выпадаем с NullReference
    }


Может уже сталкивались с проблемой вытаскивания атрибутов? Как она решается? И почему все-таки не пашет такой код — баг?
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[2]: Изучаем макросы Nemerle
От: Vermicious Knid  
Дата: 31.03.06 22:05
Оценка: 51 (3)
Здравствуйте, ie, Вы писали:

ie> Проблема в том, что для модифицируемого класса не могу получить Type:


Естественно. Тип же еще не существует в скомпилированном виде.

ie>Может уже сталкивались с проблемой вытаскивания атрибутов? Как она решается? И почему все-таки не пашет такой код — баг?


Да легко решается:

    def attributes = tb.GetModifiers().GetCustomAttributes();
    foreach (attr in attributes)
        Console.WriteLine(attr.ToString());

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

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

using System;
using Nemerle.Macros;
using Nemerle.Compiler;

macro DissectAttributes(typeName)
{
    def typer = ImplicitCTX();

    def lookupType(tn)
    {
        match(Macros.GetIfIsType(typer.Env, tn))
        {
            | Some(typeInfo) => typeInfo
            | None => null
        }        
    }

    def getAttrType(attrName)
    {
        def attrType = lookupType(<[ $(attrName : name) ]>);
        if (attrType == null)
        {
            def altName = attrName.NewName(attrName.ToString() + "Attribute");
            lookupType(<[ $(altName : name) ]>)
        }
        else
            attrType
    }

    def attributes = lookupType(typeName).GetModifiers().GetCustomAttributes();
    def serializable = lookupType(<[ System.SerializableAttribute ]>);
    foreach (attr in attributes)
    {
        | <[ $(attrName : name) ]> =>
            def attrType = getAttrType(attrName);
            Console.WriteLine("expr: {0}, type: {1}",
                              attr, attrType.ToString());

            when (serializable.Equals(attrType))
                Console.WriteLine("yay! it's serializable");

        | <[ $(attrName : name)(.. $attrParams) ]> =>
            def attrType = getAttrType(attrName);
            Console.WriteLine("expr: {0}, type: {1}, params: {2}", 
                              attr, attrType.ToString(), attrParams);
        | _ => Console.WriteLine("should not happen, attr was {0}", attr);
    }
    <[ ]>
}

Пример использования:
using System;

[AttributeUsage(AttributeTargets.Class, Inherited=true, AllowMultiple=true)]
public class FooAttribute : Attribute
{
    public this(_name : string)  {}
}


[AttributeUsage(AttributeTargets.Class, Inherited=true, AllowMultiple=true)]
public class Bar : Attribute
{
    public this(_foo : string, _bar : int)  {}
}

[Foo("foobar"), Bar("hellothere!", 2), Serializable]
public class Test
{
    static Main() : void
    {
        DissectAttributes(Test);
    }
}

Результат:
expr: Serializable, type: System.SerializableAttribute
yay! it's serializable
expr: Bar ("hellothere!", 2), type: Bar, params: ["hellothere!", 2]
expr: Foo ("foobar"), type: FooAttribute, params: ["foobar"]

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

Сама концепция макросов и квази-цитирования очень хороша, но вот api компилятора весьма убогое на мой взгляд. В любом случае если язык приобретет популярность, то api поменяется(или наооборот ). И переписывать код макросов будет гораздо проще, если он будет более читабелен.
Re[2]: Изучаем макросы Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 31.03.06 22:22
Оценка:
Здравствуйте, ie, Вы писали:

ie>В процессе изучения этих самых макросов, столкнулся с задачей. Хочется понять есть ли у класса, к которому применяется макрос, тот или иной атрибут. И если есть, выдернуть из него значения. Проблема в том, что для модифицируемого класса не могу получить Type:


Сейчас разбираться нет времени, но настараживает стадия BeforeInheritance. В этот момент типы еще не должны быть доступны в полном объеме. Нужно попробовать более поздние стадии. Поищи описание на сайте Немерла.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Изучаем макросы Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.04.06 17:02
Оценка:
Здравствуйте, Vermicious Knid, Вы писали:

VK> Еще невозможно получить таким образом макро-атрибуты(у меня кстати вообще были сомнения, что это возможно).


Погади. Ведь метаатрибуты просто вызываются компилятором. Причем происходит это на разных стадиях. Так?

Раз так, то можно на ранних стадиях читать информацию переданную в метаатрибуты и складывать ее, например, в хеш-таблицу. На следующей стадии можно эту информацию использовать.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Изучаем макросы Nemerle
От: ie Россия http://ziez.blogspot.com/
Дата: 03.04.06 10:29
Оценка: 33 (2)
Здравствуйте, Vermicious Knid, Вы писали:

ie>>Может уже сталкивались с проблемой вытаскивания атрибутов? Как она решается? И почему все-таки не пашет такой код — баг?


VK>Да легко решается:


VK>
VK>    def attributes = tb.GetModifiers().GetCustomAttributes();
VK>    foreach (attr in attributes)
VK>        Console.WriteLine(attr.ToString());
VK>

VK>Проблема только в том, что атрибуты в данном случае будут представлять из себя выражения. Соотвественно работать с атрибутами штатным способом не выйдет.

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

VK> Выдернуть получится только название и аргументы конструктора. Еще невозможно получить таким образом макро-атрибуты(у меня кстати вообще были сомнения, что это возможно).


У класса Modifiers есть публичное поле macro_attrs, которое по всей видимости и есть список макро-атрибутов. Я не проверял.

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


Мне все же захотелось работать с атрибутами как с атрибутами, поэтому доработал твой пример и вот что собственно получилось:

Хэлпер, инстанциирующий атрибуты и пара тестовых атрибутов:
    [Record]
    public class MyAttrAttribute : Attribute
    {
        [Accessor]
        mutable _name : string = "(Default)";
        public this(){}
    }
    [Record]
    public class MyAttr2Attribute : Attribute
    {
        [Accessor]
        mutable _name : string;
        [Accessor]
        mutable _number : int;
        [Accessor]
        mutable _char : char;
        [Accessor]
        mutable _money : decimal;
        public this(){}
    }

    internal module AttributeHelper
    {
        public GetAttributes(tb : TypeBuilder) : array[Attribute]
        {
            def env = tb.GlobalEnv;

            def lookupType(tn)
            {
                match(Macros.GetIfIsType(env, tn))
                {
                    | Some(typeInfo) => typeInfo
                    | None => null
                }
            }
            def getAttrType(attrName)
            {
                def attrType = lookupType(<[ $(attrName : name) ]>);
                if (attrType == null)
                {
                    def altName = attrName.NewName(attrName.ToString() + "Attribute");
                    lookupType(<[ $(altName : name) ]>)
                }
                else
                    attrType
            }
            def getParamValue(param)
            {
                def resolveLiteralInteger(lit : Literal.Integer)
                {
                    def val = lit.val;
                    def is_negative = lit.is_negative;
                    def treat_as = lit.treat_as;


                    def mul = if (is_negative) -1 else 1;
                    
                    // смотриться убого, надо понять как это все переписать на match
                    if (treat_as.Equals(InternalType.Byte))
                        (val :> Byte) : object;
                    else if (treat_as.Equals(InternalType.UInt16))
                        (val :> UInt16) : object
                    else if (treat_as.Equals(InternalType.UInt32))
                        (val :> UInt32) : object
                    else if (treat_as.Equals(InternalType.UInt64))
                        val : object
                    else if (treat_as.Equals(InternalType.SByte))
                        ((val :> SByte)*mul) : object
                    else if (treat_as.Equals(InternalType.Int16))
                        ((val :> Int16)*mul) : object
                    else if (treat_as.Equals(InternalType.Int32))
                        ((val :> Int32)*mul) : object
                    else if (treat_as.Equals(InternalType.Int64))
                        ((val :> Int64)*mul) : object
                    else
                        throw Exception("Should never be throwed...")
                }
                def resolveLiteral(param)
                {
                    match (param)
                    {
                        | lit is PExpr.Literal =>
                            match (lit.val)
                            {
                                | Literal.Void => null : object
                                | Literal.Null => null : object
                                | Literal.String (val) => val : object
                                | Literal.Float (val) => val : object
                                | Literal.Double (val) => val : object
                                | Literal.Decimal (val) => val : object
                                
                                | Literal.Integer (_, _, _) => 
                                    resolveLiteralInteger(lit.val :> Literal.Integer)
                                
                                | Literal.Bool (val) => val : object
                                | Literal.Char (val) => val : object
                                | Literal.Enum (val, _) => val : object

                                | _ => throw Exception("This literal is not supported yet...")
                            }
                        | _ => throw Exception("Should never be throwed...")
                    }
                }

                resolveLiteral(param);
            }

            def attributes = tb.GetModifiers().GetCustomAttributes();
            mutable resolvedAttributes = [];

            foreach (attr in attributes)
            {
                | <[ $(attrName : name) ]> =>
                    def attrTypeName = getAttrType(attrName).ToString();
                    def attrObj = 
                        Activator.CreateInstance(null, attrTypeName)
                            .Unwrap() :> Attribute;
                    resolvedAttributes = attrObj :: resolvedAttributes;

                | <[ $(attrName : name)(.. $attrParams) ]> =>
                    def attrTypeName = getAttrType(attrName).ToString();
                    def cctorParams = attrParams.Map(getParamValue).ToArray();
                    def attrObj =
                        Activator
                            .CreateInstance(null, attrTypeName, false, 
                                            BindingFlags.CreateInstance, null,
                                            cctorParams, null, null, null)
                            .Unwrap() :> Attribute;
                    resolvedAttributes = attrObj :: resolvedAttributes;

                | _ => throw Exception("Should not happen");
            }

            resolvedAttributes.ToArray();
        }

    }


И собственно употребление этого хэлпера:
    [Nemerle.MacroUsage(Nemerle.MacroPhase.BeforeInheritance,
                        Nemerle.MacroTargets.Class,
                        Inherited = true)]
    macro TestMacro(tb : TypeBuilder)
    {
        def attrs = AttributeHelper.GetAttributes(tb);
        foreach (attr in attrs)
        {
            | a is MyAttrAttribute => 
                Console.WriteLine("MyAttrAttribute(\"{0}\")", a.Name);
            | a is MyAttr2Attribute => 
                Console.WriteLine("MyAttr2Attribute(\"{0}\", {1}, '{2}', {3})",
                                  a.Name, a.Number, a.Char, a.Money);
            | _ => 
                Console.WriteLine("This attribute isn't supported...");
        }
    }


После применения к классу:
    [TestMacro]
    [MyAttr("TestName")]
    [MyAttr2("TestName2", 23, 't', 34.3434m)]
    class TestMacroTest
    {}


Получаем:

MyAttrAttribute("TestName")
MyAttr2Attribute("TestName2", 23, 't', 34.3434)


Однако, скомпилировать такой пример полностью не удалось. Макросы выполнились нормально, но атрибут MyAttr2 компилятору не понравился, упали с исключением:

error : internal compiler error: got ArgumentException (An invalid type was used as a custom attribute constructor argument, field or property.)
at System.Reflection.Emit.CustomAttributeBuilder.InitCustomAttributeBuilder(ConstructorInfo con, Object[] constructorArgs, PropertyInfo[] namedProperties, Object[] propertyValues, FieldInfo[] namedFields, Object[] fieldValues)
at Nemerle.Compiler.AttributeCompiler.do_compile(GlobalEnv env, TypeBuilder ti, TypeInfo attr, list`1 parms)
at Nemerle.Compiler.AttributeCompiler.CompileAttribute(GlobalEnv env, TypeBuilder ti, PExpr expr)
at Nemerle.Compiler.Modifiers.SaveCustomAttributes(TypeBuilder ti, Function`3 adder)
at Nemerle.Compiler.TypeBuilder.EmitImplementation()
at Nemerle.Compiler.TypesManager._N_emit_impl_23651.apply_void(TypeBuilder _N_23650)
at Nemerle.Compiler.TypesManager._N_maybe_f_24088.apply_void(TypeBuilder _N_24087)
at Nemerle.Collections.List.Iter['a](list`1 l, FunctionVoid`1 f)
at Nemerle.Core.list`1.Iter(FunctionVoid`1 f)
at Nemerle.Compiler.TypesManager.Iter(list`1 builders, FunctionVoid`1 f)
at Nemerle.Compiler.TypesManager.compile_all_tyinfos(Boolean aux_phase)
at Nemerle.Compiler.TypesManager._N__N_l23232_23319.apply_void()
at Nemerle.Compiler.Solver.Enqueue(FunctionVoid action)
at Nemerle.Compiler.Passes.Run()
at Nemerle.CommandlineCompiler.MainClass.main_with_catching()


По выделенному я понял, что компилятор сам компилит атрибуты, но как он это делает еще не глянул, пока нет времени

VK>Вообще этот пример и другие примеры из моего личного опыта показывают, что для разработки серьезных макросов неизбежно придется писать удобоваримую обертку над api компилятора. Может быть даже макро-библиотеку для написания макросов.


+1

VK>Сама концепция макросов и квази-цитирования очень хороша, но вот api компилятора весьма убогое на мой взгляд. В любом случае если язык приобретет популярность, то api поменяется(или наооборот ). И переписывать код макросов будет гораздо проще, если он будет более читабелен.


+1
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
Превратим окружающую нас среду в воскресенье.
Re[4]: Изучаем макросы Nemerle
От: Vermicious Knid  
Дата: 04.04.06 23:07
Оценка: 15 (1)
Здравствуйте, ie, Вы писали:

ie>У меня как-раз было огромное желание работать с атрибутами штатным способом. Идея была в том, чтобы использовать атрибуты, как управляющую состовляющую макросов.


Понятно. Посмотрел твой код. Странно как-то ты работаешь с литералами. Нужно что-то вроде этого:
match(lit) // где lit это один из подтипов PExpr, т.е. выражение
{
  // void
  <[ () ]> => // ...
  <[ null ]> => // ...
  <[ $(n : int) ]> => // ...
  <[ $(str : string) ]> => // ...
}

ie>У класса Modifiers есть публичное поле macro_attrs, которое по всей видимости и есть список макро-атрибутов. Я не проверял.

Это по документации. На деле macro_attrs это внутреннее(aka internal) поле. Вообще документация кроме того, что она слабая, она еще и устаревшая. Так что лучшая документация на сегодняшний день это исходники компилятора.

ie>Мне все же захотелось работать с атрибутами как с атрибутами, поэтому доработал твой пример и вот что собственно получилось:


Ясно. Рекомендую отправить баг-репорт. По поводу работы с атрибутами как с атрибутами — на самом деле если не не брать в расчет сложности с пространствами имен, то работа с атрибутами как с выражениями не намного сложнее. Приведу пример:

foreach(attr in attributes)
{
  | <[ Serializable ]> => //...
  | <[ MyAttr($(money : decimal), $(name : string)) ]> => Console.WriteLine($"$money $name");
}

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

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

2) невозможно будет работать с атрибутами определяемыми в компилируемых в данный момент исходников

Можно конечно использовать и совершенно иной подход, который частенько используется(мною и в стандартных макросах) для целей схожих с твоими. Вот примитивный пример(не очень полезный правда ):
using System;
using Nemerle.Macros;
using Nemerle.Compiler;
using Nemerle.Collections;

namespace PlainWeird
{
    internal variant WeirdAttribute
    {
        | Bad  { money : decimal }
        | Good { name : string   }
        | Ugly { name : string; number : int; money : decimal }
    }

    internal module WeirdCache
    {
        // можно и по TypeBuilder кэшировать, но:
        // 1) TypeInfo.FullName обязано обеспечивать уникальное имя типа
        // 2) я не уверен, что у нас всегда будет доступ 
        //    к TypeBuilder классов(в отличие от TypeInfo)
        private AttributeCache : Hashtable[string, list[WeirdAttribute]] = Hashtable();

        public AddAttribute(typeInfo : TypeInfo, attribute : WeirdAttribute) : void
        {
            def fullName = typeInfo.FullName;
            AttributeCache[fullName] = attribute :: AttributeCache[fullName];
        }

        public ContainsAttributes(typeInfo : TypeInfo) : bool
        {
            AttributeCache.Contains(typeInfo.FullName)
        }

        public GetAttributes(typeInfo : TypeInfo) : list[WeirdAttribute]
        {
            AttributeCache[typeInfo.FullName]
        }
    }
    
    [Nemerle.MacroUsage (Nemerle.MacroPhase.BeforeInheritance, 
                         Nemerle.MacroTargets.Class)]
    macro WeirdlyBad(tb : TypeBuilder, money : decimal)
    {
        WeirdCache.AddAttribute(tb, WeirdAttribute.Bad(money));
    }

    [Nemerle.MacroUsage (Nemerle.MacroPhase.BeforeInheritance, 
                         Nemerle.MacroTargets.Class)]
    macro WeirdlyGood(tb : TypeBuilder, name : string)
    {
        WeirdCache.AddAttribute(tb, WeirdAttribute.Good(name));    
    }

    [Nemerle.MacroUsage (Nemerle.MacroPhase.BeforeInheritance, 
                         Nemerle.MacroTargets.Class)]
    macro WeirdAndUgly(tb : TypeBuilder, name : string, number : int, money : decimal)
    {
        WeirdCache.AddAttribute(tb, WeirdAttribute.Ugly(name, number, money));    
    }

    // если я не ошибаюсь можно определять несколько макросов с одним названием,
    // если они отличаются фазами и/или целями применения
    [Nemerle.MacroUsage (Nemerle.MacroPhase.BeforeTypedMembers, 
                         Nemerle.MacroTargets.Class)]
    macro WeirdlyBad(_tb : TypeBuilder, _money : decimal)
    {
        // сделать что-нибудь нехорошее на другой стадии
    }

    macro IsItWeird(typeName)
    {
        def typer = ImplicitCTX();
        def lookupType(tn)
        {
            match(Macros.GetIfIsType(typer.Env, tn))
            {
                | Some(typeInfo) => typeInfo
                | None => null
            }        
        }
        def t = lookupType(typeName);
        when (t == null)
            Message.FatalError($"Expected user-defined type name, got '$typeName'");
        when (WeirdCache.ContainsAttributes(t))
        {
            Console.WriteLine($"$(t.FullName):");
            foreach(attr in WeirdCache.GetAttributes(t))
            {
                | Bad(money) => Console.WriteLine($"    WeirdlyBad($money)")
                | Good(name) => Console.WriteLine($"    WeirdlyGood($name)")
                | Ugly(name, number, money) =>
                    Console.WriteLine($"    WeirdAndUgly($name,$number,$money)")
            }
            Console.WriteLine();
        }
        <[ ]>
    }
}


Пример использования:
using PlainWeird;

namespace Kansas
{
    [WeirdAndUgly("Billy The Kid", 1, 50000.0m), WeirdlyBad(20000.0m)]
    class BillyTheKid
    {
        public this()
        {
            IsItWeird(Sheriff);
            IsItWeird(Texas.Sheriff);
            IsItWeird(Texas.UnknownBandit);
        }
    }

    [WeirdlyGood("Tony Smith")]
    class Sheriff
    {
    }
}

namespace Texas
{
    [WeirdAndUgly("Joe Hulligan", 1, 35000.0m), WeirdlyBad(35000.0m)]
    class JoeHulligan
    {
    }

    [WeirdAndUgly("Billy The Kid #2", 2, 30000.0m)]
    class BillyTheKid
    {
    }

    [WeirdlyBad(3000.0m)]
    class UnknownBandit
    {
    }

    [WeirdlyGood("John Smith")]
    class Sheriff
    {
    }

}

IsItWeird(Kansas.BillyTheKid);
IsItWeird(Texas.BillyTheKid);
IsItWeird(Texas.JoeHulligan);
IsItWeird(IncomprehensibleIdiot);


А вот что компилятор выдаст в консоль:
Kansas.Sheriff:
    WeirdlyGood(Tony Smith)

Texas.Sheriff:
    WeirdlyGood(John Smith)

Texas.UnknownBandit:
    WeirdlyBad(3000.0)

Kansas.BillyTheKid:
    WeirdlyBad(20000.0)
    WeirdAndUgly(Billy The Kid,1,50000.0)

Texas.BillyTheKid:
    WeirdAndUgly(Billy The Kid #2,2,30000.0)

Texas.JoeHulligan:
    WeirdlyBad(35000.0)
    WeirdAndUgly(Joe Hulligan,1,35000.0)

attrtest.n:49:1:49:10: Expected user-defined type name, got 'IncomprehensibleIdiot'
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.