// // Late Binding Macro for Nemerle // Copyright (c) 2006, Alexey Borzenkov (snaury@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. // #pragma indent namespace Nemerle.Late using Nemerle.IO using Nemerle.Compiler using Nemerle.Compiler.Parsetree using BF = System.Reflection.BindingFlags using PM = System.Reflection.ParameterModifier [Record] \ public class LateMacro env : GlobalEnv /// This class holds info used when passing it to Type.InvokeMember public class CallInfo public mutable name : string public mutable args : list[PExpr] public mutable byref : list[bool] public mutable isout : list[bool] public mutable names : list[PExpr] public this(name : string, args : list[PExpr], tail : list[PExpr] = null) this.name = name this.args = args transform() unless(tail == null) this.args = this.args + tail transform() : void def transform_named() def loop(args, named_args = [], named_names = [], unnamed_args = []) match(args) | [] => (named_args.Rev() + unnamed_args.Rev(), named_names.Rev()) | arg :: args => match(arg) | <[ $(id : name) = $val ]> => loop(args, val :: named_args, <[ $(id.Id : string) ]> :: named_names, unnamed_args) | _ => loop(args, named_args, named_names, arg :: unnamed_args) (args, names) = loop(args) def transform_byref() def loop(args, _args = [], _byref = [], _isout = []) match(args) | [] => (_args.Rev(), _byref.Rev(), _isout.Rev()) | arg :: args => match(arg) | <[ out $arg ]> => loop(args, arg :: _args, true :: _byref, true :: _isout) | <[ ref $arg ]> => loop(args, arg :: _args, true :: _byref, false :: _isout) | _ => loop(args, arg :: _args, false :: _byref, false :: _isout) (args, byref, isout) = loop(args) transform_named() transform_byref() public variant LateCall | SetProperty { loc: Location; callinfo: CallInfo } | GetProperty { loc: Location; callinfo: CallInfo } | Method { loc: Location; callinfo: CallInfo } /// returns expression that does a single late invoke /// expr_name is used to hold expr public static invoke(expr_name : Name, expr : PExpr, loc : Location, info : CallInfo, flags : BF, ignore_result = false) : PExpr Util.locate(loc, invoke_located(expr_name, expr, info, flags, ignore_result)) /// returns expression that does a single late invoke /// expr_name is used to hold expr /// note: locations for most expressions are taken off location stack private static invoke_located(expr_name : Name, expr : PExpr, info : CallInfo, flags : BF, ignore_result : bool) : PExpr def array_literal(ty, args, exclude = []) def transform(args, exclude, r = []) match(args) | [] => r.Rev() | arg :: args => match(exclude) | true :: exclude => def e = Util.locate(arg.Location, <[ null : object ]>); transform(args, exclude, e :: r) | _ :: exclude \ | exclude => def e = Util.locate(arg.Location, <[ $arg : $ty ]>); transform(args, exclude, e :: r) if(args.Length != 0) <[ array[..$(transform(args, exclude))] ]> else <[ null ]> def pms_literal(numargs, byrefs) def pms_needed = need: foreach(byref in byrefs) when(byref) need(true) false if(pms_needed) def pms = Macros.NewSymbol() def init(byrefs, i = 0, r = []) match(byrefs) | true :: byrefs when (i < numargs) => init(byrefs, i + 1, <[ $(pms : name)[$(i : int)] = true ]> :: r) | _ => <[ { ..$(r.Rev()) } ]> <[ array[{ def $(pms : name) = PM($(numargs : int)); $(init(byrefs)); $(pms : name) }] ]> else <[ null ]> def name_expr = <[ $(info.name : string) ]> def flags_expr = <[ $(flags : enum) ]> def args_expr = array_literal(<[ object ]>, info.args, info.isout) def pms_expr = pms_literal(info.args.Length, info.byref) def names_expr = array_literal(<[ string ]>, info.names) def call_expr(args_expr) <[ { $(expr_name : name) = $expr; $(expr_name : name).GetType().InvokeMember($name_expr, $flags_expr, null, $(expr_name : name), $args_expr, $pms_expr, null, $names_expr) } ]> def need_copyback = need: foreach(byref in info.byref) when(byref) need(true) false if(need_copyback) def args_name = Macros.NewSymbol() def init = <[ def $(args_name : name) = $args_expr ]> def call = call_expr(<[ $(args_name : name) ]>) def copyback(args, byref, i = 0, r = []) match(args) | arg :: args => match(byref) | true :: byref => copyback(args, byref, i + 1, <[ $arg = $(args_name : name)[$(i : int)] ]> :: r) | _ :: byref => copyback(args, byref, i + 1, r) | _ => <[ {..$(r.Rev())} ]> | _ => <[ {..$(r.Rev())} ]> def copyback = copyback(info.args, info.byref) if(ignore_result) <[ $init; _ = $call; $copyback; ]> else def result_name = Macros.NewSymbol() <[ $init; def $(result_name : name) = $call; $copyback; $(result_name : name) ]> else def call = call_expr(args_expr) if(ignore_result) <[ _ = $call ]> else call /// returns true if expr is class or macro name public is_class_or_macro_name(expr : PExpr) : bool match(Util.QidOfExpr(expr)) | Some((id, name)) => def ctx = name.GetEnv(env) if(ctx.LookupType(id) is Some(_)) true else if(ctx.LookupMacro(id) is Some(_)) true else false | _ => false /// returns true is expr is of some special kind /// and shouldn't be transformed by late rules public special(expr : PExpr) : bool | PExpr.This => true | PExpr.Base => true | PExpr.Indexer(func, _) => is_class_or_macro_name(func) | PExpr.GenericSpecifier(func, _) => is_class_or_macro_name(func) | _ => is_class_or_macro_name(expr) /// returns expression that does all late invokes in chain /// expr_name is used to hold expr with each invoke public static build(expr_name : Name, expr : PExpr, chain : list[LateCall]) : PExpr def loop(expr, chain) match(chain) | null => expr | [] => expr | x :: xs => match(x) | null => loop(expr, xs) | LateCall.SetProperty(loc, info) => loop(invoke(expr_name, expr, loc, info, if(info.args.Length > 1) BF.SetProperty else BF.SetField %| BF.SetProperty, true), xs) | LateCall.GetProperty(loc, info) => loop(invoke(expr_name, expr, loc, info, if(info.args.Length > 0) BF.GetProperty else BF.GetField %| BF.GetProperty), xs) | LateCall.Method(loc, info) => loop(invoke(expr_name, expr, loc, info, BF.InvokeMethod), xs) loop(expr, chain) /// scans single expression and returns (expr, list[LateCall]) /// returns (expr, []) if nothing in expression can be late bound public scan(expr : PExpr) : PExpr * list[LateCall] def loop(expr, r = []) match(expr) | null => (expr, r) | _ when special(expr) => (expr, r) | <[ $rest (.. $_) ]> when special(rest) \ | <[ $rest [.. $_] ]> when special(rest) => (expr, r) | <[ $rest . $(id : name) [.. $args] = $value ]> when !special(rest) \ | <[ $rest . $(id : name) = $value ]> when !special(rest) with (args = []) \ | <[ $rest . [.. $args] = $value ]> when !special(rest) with(id = Name("")) \ | <[ $rest [.. $args] = $value ]> when !special(rest) with(id = Name("")) => when(id.Id == "" && args.Length == 0) Message.FatalError(expr.Location, "default indexer must have parameters") loop(rest, LateCall.SetProperty(expr.Location, CallInfo(id.Id, args, [value])) :: r) | <[ $rest . $(id : name) [.. $args] ]> when !special(rest) \ | <[ $rest . $(id : name) ]> when !special(rest) with(args = []) \ | <[ $rest . [.. $args] ]> when !special(rest) with(id = Name("")) \ | <[ $rest [.. $args] ]> when !special(rest) with(id = Name("")) => when(id.Id == "" && args.Length == 0) Message.FatalError(expr.Location, "default indexer must have parameters") loop(rest, LateCall.GetProperty(expr.Location, CallInfo(id.Id, args)) :: r) | <[ $rest . $(id : name) (.. $args) ]> when !special(rest) => loop(rest, LateCall.Method(expr.Location, CallInfo(id.Id, args)) :: r) | _ => (expr, r) loop(expr) /// transform any first class lateexpr by transformation rules /// sets expr' and returns true if expr is first class lateexpr /// returns false otherwise /// note: if deep is true then any subexpr in expr' is sent to transform(expr, true) public latebound(expr : PExpr, expr' : out PExpr, deep = true) : bool def (expr, chain) = scan(expr) if(chain is []) false else def expr = if(deep) def deeptransform(expr) transform(expr, true) def expr = deeptransform(expr) def loop(chain) | x :: chain => match(x) | LateCall.SetProperty(_, info) \ | LateCall.GetProperty(_, info) \ | LateCall.Method(_, info) => info.args = info.args.Map(deeptransform) loop(chain) | _ => () loop(chain) expr else expr def expr_name = Macros.NewSymbol() expr' = PExpr.Sequence(expr.Location, [PExpr.DefMutable(expr.Location, <[ $(expr_name : name) ]>, null), build(expr_name, expr, chain)]) true /// transforms any lateexpr in expr by lateexpr transformation rules /// if deep is true, then also transforms any subexpr in any lateexpr /// note: mostly based on traverse in typing/Macros.n /// too bad that traverse can't be used here directly public transform(expr : PExpr, deep = true) : PExpr def loc = expr.Location def recurse(expr) transform(expr, deep) def recurse_fun(f) def recurse_funparm(p) def recurse_attr(attr) | <[ System.ComponentModel.DefaultValueAttribute($e) ]> => <[ System.ComponentModel.DefaultValueAttribute($(recurse(e))) ]> | _ => attr Fun_parm(p.Location, p.name, p.ty, Modifiers(p.modifiers.mods, p.modifiers.custom_attrs.Map(recurse_attr))) Function_decl(Fun_header(f.header.Location, f.header.typarms, f.header.name, f.header.ret_type, f.header.parms.Map(recurse_funparm)), recurse(f.body)) mutable expr' def result = match(expr) | null => null | _ when special(expr) => expr | _ when latebound(expr, out expr', deep) => expr' | PExpr.Wildcard \ | PExpr.Void => expr | PExpr.As(pat, name) => PExpr.As(loc, recurse(pat), name) | PExpr.Is(pat, ty) => PExpr.Is(loc, recurse(pat), ty) | PExpr.Where(name, fields) => PExpr.Where(loc, recurse(name), recurse(fields)) | PExpr.Match(mexpr, cases, expr_loc) => def recurse_case(c) def recurse_guard(g) | PExpr.Call(<[ $("when" : dyn) ]> as w, [pat, expr]) => PExpr.Call(g.Location, w, [pat, recurse(expr)]) | _ => g MatchCase(c.patterns.Map(recurse_guard), recurse(c.body), c.disable_warnings) PExpr.Match(loc, recurse(mexpr), cases.Map(recurse_case), expr_loc) | PExpr.Ref => expr | PExpr.Member(obj, mem) => if (Macros.IsTypeName (obj)) expr else PExpr.Member(loc, recurse(obj), mem) | PExpr.Call(func, parms) => PExpr.Call(loc, recurse(func), parms.Map(recurse)) | PExpr.GenericSpecifier(func, parms) => PExpr.GenericSpecifier(loc, recurse(func), parms.Map(recurse)) | PExpr.ListLiteral(elems) => PExpr.ListLiteral(loc, elems.Map(recurse)) | PExpr.Assign(target, source) => PExpr.Assign(loc, recurse(target), recurse(source)) | PExpr.DefMutable(name, val) => PExpr.DefMutable(loc, name, recurse(val)) | PExpr.Define(name, val) => PExpr.Define(loc, name, recurse(val)) | PExpr.DefFunctions(funs) => PExpr.DefFunctions(loc, funs.Map(recurse_fun)) | PExpr.Lambda(decl) => PExpr.Lambda(loc, recurse_fun(decl)) | PExpr.Throw(expr) => PExpr.Throw(loc, recurse(expr)) | PExpr.TryFinally(body, handler) => PExpr.TryFinally(loc, recurse(body), recurse(handler)) | PExpr.Try(body, cases) => def recurse_case(case) | TryCase.Catch(ex, ty, handler) => TryCase.Catch(ex, ty, recurse(handler)) | TryCase.Filter(ex, ty, filter, handler) => TryCase.Filter(ex, ty, recurse(filter), recurse(handler)) | TryCase.Ellipsis(e) => TryCase.Ellipsis(recurse(e)) PExpr.Try(loc, recurse(body), cases.Map(recurse_case)) | PExpr.Literal \ | PExpr.This \ | PExpr.Base \ | PExpr.Typeof => expr | PExpr.TypeConversion(expr, ty) => PExpr.TypeConversion(loc, recurse(expr), ty) | PExpr.TypeEnforcement(expr, ty) => PExpr.TypeEnforcement(loc, recurse(expr), ty) | PExpr.Sequence(seq) => PExpr.Sequence(loc, seq.Map(recurse)) | PExpr.Tuple(args) => PExpr.Tuple(loc, args.Map(recurse)) | PExpr.Array(rank, args) => PExpr.Array(loc, recurse(rank), recurse(args)) | PExpr.EmptyArray(sizes) => PExpr.EmptyArray(loc, sizes.Map(recurse)) | PExpr.Indexer(obj, args) => PExpr.Indexer(loc, recurse(obj), args.Map(recurse)) | PExpr.ParmByRef \ | PExpr.ParmOut \ | PExpr.Error => expr | PExpr.MacroCall(name, ns, parms) => match(ns.Value) | NamespaceTree.TypeInfoCache.MacroCall(m) when (m is late_macroMacro || m is late_parens_macroMacro || m is nolate_macroMacro || m is nolate_parens_macroMacro) => expr | _ => def recurse_parm(parm) | SyntaxElement.Expression(expr) => SyntaxElement.Expression(recurse(expr)) | _ => parm PExpr.MacroCall(loc, name, ns, parms.Map(recurse_parm)) | PExpr.Quoted(quot) => def inner = match(quot) | SyntaxElement.Expression(expr) => SyntaxElement.Expression(recurse(expr)) | _ => quot // perhaps I shouldn't be lazy here, what if something new is added to the compiler? PExpr.Quoted(loc, inner) | PExpr.Spliced(expr) => PExpr.Spliced(loc, recurse(expr)) | PExpr.ToComplete => expr | PExpr.Ellipsis(expr) => PExpr.Ellipsis(loc, recurse(expr)) | PExpr.Typed \ | PExpr.TypedPattern \ | PExpr.TypedType => expr result macro late_parens_macro(expr) \ syntax("late", "(", expr, ")") def result = LateMacro (Macros.ImplicitCTX().Env).transform(expr) result macro late_macro(expr) \ syntax("late", expr) def result = LateMacro (Macros.ImplicitCTX().Env).transform(expr) result macro nolate_parens_macro(expr) \ syntax("nolate", "(", expr, ")") expr macro nolate_macro(expr) \ syntax("nolate", expr) expr