//
// Anonymous Class Macro for Nemerle
// Copyright (c) 2010, Stanislav Matveev (hardcaseminator@gmail.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the author nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
using System;
using Nemerle;
using Nemerle.Assertions;
using Nemerle.Utility;
using Nemerle.Compiler;
using Nemerle.Collections;
using PT = Nemerle.Compiler.Parsetree;
using TT = Nemerle.Compiler.Typedtree;
namespace Nemerle.Extensions {
public macro AnonymousClassNormalCtor(params ctor_def : array[expr])
syntax("new", "(", ctor_def, ")")
{
AnonymousClassImpl.MakeNormalCtor(NList.ToList(ctor_def))
}
public macro AnonymousClassTupleCtor(e)
syntax("new", e)
{
def decorate(e) {
| <[ $left.$right ]> => <[ $(decorate(left)).$right ]>
| <[ $left.$right(..$args) ]> => <[ $(decorate(left)).$right(..$args) ]>
| <[ [ ..$ctor_def ] ( ..$ctor_arg ) ]> =>
AnonymousClassImpl.MakeTupleCtor(ctor_def, ctor_arg)
| _ =>
Message.FatalError("Usage: 'new [a, b, c](x, y, z)' or 'new [a, b, c](t)' where 't' is tuple.")
}
decorate(e)
}
///
/// This macros fixes Mono compilation since Mono puts macros in the reverse order.
///
public macro AnonymousClassNormalCtor2(params ctor_def : array[expr])
syntax("new", "(", ctor_def, ")")
{
AnonymousClassImpl.MakeNormalCtor(NList.ToList(ctor_def))
}
[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Assembly)]
public macro AnonymousClassOptions(params opts : array[expr])
{
def set_visibility(_) {
| <[ Public ]> => AnonymousClassImpl.VisibilityAttribute = NemerleAttributes.Public;
| <[ Internal ]> => AnonymousClassImpl.VisibilityAttribute = NemerleAttributes.Internal;
| e => Message.Error($"Invalid anonymous class visibility option '$e', allowed only 'Public' and 'Internal' (default).")
}
foreach(opt in opts) {
| <[ visibility = $e ]>
| <[ Visibility = $e ]> => set_visibility(e)
| e => Message.Error($"Invalid anonymous class option '$e'. Usage: 'Visibility = Public'.");
}
}
[ManagerAccess(ManagerClass.Instance)]
internal module AnonymousClassImpl {
AnonymousClassNamespace = "<>_N_AnonymousClasses";
[Record]
private class FieldInfo {
[Accessor] field : PT.Name;
[Accessor] value : PT.PExpr;
public PropertyName : string { get { field.ToString() } }
public FieldName : string { get { "_" + field.ToString() } }
[RecordIgnore] mutable generic_arg : PT.Name = null;
public GenericArg : PT.Name {
get {
when(null == generic_arg)
generic_arg = Macros.NewSymbol();
generic_arg
}
}
}
public MakeNormalCtor(ctor_def : list[PT.PExpr]) : PT.PExpr {
def fields = ctor_def.FoldRight([], fun(cd, acc) {
def append_field_info(field_init) {
| <[ $(field : name) = $value ]> => FieldInfo(field, value) :: acc
| <[ $(field : name) ]> => FieldInfo(field, field_init) :: acc
| <[ $_.$(field : name) ]> => FieldInfo(field, field_init) :: acc
| _ => Message.Error($"Expected 'a = foo()' or 'foo().x' or 'foo' got '$cd'."); acc
}
match(cd) {
| PT.PExpr.Member(_, member) as member_access =>
append_field_info(<[ $(member.GetName() : name) = $member_access ]>)
| _ => append_field_info(cd)
}
});
def ty = FindOrBuildClass(fields);
<[ $(AnonymousClassNamespace : usesite).$(ty.Name : usesite) ( ..$(fields.Map(f => f.Value) ) ) ]>
}
public MakeTupleCtor(ctor_def : list[PT.PExpr], args : list[PT.PExpr]) : PT.PExpr {
def fields = ctor_def.FoldRight([], fun(cd, acc) {
def append_field_info(field_init) {
| <[ $(field : name) ]> => FieldInfo(field, <[ () ]>) :: acc
| _ => Message.Error($"Field name expected, got '$cd'."); acc
}
match(cd) {
| PT.PExpr.Member(_, member) as member_access =>
append_field_info(<[ $(member.GetName() : name) = $member_access ]>)
| _ => append_field_info(cd)
}
});
def ty = FindOrBuildClass(fields);
match(args, fields) {
| ([], []) =>
<[ $(AnonymousClassNamespace : usesite).$(ty.Name : usesite) ( ) ]>
| ([one], fields) when (0 < fields.Length) =>
<[ $(AnonymousClassNamespace : usesite).$(ty.Name : usesite) ( $one ) ]>
| (many, fields) when (many.Length == fields.Length) =>
<[ $(AnonymousClassNamespace : usesite).$(ty.Name : usesite) ( ..$many ) ]>
| _ =>
Message.FatalError("Invalid argument list for anonymous class constructor specified.")
}
}
public VisibilityAttribute : NemerleAttributes {
key : string = "Nemerle.Extensions.AnonymousClassImpl.VisibilityAttribute";
get {
match(Manager.UserData[key]) {
| null => NemerleAttributes.Internal
| obj => obj :> NemerleAttributes
}
}
set {
Manager.UserData[key] = value
}
}
private ClassTable : Hashtable.[string, TypeInfo] {
key : string = "Nemerle.Extensions.AnonymousClassImpl.ClassTable";
get {
match(Manager.UserData[key]) {
| null =>
def table = Hashtable();
Manager.UserData[key] = table;
table
| obj => obj :> Hashtable.[string, TypeInfo]
}
}
}
private unique_name_seed : long = DateTime.Now.Ticks;
private FindOrBuildClass(fields : list[FieldInfo]) : TypeInfo {
def id = fields.FoldLeft("_N_Anonymous_", (f, name) => name + $"<$(f.PropertyName)>_") + "<>";
def name = id + unchecked((DateTime.Now.Ticks * unique_name_seed) :> uint).ToString();
match(ClassTable.TryGetValue(id)) {
| (ty, true) => ty
| _ =>
def ty = BuildClass(name, id, fields);
ClassTable[id] = ty;
ty
}
}
private MIN_TUPLE_SIZE : int = 2;
private MAX_TUPLE_SIZE : int = 20;
private BuildClass(name : string, id : string, fields : list[FieldInfo]) : TypeInfo {
def generic_args = fields.Map(f => <[ $(f.GenericArg : name) ]>);
def ty_name = <[ $(AnonymousClassNamespace : usesite).$(name : usesite) ]>;
def ty_ref = match(fields) {
| [] => ty_name
| _ => <[ $ty_name [ ..$generic_args ] ]>
}
def anonymous_ref = <[ Nemerle.Extensions.IAnonymous ]>;
def equality_comparer(t) {
<[ System.Collections.Generic.EqualityComparer.[ $t ].Default ]>
}
def external_anonymous_types = Manager.CoreEnv.NameTree.NamespaceTree.LookupTypes([AnonymousClassNamespace, id], true);
def ty = {
// Format string for debugger view
def debugger_display_fmt = {
def field_fmt(f) {
$"$(f.PropertyName) = {$(f.PropertyName)}"
}
$<#\{ ..$(fields; ", "; field_fmt) \}#>
}
// fields and properties declaration
def members = fields.Map(field => <[ decl :
[Nemerle.Utility.Accessor($(field.PropertyName : usesite))]
private $(field.FieldName : usesite) : $(field.GenericArg : name);
]>);
def attrs = Modifiers(VisibilityAttribute %| NemerleAttributes.Sealed, [
<[ Record ]>,
<[ StructuralHashCode ]>,
<[ System.Diagnostics.DebuggerDisplay( $(debugger_display_fmt : string), @Type = "" )]> ]);
def env = Manager.CoreEnv.EnterIntoNamespace([AnonymousClassNamespace]);
env.Define(match(fields) {
| [] =>
<[ decl:
..$attrs
class $(name : usesite) : $anonymous_ref, System.IEquatable[ $ty_ref ] {
..$members
}
]>
| _ =>
def generic_args_decl = fields.Map(f => PT.Splicable.Name(f.GenericArg));
<[ decl:
..$attrs
class $(name : usesite) [ ..$generic_args_decl ] : $anonymous_ref, System.IEquatable[ $ty_ref ] {
..$members
}
]>
})
}
// GetFields() implementation
{
def field_list_name = Macros.NewSymbol("field_list");
def field_list = fields.Map(f => <[ $(f.PropertyName : string) ]>);
ty.Define(<[ decl:
private static $(field_list_name : name) : System.Collections.ObjectModel.ReadOnlyCollection[string] = System.Array.AsReadOnly(array[ ..$field_list ]);
]>);
ty.Define(<[ decl:
GetFields() : System.Collections.ObjectModel.ReadOnlyCollection[string] implements $anonymous_ref.GetFields {
$(field_list_name : name)
}
]>);
}
// Item indexer implementation
{
def body = match(fields) {
| [] => <[ ignore(field); null ]>
| _ =>
def cases = fields.FoldRight([<[ case : | _ => null ]>],
(f, cases) => <[ case: | $(f.PropertyName : string) => this.$(f.FieldName : usesite) : object ]> :: cases );
<[ match(field) { ..$cases } ]>
}
ty.Define(<[ decl:
Item[field : string] : object implements $anonymous_ref.Item {
get { $body }
}
]>);
}
def can_be_tuple(fields) {
def len = fields.Length;
(MIN_TUPLE_SIZE <= len) && (len <= MAX_TUPLE_SIZE)
}
// GetContent implementation
{
def body = match(fields) {
| [] => <[ null ]>
| [field] => <[ this.$(field.FieldName : usesite) ]>
| fields when can_be_tuple(fields) => <[ ToTuple() ]>
| _ =>
def list_items = fields.Map(f => <[ this.$(f.FieldName : usesite) : object ]>);
<[ [ ..$list_items ] ]>
}
ty.Define(<[ decl:
GetContent() : object implements $anonymous_ref.GetContent {
$body
}
]>);
}
// Tuple interop
match(fields) {
| [] | [_] => ()
| fields when can_be_tuple(fields) =>
// ToTuple method
{
def tuple_args = fields.Map(f => <[ this.$(f.FieldName : usesite) ]>);
ty.Define(<[ decl:
public ToTuple() : Nemerle.Builtins.Tuple.[ ..$generic_args ] {
Nemerle.Builtins.Tuple( ..$tuple_args )
}
]>);
}
| _ => Message.Warning("Anonymous class contains too many fields to be convertible to tuple.");
}
// ToString implementation
{
def (_, sb) = fields.FoldLeft( (" ", <[System.Text.StringBuilder("{")]> ),
(f, (div, sb)) => (", ", <[ $sb.Append($(div + f.PropertyName + " = " : string)).Append(this.$(f.FieldName : usesite)) ]>) );
ty.Define(<[ decl:
public override ToString() : string {
$sb.Append(" }").ToString()
}
]>);
}
def equals_generic_body = fields.FoldLeft(<[ true ]>, (f, body) => <[
$body && $(equality_comparer(PT.PExpr.Ref(f.GenericArg))).Equals(this.$(f.FieldName : usesite), other.$(f.FieldName : usesite))
]>);
// Equals(other : object) implementation
{
def body = match(fields) {
| [] => <[
match(other) {
| _ is $ty_ref => true
| other is $anonymous_ref => (other.GetFields().Count == 0)
| _ => false
}
]>
| _ => <[
match(other) {
| other is $ty_ref => $equals_generic_body
| other is $anonymous_ref =>
Nemerle.Extensions.Anonymous.Equals(this, other)
| _ => false
}
]>
}
ty.Define(<[ decl:
public override Equals(other : object) : bool { $body }
]>);
}
// Typed Equals implementation
{
def body = match(fields) {
| [] => <[ !ReferenceEquals(null, other) ]>
| _ => <[ !ReferenceEquals(null, other) && $equals_generic_body ]>
}
ty.Define(<[ decl:
Equals(other : $ty_ref) : bool implements System.IEquatable[ $ty_ref ].Equals { $body }
]>);
}
// Equality operation
{
def define_eq(a, b, body) {
ty.Define(<[ decl: public static @== (a : $a, b : $b) : bool { $body } ]>);
ty.Define(<[ decl: public static @!= (a : $a, b : $b) : bool { ! $body } ]>);
}
def define_eq_b(b) {
define_eq(ty_ref, b, <[ $(equality_comparer(b)).Equals(a, b) ]>)
}
def define_eq_a(a) {
define_eq(a, ty_ref, <[ $(equality_comparer(a)).Equals(b, a) ]>)
}
define_eq_b(ty_ref);
define_eq_b(<[ object ]>);
//define_eq_b(anonymous_ref);
define_eq_a(<[ object ]>);
//define_eq_a(anonymous_ref);
}
// External anonymous classes implicit conversions
foreach(ext_ty in external_anonymous_types) {
def ext_ty_name = <[ $(AnonymousClassNamespace : usesite).$(ext_ty.Name : usesite) ]>;
def ext_ty_ref = match(fields) {
| [] => ext_ty_name
| _ => <[ $ext_ty_name [ ..$generic_args ] ]>
}
// From
def body = fields.Map(f => <[ this.$(f.FieldName : usesite) = e.$(f.PropertyName : usesite) ]>);
ty.Define(<[ decl:
public this(e : $ext_ty_ref) {
..$body
}
]>);
ty.Define(<[ decl:
public static @: (e : $ext_ty_ref) : $ty_ref {
$ty_name(e)
}
]>);
// To
def body = fields.Map(f => <[ e.$(f.FieldName : usesite) ]>);
ty.Define(<[ decl:
public static @: (e : $ty_ref) : $ext_ty_ref {
$ext_ty_name( ..$body )
}
]>);
}
//unless(Message.ErrorCount > 0)
ty.Compile();
ty
}
}
}